diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 29e3f06388..08ae7cfc9c --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ -node_modules +node_modules/ package-lock.json package.json yarn.lock -compile.sh -deploy.sh - - +scripts/ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index d0b036aba0..4104b77026 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ------ -通过 gitbook 的形式整理了自己的工作和学习经验,[JavaKeeper](https://Jstarfish.github.io) 直接访问即可,也推荐大家采用这种形式创建属于自己的“笔记本”,让成长看的见。 +通过 gitbook 的形式整理了自己的工作和学习经验,[JavaKeeper](http://javakeeper.starfish.ink) 直接访问即可,也推荐大家采用这种形式创建属于自己的“笔记本”,让成长看的见。 > 欢迎关注公众号 [JavaKeeper](#公众号) ,有 500+ 本电子书,大佬云集的微信群,等你来撩~ @@ -24,9 +24,9 @@ | Project | Version | Article | | :-----: | :-----: | :----------------------------------------------------------- | -| JVM | | [JVM与Java体系结构](http://www.starfish.ink/java/JVM/JVM-Java.html)[类加载子系统](http://www.starfish.ink/java/JVM/Class-Loading.html)[运行时数据区](http://www.starfish.ink/java/JVM/Runtime-Data-Areas.html)[看完这篇垃圾回收,和面试官扯皮没问题了](https://mp.weixin.qq.com/s/lmROkzhkVR0oeH-2hS-vfA)[垃圾回收-实战篇](https://mp.weixin.qq.com/s/8L_u2Pu2iUGb3Ub0pe8fAQ)[你有认真了解过自己的Java“对象”吗](https://mp.weixin.qq.com/s/4NDnFf3sripUIp3uT8W3HQ) | -| Java8 | | [Java8 通关攻略](https://mp.weixin.qq.com/s/_eAT_tkRIywEErA7GUxZkQ) | -| JUC | | [不懂Java 内存模型,就先别扯什么高并发](https://mp.weixin.qq.com/s/FUHFppzcISLDMx4vc8tz4A)[面试必问的 volatile,你真的理解了吗](http://www.starfish.ink/java/JUC/volatile.html)[从 Atomic 到 CAS ,竟然衍生出这么多 20k+ 面试题](http://www.starfish.ink/java/JUC/CAS.html)[「阻塞队列」手写生产者消费者、线程池原理面试题真正的答案](https://mp.weixin.qq.com/s/NALM27_K4GIqNmm7kScTAw)[线程池解毒](http://www.starfish.ink/java/JUC/Thread-Pool.html) | +| JVM | | [JVM与Java体系结构](https://javakeeper.starfish.ink/java/JVM/JVM-Java.html)[类加载子系统](https://javakeeper.starfish.ink/java/JVM/Class-Loading.html)[运行时数据区](https://javakeeper.starfish.ink/java/JVM/Runtime-Data-Areas.html)[看完这篇垃圾回收,和面试官扯皮没问题了](https://javakeeper.starfish.ink/java/JVM/GC.html)[垃圾回收-实战篇](https://javakeeper.starfish.ink/java/JVM/GC-%E5%AE%9E%E6%88%98.html)[你有认真了解过自己的Java“对象”吗](https://javakeeper.starfish.ink/java/JVM/Java-Object.html)[JVM 参数配置](https://javakeeper.starfish.ink/java/JVM/JVM%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE.html)[谈谈你对 OOM 的认识](https://javakeeper.starfish.ink/java/JVM/OOM.html)[阿里面试回顾: 说说强引用、软引用、弱引用、虚引用?](https://javakeeper.starfish.ink/java/JVM/Reference.html)[JVM 性能监控和故障处理工具](https://javakeeper.starfish.ink/java/JVM/JVM%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7%E5%92%8C%E6%95%85%E9%9A%9C%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7.html) | +| Java8 | | [Java8 通关攻略](https://javakeeper.starfish.ink/java/Java-8.html) | +| JUC | | [不懂Java 内存模型,就先别扯什么高并发](https://javakeeper.starfish.ink/java/JUC/Java-Memory-Model.html)[面试必问的 volatile,你真的理解了吗](https://javakeeper.starfish.ink/java/JUC/volatile.html)[从 Atomic 到 CAS ,竟然衍生出这么多 20k+ 面试题](https://javakeeper.starfish.ink/java/JUC/CAS.html)[「阻塞队列」手写生产者消费者、线程池原理面试题真正的答案](https://javakeeper.starfish.ink/java/JUC/BlockingQueue.html)[线程池解毒](https://javakeeper.starfish.ink/java/JUC/Thread-Pool.html) | | NIO | | | @@ -36,8 +36,8 @@ | Project | Version | Article | | :----------------------------------------------------------: | :-----: | :----------------------------------------------------------- | -|  **MySQL** | 5.7.25 | [1、MySQL架构概述](docs/data-store/MySQL/MySQL-Framework.md)[2、MySQL存储引擎](docs/data-store/MySQL/MySQL-Storage-Engines.md)[3、索引](docs/data-store/MySQL/MySQL-Index.md)[4、事务](docs/data-store/MySQL/MySQL-Transaction.md)5、表设计[6、性能优化](docs/data-store/MySQL/MySQL-Optimization.md)7、锁机制8、分区分表分库9 、主从复制 | -|  **Redis** | 5.0.6 | [1、NoSQL概述](docs/data-store/Redis/1.Nosql-Overview.md)[2、Redis概述](docs/data-store/Redis/2.readRedis.md)[3、Redis数据类型](docs/data-store/Redis/3.Redis-Datatype.md)[4、Redis配置](docs/data-store/Redis/4.Redis-Conf.md)[5、深入理解 Redis 的持久化](docs/data-store/Redis/5.Redis-Persistence.md) | +|  **MySQL** | 5.7.25 | [1、MySQL架构概述](https://javakeeper.starfish.ink/data-management/MySQL/MySQL-Framework.html)[2、MySQL存储引擎](https://javakeeper.starfish.ink/data-management/MySQL/MySQL-Storage-Engines.html)[3、索引](https://javakeeper.starfish.ink/data-management/MySQL/MySQL-Index.html)[4、事务](https://javakeeper.starfish.ink/data-management/MySQL/MySQL-Transaction.html)5、表设计[6、性能优化](docs/data-store/MySQL/MySQL-Optimization.md)7、锁机制8、分区分表分库9 、主从复制 | +|  **Redis** | 5.0.6 | [1、NoSQL概述]()[2、Redis概述](https://javakeeper.starfish.ink/data-management/Redis/ReadRedis.html)[3、Redis数据类型](https://javakeeper.starfish.ink/data-management/Redis/Redis-Datatype.html)[4、Redis配置](https://javakeeper.starfish.ink/data-management/Redis/Redis-Conf.html)[5、深入理解 Redis 的持久化](https://javakeeper.starfish.ink/data-management/Redis/Redis-Persistence.html) | | **Elasticsearch** | | | | **Amazon S3** | | | | MongoDB | | | diff --git a/docs/.DS_Store b/docs/.DS_Store index da0c7fecf5..cfba763044 100644 Binary files a/docs/.DS_Store and b/docs/.DS_Store differ diff --git a/docs/.obsidian/app.json b/docs/.obsidian/app.json new file mode 100644 index 0000000000..036417784d --- /dev/null +++ b/docs/.obsidian/app.json @@ -0,0 +1,3 @@ +{ + "communityThemeSortOrder": "download" +} \ No newline at end of file diff --git a/docs/.obsidian/appearance.json b/docs/.obsidian/appearance.json new file mode 100644 index 0000000000..acf703af81 --- /dev/null +++ b/docs/.obsidian/appearance.json @@ -0,0 +1,5 @@ +{ + "theme": "obsidian", + "cssTheme": "Obsidian Nord", + "accentColor": "" +} \ No newline at end of file diff --git a/docs/.obsidian/core-plugins-migration.json b/docs/.obsidian/core-plugins-migration.json new file mode 100644 index 0000000000..318ec8585d --- /dev/null +++ b/docs/.obsidian/core-plugins-migration.json @@ -0,0 +1,31 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "outgoing-link": true, + "tag-pane": true, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "starred": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false, + "canvas": true, + "bookmarks": true, + "properties": false +} \ No newline at end of file diff --git a/docs/.obsidian/core-plugins.json b/docs/.obsidian/core-plugins.json new file mode 100644 index 0000000000..30410c96ee --- /dev/null +++ b/docs/.obsidian/core-plugins.json @@ -0,0 +1,32 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "outgoing-link": true, + "tag-pane": true, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "starred": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false, + "canvas": true, + "bookmarks": true, + "properties": false, + "webviewer": false +} \ No newline at end of file diff --git a/docs/.obsidian/graph.json b/docs/.obsidian/graph.json new file mode 100644 index 0000000000..f1c3cdafa8 --- /dev/null +++ b/docs/.obsidian/graph.json @@ -0,0 +1,22 @@ +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": true, + "colorGroups": [], + "collapse-display": false, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.518713248970312, + "repelStrength": 9.42057291666667, + "linkStrength": 0.345987955729167, + "linkDistance": 250, + "scale": 0.8171482109060346, + "close": true +} \ No newline at end of file diff --git a/docs/.obsidian/hotkeys.json b/docs/.obsidian/hotkeys.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/docs/.obsidian/hotkeys.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/docs/.obsidian/themes/Minimal.css b/docs/.obsidian/themes/Minimal.css new file mode 100644 index 0000000000..3421fc8c8b --- /dev/null +++ b/docs/.obsidian/themes/Minimal.css @@ -0,0 +1,12636 @@ +/* --------------------------------------------------------------------------- + +Minimal Obsidian 5.3.2 by @kepano + +Important: this is an archived copy of Minimal +only for use with Obsidian 0.15.x and below. + +For Obsidian 0.16+ use Minimal 6.0+ + +--------------------------------------------------------------------------- + +User interface replacement for Obsidian. + +Designed to be used with the Minimal Theme Settings +plugin and the Hider plugin. + +Sponsor my work: +https://www.buymeacoffee.com/kepano + +Readme: +https://github.com/kepano/obsidian-minimal + +----------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2020-2022 Stephan Ango (@kepano) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +@charset "UTF-8"; +/* Variables */ +body { + --font-text-theme:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,Ubuntu,sans-serif; + --font-editor-theme:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,Ubuntu,sans-serif; + --font-monospace-theme:Menlo,SFMono-Regular,Consolas,"Roboto Mono",monospace; + --font-interface-theme:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,Ubuntu,sans-serif; + --font-editor:var(--font-editor-override), var(--font-text-override), var(--font-editor-theme); + --minimal-version:"You are currently using Minimal 5.3.2\a\aIf you run into any issues, try updating to the latest version of the theme. It is also highly recommended to install Minimal Theme Settings and Contextual Typography plugins.\a\a Full documentation:\a minimal.guide\a\a Support my work:\a buymeacoffee.com/kepano"; } + +:root { + /* Cursor */ + --cursor:default; + /* Font sizes */ + --font-small:13px; + --font-smaller:11px; + --font-smallest:10px; + --font-inputs:13px; + --font-settings:15px; + --font-settings-small:12px; + /* Font weights */ + --normal-weight:400; + --bold-weight:600; + --link-weight:inherit; + /* Headings */ + --title-size:1.1em; + --title-weight:600; + /* Headings */ + --h1:1.125em; + --h2:1.05em; + --h3:1em; + --h4:0.90em; + --h5:0.85em; + --h6:0.85em; + --h1-weight:600; + --h2-weight:600; + --h3-weight:500; + --h4-weight:500; + --h5-weight:500; + --h6-weight:400; + --h1-variant:normal; + --h2-variant:normal; + --h3-variant:normal; + --h4-variant:small-caps; + --h5-variant:small-caps; + --h6-variant:small-caps; + --h1-style:normal; + --h2-style:normal; + --h3-style:normal; + --h4-style:normal; + --h5-style:normal; + --h6-style:normal; + /* Blockquotes */ + --blockquote-style:normal; + /* Line widths */ + --line-width:40rem; + --line-height:1.5; + --max-width:88%; + --max-col-width:18em; + /* Icons */ + --icon-muted:0.5; + --icon-size:18px; + --border-width:1px; + --border-width-alt:1px; + /* Quotes and transclusions */ + --nested-padding:1.1em; + /* Lists */ + --folding-offset:10px; + --list-edit-offset:0.5em; + --list-indent:2em; + --list-spacing:0.075em; + /* Radiuses */ + --radius-s:2px; + --radius-m:5px; + --radius-l:12px; + --radius-xl:16px; + --input-height:32px; + --header-height:40px; + /* Mobile sidebars */ + --mobile-left-sidebar-width:280pt; + --mobile-right-sidebar-width:240pt; + /* Tags */ + --tag-radius:14px; + --tag-border-width:1px; + --top-left-padding-y:0px; + /* Image opacity in dark mode */ + --image-muted:0.7; + /* Spacing */ + --spacing-p: 0.75em; } + +.mod-macos { + --top-left-padding-y:24px; } + +/* Dynamic colors + + Most colors are driven from the following values, meaning that + the backgrounds, borders, and various shades are + automatically generated. + + - Base color is used for the backgrounds, text and borders. + - Accent color is used for links and some interactive elements. + + The colors use HSL (hue, saturation, lightness) + + - Hue (0-360 degrees):0 is red, 120 is green, and 240 is blue + - Saturation (0-100%):0% is desaturated, 100% is full saturation + - Lightness (0-100%):0% is black, 100% is white + +*/ +:root { + --base-h:0; + /* Base hue */ + --base-s:0%; + /* Base saturation */ + --base-d:15%; + /* Base lightness Dark Mode - 0 is black */ + --base-l:96%; + /* Base lightness Light Mode - 100 is white */ + --accent-h:201; + /* Accent hue */ + --accent-s:17%; + /* Accent saturation */ + --accent-d:60%; + /* Accent lightness Dark Mode */ + --accent-l:50%; + /* Accent lightness Light Mode */ + --red:#d04255; + --yellow:#e5b567; + --green:#a8c373; + --orange:#d5763f; + --cyan:#73bbb2; + --blue:#6c99bb; + --purple:#9e86c8; + --pink:#b05279; } + +.theme-light, +.theme-light.minimal-default-light, +body .excalidraw { + --accent-l:50%; + --base-l:96%; + --bg1:white; + --bg2: + hsl( + var(--base-h), + var(--base-s), + var(--base-l) + ); + --bg3: + hsla( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 50%), + 0.12 + ); + --ui1: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 6%) + ); + --ui2: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 12%) + ); + --ui3: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 20%) + ); + --tx1: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 90%) + ); + --tx2: + hsl( + var(--base-h), + calc(var(--base-s) - 20%), + calc(var(--base-l) - 45%) + ); + --tx3: + hsl( + var(--base-h), + calc(var(--base-s) - 10%), + calc(var(--base-l) - 25%) + ); + --tx4: + hsl( + var(--base-h), + calc(var(--base-s) - 10%), + calc(var(--base-l) - 60%) + ); + --ax1: + hsl( + var(--accent-h), + var(--accent-s), + var(--accent-l) + ); + --ax2: + hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) - 10%) + ); + --ax3: + hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) + 10%) + ); + --hl1: + hsla( + var(--accent-h), + 50%, + calc(var(--base-l) - 20%), + 30% + ); + --hl2:rgba(255, 225, 0, 0.5); } + +.theme-light.minimal-light-contrast .titlebar, +.theme-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark, +.theme-dark.minimal-default-dark, +.excalidraw.theme--dark { + --accent-l:60%; + --base-l:15%; + --bg1: + hsl( + var(--base-h), + var(--base-s), + var(--base-l) + ); + --bg2: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 2%) + ); + --bg3: + hsla( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 40%), + 0.12 + ); + --ui1: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 6%) + ); + --ui2: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 12%) + ); + --ui3: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 20%) + ); + --tx1: + hsl( + var(--base-h), + calc(var(--base-s) - 10%), + calc(var(--base-l) + 67%) + ); + --tx2: + hsl( + var(--base-h), + calc(var(--base-s) - 20%), + calc(var(--base-l) + 45%) + ); + --tx3: + hsl( + var(--base-h), + calc(var(--base-s) - 10%), + calc(var(--base-l) + 20%) + ); + --tx4: + hsl( + var(--base-h), + calc(var(--base-s) - 10%), + calc(var(--base-l) + 50%) + ); + --ax1: + hsl( + var(--accent-h), + var(--accent-s), + var(--accent-l) + ); + --ax2: + hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) + 12%) + ); + --ax3: + hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) - 12%) + ); + --hl1: + hsla( + var(--accent-h), + 70%, + 40%, + 30% + ); + --hl2:rgba(255, 177, 80, 0.3); + --sp1:#fff; } + +.theme-light.minimal-light-white { + --background-primary: white; + --background-secondary: white; + --background-secondary-alt: white; } + +.theme-dark.minimal-dark-black { + --base-d:0%; + --background-primary: black; + --background-secondary: black; + --background-secondary-alt: black; + --background-tertiary: + hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 10%)) ; + --tx1:hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 75%) + ); + --tx2:hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 50%) + ); + --tx3:hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 25%) + ); + --ui1:hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 12%) + ); + --ui2:hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 20%) + ); + --ui3:hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 30%) + ); } + +/* Map colors to semantic Obsidian names */ +.theme-light { + --mono100:black; + --mono0:white; } + +.theme-dark { + --mono100:white; + --mono0:black; } + +.theme-light, +.theme-dark { + --h1-color:var(--text-normal); + --h2-color:var(--text-normal); + --h3-color:var(--text-normal); + --h4-color:var(--text-normal); + --h5-color:var(--text-normal); + --h6-color:var(--text-muted); } + +.theme-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-light-contrast .titlebar, +.theme-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark, +.theme-light, +.excalidraw.theme--dark, +body .excalidraw { + --text-normal: var(--tx1); + --text-bold: var(--tx1); + --text-italic: var(--tx1); + --text-muted: var(--tx2); + --text-faint: var(--tx3); + --title-color: var(--tx1); + --title-color-inactive: var(--tx2); + --text-code: var(--tx4); + --text-error: var(--red); + --text-blockquote: var(--tx2); + --text-accent: var(--ax1); + --text-accent-hover: var(--ax2); + --text-on-accent: white; + --text-selection: var(--hl1); + --text-highlight-bg: var(--hl2); + --background-primary: var(--bg1); + --background-primary-alt: var(--bg2); + --background-secondary: var(--bg2); + --background-secondary-alt: var(--bg1); + --background-tertiary: var(--bg3); + --background-table-rows: var(--bg2); + --background-modifier-form-field: var(--bg1); + --background-modifier-form-field-highlighted: + var(--bg1); + --interactive-hover: var(--ui1); + --interactive-accent: var(--ax3); + --interactive-accent-hover: var(--ax3); + --background-modifier-accent: var(--ax3); + --background-modifier-border: var(--ui1); + --background-modifier-border-hover: var(--ui2); + --background-modifier-border-focus: var(--ui3); + --background-modifier-success: var(--green); + --background-divider: var(--ui1); + --scrollbar-bg: transparent; + --scrollbar-thumb-bg: var(--ui1); + --scrollbar-active-thumb-bg: var(--ui3); + --quote-opening-modifier: var(--ui2); + --modal-border: var(--ui2); + --icon-color: var(--tx2); + --icon-color-hover: var(--tx2); + --icon-color-active: var(--tx1); + --icon-hex: var(--mono0); + --tag-color: var(--tx2); + --tag-bg: transparent; + --tag-bg2: transparent; + --shadow-m: + 0px 2.7px 6.7px rgba(0, 0, 0, 0.04), + 0px 8.9px 22.3px rgba(0, 0, 0, 0.06), + 0px 40px 100px rgba(0, 0, 0, 0.1); + --shadow-l: + 0px 0.8px 4.2px rgba(0, 0, 0, 0.014), + 0px 2px 10px rgba(0, 0, 0, 0.02), + 0px 3.8px 18.8px rgba(0, 0, 0, 0.025), + 0px 6.7px 33.5px rgba(0, 0, 0, 0.03), + 0px 12.5px 62.7px rgba(0, 0, 0, 0.036), + 0px 30px 150px rgba(0, 0, 0, 0.05); } + +.theme-light, +body .excalidraw { + --interactive-normal: var(--bg1); + --interactive-accent-rgb:220, 220, 220; + --active-line-bg:rgba(0,0,0,0.035); + --background-modifier-cover:hsla(var(--base-h),calc(var(--base-s) - 50%),calc(var(--base-l) - 7%),0.7); + --text-highlight-bg-active:rgba(0, 0, 0, 0.1); + /* Errors */ + --background-modifier-error:rgba(255,0,0,0.14); + --background-modifier-error-hover:rgba(255,0,0,0.08); + /* Shadows */ + --shadow-color:rgba(0, 0, 0, 0.1); + --btn-shadow-color:rgba(0, 0, 0, 0.05); } + +.theme-dark, +.excalidraw.theme--dark { + --interactive-normal: var(--bg3); + --interactive-accent-rgb:66, 66, 66; + --active-line-bg:rgba(255,255,255,0.04); + --background-modifier-cover:hsla(var(--base-h),var(--base-s),calc(var(--base-d) - 12%),0.7); + --text-highlight-bg-active:rgba(255, 255, 255, 0.1); + /* Errors */ + --background-modifier-error:rgba(255,20,20,0.12); + --background-modifier-error-hover:rgba(255,20,20,0.18); + /* Shadows */ + --background-modifier-box-shadow:rgba(0, 0, 0, 0.3); + --shadow-color:rgba(0, 0, 0, 0.3); + --btn-shadow-color:rgba(0, 0, 0, 0.2); } + +.theme-light.minimal-light-white { + --background-table-rows: var(--bg2); } + +.theme-light.minimal-light-tonal { + --background-primary: var(--bg2); + --background-primary-alt: var(--bg3); + --background-table-rows: var(--bg3); } + +.theme-dark.minimal-dark-tonal { + --background-secondary: var(--bg1); + --background-table-rows: var(--bg3); } + +.theme-dark.minimal-dark-black { + --background-primary-alt: var(--bg3); + --background-table-rows: var(--bg3); + --modal-border: var(--ui2); + --active-line-bg:rgba(255,255,255,0.085); + --background-modifier-form-field: var(--bg3); + --background-modifier-cover:hsla(var(--base-h),var(--base-s),calc(var(--base-d) + 8%),0.9); + --background-modifier-box-shadow:rgba(0, 0, 0, 1); } + +/* Desktop font sizes */ +body { + --font-adaptive-normal:var(--font-text-size,var(--editor-font-size)); + --font-adaptive-small:var(--font-small); + --font-adaptive-smaller:var(--font-smaller); + --font-adaptive-smallest:var(--font-smallest); + --line-width-adaptive:var(--line-width); + --line-width-wide:calc(var(--line-width) + 12.5%); + --font-code:calc(var(--font-adaptive-normal) * 0.9); + --table-font-size:calc(var(--font-adaptive-normal) * 0.875); } + +/* Phone font sizes */ +@media (max-width: 400pt) { + .is-mobile { + --font-adaptive-small:calc(var(--font-small) + 2px); + --font-adaptive-smaller:calc(var(--font-smaller) + 2px); + --font-adaptive-smallest:calc(var(--font-smallest) + 2px); + --max-width:88%; } } +/* Tablet font sizes */ +@media (min-width: 400pt) { + .is-mobile { + --font-adaptive-small:calc(var(--font-small) + 3px); + --font-adaptive-smaller:calc(var(--font-smaller) + 2px); + --font-adaptive-smallest:calc(var(--font-smallest) + 2px); + --line-width-adaptive:calc(var(--line-width) + 6rem); + --max-width:90%; } } +/* Disabled features */ +/* Disabled features */ +/* Search counts */ +.tree-item-flair:not(.tag-pane-tag-count) { + display: none; } + +/* Folder name */ +.tree-item-inner-subtext { + display: none; } + +/* Obsidian */ +/* Block width snippet */ +.minimal-dev-block-width { + /* Green — Folding offset width */ + /* Red — Max width */ + /* Orange — Wide line width*/ + /* Blue — Normal line width */ } + .minimal-dev-block-width .mod-root .workspace-leaf-content:after { + display: flex; + align-items: flex-end; + content: "\00a0pane\00a0"; + font-size: 12px; + color: gray; + font-family: var(--font-monospace); + width: 100%; + max-width: 100%; + height: 100vh; + top: 0; + z-index: 999; + position: fixed; + pointer-events: none; } + .minimal-dev-block-width.minimal-readable .mod-root .view-header:after { + display: flex; + align-items: flex-end; + color: green; + font-size: 12px; + font-family: var(--font-monospace); + content: " "; + width: var(--folding-offset); + height: 100vh; + border-left: 1px solid green; + border-right: 1px solid green; + background-color: rgba(0, 128, 0, 0.1); + top: 0; + left: max(calc(50% - var(--line-width-adaptive)/2 - 1px), calc(50% - var(--max-width)/2 - 1px)); + z-index: 999; + position: fixed; + pointer-events: none; } + .minimal-dev-block-width.minimal-readable-off .mod-root .view-header:after { + display: flex; + align-items: flex-end; + color: green; + font-size: 12px; + font-family: var(--font-monospace); + content: " "; + width: var(--folding-offset); + height: 100vh; + border-left: 1px solid green; + border-right: 1px solid green; + background-color: rgba(0, 128, 0, 0.1); + top: 0; + left: calc(50% - var(--max-width)/2 - 1px); + z-index: 999; + position: fixed; + pointer-events: none; } + .minimal-dev-block-width .mod-root .view-content:before { + display: flex; + align-items: flex-end; + content: "\00a0max\00a0"; + font-size: 12px; + color: red; + width: var(--max-width); + height: 100vh; + border-left: 1px solid red; + border-right: 1px solid red; + top: 0; + left: 50%; + transform: translate(-50%, 0); + z-index: 999; + position: fixed; + pointer-events: none; } + .minimal-dev-block-width.minimal-readable .mod-root .view-header:before { + display: flex; + align-items: flex-end; + content: "\00a0wide\00a0"; + font-size: 12px; + color: orange; + font-family: var(--font-monospace); + width: var(--line-width-wide); + max-width: var(--max-width); + height: 100vh; + border-left: 1px solid orange; + border-right: 1px solid orange; + background-color: rgba(255, 165, 0, 0.05); + top: 0; + left: 50%; + transform: translate(-50%, 0); + z-index: 999; + position: fixed; + pointer-events: none; } + .minimal-dev-block-width.minimal-readable .mod-root .view-content:after { + display: flex; + align-items: flex-end; + color: blue; + font-size: 12px; + font-family: var(--font-monospace); + content: "\00a0normal"; + width: var(--line-width-adaptive); + max-width: var(--max-width); + height: 100vh; + border-left: 1px solid blue; + border-right: 1px solid blue; + background-color: rgba(0, 0, 255, 0.08); + top: 0; + left: 50%; + transform: translate(-50%, 0); + z-index: 999; + position: fixed; + pointer-events: none; } + +/* Obsidian */ +/* Blockquotes */ +/* Preview */ +.markdown-preview-view blockquote { + border-radius: 0; + border: solid var(--quote-opening-modifier); + border-width: 0px 0px 0px 1px; + background-color: transparent; + padding: 0 0 0 var(--nested-padding); + margin-inline-start: 0; + margin-inline-end: 0; + font-size: var(--blockquote-size); + font-style: var(--blockquote-style); + color: var(--text-blockquote); } + +.cm-s-obsidian span.cm-quote, +.markdown-preview-view blockquote em, +.markdown-preview-view blockquote strong { + color: var(--text-blockquote); } + +/* Editor */ +.markdown-source-view.mod-cm6.is-live-preview .HyperMD-quote, +.markdown-source-view.mod-cm6 .HyperMD-quote { + background-color: transparent; + color: var(--text-blockquote); + font-size: var(--blockquote-size); + font-style: var(--blockquote-style); + border-left: 1px solid var(--quote-opening-modifier); } + +.markdown-source-view.mod-cm6 .cm-blockquote-border { + width: 20px; + display: inline-block; + border-left: none; + border-right: 1px solid var(--quote-opening-modifier); } + +.markdown-source-view.mod-cm6 .cm-hmd-indent-in-quote { + margin-left: 10px; } + +.is-live-preview .cm-hmd-indent-in-quote { + color: var(--text-faint); } + +/* Callouts */ +.is-live-preview.is-readable-line-width > .cm-callout .callout { + max-width: var(--max-width); + margin: 0 auto; } + +/* Checklists, task lists, checkboxes */ +:root { + --checkbox-size:17px; + --checkbox-icon:20px; + --checkbox-radius:50%; + --checkbox-top:2px; + --checkbox-left:0px; + --checkbox-margin:0px 6px 0px -1.35em; } + +.checkbox-square { + --checkbox-size:15px; + --checkbox-icon:17px; + --checkbox-radius:4px; + --checkbox-top:1px; + --checkbox-left:0px; + --checkbox-margin:0px 8px 0px -1.35em; } + +input[type=checkbox] { + -webkit-appearance: none; + appearance: none; + border-radius: var(--checkbox-radius); + border: 1px solid var(--text-faint); + padding: 0; + margin: 0 6px 0 0; + width: var(--checkbox-size); + height: var(--checkbox-size); } + +input[type=checkbox]:hover, +input[type=checkbox]:focus { + outline: 0; + border-color: var(--text-muted); } + +.checklist-plugin-main .group .compact > .toggle .checked, +.is-flashing input[type=checkbox]:checked, +input[type=checkbox]:checked { + background-color: var(--background-modifier-accent); + border: 1px solid var(--background-modifier-accent); + background-position: 44% 55%; + background-size: 70%; + background-repeat: no-repeat; + background-image: url('data:image/svg+xml; utf8, '); } + +.markdown-preview-section > .contains-task-list { + padding-bottom: 0.5em; } + +body .markdown-source-view.mod-cm6 .HyperMD-task-line[data-task]:not([data-task=" "]), +body .markdown-preview-view ul > li.task-list-item.is-checked { + text-decoration: none; + color: var(--text-normal); } + +body.minimal-strike-lists .markdown-source-view.mod-cm6 .HyperMD-task-line[data-task]:is([data-task="x"]), +body.minimal-strike-lists .markdown-preview-view ul li[data-task="x"].task-list-item.is-checked, +body.minimal-strike-lists li[data-task="x"].task-list-item.is-checked { + color: var(--text-faint); + text-decoration: line-through; } + +/* Preview offset */ +ul > li.task-list-item .task-list-item-checkbox { + margin-left: -1.35em; } + +/* Editor */ +.mod-cm6 .HyperMD-task-line[data-task] .task-list-item-checkbox { + margin: -2px 1px 0 -0.6em; } + +.is-mobile .mod-cm6 .HyperMD-task-line[data-task] .task-list-item-checkbox { + margin-left: -0.4em; } + +.is-mobile .markdown-preview-view input[type=checkbox].task-list-item-checkbox { + top: 0.2em; } + +.task-list-item-checkbox, +.markdown-preview-view .task-list-item-checkbox { + filter: none; + width: var(--checkbox-size); + height: var(--checkbox-size); } + +.markdown-preview-view .task-list-item-checkbox { + position: relative; + top: var(--checkbox-top); + left: var(--checkbox-left); + line-height: 0; + margin: var(--checkbox-margin); } + +.markdown-preview-view ul > li.task-list-item { + text-indent: 0; + line-height: var(--line-height); } + +.markdown-preview-view .task-list-item { + padding-inline-start: 0; } + +.side-dock-plugin-panel-inner { + padding-right: 6px; + padding-left: 6px; } + +/* Code blocks */ +/* Live Preview */ +.markdown-source-view.mod-cm6.is-readable-line-width .cm-editor .HyperMD-codeblock.cm-line, +.mod-cm6 .cm-editor .HyperMD-codeblock.cm-line { + padding-left: 10px; + padding-right: 10px; } + +/* Reading */ +.cm-s-obsidian span.cm-inline-code, +.markdown-rendered code, +.markdown-preview-view code { + color: var(--text-code); + font-size: var(--font-code); } + +.markdown-preview-view td code, +.markdown-source-view.mod-cm6 td code { + font-size: calc(var(--font-code) - 2px); } + +.markdown-preview-view pre code { + background-color: transparent; } + +.markdown-preview-view pre, +.markdown-source-view.mod-cm6 .cm-preview-code-block pre.dataview-error, +.mod-cm6 .cm-editor .HyperMD-codeblock.cm-line, +.cm-s-obsidian .HyperMD-codeblock { + color: var(--text-code); + font-size: var(--font-code); } + +button.copy-code-button { + cursor: var(--cursor); + box-shadow: none; + font-size: var(--font-adaptive-smaller); + background-color: transparent; + color: var(--text-faint); + padding: 0.25em 0.75em; } + +button.copy-code-button:hover { + background-color: var(--interactive-normal); + color: var(--text-muted); } + +.theme-light :not(pre) > code[class*="language-"], +.theme-light pre[class*="language-"] { + background-color: var(--background-primary-alt); } + +.theme-light code[class*="language-"], +.theme-light pre[class*="language-"] { + text-shadow: none; } + +.markdown-source-view.mod-cm6 .code-block-flair { + font-size: var(--font-smaller); + padding: 5px 0; + color: var(--text-muted); } + +.cm-s-obsidian .hmd-fold-html-stub, +.cm-s-obsidian .hmd-fold-code-stub, +.cm-s-obsidian.CodeMirror .HyperMD-hover > .HyperMD-hover-content code, +.cm-s-obsidian .cm-formatting-hashtag, +.cm-s-obsidian .cm-inline-code, +.cm-s-obsidian .HyperMD-codeblock, +.cm-s-obsidian .HyperMD-hr, +.cm-s-obsidian .cm-hmd-frontmatter, +.cm-s-obsidian .cm-hmd-orgmode-markup, +.cm-s-obsidian .cm-formatting-code, +.cm-s-obsidian .cm-math, +.cm-s-obsidian span.hmd-fold-math-placeholder, +.cm-s-obsidian .CodeMirror-linewidget kbd, +.cm-s-obsidian .hmd-fold-html kbd +.CodeMirror-code { + font-family: var(--font-monospace); } + +/* Drag ghost */ +body.is-dragging { + cursor: grabbing; + cursor: -webkit-grabbing; } + +.workspace-drop-overlay:before, +.mod-drag { + opacity: 0; + border-radius: 0 !important; } + +.drag-ghost, +.drag-ghost.mod-leaf { + border: none; + background-color: rgba(0, 0, 0, 0.7); + font-size: var(--font-adaptive-small); + padding: 3px 8px 4px; + color: white; + font-weight: 500; + border-radius: 5px; } + +.drag-ghost-icon { + display: none; } + +.drag-ghost-self svg { + margin-right: 4px; + opacity: 0.5; + display: none; } + +.drag-ghost-action { + padding: 0; + font-weight: 400; + color: rgba(255, 255, 255, 0.7); + font-size: var(--font-adaptive-smaller); } + +.mod-drag { + opacity: 0; + border: 2px solid var(--text-accent); + background-color: var(--background-primary); } + +.view-header.is-highlighted:after { + background-color: var(--text-selection); } + +.view-header.is-highlighted .view-actions { + background: transparent; } + +/* +.workspace-fake-target-overlay, +.workspace-fake-target-overlay.is-in-sidebar, +.workspace-drop-overlay, +.view-header.is-highlighted:after { + opacity:0; + background-color:var(--background-primary); +} +*/ +/* Editor mode (CodeMirror 6 Live Preview) */ +/* Fix strange Obsidian ghost textearea bug on right click */ +.CodeMirror-wrap > div > textarea { + opacity: 0; } + +.markdown-source-view.mod-cm6 hr { + border-width: 2px; } + +.mod-cm6 .cm-editor .cm-line { + padding: 0; } + +.cm-editor .cm-content { + padding-top: 0.5em; } + +.markdown-source-view { + color: var(--text-normal); } + +.markdown-source-view.mod-cm6 .cm-scroller { + padding-top: 15px; + padding-left: 0; + padding-right: 0; } + +/* Gutters */ +body:not(.is-mobile) .markdown-source-view.mod-cm6 .cm-gutters { + position: absolute !important; + z-index: 0; } + +.cm-editor .cm-lineNumbers .cm-gutterElement { + min-width: 25px; } + +/* Line numbers */ +@media (max-width: 400pt) { + .cm-editor .cm-lineNumbers .cm-gutterElement { + padding-right: 4px; + padding-left: 8px; } } +.cm-editor .cm-lineNumbers .cm-gutterElement { + font-variant-numeric: tabular-nums; } + +.cm-editor .cm-lineNumbers .cm-gutterElement.cm-active, +.cm-editor .cm-gutterElement.cm-active .cm-heading-marker { + color: var(--text-muted); } + +/* Code execution blocks, e.g. Dataview */ +.markdown-source-view.mod-cm6 .edit-block-button { + cursor: var(--cursor); + color: var(--text-faint); + background-color: var(--background-primary); + top: 0; + right: auto; + left: 0px; + opacity: 0; + transition: opacity 200ms; + padding: 4px 4px 4px 9px; } + .markdown-source-view.mod-cm6 .edit-block-button svg { + margin: 0 !important; } + +.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .edit-block-button { + width: 30px !important; + padding-left: 7px !important; + transform: none !important; + margin-left: 0 !important; } + +.is-live-preview:not(.is-readable-line-width) .cm-embed-block > .edit-block-button { + padding-left: 0px !important; + margin-left: 0 !important; + transform: none !important; + right: 0; + left: auto; + padding: 4px; } + +.markdown-source-view.mod-cm6 .edit-block-button:hover { + background-color: var(--background-primary); + color: var(--text-muted); } + +.markdown-source-view.mod-cm6 .edit-block-button svg { + opacity: 1; + width: var(--icon-size); + height: var(--icon-size); } + +.markdown-source-view.mod-cm6 .edit-block-button:hover svg { + opacity: 1; } + +.markdown-source-view.mod-cm6 .cm-embed-block { + padding: 0; + border: 0; + border-radius: 0; } + +.markdown-source-view.mod-cm6 .cm-embed-block:hover { + border: 0; } + +/* Live Preview folding */ +.markdown-source-view.mod-cm6 .cm-foldPlaceholder { + color: var(--text-faint); } + +.markdown-source-view.mod-cm6.is-live-preview .HyperMD-quote { + background-color: transparent; + border-left-width: 1px; } + +.cm-editor .cm-foldPlaceholder, +.markdown-source-view.mod-cm6 .cm-fold-indicator .collapse-indicator { + cursor: var(--cursor); } + +.markdown-source-view.mod-cm6 .HyperMD-list-line.HyperMD-list-line-1 .cm-fold-indicator .collapse-indicator { + right: 8px; } + +.markdown-source-view.mod-cm6 .HyperMD-list-line.HyperMD-task-line:not(.HyperMD-list-line-1) .cm-fold-indicator .collapse-indicator { + right: 8px; + width: auto; } + +.markdown-source-view.mod-cm6 .HyperMD-list-line:not(.HyperMD-list-line-1) .cm-fold-indicator .collapse-indicator { + right: -8px; + top: 1px; + width: 26px; } + +ul > li.is-collapsed::marker, +.markdown-source-view.mod-cm6 .is-collapsed ~ .cm-formatting-list .list-bullet:after { + color: var(--text-accent); } + +.cm-gutterElement .collapse-indicator, +.markdown-source-view.mod-cm6 .cm-fold-indicator .collapse-indicator, +.markdown-source-view.mod-cm6 .fold-gutter { + opacity: 0; } + +.cm-gutterElement:hover .collapse-indicator, +.cm-gutterElement .is-collapsed .collapse-indicator, +.markdown-source-view.mod-cm6 .cm-line:hover .cm-fold-indicator .collapse-indicator, +.markdown-source-view.mod-cm6 .cm-fold-indicator.is-collapsed .collapse-indicator, +.markdown-source-view.mod-cm6 .fold-gutter.is-collapsed, +.markdown-source-view.mod-cm6 .fold-gutter:hover, +.markdown-source-view.mod-cm6 .cm-fold-indicator.is-collapsed .collapse-indicator svg { + opacity: 1; } + +/* Live Preview text selection */ +.markdown-source-view.mod-cm6 .cm-line .cm-selection, +.markdown-source-view.mod-cm6 .cm-line .cm-inline-code .cm-selection { + background-color: var(--text-selection); } + +.cm-selectionBackground { + background-color: transparent !important; } + +body .markdown-source-view.mod-cm6.is-readable-line-width:not(.is-rtl) .cm-contentContainer { + max-width: 100%; } + +body:not(.is-mobile).minimal-folding .markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer { + max-width: 100%; } + +/* Editor mode (Legacy) */ +.theme-light .token.operator, +.theme-light .token.entity, +.theme-light .token.url, +.theme-light .language-css .token.string, +.theme-light .style .token.string, +.theme-light .cm-operator, +.theme-light .cm-string, +.theme-light .cm-string-2, +.theme-light .cm-link { + background-color: transparent; } + +.markdown-source-view.mod-cm6, +.markdown-source-view.mod-cm5, +.markdown-source-view { + padding: 0; } + +.cm-s-obsidian .CodeMirror-code { + padding-right: 0; } + +.CodeMirror-lines { + padding-bottom: 170px; } + +.CodeMirror pre.CodeMirror-line, +.CodeMirror pre.CodeMirror-line-like { + padding-left: 0; + padding-right: 0; } + +.cm-s-obsidian pre.HyperMD-list-line { + padding-top: 0; } + +.workspace .markdown-preview-view { + padding: 0; } + +.workspace .markdown-preview-view .markdown-embed { + margin: 0; } + +.workspace .markdown-preview-view .markdown-embed-content { + max-height: none; } + +.markdown-embed-title, +.internal-embed .markdown-preview-section { + max-width: 100%; } + +.CodeMirror-linenumber { + font-size: var(--font-adaptive-small) !important; + font-feature-settings: 'tnum'; + color: var(--text-faint); + padding-top: 3px; } + +span.cm-image-marker, +.cm-s-obsidian span.cm-footref.cm-formatting.cm-formatting-link.cm-formatting-link-end, +.cm-s-obsidian .cm-formatting-link + span.cm-link.cm-formatting.cm-formatting-link-end, +.cm-s-obsidian .cm-active span.cm-link.cm-hmd-barelink.cm-formatting-link-start, +.cm-s-obsidian span.cm-link.cm-hmd-barelink.cm-formatting-link-start, +.cm-s-obsidian span.cm-formatting-link { + color: var(--text-faint); } + +/* Editor Mode Footnotes */ +.cm-s-obsidian span.cm-footref { + font-size: var(--font-adaptive-normal); } + +.cm-s-obsidian pre.HyperMD-footnote { + font-size: var(--font-adaptive-small); + padding-left: 20px; } + +/* Editor Mode Quotes */ +.cm-formatting-quote { + color: var(--text-faint) !important; } + +/* Transcluded notes and embeds */ +/* Strict embeds (naked) */ +.embed-strict .internal-embed .markdown-embed { + padding: 0; + border: none; } + +.embed-strict .internal-embed .markdown-embed .markdown-embed-title { + display: none; } + +.embed-strict .internal-embed:not([src*="#^"]) .markdown-embed-link { + width: 30px; } + +.embed-strict.contextual-typography .internal-embed .markdown-preview-view .markdown-preview-sizer > div, +.contextual-typography .embed-strict .internal-embed .markdown-preview-view .markdown-preview-sizer > div { + margin: 0; + width: 100%; } + +.markdown-embed .markdown-preview-view .markdown-preview-sizer { + padding-bottom: 0 !important; } + +.markdown-preview-view.markdown-embed .markdown-preview-sizer, +.markdown-preview-view.is-readable-line-width .markdown-embed .markdown-preview-sizer { + max-width: 100%; + width: 100%; + min-height: 0 !important; + padding-bottom: 0 !important; } + +.markdown-embed .markdown-preview-section div:last-child p, +.markdown-embed .markdown-preview-section div:last-child ul { + margin-block-end: 2px; } + +.markdown-preview-view .markdown-embed { + margin-top: var(--nested-padding); + padding: 0 calc(var(--nested-padding) / 2) 0 var(--nested-padding); } + +.markdown-embed-title { + line-height: 18px; + height: 24px; } + +.internal-embed:not([src*="#^"]) .markdown-embed-link { + right: 0; + width: 100%; } + +.markdown-embed-link, +.file-embed-link { + top: 0px; + right: 0; + text-align: right; } + +.file-embed-link svg, +.markdown-embed-link svg { + width: 16px; + height: 16px; + opacity: 0; } + +.markdown-embed .file-embed-link:hover svg, +.markdown-embed .markdown-embed-link:hover svg { + opacity: 1; } + +.markdown-embed-link:hover, .file-embed-link:hover { + color: var(--text-muted); } + +.markdown-preview-view .markdown-embed-content > .markdown-preview-view { + max-height: none !important; } + +.markdown-embed-content { + max-height: none !important; } + +.markdown-embed .markdown-preview-view { + padding: 0; } + +.internal-embed .markdown-embed { + border: 0; + border-left: 1px solid var(--quote-opening-modifier); + border-radius: 0; } + +/* Headings and fonts */ +h1, h2, h3, h4, h5, strong { + font-weight: var(--bold-weight); } + +h1, h2, h3, h4 { + letter-spacing: -0.02em; } + +body, input, button { + font-family: var(--font-interface); } + +.cm-s-obsidian span.cm-error { + color: var(--red); } + +.markdown-preview-view, +.popover, +.workspace-leaf-content[data-type=markdown] { + font-family: var(--font-text); } + +body, input, button, +.markdown-preview-view, +.markdown-source-view.mod-cm6.is-live-preview .cm-scroller, +.cm-s-obsidian, +.cm-s-obsidian .cm-formatting-hashtag { + font-size: var(--font-adaptive-normal); + font-weight: var(--normal-weight); + line-height: var(--line-height); + -webkit-font-smoothing: subpixel-antialiased; } + +.markdown-source-view.mod-cm6 .cm-scroller, +.markdown-source-view, +.cm-s-obsidian .cm-formatting-hashtag, +.cm-s-obsidian, +.cm-s-obsidian span.cm-formatting-task { + line-height: var(--line-height); + font-family: var(--font-editor); + -webkit-font-smoothing: subpixel-antialiased; } + +/* Use reading font in live preview */ +.lp-reading-font .markdown-source-view.mod-cm6.is-live-preview .cm-scroller { + font-family: var(--font-text); } + +.cm-s-obsidian span.cm-formatting-task { + font-family: var(--font-editor); + line-height: var(--line-height); } + +.cm-s-obsidian .cm-header, +.cm-s-obsidian .cm-strong { + font-weight: var(--bold-weight); } + +strong, +.cm-s-obsidian .cm-strong { + color: var(--text-bold); } + +em, +.cm-s-obsidian .cm-em { + color: var(--text-italic); } + +.cm-formatting-header, +.cm-s-obsidian .cm-formatting-header.cm-header-1, +.cm-s-obsidian .cm-formatting-header.cm-header-2, +.cm-s-obsidian .cm-formatting-header.cm-header-3, +.cm-s-obsidian .cm-formatting-header.cm-header-4, +.cm-s-obsidian .cm-formatting-header.cm-header-5, +.cm-s-obsidian .cm-formatting-header.cm-header-6 { + color: var(--text-faint); } + +.view-header-title, +.file-embed-title, +.markdown-embed-title { + letter-spacing: -0.02em; + text-align: left; + font-size: var(--title-size); + font-weight: var(--title-weight); } + +.view-header-title { + color: var(--title-color-inactive); } + +.file-embed-title, +.markdown-embed-title, +.workspace-leaf.mod-active .view-header-title { + color: var(--title-color); } + +.cm-s-obsidian .HyperMD-header { + line-height: 1.3; } + +.mod-cm6 .cm-editor .HyperMD-header-1, +.mod-cm6 .cm-editor .HyperMD-header-2, +.mod-cm6 .cm-editor .HyperMD-header-3, +.mod-cm6 .cm-editor .HyperMD-header-4, +.mod-cm6 .cm-editor .HyperMD-header-5, +.mod-cm6 .cm-editor .HyperMD-header-6 { + padding-top: 0.5em; } + +h1, +.empty-state-title, +.markdown-rendered h1, +.markdown-preview-view h1, +.cm-s-obsidian .cm-header-1 { + font-variant: var(--h1-variant); + letter-spacing: -0.02em; + line-height: 1.3; + font-family: var(--h1-font); + font-size: var(--h1); + color: var(--h1-color); + font-weight: var(--h1-weight); + font-style: var(--h1-style); } + h1 a, + .empty-state-title a, + .markdown-rendered h1 a, + .markdown-preview-view h1 a, + .cm-s-obsidian .cm-header-1 a { + font-weight: var(--h1-weight); } + +.markdown-rendered h2, +.markdown-preview-view h2, +.cm-s-obsidian .cm-header-2 { + font-variant: var(--h2-variant); + letter-spacing: -0.01em; + line-height: 1.3; + font-family: var(--h2-font); + font-size: var(--h2); + color: var(--h2-color); + font-weight: var(--h2-weight); + font-style: var(--h2-style); } + .markdown-rendered h2 a, + .markdown-preview-view h2 a, + .cm-s-obsidian .cm-header-2 a { + font-weight: var(--h2-weight); } + +.markdown-rendered h3, +.markdown-preview-view h3, +.cm-s-obsidian .cm-header-3 { + font-variant: var(--h3-variant); + letter-spacing: -0.01em; + line-height: 1.4; + font-family: var(--h3-font); + font-size: var(--h3); + color: var(--h3-color); + font-weight: var(--h3-weight); + font-style: var(--h3-style); } + .markdown-rendered h3 a, + .markdown-preview-view h3 a, + .cm-s-obsidian .cm-header-3 a { + font-weight: var(--h3-weight); } + +.markdown-rendered h4, +.markdown-preview-view h4, +.cm-s-obsidian .cm-header-4 { + font-variant: var(--h4-variant); + letter-spacing: 0.02em; + font-family: var(--h4-font); + font-size: var(--h4); + color: var(--h4-color); + font-weight: var(--h4-weight); + font-style: var(--h4-style); } + .markdown-rendered h4 a, + .markdown-preview-view h4 a, + .cm-s-obsidian .cm-header-4 a { + font-weight: var(--h4-weight); } + +.markdown-rendered h5, +.markdown-preview-view h5, +.cm-s-obsidian .cm-header-5 { + font-variant: var(--h5-variant); + letter-spacing: 0.02em; + font-family: var(--h5-font); + font-size: var(--h5); + color: var(--h5-color); + font-weight: var(--h5-weight); + font-style: var(--h5-style); } + .markdown-rendered h5 a, + .markdown-preview-view h5 a, + .cm-s-obsidian .cm-header-5 a { + font-weight: var(--h5-weight); } + +.markdown-rendered h6, +.markdown-preview-view h6, +.cm-s-obsidian .cm-header-6 { + font-variant: var(--h6-variant); + letter-spacing: 0.02em; + font-family: var(--h6-font); + font-size: var(--h6); + color: var(--h6-color); + font-weight: var(--h6-weight); + font-style: var(--h6-style); } + .markdown-rendered h6 a, + .markdown-preview-view h6 a, + .cm-s-obsidian .cm-header-6 a { + font-weight: var(--h6-weight); } + +/* Footnotes */ +/* Preview mode */ +.footnotes-list { + margin-block-start: -10px; + padding-inline-start: 20px; + font-size: var(--font-adaptive-small); } + +.footnotes-list p { + display: inline; + margin-block-end: 0; + margin-block-start: 0; } + +.footnote-ref a { + text-decoration: none; } + +.footnote-backref { + color: var(--text-faint); } + +.footnotes .is-flashing, +.minimal-folding .footnotes .is-flashing { + box-shadow: -1px 0px 0 3px var(--text-highlight-bg); } + +.cm-s-obsidian .HyperMD-footnote, +.footnotes { + font-size: calc(var(--font-adaptive-normal) - 2px); } + +.markdown-preview-view .footnotes hr { + margin: 0.5em 0 1em; + border-width: 1px 0 0 0; } + +/* YAML Frontmatter */ +.theme-dark pre.frontmatter[class*="language-yaml"], +.theme-light pre.frontmatter[class*="language-yaml"] { + padding: 0 0 0px 0; + background: transparent; + font-family: var(--font-text); + line-height: 1.2; + border-radius: 0; + border-bottom: 0px solid var(--background-modifier-border); } + +.markdown-preview-view .table-view-table > thead > tr > th { + border-color: var(--background-modifier-border); } + +.theme-dark .frontmatter .token, +.theme-light .frontmatter .token, +.markdown-preview-section .frontmatter code { + font-family: var(--font-text); + color: var(--text-faint) !important; } + +.markdown-source-view .cm-s-obsidian .cm-hmd-frontmatter { + font-family: var(--font-editor); + color: var(--text-muted); } + +.markdown-preview-section .frontmatter code { + color: var(--text-muted); + font-size: var(--font-adaptive-small); } + +.cm-s-obsidian .cm-hmd-frontmatter, +.cm-s-obsidian .cm-def.cm-hmd-frontmatter { + font-size: var(--font-adaptive-small); + color: var(--text-muted); } + +/* Preview mode */ +.frontmatter code.language-yaml { + padding: 0; } + +.frontmatter-collapse-indicator.collapse-indicator { + display: none; } + +.frontmatter-container .tag { + font-size: var(--font-adaptive-smaller); } + +.frontmatter-container .frontmatter-alias { + color: var(--text-muted); } + +.frontmatter-container { + font-size: var(--font-adaptive-small); + padding: 10px 0; + background: transparent; + border-radius: 0; + margin: 0; + border: 0; + border-bottom: 1px solid var(--background-modifier-border); } + +.frontmatter-container .frontmatter-container-header { + padding: 0; + font-weight: 500; + border-bottom: 0; + font-size: var(--font-adaptive-small); } + +/* File browser */ +.is-mobile .nav-folder.mod-root > .nav-folder-title .nav-folder-title-content { + display: none; } + +.nav-file-tag { + font-weight: 400; } + +.nav-header { + padding: 0; } + +.nav-buttons-container { + padding: 10px 5px 0px 8px; + margin-bottom: 0px !important; + justify-content: flex-start; + border: 0; } + +.nav-files-container { + overflow-x: hidden; + padding-bottom: 50px; } + +body:not(.is-mobile) .nav-folder.mod-root > .nav-folder-title .nav-folder-title-content { + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + padding-bottom: 7px; + margin-left: -7px; + font-size: var(--font-adaptive-smaller); } + +.nav-folder-title { + margin: 0 0 0 8px; + min-width: auto; + width: calc(100% - 16px); + padding: 0 10px 0 16px; + line-height: 1.5; + cursor: var(--cursor); + border: none; } + +.nav-folder.mod-root > .nav-folder-title.is-being-dragged-over { + background-color: var(--text-selection); } + +.nav-folder-title.is-being-dragged-over { + background-color: var(--text-selection); + border-color: var(--text-selection); + border-radius: var(--radius-m); + border: 0px solid transparent; } + +.nav-folder-title-content { + padding: 1px 4px; } + +.nav-folder-collapse-indicator { + top: 1px; + margin-left: -10px; } + +/* Fix :active state when right-clicking in file explorer */ +.nav-file-title.is-being-dragged, +.nav-folder-title.is-being-dragged, +body:not(.is-grabbing) .nav-file-title.is-being-dragged:hover, +body:not(.is-grabbing) .nav-folder-title.is-being-dragged:hover { + background-color: var(--background-tertiary); + color: var(--text-normal); + box-shadow: 0 0 0 2px var(--background-modifier-border-focus); + z-index: 1; } + +.workspace-leaf.mod-active .nav-folder.has-focus > .nav-folder-title, +.workspace-leaf.mod-active .nav-file.has-focus { + border: none; + background-color: transparent; } + +.nav-file { + margin-left: 12px; + padding-right: 4px; + border: none; } + +.nav-file-title { + width: calc(100% - 30px); + margin: 0 8px 0 -4px; + padding: 0; + border-width: 0; + line-height: 1.6; + border-color: var(--background-secondary); + border-radius: var(--radius-m); + cursor: var(--cursor); } + +.nav-file-title.is-active, +.nav-folder-title.is-active, +.nav-file-title.is-being-dragged, +body:not(.is-grabbing) .nav-folder-title.is-active:hover, +body:not(.is-grabbing) .nav-folder-title:hover, +body:not(.is-grabbing) .nav-file-title.is-active:hover { + background-color: var(--background-tertiary); + color: var(--text-normal); } + +.nav-file-title-content { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 2px 7px; + border: 0; + vertical-align: middle; + cursor: var(--cursor); } + +.drop-indicator { + border-width: 1px; } + +.nav-file-icon { + margin: 1px 0 0 0; + vertical-align: bottom; + padding: 0 0 0 5px; } + +.workspace-leaf-content[data-type=starred] .nav-file-title-content { + width: calc(100% - 15px); } + +.workspace-leaf-content[data-type=starred] .nav-file-icon { + opacity: 0.5; } + +body:not(.is-grabbing) .nav-file-title:hover .nav-folder-collapse-indicator, +body:not(.is-grabbing) .nav-folder-title:hover .nav-folder-collapse-indicator, +body:not(.is-grabbing) .nav-file-title:hover, +body:not(.is-grabbing) .nav-folder-title:hover { + background: transparent; } + +.nav-file-title, +.tree-item-self, +.nav-folder-title, +.is-collapsed .search-result-file-title, +.tag-pane-tag { + font-size: var(--font-adaptive-small); + color: var(--text-muted); } + +.search-result-file-title { + font-size: var(--font-adaptive-small); + color: var(--text-normal); + font-weight: var(--normal-weight); } + +.side-dock-collapsible-section-header { + font-size: var(--font-adaptive-small); + color: var(--text-muted); + cursor: var(--cursor); + margin-right: 0; + margin-left: 0; } + +.side-dock-collapsible-section-header:hover, +.side-dock-collapsible-section-header:not(.is-collapsed) { + color: var(--text-muted); + background: transparent; } + +.tree-view-item-self:hover .tree-view-item-collapse, +.tree-item-self.is-clickable:hover { + color: var(--text-muted); + background: transparent; + cursor: var(--cursor); } + +.tree-item-self.is-clickable { + cursor: var(--cursor); } + +.search-result-collapse-indicator svg, +.search-result-file-title:hover .search-result-collapse-indicator svg, +.side-dock-collapsible-section-header-indicator:hover svg, +.side-dock-collapsible-section-header:hover .side-dock-collapsible-section-header-indicator svg, +.markdown-preview-view .collapse-indicator svg, +.tree-view-item-collapse svg, +.is-collapsed .search-result-collapse-indicator svg, +.nav-folder-collapse-indicator svg, +.side-dock-collapsible-section-header-indicator svg, +.is-collapsed .side-dock-collapsible-section-header-indicator svg { + color: var(--text-faint); + cursor: var(--cursor); } + +.search-result-collapse-indicator, +.search-result-file-title:hover .search-result-collapse-indicator, +.side-dock-collapsible-section-header-indicator:hover, +.side-dock-collapsible-section-header:hover .side-dock-collapsible-section-header-indicator, +.markdown-preview-view .collapse-indicator, +.tree-view-item-collapse, +.is-collapsed .search-result-collapse-indicator, +.nav-folder-collapse-indicator, +.side-dock-collapsible-section-header-indicator, +.is-collapsed .side-dock-collapsible-section-header-indicator { + color: var(--text-faint); + cursor: var(--cursor); } + +.is-collapsed .search-result-file-title:hover, +.search-result-file-title:hover, +.nav-folder-title.is-being-dragged-over .nav-folder-collapse-indicator svg { + color: var(--text-normal); } + +/* --------------- */ +/* Nested items */ +.nav-folder-collapse-indicator, +.tree-item-self .collapse-icon { + color: var(--background-modifier-border-hover); } + +.tree-item-self .collapse-icon { + padding-left: 0; + width: 18px; + margin-left: -18px; + justify-content: center; } + +.tree-item-self:hover .collapse-icon { + color: var(--text-normal); } + +.tree-item-self { + padding-left: 15px; } + +.tree-item { + padding-left: 5px; } + +.tree-item-flair { + font-size: var(--font-adaptive-smaller); + right: 0; + background: transparent; + color: var(--text-faint); } + +.tree-item-flair-outer:after { + content: ''; } + +.tree-item-self.is-clickable { + cursor: var(--cursor); } + +.tree-item-self.is-clickable:hover { + background: transparent; } + +.tree-item-self:hover .tree-item-flair { + background: transparent; + color: var(--text-muted); } + +.tree-item-children { + margin-left: 5px; } + +/* Folding icons in Preview */ +.collapse-indicator svg, +.markdown-preview-view .heading-collapse-indicator.collapse-indicator svg, +.markdown-preview-view ol > li .collapse-indicator svg, +.markdown-preview-view ul > li .collapse-indicator svg { + opacity: 0; } + +h1:hover .heading-collapse-indicator.collapse-indicator svg, +h2:hover .heading-collapse-indicator.collapse-indicator svg, +h3:hover .heading-collapse-indicator.collapse-indicator svg, +h4:hover .heading-collapse-indicator.collapse-indicator svg, +h5:hover .heading-collapse-indicator.collapse-indicator svg, +.HyperMD-header:hover .collapse-indicator svg, +.markdown-preview-view .is-collapsed .collapse-indicator svg, +.markdown-preview-view .collapse-indicator:hover svg, +.collapse-indicator:hover svg { + opacity: 1; } + +.markdown-preview-view div.is-collapsed h1::after, +.markdown-preview-view div.is-collapsed h2::after, +.markdown-preview-view div.is-collapsed h3::after, +.markdown-preview-view div.is-collapsed h4::after, +.markdown-preview-view div.is-collapsed h5::after, +.markdown-preview-view ol .is-collapsed::after, +.markdown-preview-view ul .is-collapsed::after { + content: "..."; + padding: 5px; + color: var(--text-faint); } + +.markdown-preview-view ol > li.task-list-item .collapse-indicator, +.markdown-preview-view ul > li.task-list-item .collapse-indicator { + margin-left: -48px; + position: absolute; } + +.markdown-preview-view ol > li .collapse-indicator { + padding-right: 20px; } + +.markdown-preview-view .heading-collapse-indicator.collapse-indicator { + margin-left: -28px; + padding-right: 7px 8px 7px 0; } + +.markdown-preview-view .collapse-indicator { + position: absolute; + margin-left: -44px; + padding-bottom: 10px; + padding-top: 0px; } + +.markdown-preview-view ul > li:not(.task-list-item) .collapse-indicator { + padding-right: 20px; } + +.list-collapse-indicator .collapse-indicator .collapse-icon { + opacity: 0; } + +.markdown-preview-view ul > li h1, +.markdown-preview-view ul > li h2, +.markdown-preview-view ul > li h3, +.markdown-preview-view ul > li h4 { + display: inline; } + +/* Folding icons in Edit mode */ +.markdown-source-view.mod-cm6.is-folding .cm-contentContainer { + padding-left: 0; } + +.CodeMirror-foldgutter-folded, +.CodeMirror-foldgutter-open { + cursor: var(--cursor); } + +body .frontmatter-collapse-indicator svg.right-triangle { + background-color: currentColor; + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body span[title="Fold line"], +body span[title="Unfold line"] { + position: relative; + font-size: 0; + color: transparent; + display: flex; + height: auto; + align-items: center; } + +body span[title="Fold line"]:hover, +body span[title="Unfold line"]:hover, +body .CodeMirror-foldgutter-open:hover, +body .CodeMirror-foldgutter-folded:hover { + color: var(--text-muted); } + +body span[title="Fold line"]:after, +body span[title="Unfold line"]:after, +body .CodeMirror-foldgutter-open:after, +body .CodeMirror-foldgutter-folded:after { + text-align: center; + color: var(--text-faint); + font-size: 1.25rem; + display: flex; + align-items: center; + justify-content: center; + margin-left: 0px; + width: 1rem; + height: 1rem; } + +body:not(.is-mobile) span[title="Fold line"]:after, +body:not(.is-mobile) span[title="Unfold line"]:after, +body:not(.is-mobile) .CodeMirror-foldgutter-open:after, +body:not(.is-mobile) .CodeMirror-foldgutter-folded:after { + margin-top: 0.35rem; + margin-left: 2px; } + +body .is-mobile .cm-editor .cm-lineNumbers .cm-gutterElement { + padding: 0 3px 0 0px; + min-width: 15px; + text-align: right; + white-space: nowrap; } + +body span[title="Fold line"]:after, +body span[title="Unfold line"]:after { + font-size: 1rem; + line-height: 1; } + +body span[title="Fold line"]:after, +body span[title="Unfold line"]:after { + font-size: 1rem; + line-height: 1; } + +body span[title="Unfold line"]:after, +body .CodeMirror-foldgutter-folded:after { + background-color: var(--text-faint); + height: 12px; + width: 12px; + -webkit-mask-image: url('data:image/svg+xml;utf8,'); + transform: translateY(-2px); + transform: rotate(-90deg); } + +body span[title="Fold line"]:after, +body .CodeMirror-foldgutter-open:after { + background-color: var(--text-faint); + height: 12px; + width: 12px; + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +.is-mobile span[title="Fold line"]:after, +.is-mobile .CodeMirror-foldgutter-open:after { + transform: translateX(-2px) !important; } + +span[title="Fold line"], +.CodeMirror-foldgutter-open:after { + opacity: 0; } + +span[title="Fold line"]:hover, +span[title="Unfold line"], +.CodeMirror-foldgutter-folded:after, +.CodeMirror-code > div:hover .CodeMirror-foldgutter-open:after { + opacity: 1; } + +span[title="Unfold line"]:hover, +.CodeMirror-code > div:hover .CodeMirror-foldgutter-open:hover:after, +.CodeMirror-code > div:hover .CodeMirror-foldgutter-folded:hover:after { + opacity: 1; } + +body.is-mobile span[title="Unfold line"]:after, +body.is-mobile .CodeMirror-foldgutter-folded:after { + content: "›"; + font-family: sans-serif; + transform: translateY(-2px); + transform: rotate(-90deg) translateY(2px) translateX(-0.45em); } + +body.is-mobile span[title="Fold line"]:after, +body.is-mobile .CodeMirror-foldgutter-open:after { + content: "›"; + font-family: sans-serif; + transform: rotate(360deg); } + +/* Icons and icon buttons */ +body svg.right-triangle { + color: var(--text-muted); + background-color: var(--text-muted); + height: 12px; + width: 12px; + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +.nav-action-button svg { + width: 15px; + height: 15px; } + +body .view-header-icon, +body .graph-controls-button, +body .clickable-icon, +body .menu-item-icon, +body .side-dock-ribbon-action, +body .nav-action-button, +body .view-action, +body .workspace-tab-header-inner-icon { + line-height: 0; } + +body .view-header-icon svg path, +body .graph-controls-button svg path, +body .clickable-icon svg path, +body .menu-item-icon svg path, +body .side-dock-ribbon-action svg path, +body .nav-action-button svg path, +body .view-action svg path, +body .workspace-tab-header-inner-icon svg path { + stroke-width: 2px; } + +body .view-action svg.cross path { + stroke-width: 2px; } + +.workspace-ribbon-collapse-btn svg path { + stroke-width: 4px; } + +.nav-action-button svg path { + stroke-width: 2px; } + +.clickable-icon { + cursor: var(--cursor); } + +.graph-controls-button, +.view-action, +.view-header-icon, +.nav-action-button, +.workspace-tab-header, +.side-dock-ribbon-tab, +.side-dock-ribbon-action, +.workspace-tab-header { + background: transparent; + color: var(--icon-color); + opacity: var(--icon-muted); + transition: opacity 0.1s ease-in-out; + cursor: var(--cursor); + line-height: 0; } + +.graph-controls-button, +.view-header-icon, +.workspace-tab-header-inner-icon, +.side-dock-ribbon-action, +.workspace-ribbon-collapse-btn { + margin: 0; + padding: 4px 4px; + height: 26px; + border-radius: var(--radius-m); } + +.view-header-icon { + display: flex; + align-items: center; } + +.workspace-ribbon-collapse-btn { + margin: 0; + padding: 2px 4px; } + +.side-dock-ribbon-action { + border-left: 0; + margin: 0 6px 6px; } + +.nav-action-button, +.workspace-leaf-content[data-type='search'] .nav-action-button, +.workspace-leaf-content[data-type='backlink'] .nav-action-button { + padding: 3px 5px 3px; + margin: 0 0 7px 0px; + height: 26px; + text-align: center; + border-radius: var(--radius-m); } + +.nav-action-button.is-active, +.workspace-leaf-content[data-type='dictionary-view'] .nav-action-button.is-active, +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active, +.workspace-leaf-content[data-type='backlink'] .nav-action-button.is-active, +.workspace-leaf-content[data-type='tag'] .nav-action-button.is-active, +.workspace-tab-header.is-active, +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active { + background: transparent; + color: var(--icon-color); + opacity: 1; + transition: opacity 0.1s ease-in-out; } + +.nav-action-button.is-active, +.workspace-tab-header.is-active:hover { + color: var(--icon-color); } + +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active { + background: transparent; } + +.graph-controls-button:hover, +.view-action:hover, +.view-action.is-active:hover, +.view-header-icon:hover, +.nav-action-button:hover, +.nav-action-button.is-active:hover, +.workspace-tab-header:hover, +.side-dock-ribbon-tab:hover, +.side-dock-ribbon-action:hover { + color: var(--icon-color-hover); + opacity: 1; + transition: opacity 0.1s ease-in-out; } + +.graph-controls-button:hover, +.view-action:hover, +.nav-action-button:hover, +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active:hover, +.workspace-leaf-content[data-type='backlink'] .nav-action-button.is-active:hover, +.workspace-drawer-tab-option-item:hover, +.workspace-drawer-header-icon:hover, +.workspace-tab-header-inner-icon:hover, +.side-dock-ribbon-action:hover { + background-color: var(--background-tertiary); + border-radius: var(--radius-m); } + +/* Search */ +.is-mobile .document-search-container .document-search { + position: relative; } + +.is-mobile .search-input-container:before, +.is-mobile .workspace-leaf-content[data-type='search'] .search-input-container:before, +.is-mobile .document-search-container .document-search:before { + content: " "; + position: absolute; + z-index: 9; + top: 50%; + transform: translateY(-50%); + left: 7px; + display: block; + width: 18px; + height: 18px; + background-color: var(--text-muted); + -webkit-mask-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"%3E%3Cpath fill="currentColor" fill-rule="evenodd" d="m16.325 14.899l5.38 5.38a1.008 1.008 0 0 1-1.427 1.426l-5.38-5.38a8 8 0 1 1 1.426-1.426ZM10 16a6 6 0 1 0 0-12a6 6 0 0 0 0 12Z"%2F%3E%3C%2Fsvg%3E'); + background-position: 50% 50%; + background-repeat: no-repeat; } + +/* Indentation Guides (Obsidian 0.14.0+) */ +body { + --ig-adjust-reading:-0.65em; + --ig-adjust-edit:-1px; } + +.markdown-rendered.show-indentation-guide li.task-list-item > ul::before, +.markdown-rendered.show-indentation-guide li.task-list-item > ol::before, +.markdown-rendered.show-indentation-guide li > ul::before, +.markdown-rendered.show-indentation-guide li > ol::before { + left: var(--ig-adjust-reading); } + +/* Live Preview */ +.markdown-source-view.mod-cm6 .cm-indent::before { + transform: translateX(var(--ig-adjust-edit)); } + +.is-mobile .markdown-rendered.show-indentation-guide li > ul::before, +.is-mobile .markdown-rendered.show-indentation-guide li > ol::before { + left: calc(0em + var(--ig-adjust-reading)); } +.is-mobile .markdown-source-view.mod-cm6 .cm-indent::before { + transform: translateX(calc(2px + var(--ig-adjust-edit))); } + +/* Links */ +a { + color: var(--text-accent); + font-weight: var(--link-weight); } + +strong a { + color: var(--text-accent); + font-weight: var(--bold-weight); } + +a[href*="obsidian://search"] { + background-image: url("data:image/svg+xml,"); } + +.theme-dark a[href*="obsidian://search"] { + background-image: url("data:image/svg+xml,"); } + +.cm-s-obsidian span.cm-url:hover, +.is-live-preview.cm-s-obsidian span.cm-hmd-internal-link:hover, +.is-live-preview.cm-s-obsidian span.cm-url:hover, +.is-live-preview.cm-s-obsidian span.cm-link:hover { + color: var(--text-accent-hover); } + +a em, +.cm-s-obsidian span.cm-url, +.cm-s-obsidian .cm-url, +.cm-s-obsidian .cm-active .cm-url, +.is-live-preview.cm-s-obsidian .cm-link, +.cm-s-obsidian.mod-cm6 .cm-hmd-internal-link { + color: var(--text-accent); } + +.cm-url, +.cm-link, +.cm-hmd-internal-link { + font-weight: var(--link-weight); } + +.cm-s-obsidian .cm-active span.cm-link.cm-hmd-barelink, +.cm-s-obsidian span.cm-link.cm-hmd-barelink, +.cm-s-obsidian span.cm-link.cm-hmd-barelink:hover { + color: var(--text-normal); } + +.cm-s-obsidian .cm-active .cm-formatting.cm-formatting-link, +.cm-s-obsidian span.cm-image-alt-text.cm-link, +.cm-s-obsidian:not(.is-live-preview) .cm-formatting-link + span.cm-link { + color: var(--text-muted); } + +/* Reader Mode Lists */ +div > ol, +div > ul { + padding-inline-start: 1.4em; } + +ul > li { + min-height: 1.4em; } + +ol > li { + margin-left: 0em; } + +ul { + padding-inline-start: var(--list-indent); } + +ol { + padding-inline-start: var(--list-indent); + margin-left: 0; + list-style: default; } + +.is-mobile { + /* first level */ } + .is-mobile ul > li:not(.task-list-item)::marker { + font-size: 0.8em; } + .is-mobile .markdown-rendered ul, + .is-mobile .markdown-rendered ol { + padding-inline-start: var(--list-indent); } + .is-mobile .markdown-rendered div > ol, + .is-mobile .markdown-rendered div > ul { + padding-inline-start: 2em; } + .is-mobile .el-ol > ol, + .is-mobile .el-ul > ul { + margin-left: 0; } + +/* Live Preview */ +.cm-line:not(.HyperMD-codeblock) { + tab-size: var(--list-indent); } + +.markdown-source-view.mod-cm6 .cm-content .HyperMD-list-line { + margin-left: var(--list-edit-offset) !important; } + +/* Space between list items */ +.markdown-source-view ol > li, +.markdown-source-view ul > li, +.markdown-preview-view ol > li, +.markdown-preview-view ul > li, +.mod-cm6 .HyperMD-list-line.cm-line { + padding-top: var(--list-spacing); + padding-bottom: var(--list-spacing); } + +/* Legacy Editor Mode Lists */ +.cm-formatting-list { + color: var(--text-faint) !important; } + +/* Bullets */ +ul > li::marker, +ol > li::marker { + color: var(--text-faint); } + +ul > li:not(.task-list-item)::marker { + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; + font-size: 0.9em; } + +.mod-cm6 .HyperMD-list-line .list-bullet::after, +.mod-cm6 span.list-bullet::after { + line-height: 0.95em; + font-size: 1.4em; + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; + vertical-align: middle; + color: var(--text-faint); } + +body:not(.is-mobile) .markdown-source-view.mod-cm6 .list-bullet:after { + left: -5px; } + +body:not(.is-mobile) .markdown-source-view.mod-cm6 span.cm-formatting.cm-formatting-list.cm-formatting-list-ol { + margin-left: -5px; } + +/* Modals */ +.progress-bar-message { + color: var(--text-faint); } + +.modal { + box-shadow: var(--shadow-l); + border: none; + background: var(--background-primary); + border-radius: var(--radius-l); + overflow: hidden; + padding: 20px; } + +body:not(.is-mobile) .modal { + border: 1px solid var(--modal-border); } + +.modal.mod-settings .vertical-tab-content-container { + border-left: 1px solid var(--background-divider); + padding-bottom: 0; + padding-right: 0; } + +.modal-title { + text-align: left; + font-size: var(--h2); + line-height: 1.4; } + +.modal-content { + margin-top: 0px; + padding: 2px; + font-size: var(--font-adaptive-small); } + +.modal-content .u-center-text { + text-align: left; + font-size: var(--font-adaptive-small); } + +.modal-button-container { + margin-top: 10px; + gap: 8px; + display: flex; } + .modal-button-container button { + margin-top: 10px; } + +/* Confirm delete */ +.modal-container.mod-confirmation .modal { + width: 480px; + min-width: 0; } +.modal-container.mod-confirmation .modal-content { + margin-top: 10px; } + .modal-container.mod-confirmation .modal-content .setting-item { + margin-top: 10px; } +.modal-container.mod-confirmation .modal-button-container { + display: flex; } + .modal-container.mod-confirmation .modal-button-container > .mod-warning:nth-last-child(3) { + background: transparent; + border: none; + font-weight: 500; + color: var(--text-error); + cursor: pointer; + margin-right: auto; + box-shadow: none; + padding-left: 0; + padding-right: 0; } + .modal-container.mod-confirmation .modal-button-container > .mod-warning:nth-last-child(3):hover { + text-decoration: underline; } + .modal-container.mod-confirmation .modal-button-container > .mod-warning:nth-last-child(2) { + margin-left: auto; } + +/* Close buttons */ +.document-search-close-button, +.modal-close-button { + cursor: var(--cursor); + line-height: 20px; + text-align: center; + height: 24px; + width: 24px; + font-size: 24px; + color: var(--text-faint); + border-radius: var(--radius-m); } + +.modal-close-button { + top: 7px; + right: 7px; + padding: 0; } + +body:not(.is-mobile) .document-search-close-button:hover, +.modal-close-button:hover { + color: var(--text-normal); + background: var(--background-tertiary); } + +.document-search-close-button:before, +.modal-close-button:before { + font-family: Inter,sans-serif; + font-weight: 200; } + +/* Mobile modals */ +.is-mobile { + /* Mobile community themes */ + /* Mobile Community plugins */ + /* Tablet */ + /* Phone */ } + .is-mobile .modal { + width: 100%; + max-width: 100%; + border: none; + padding: 10px; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; } + .is-mobile .modal, + .is-mobile .modal-bg { + transition: none !important; + transform: none !important; } + .is-mobile .modal.mod-publish, + .is-mobile .modal.mod-community-plugin, + .is-mobile .modal.mod-settings { + width: 100vw; + max-height: 90vh; + padding: 0; } + .is-mobile .mod-confirmation .modal { + border-radius: 15px; } + .is-mobile .mod-confirmation .modal .modal-close-button { + display: none; } + .is-mobile .modal-content { + padding: 0; + border-radius: 15px; } + .is-mobile .modal-button-container { + padding: 0; } + .is-mobile .setting-item:not(.mod-toggle):not(.setting-item-heading) { + flex-grow: 0; } + .is-mobile .vertical-tab-header-group:last-child, + .is-mobile .vertical-tab-content, + .is-mobile .minimal-donation { + padding-bottom: 70px !important; } + .is-mobile .modal.mod-settings .vertical-tab-header:before { + content: "Settings"; + font-weight: 600; + font-size: var(--font-settings); + position: sticky; + display: flex; + height: 54px; + margin-top: 8px; + align-items: center; + justify-content: center; + text-align: center; + border-bottom: 1px solid var(--background-modifier-border); + background: var(--background-primary); + left: 0; + top: 0; + right: 0; + z-index: 1; } + .is-mobile .modal .vertical-tab-header-group-title { + padding: 15px 20px 10px 20px; + text-transform: uppercase; + letter-spacing: 0.05em; } + .is-mobile .modal .vertical-tab-nav-item { + padding: 12px 0px; + margin: 0; + border-radius: 0; + color: var(--text-primary); + border-bottom: 1px solid var(--background-modifier-border); } + .is-mobile .modal .vertical-tab-nav-item:after { + content: " "; + float: right; + width: 20px; + height: 20px; + display: block; + opacity: 0.2; + background: center right no-repeat url("data:image/svg+xml,"); } + .is-mobile.theme-dark .modal .vertical-tab-nav-item:after { + background: center right no-repeat url("data:image/svg+xml,"); } + .is-mobile .vertical-tab-header-group-items { + width: calc(100% - 40px); + margin: 0 auto; } + .is-mobile .modal .vertical-tab-nav-item:first-child { + border-top: 1px solid var(--background-modifier-border); } + .is-mobile .modal.mod-settings .vertical-tab-nav-item { + font-size: var(--font-settings); } + .is-mobile .modal svg.left-arrow-with-tail { + -webkit-mask-image: url("data:image/svg+xml,"); + height: 26px; + width: 26px; } + .is-mobile .modal-close-button { + display: block; + z-index: 2; + top: 10px; + right: 12px; + padding: 4px; + font-size: 34px; + width: 34px; + height: 34px; + background-color: var(--background-primary); } + .is-mobile .modal-close-button:before { + font-weight: 300; + color: var(--text-muted); } + .is-mobile .modal-close-button:hover { + background-color: var(--background-tertiary); } + .is-mobile .mod-community-theme .modal-title { + padding: 10px 20px; } + .is-mobile .modal.mod-community-theme, + .is-mobile .modal.mod-community-theme .modal-content { + height: unset; } + .is-mobile .community-plugin-search { + border: none; } + .is-mobile .community-plugin-item:hover { + background-color: transparent; } + .is-mobile .community-plugin-item { + margin: 0; } + .is-mobile .community-plugin-search .setting-item { + margin-right: 42px; } + .is-mobile .community-plugin-search .setting-item-control { + display: flex; + flex-direction: row; } + .is-mobile .community-plugin-search .setting-item-control button { + width: 40px; + font-size: 0; + margin-left: 10px; + justify-content: center; + color: var(--text-muted); + border: none; + box-shadow: none; + background-color: currentColor; + -webkit-mask: no-repeat center center url('data:image/svg+xml;utf8,'); + -webkit-mask-size: 22px; } + .is-mobile .community-plugin-search .setting-item-control button:hover { + background-color: var(--text-normal); } + .is-mobile .community-plugin-search .search-input-container { + margin: 0; } + .is-mobile .modal.mod-settings .vertical-tabs-container { + display: flex; + overflow: hidden; + border-top-left-radius: 15px; + border-top-right-radius: 15px; } + .is-mobile .community-plugin-details .modal-setting-back-button { + padding: 12px 20px; } + .is-mobile .modal-setting-back-button { + border-bottom: 1px solid var(--background-modifier-border); + display: flex; + margin-top: 8px; + height: 54px; + justify-content: center; + align-items: center; + background-color: var(--color-background); + box-shadow: none; } + .is-mobile .modal-setting-back-button-icon { + position: absolute; + left: 10px; } + .is-mobile .modal-setting-back-button span:nth-child(2) { + flex-grow: 1; + text-align: center; + font-weight: 600; + height: 54px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-normal); } + .is-mobile .hotkey-list-container .setting-command-hotkeys { + flex: unset; } + .is-mobile .modal.mod-settings .vertical-tab-content-container { + border: 0; } + @media (min-width: 400pt) { + .is-mobile .modal .vertical-tab-header, + .is-mobile .modal .vertical-tabs-container, + .is-mobile .modal .vertical-tab-content-container { + border-radius: 15px !important; } + .is-mobile .modal, + .is-mobile .modal-container .modal.mod-settings { + max-width: 800px; + transform: translateZ(0); + border-radius: 15px; + margin-bottom: 0; + bottom: auto; + overflow: hidden; } + .is-mobile .modal-container .modal.mod-settings .vertical-tabs-container { + transform: translateZ(0); } + .is-mobile .modal-container .modal-bg { + opacity: 0.8 !important; } + .is-mobile .search-input-container input { + width: 100%; } + .is-mobile .modal-setting-back-button, + .is-mobile .modal.mod-settings .vertical-tab-header:before { + margin-top: 0; } } + @media (max-width: 400pt) { + .is-mobile .modal { + border-radius: 0; + border: none; } + .is-mobile .modal.mod-publish, + .is-mobile .modal.mod-community-plugin, + .is-mobile .modal.mod-settings { + max-height: calc(100vh - 32px); + box-shadow: 0 -32px 0 0 var(--background-primary); } + .is-mobile .mod-confirmation .modal { + bottom: 4.5vh; } + .is-mobile .modal .search-input-container { + width: 100%; + margin: 0; } + .is-mobile .modal-close-button { + top: 18px; + right: 0px; + padding: 4px 16px 2px 4px; + width: 46px; } + .is-mobile .modal-close-button:hover { + background: var(--background-primary); } } + +/* Menus */ +.menu { + padding: 7px 5px; + background-color: var(--background-secondary); } + +.menu-item { + font-size: var(--font-adaptive-small); + border-radius: var(--radius-m); + padding: 3px 6px 3px 6px; + margin: 0 2px; + cursor: var(--cursor); + height: auto; + line-height: 20px; + display: flex; + align-items: center; + overflow: hidden; } + .menu-item:hover, .menu-item:hover:not(.is-disabled):not(.is-label), .menu-item.selected:not(.is-disabled):not(.is-label) { + background-color: var(--background-tertiary); } + +.menu-separator { + margin: 8px -5px; } + +.menu-item-icon { + width: 20px; + opacity: 0.6; + line-height: 10px; + position: static; + margin-right: 2px; } + .menu-item-icon svg { + width: 12px; + height: 12px; } + +.menu-item-icon +div.menu-item:hover .menu-item-icon svg, +div.menu-item:hover .menu-item-icon svg path { + color: var(--text-normal); } + +/* Mobile */ +.is-mobile { + /* Tablet */ + /* Phone */ } + .is-mobile:not(.minimal-icons-off) .menu-item-icon svg { + width: 18px; + height: 18px; } + .is-mobile .menu { + border: none; + width: 100%; + max-width: 100%; + left: 0 !important; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; } + .is-mobile .menu-item { + padding: 5px 10px; + margin: 0; } + .is-mobile .menu-item-icon { + margin-right: 10px; } + .is-mobile .menu-item.is-label { + color: var(--text-normal); + font-weight: var(--bold-weight); } + .is-mobile .menu-item.is-label .menu-item-icon { + display: none; } + @media (min-width: 400pt) { + .is-mobile .menu { + top: 60px !important; + right: 0 !important; + bottom: auto; + left: auto; + margin: 0 auto; + width: 360px; + padding: 10px 10px 10px; + border-radius: 15px; + box-shadow: 0 0 100vh 100vh rgba(0, 0, 0, 0.5); } + .is-mobile .menu .menu-item:hover { + background-color: var(--background-tertiary); } } + @media (max-width: 400pt) { + .is-mobile .menu { + padding-bottom: 30px; } + .is-mobile .menu-item.is-label { + font-size: var(--font-settings-title); } } + +/* Preview mode */ +.markdown-preview-view blockquote, +.markdown-preview-view p, +.markdown-preview-view ol, +.markdown-preview-view ul { + margin-block-start: var(--spacing-p); + margin-block-end: var(--spacing-p); } +.markdown-preview-view ul ol, +.markdown-preview-view ol ol, +.markdown-preview-view ol ul, +.markdown-preview-view ul ul { + margin-block-start: 0em; + margin-block-end: 0em; } +.markdown-preview-view h1, +.markdown-preview-view h2, +.markdown-preview-view h3, +.markdown-preview-view h4, +.markdown-preview-view h5, +.markdown-preview-view h6 { + margin-block-start: 1em; + margin-block-end: var(--spacing-p); } + +.markdown-preview-view hr { + height: 1px; + border-width: 2px 0 0 0; } + +iframe { + border: 0; } + +.markdown-preview-view .mod-highlighted { + transition: background-color 0.3s ease; + background-color: var(--text-selection); + color: inherit; } + +/* Backlinks in Preview */ +.mod-root .workspace-leaf-content[data-type='markdown'] .nav-header { + border-top: 1px solid var(--background-modifier-border); + margin-top: 3em; + position: relative; } + +.mod-root .workspace-leaf-content[data-type='markdown'] .nav-buttons-container, +.mod-root .workspace-leaf-content[data-type='markdown'].backlink-pane, +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane .search-result-container, +.mod-root .workspace-leaf-content[data-type='markdown'] .search-input-container, +.mod-root .workspace-leaf-content[data-type='markdown'] .tree-item, +.mod-root .workspace-leaf-content[data-type='markdown'] .search-empty-state { + padding-left: 0; + margin-left: 0; } + +.is-mobile .workspace-leaf-content:not([data-type='search']) .workspace-leaf-content[data-type='markdown'] .nav-buttons-container { + border-bottom: none; + padding-top: 5px; } + +.mod-root .workspace-leaf-content[data-type='markdown'] .search-input-container { + margin-bottom: 0px; + width: calc(100% - 130px); + margin-top: 10px; } + +.is-mobile .mod-root .workspace-leaf-content[data-type='markdown'] .search-input-container { + width: calc(100% - 160px); } + +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane { + padding-top: 10px; } + +.mod-root .workspace-leaf-content[data-type='markdown'] .nav-buttons-container { + position: absolute; + right: 0; + top: 3px; } + +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane > .tree-item-self:hover, +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane > .tree-item-self { + padding-left: 0px; + text-transform: none; + color: var(--text-normal); + font-size: var(--font-adaptive-normal); + font-weight: 500; + letter-spacing: unset; } + +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane > .tree-item-self.is-collapsed { + color: var(--text-faint); } + +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane > .tree-item-self.is-collapsed:hover { + color: var(--text-muted); } + +.mod-root .workspace-leaf-content[data-type='markdown'] .backlink-pane .search-result-file-title { + font-size: calc(var(--font-adaptive-normal) - 2px); } + +.mod-root .workspace-leaf-content[data-type=markdown] .markdown-source-view .embedded-backlinks .nav-header { + margin-top: 0; } + +/* Embedded searches */ +.internal-query { + border-top: none; + border-bottom: none; } + +.internal-query .internal-query-header { + padding-top: 10px; + justify-content: left; + border-top: 1px solid var(--ui1); } + +.internal-query .internal-query-header-title { + font-weight: 500; + color: var(--text-normal); + font-size: var(--text-adaptive-normal); } + +.internal-query .search-result-container { + border-bottom: 1px solid var(--ui1); } + +/* Default ribbon sidedock icons */ +.workspace-ribbon.mod-left .workspace-ribbon-collapse-btn, +.workspace-ribbon.mod-right .workspace-ribbon-collapse-btn { + opacity: 1; + position: fixed; + width: 26px; + display: flex; + align-items: center; + top: auto; + text-align: center; + bottom: 32px; + z-index: 9; } + +.workspace-ribbon.mod-left .workspace-ribbon-collapse-btn { + left: 8px; } + +.workspace-ribbon.mod-right { + right: 4px; + bottom: 0; + height: 32px; + padding-top: 6px; + position: absolute; + background: transparent; + border: 0; } + +.mod-right .workspace-ribbon-collapse-btn { + background-color: var(--background-primary); } + +.workspace-ribbon-collapse-btn, +.view-action, +.side-dock-ribbon-tab, +.side-dock-ribbon-action { + cursor: var(--cursor); } + +.workspace-ribbon-collapse-btn:hover { + background-color: var(--background-tertiary); } + +.workspace-ribbon { + border-width: var(--border-width-alt); + border-color: var(--background-divider); + background: var(--background-secondary); + flex: 0 0 42px; + padding-top: 7px; } + +.mod-right:not(.is-collapsed) ~ .workspace-split.mod-right-split { + margin-right: 0; } + +.side-dock-settings { + padding-bottom: 20px; } + +body.hider-frameless:not(.hider-ribbon):not(.is-fullscreen) .side-dock-actions { + padding-top: var(--top-left-padding-y); } + +/* Scroll bars */ +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar, +body:not(.native-scrollbars) ::-webkit-scrollbar { + width: 11px; + background-color: transparent; } +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar:horizontal, +body:not(.native-scrollbars) ::-webkit-scrollbar:horizontal { + height: 11px; } +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-corner, +body:not(.native-scrollbars) ::-webkit-scrollbar-corner { + background-color: transparent; } +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-track, +body:not(.native-scrollbars) ::-webkit-scrollbar-track { + background-color: transparent; } +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-thumb, +body:not(.native-scrollbars) ::-webkit-scrollbar-thumb { + background-clip: padding-box; + border-radius: 20px; + border: 3px solid transparent; + background-color: var(--background-modifier-border); + border-width: 3px 3px 3px 3px; + min-height: 45px; } +body:not(.hider-scrollbars).styled-scrollbars .modal .vertical-tab-header::-webkit-scrollbar-thumb:hover, +body:not(.hider-scrollbars).styled-scrollbars .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:hover, +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-thumb:hover, +body:not(.native-scrollbars) .modal .vertical-tab-header::-webkit-scrollbar-thumb:hover, +body:not(.native-scrollbars) .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:hover, +body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:hover { + background-color: var(--background-modifier-border-hover); } +body:not(.hider-scrollbars).styled-scrollbars .modal .vertical-tab-header::-webkit-scrollbar-thumb:active, +body:not(.hider-scrollbars).styled-scrollbars .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:active, +body:not(.hider-scrollbars).styled-scrollbars ::-webkit-scrollbar-thumb:active, +body:not(.native-scrollbars) .modal .vertical-tab-header::-webkit-scrollbar-thumb:active, +body:not(.native-scrollbars) .mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:active, +body:not(.native-scrollbars) ::-webkit-scrollbar-thumb:active { + background-color: var(--background-modifier-border-focus); } + +/* Search and replace (in file) */ +.is-flashing { + border-radius: 2px; + box-shadow: 2px 1px 0 4px var(--text-highlight-bg); + transition: all 0s ease-in-out; } + +.minimal-folding .is-flashing { + box-shadow: 5px 1px 0 6px var(--text-highlight-bg); } + +.is-flashing .tag { + border-color: var(--text-highlight-bg-active); } + +.suggestion-container.mod-search-suggestion { + max-width: 240px; } + +.mod-search-suggestion .suggestion-item { + font-size: var(--font-adaptive-small); } + +.mod-search-suggestion .clickable-icon { + margin: 0; } + +.search-suggest-item.mod-group { + font-size: var(--font-adaptive-smaller); } + +.cm-s-obsidian span.obsidian-search-match-highlight { + background: inherit; + background: var(--text-highlight-bg); + padding-left: 0; + padding-right: 0; } + +.markdown-preview-view .search-highlight > div { + box-shadow: 0 0 0px 2px var(--text-normal); + border-radius: 2px; + background: transparent; } + +.markdown-preview-view .search-highlight > div { + opacity: 0.4; } + +.markdown-preview-view .search-highlight > div.is-active { + background: transparent; + border-radius: 2px; + opacity: 1; + mix-blend-mode: normal; + box-shadow: 0 0 0px 3px var(--text-accent); } + +/* Live Preview */ +.cm-s-obsidian span.obsidian-search-match-highlight { + background-color: transparent; + box-shadow: 0 0 0px 3px var(--text-accent); + mix-blend-mode: multiply; + border-radius: 2px; } + +body:not(.is-mobile).borders-title .document-search-container { + padding-top: 0; } + +body input.document-search-input.mod-no-match:hover, +body input.document-replace-input.mod-no-match:hover, +body input.document-search-input.mod-no-match, +body input.document-replace-input.mod-no-match { + background-color: var(--background-primary); } + +body:not(.is-mobile) .document-search-container.mod-replace-mode { + height: 72px; } + +body:not(.is-mobile) .document-replace-buttons, +body:not(.is-mobile) .document-search-buttons { + padding-top: 3px; } + +.document-replace-buttons, +.document-search-buttons { + height: 30px; + padding-top: 0; + gap: 5px; + display: flex; } + +.document-search-button, +.document-search-close-button { + cursor: var(--cursor); + color: var(--text-muted); + font-weight: 500; } + +body:not(.is-mobile) .document-search-button, +body:not(.is-mobile) .document-search-close-button { + background: var(--background-tertiary); + height: 26px; } + +.document-search-button:hover { + box-shadow: none; + background: var(--background-tertiary); } + +body .document-search-close-button { + bottom: 0; + top: 0; + display: inline-flex; + height: 26px; + width: 26px; + line-height: 24px; } + +.document-search-button { + margin: 0; + padding-left: 0.75em; + padding-right: 0.75em; } + +body .document-search-container { + margin-top: 12px; + padding: 0; + height: 38px; + background-color: var(--background-primary); + border-top: none; + width: 100%; } + +.document-search, +.document-replace { + max-width: var(--max-width); + width: var(--line-width); + margin: 0 auto; + padding: 0 5px; } + +.minimal-readable-off .document-search, +.minimal-readable-off .document-replace { + width: 100%; } + +.markdown-source-view.is-searching, +.markdown-source-view.is-replacing, +.markdown-reading-view.is-searching { + flex-direction: column-reverse; } + +body input.document-search-input, +body input.document-replace-input { + margin-top: 2px; + font-size: var(--font-adaptive-small); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-m); + height: 28px; + background: var(--background-primary); + transition: border-color 0.1s ease-in-out; } + +input.document-search-input:hover, +input.document-replace-input:hover { + border: 1px solid var(--background-modifier-border-hover); + background: var(--background-primary); + transition: border-color 0.1s ease-in-out; } + +input.document-search-input:focus, +input.document-replace-input:focus { + border: 1px solid var(--background-modifier-border-hover); + background: var(--background-primary); + transition: all 0.1s ease-in-out; } + +.document-search-button { + font-size: var(--font-adaptive-small); } + +/* Mobile */ +.is-mobile .document-search, +.is-mobile .document-replace { + flex-direction: row; } +.is-mobile .document-replace { + padding-top: 6px; } + .is-mobile .document-replace .document-replace-buttons { + flex-shrink: 1; + flex-grow: 0; } +.is-mobile .document-search-container { + padding: 8px 0 8px 0; + background-color: var(--background-primary); + margin: 0 auto 0 auto; + height: auto; + width: 100%; + border-bottom: 1px solid var(--background-modifier-border); + padding-left: var(--folding-offset); } +.is-mobile .document-search, +.is-mobile .document-replace { + margin: 0 auto; + padding-left: 0; + padding-right: 0; + max-width: calc(var(--max-width) + 2%); + width: var(--line-width-adaptive); } +.is-mobile.minimal-readable-off .document-search, +.is-mobile.minimal-readable-off .document-replace { + width: 100%; } +.is-mobile .document-search-container input[type='text'] { + width: auto; + margin: 0 8px 0 0; + height: 36px; + padding: 5px 10px 5px 10px; + border-radius: 6px; + min-width: 90px; + border: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); } +.is-mobile .document-search-container .document-search-input[type='text'] { + padding-left: 30px; } +.is-mobile .document-search .document-search-buttons, +.is-mobile .document-replace button { + flex-grow: 0; } +.is-mobile .document-search-container button.document-search-button { + width: auto; + margin: 0px; + background: transparent; + font-size: 14px; + height: 36px; + padding: 0 2px; + white-space: nowrap; } +.is-mobile .document-search .document-search-close-button, +.is-mobile .document-replace .document-search-close-button { + height: 30px; + line-height: 30px; } + +/* Settings */ +.modal.mod-sync-history, +.modal.mod-sync-log, +.modal.mod-publish, +.modal.mod-community-plugin, +.modal.mod-settings { + width: 90vw; + height: 100vh; + max-height: 90vh; + max-width: 1000px; } + +.modal.mod-settings .vertical-tab-header, +.modal.mod-settings .vertical-tab-content-container { + height: 90vh; } + +.setting-item-name, +.community-plugin-name, +.modal.mod-settings .vertical-tab-content-container { + font-size: var(--font-settings); + line-height: 1.3; } + +.modal .modal-content > h2 { + text-align: left; + font-size: var(--h1); + font-weight: 600; } + +.modal.mod-settings .vertical-tab-content h1, +.modal.mod-settings .vertical-tab-content h2, +.modal.mod-settings .vertical-tab-content h3 { + text-align: left; + font-size: var(--h1); + font-weight: 600; } + +.modal .modal-content > h2:first-child, +.modal.mod-settings .vertical-tab-content > h2:first-child, +.modal.mod-settings .vertical-tab-content > h3:first-child { + margin-top: 0; } + +.community-plugin-search-summary, +.setting-item-description, +.community-plugin-item .community-plugin-author, +.community-plugin-downloads, +.community-plugin-item .community-plugin-desc { + font-size: var(--font-settings-small); + line-height: 1.3; + font-weight: 400; } + +.style-settings-collapse-indicator { + margin-right: 6px; } + +.modal .vertical-tab-nav-item { + font-size: var(--font-small); + line-height: 1.3; } + +.community-plugin-search .setting-item { + margin-right: 10px; } + +.flair.mod-pop { + letter-spacing: 0; + text-transform: none; + vertical-align: unset; + top: -1px; } + +.community-plugin-search { + padding: 20px 0 0 0; + background-color: var(--background-secondary); + border-right: 1px solid var(--background-divider); + flex: 0 0 270px; } + +.community-plugin-search-summary { + border-bottom: 1px solid var(--background-divider); + padding-bottom: 10px; } + +.community-plugin-info p button { + margin-right: 8px; } + +.community-plugin-item { + margin: 0; + cursor: var(--cursor); + padding-top: 15px; + border-bottom: 1px solid var(--background-divider); } + +.community-plugin-item:hover { + background-color: var(--background-tertiary); } + +.community-plugin-item .community-plugin-name { + font-weight: 500; } + +.community-plugin-item .community-plugin-author { + color: var(--text-muted); + padding-bottom: 10px; } + +.community-plugin-item .community-plugin-desc { + color: var(--text-normal); + font-size: var(--font-small); } + +.community-plugin-search .setting-item-info { + flex-grow: 0; } + +.community-plugin-search .search-input-container { + margin-left: -5px; + margin-right: 5px; } + +.modal .community-plugin-search .setting-item-control button { + display: flex; + align-items: center; } + +.setting-item-control button { + padding: 0.5em 0.75em; } + +button.mod-cta, +.modal button, +.modal button.mod-cta a { + font-size: var(--font-settings-small); + height: var(--input-height); + cursor: var(--cursor); + margin-right: 0px; + margin-left: 0px; } + +/* Settings */ +.modal.mod-settings .modal-content { + padding: 0; } +.modal.mod-settings .vertical-tab-content-container { + padding-top: 0; } + .modal.mod-settings .vertical-tab-content-container .vertical-tab-content { + padding-top: 30px; } + +.horizontal-tab-content, +.vertical-tab-content { + background: var(--background-primary); + padding-bottom: 100px; + padding-left: 40px; + padding-right: 40px; } + +.vertical-tab-header, +.vertical-tab-content { + padding-bottom: 100px; } + +.modal.mod-community-plugin .modal-content { + padding: 0; } + +.plugin-list-plugins { + overflow: visible; } + +.clickable-icon { + margin: 0; } + +.installed-plugins-container .clickable-icon { + margin: 0; } + +.installed-plugins-container .clickable-icon[aria-label="Uninstall"] { + margin: 0; } + +.plugin-list-plugins .clickable-icon { + margin: 0; } + +.hotkey-list-container { + padding-right: 0; } + +/* Themes */ +body .modal.mod-community-theme { + max-width: 1000px; } + +.community-theme-container { + padding-top: 10px; } + +.community-theme-container, +.hotkey-settings-container { + height: auto; + overflow: visible; } + +.theme-list { + justify-content: space-evenly; } + +.community-theme-filters-container, +.hotkey-search-container { + padding: 0 0 20px 0; } + +.modal.mod-community-theme { + padding: 0; } + +.modal.mod-community-theme .modal-content { + padding: 30px; } + +.community-theme { + padding: 0; + margin: 0 0 2em 0; + align-items: stretch; + background: transparent; } + +.community-theme-title { + text-align: left; + font-size: var(--font-settings); } + +.community-theme-info + div { + background-color: var(--background-secondary); + display: flex; + align-items: center; + padding: 0; + flex-grow: 1; + border-radius: 20px; } + +.community-theme-info { + line-height: 1; + flex-grow: 0; + padding: 0 0 10px 0; + align-items: flex-end; + justify-content: flex-start; + flex-wrap: wrap; } + +.community-theme-remove-button { + padding: 4px 6px; + display: flex; + color: var(--text-muted); + background-color: transparent; } + +.community-theme .community-theme-screenshot { + max-width: 100%; } + +body:not(.is-mobile) .theme-list { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0 2em; } + body:not(.is-mobile) .theme-list .community-theme { + align-self: stretch; + justify-self: center; + max-width: 100%; + width: 100%; + background-color: var(--background-secondary); + padding: 18px; + border-radius: var(--radius-l); + border: 2px solid transparent; } + body:not(.is-mobile) .theme-list .community-theme:hover { + border: 2px solid var(--text-accent); } + body:not(.is-mobile) .theme-list .community-theme.is-selected { + grid-column: 1/4; + grid-row: 1; + max-width: 100%; + display: grid; + grid-template-columns: 1.5fr 2fr; + padding: 20px 20px; + border-radius: var(--radius-xl); + border-color: transparent; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-info { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: 30px 50px 440px; + margin: 0 40px 0 0; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-title { + grid-column: 1/3; + grid-row: 1/2; + text-align: left; + font-size: 2em; + font-weight: 500; + margin: 0; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-info + div { + display: flex; + align-items: center; + flex-grow: 1; + box-shadow: none; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-downloads { + text-align: right; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-remove-button { + bottom: 20px; + left: 0px; + right: auto; + top: auto; + color: var(--text-faint); + display: flex; + align-items: center; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-remove-button:after { + content: 'Delete theme'; + padding-left: 5px; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-remove-button:hover { + color: var(--text-error); } + body:not(.is-mobile) .theme-list .community-theme.is-selected .modal-button-container { + grid-column: 2; + grid-row: 1/2; + margin-top: 0; + margin-left: auto; + margin-right: 0; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .modal-button-container button { + margin: 0; + width: 160px; + height: 36px; + cursor: pointer; + border: none; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); } + body:not(.is-mobile) .theme-list .community-theme.is-selected .modal-button-container button:hover { + background-color: var(--ax2); } + body:not(.is-mobile) .theme-list .community-theme.is-selected .modal-button-container button:not(.mod-cta) { + display: none; } + body:not(.is-mobile) .theme-list .community-theme.is-selected .community-theme-info::after { + grid-column: 1/3; + grid-row: 3/4; + padding-top: 20px; + align-self: flex-start; + justify-self: flex-start; + content: var(--minimal-version); + color: var(--text-normal); + font-size: var(--font-adaptive-normal); + line-height: 1.4; + width: 100%; + position: relative; + white-space: pre-wrap; + text-align: left; + border: none; } + +.community-theme-remove-button { + top: 15px; } + .community-theme-remove-button:hover { + color: var(--text-error); } + +.community-theme.is-selected { + padding-left: 0; + padding-right: 0; + background-color: transparent; + color: var(--text-normal); } + .community-theme.is-selected .community-theme-info + div { + box-shadow: 0px 0.5px 1px 0.5px rgba(0, 0, 0, 0.1), inset 0 0 0 2px var(--text-accent); } + .community-theme.is-selected .community-theme-downloads, + .community-theme.is-selected .community-theme-info { + margin-bottom: 0; + color: var(--text-muted); } + .community-theme.is-selected .community-theme-info .clickable-icon { + width: 100%; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + color: var(--text-normal); + cursor: pointer; + display: block; + text-align: center; + grid-column: 1/3; + padding: 7px 0; + margin: 20px 0 0; + height: 36px; + border-radius: 5px; + box-shadow: 0 1px 1px 0px var(--btn-shadow-color); } + .community-theme.is-selected .community-theme-info .clickable-icon:hover { + border: 1px solid var(--background-modifier-border-hover); + box-shadow: 0 2px 3px 0px var(--btn-shadow-color); } + .community-theme.is-selected .community-theme-info .clickable-icon::after { + content: "Learn more"; + padding-left: 4px; } + .community-theme.is-selected .modal-button-container .mod-cta { + background-color: var(--interactive-accent); + color: white; } + .community-theme.is-selected .modal-button-container .mod-cta:hover { + background-color: var(--interactive-accent-hover); } + +.modal.mod-settings .vertical-tab-header { + background: var(--background-secondary); + padding-top: 5px; + flex: 0 0 220px; + padding-bottom: 100px; } + +.vertical-tab-header-group-title { + color: var(--text-faint); + text-transform: none; + font-size: 12px; + letter-spacing: 0; + font-weight: 500; } + +.vertical-tab-nav-item { + padding: 5px 8px; + margin: 0 8px 0; + color: var(--text-muted); + font-weight: 400; + border: none; + background: var(--background-secondary); + cursor: var(--cursor); + border-radius: var(--radius-m); } + +.vertical-tab-nav-item:hover { + color: var(--text-normal); } + +.vertical-tab-nav-item.is-active { + color: var(--text-normal); + background-color: var(--background-tertiary); } + +.setting-hotkey { + background-color: var(--background-tertiary); + padding: 3px 4px 3px 8px; + display: flex; + align-items: center; } + +.setting-hotkey-icon.setting-delete-hotkey { + margin-left: 3px; + cursor: var(--cursor); } + +.setting-delete-hotkey:hover { + background-color: transparent; } + +body:not(.minimal-icons) .setting-hotkey-icon.setting-delete-hotkey svg { + width: 16px; + height: 16px; } + +.setting-hotkey.mod-empty { + background: transparent; + color: var(--text-faint); } + +.setting-item { + padding: 0.75rem 0; } + +.setting-item-description { + padding-top: 4px; } + +.setting-item-control { + margin-right: 0; + gap: 8px; } + +/* Status bar */ +.workspace-split.mod-left-split > .workspace-leaf-resize-handle, +.workspace-split.mod-right-split > .workspace-leaf-resize-handle { + height: 100%; } + +.status-bar { + transition: color 200ms linear; + color: var(--text-faint); + font-size: var(--font-adaptive-smaller); + border-top: var(--border-width) solid var(--background-divider); + line-height: 1; + max-height: 24px; } + +.minimal-status-off .status-bar { + background-color: var(--background-secondary); + border-width: var(--border-width); + padding: 2px 6px 4px; } + +body:not(.minimal-status-off) .status-bar { + background-color: var(--background-primary); + z-index: 30; + border-top-left-radius: 5px; + width: auto; + position: absolute; + left: auto; + border: 0; + bottom: 0; + right: 0; + max-height: 26px; + padding: 2px 8px 6px 3px; } + +/* +body.plugin-sliding-panes-rotate-header:not(.minimal-status-off) .status-bar { + border-top:1px solid var(--background-modifier-border); + border-left:1px solid var(--background-modifier-border); +}*/ +.sync-status-icon.mod-working, +.sync-status-icon.mod-success { + color: var(--text-faint); + cursor: var(--cursor); } + +.status-bar:hover .sync-status-icon.mod-working, +.status-bar:hover .sync-status-icon.mod-success, +.status-bar:hover { + color: var(--text-muted); + transition: color 200ms linear; } + +.status-bar .plugin-sync:hover .sync-status-icon.mod-working, +.status-bar .plugin-sync:hover .sync-status-icon.mod-success { + color: var(--text-normal); } + +.status-bar-item-segment { + margin-right: 10px; } + +.status-bar-item, +.sync-status-icon { + display: flex; + align-items: center; } + +.status-bar-item { + padding: 7px 4px; + margin: 0 0 0 0; + cursor: var(--cursor) !important; } + .status-bar-item .status-bar-item-icon { + line-height: 0; } + .status-bar-item.plugin-editor-status:hover, .status-bar-item.plugin-sync:hover, .status-bar-item.cMenu-statusbar-button:hover, .status-bar-item.mod-clickable:hover { + text-align: center; + background-color: var(--background-tertiary) !important; + border-radius: 4px; } + .status-bar-item.plugin-editor-status svg, .status-bar-item.plugin-sync svg { + height: 15px; + width: 15px; } + +/* Syntax highlighting */ +.theme-light code[class*="language-"], +.theme-light pre[class*="language-"], +.theme-dark code[class*="language-"], +.theme-dark pre[class*="language-"] { + color: var(--tx1); } +.theme-light .token.prolog, +.theme-light .token.doctype, +.theme-light .token.cdata, +.theme-light .cm-meta, +.theme-light .cm-qualifier, +.theme-dark .token.prolog, +.theme-dark .token.doctype, +.theme-dark .token.cdata, +.theme-dark .cm-meta, +.theme-dark .cm-qualifier { + color: var(--tx2); } +.theme-light .cm-comment, +.theme-light .token.comment, +.theme-dark .cm-comment, +.theme-dark .token.comment { + color: var(--tx2); } +.theme-light .token.tag, +.theme-light .token.constant, +.theme-light .token.symbol, +.theme-light .token.deleted, +.theme-light .cm-tag, +.theme-dark .token.tag, +.theme-dark .token.constant, +.theme-dark .token.symbol, +.theme-dark .token.deleted, +.theme-dark .cm-tag { + color: var(--red); } +.theme-light .token.punctuation, +.theme-light .cm-punctuation, +.theme-light .cm-bracket, +.theme-light .cm-hr, +.theme-dark .token.punctuation, +.theme-dark .cm-punctuation, +.theme-dark .cm-bracket, +.theme-dark .cm-hr { + color: var(--tx2); } +.theme-light .token.boolean, +.theme-light .token.number, +.theme-light .cm-number, +.theme-dark .token.boolean, +.theme-dark .token.number, +.theme-dark .cm-number { + color: var(--purple); } +.theme-light .token.selector, +.theme-light .token.attr-name, +.theme-light .token.string, +.theme-light .token.char, +.theme-light .token.builtin, +.theme-light .token.inserted, +.theme-light .cm-string, +.theme-light .cm-string-2, +.theme-dark .token.selector, +.theme-dark .token.attr-name, +.theme-dark .token.string, +.theme-dark .token.char, +.theme-dark .token.builtin, +.theme-dark .token.inserted, +.theme-dark .cm-string, +.theme-dark .cm-string-2 { + color: var(--green); } +.theme-light .cm-property, +.theme-light .token.property, +.theme-light .token.operator, +.theme-light .token.entity, +.theme-light .token.url, +.theme-light .language-css .token.string, +.theme-light .style .token.string, +.theme-light .token.variable, +.theme-light .cm-operator, +.theme-light .cm-link, +.theme-light .cm-variable-2, +.theme-light .cm-variable-3, +.theme-dark .cm-property, +.theme-dark .token.property, +.theme-dark .token.operator, +.theme-dark .token.entity, +.theme-dark .token.url, +.theme-dark .language-css .token.string, +.theme-dark .style .token.string, +.theme-dark .token.variable, +.theme-dark .cm-operator, +.theme-dark .cm-link, +.theme-dark .cm-variable-2, +.theme-dark .cm-variable-3 { + color: var(--cyan); } +.theme-light .token.atrule, +.theme-light .token.attr-value, +.theme-light .token.function, +.theme-light .token.class-name, +.theme-light .cm-attribute, +.theme-light .cm-variable, +.theme-light .cm-type, +.theme-light .cm-def, +.theme-dark .token.atrule, +.theme-dark .token.attr-value, +.theme-dark .token.function, +.theme-dark .token.class-name, +.theme-dark .cm-attribute, +.theme-dark .cm-variable, +.theme-dark .cm-type, +.theme-dark .cm-def { + color: var(--yellow); } +.theme-light .token.keyword, +.theme-light .cm-keyword, +.theme-light .cm-builtin, +.theme-dark .token.keyword, +.theme-dark .cm-keyword, +.theme-dark .cm-builtin { + color: var(--pink); } +.theme-light .token.regex, +.theme-light .token.important, +.theme-dark .token.regex, +.theme-dark .token.important { + color: var(--orange); } + +/* Preview mode tables */ +.markdown-source-view.mod-cm6 table { + border-collapse: collapse; } + +.markdown-preview-view table { + margin-block-start: 1em; } + +.markdown-source-view.mod-cm6 td, +.markdown-source-view.mod-cm6 th, +.markdown-preview-view th, +.markdown-preview-view td { + padding: 4px 10px; } + +.markdown-source-view.mod-cm6 td, +.markdown-preview-view td { + font-size: var(--table-font-size); } + +.markdown-source-view.mod-cm6 th, +.markdown-preview-view th { + font-weight: 400; + font-size: var(--table-font-size); + color: var(--text-muted); + border-top: none; + text-align: left; } + .markdown-source-view.mod-cm6 th[align="center"], + .markdown-preview-view th[align="center"] { + text-align: center; } + .markdown-source-view.mod-cm6 th[align="right"], + .markdown-preview-view th[align="right"] { + text-align: right; } + +.markdown-source-view.mod-cm6 th:last-child, +.markdown-source-view.mod-cm6 td:last-child, +.markdown-preview-view th:last-child, +.markdown-preview-view td:last-child { + border-right: none; } + +.markdown-source-view.mod-cm6 th:first-child, +.markdown-source-view.mod-cm6 td:first-child, +.markdown-preview-view th:first-child, +.markdown-preview-view td:first-child { + border-left: none; + padding-left: 0; } + +.markdown-source-view.mod-cm6 tr:last-child td, +.markdown-preview-view tr:last-child td { + border-bottom: none; } + +/* Legacy Editor Tables */ +.CodeMirror pre.HyperMD-table-row { + font-family: var(--font-monospace); + font-size: var(--table-font-size); } + +/* Live Preview Tables */ +.is-live-preview .el-table { + width: 100%; + max-width: 100%; } + +.cm-s-obsidian .HyperMD-table-row { + font-size: var(--table-font-size); } + +.cm-s-obsidian .HyperMD-table-row span.cm-hmd-table-sep, +.cm-hmd-table-sep-dummy { + color: var(--text-faint); + font-weight: 400; } + +/* Tags */ +body.minimal-unstyled-tags .frontmatter-container .tag, +body.minimal-unstyled-tags a.tag, +body.minimal-unstyled-tags .cm-s-obsidian span.cm-hashtag { + color: var(--tag-color); + font-weight: var(--link-weight); + text-decoration: none; } + body.minimal-unstyled-tags .frontmatter-container .tag:hover, + body.minimal-unstyled-tags a.tag:hover, + body.minimal-unstyled-tags .cm-s-obsidian span.cm-hashtag:hover { + color: var(--text-normal); } + +body:not(.minimal-unstyled-tags) .frontmatter-container .tag, +body:not(.minimal-unstyled-tags) a.tag { + background-color: var(--tag-bg); + border: var(--tag-border-width) solid var(--background-modifier-border); + color: var(--tag-color); + font-size: calc(var(--font-adaptive-normal) * 0.8); + font-weight: var(--link-weight); + font-family: var(--font-interface); + padding: 1px 8px; + text-align: center; + text-decoration: none; + vertical-align: middle; + display: inline-block; + margin: 1px 0; + border-radius: var(--tag-radius); } +body:not(.minimal-unstyled-tags) a.tag:hover { + color: var(--text-normal); + border-color: var(--background-modifier-border-hover); + background-color: var(--tag-bg2); } +body:not(.minimal-unstyled-tags) .cm-s-obsidian span.cm-hashtag { + background-color: var(--tag-bg); + border: var(--tag-border-width) solid var(--background-modifier-border); + color: var(--tag-color); + font-size: calc(var(--font-adaptive-normal) * 0.8); + font-family: var(--font-interface); + font-weight: var(--link-weight); + text-align: center; + text-decoration: none; + margin: 0; + vertical-align: text-bottom; + padding-top: 2px; + border-left: none; + border-right: none; + padding-bottom: 3px; + cursor: text; } +body:not(.minimal-unstyled-tags) .cm-s-obsidian span.cm-hashtag:hover { + background-color: var(--tag-bg2); } +body:not(.minimal-unstyled-tags) span.cm-hashtag.cm-hashtag-begin { + border-top-left-radius: var(--tag-radius); + border-bottom-left-radius: var(--tag-radius); + padding-left: 8px; + border-right: none; + border-left: var(--tag-border-width) solid var(--background-modifier-border); } +body:not(.minimal-unstyled-tags) span.cm-hashtag.cm-hashtag-end { + border-top-right-radius: var(--tag-radius); + border-bottom-right-radius: var(--tag-radius); + border-left: none; + padding-right: 8px; + border-right: var(--tag-border-width) solid var(--background-modifier-border); } + +/* Tag pane */ +.tag-container { + padding-left: 15px; } + +.tag-pane-tag-count { + padding: 0; + color: var(--text-faint); } + +.pane-list-item-ending-flair { + background: transparent; } + +.tag-pane-tag { + padding: 2px 5px 2px 5px; + cursor: var(--cursor); } + +.tag-pane-tag:hover { + background: transparent; } + +.nav-file.is-active .nav-file-title:hover { + background: var(--background-tertiary) !important; } + +.nav-file.is-active > .nav-file-title { + background: var(--background-tertiary); } + +/* Tooltips */ +.tooltip { + font-size: var(--font-adaptive-smaller); + line-height: 1.3; + font-weight: 500; + padding: 4px 8px; + border-radius: 4px; + transition: none; + text-align: left; + animation: none; + opacity: 0.8; } + +.tooltip.mod-left, +.tooltip.mod-right { + transform: none; + animation: none; } + +/* Title Bar */ +/* Alignment */ +.title-align-left:not(.plugin-sliding-panes-rotate-header) .view-header-title-container { + margin-left: 5px; } + +.title-align-center:not(.plugin-sliding-panes-rotate-header) .view-header-title { + margin-left: 0; + padding-right: 0; + text-align: center; } + +.title-align-left:not(.plugin-sliding-panes-rotate-header) .view-header-title-container, +.title-align-center:not(.plugin-sliding-panes-rotate-header) .view-header-title-container { + width: auto; + position: static; } + +.mod-macos.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-left-split.is-collapsed + .mod-root .workspace-leaf:first-of-type .view-header-title-container { + max-width: calc(100% - (var(--traffic-x-space) * 2) - 30px); } + +.mod-macos.is-popout-window.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) .mod-root .workspace-leaf:first-of-type .view-header-title-container { + max-width: calc(100% - (var(--traffic-x-space) * 2) - 30px); } + +.view-header { + height: var(--header-height); + align-items: center; } + +/* Left side title bar icon */ +body:not(.minimal-icons-off) div.view-header-icon svg { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' enable-background='new 0 0 32 32' viewBox='0 0 32 32' xml:space='preserve'%3E%3Cpath d='M10 6h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4z'/%3E%3Cpath fill='none' d='M0 0h32v32H0z'/%3E%3C/svg%3E"); } + +.view-header-icon { + margin-left: var(--traffic-x-space); + opacity: 0; + top: 0; + left: 4px; + z-index: 20; } + +.show-grabber .view-header-icon { + opacity: var(--icon-muted); } + +.show-grabber .view-header-icon:hover { + opacity: 1; } + +.view-header-icon:hover { + cursor: grab; } + +.view-header-icon:active { + cursor: grabbing; } + +/* Right side title bar icon */ +.view-actions { + margin-right: 1px; + height: calc(var(--header-height) - 1px); + top: 0; + align-items: center; + z-index: 15; + background: var(--background-primary); } + +/* Title area */ +.view-header-title { + padding-right: 80px; } + +/* Fade out title +body:not(.is-mobile) .view-header-title:before { + background:linear-gradient(90deg,transparent 0%,var(--background-primary) 80%); + width:50px; + content:" "; + height:var(--header-height); + display:inline-block; + vertical-align:bottom; + position:absolute; + right:50px; + pointer-events:none; +}*/ +.workspace-leaf-header, +.view-header, +.workspace-leaf.mod-active .view-header, +.workspace-split.mod-root > .workspace-leaf:first-of-type:last-of-type .view-header { + background-color: var(--background-primary) !important; + border-top: none; + border-bottom: none; } + +.view-header-title-container { + padding-left: 0; + padding-right: 0px; + position: absolute; + width: var(--line-width-adaptive); + max-width: var(--max-width); + margin: 0 auto; + left: 0; + right: 0; } + +.view-header-title-container:after { + display: none; } + +.view-actions { + padding: 0px 6px; + margin-right: 0px; + margin-left: auto; + transition: opacity 0.25s ease-in-out; } + +.view-actions .view-action { + margin: 0; + top: 0; + padding: 4px; + border-radius: var(--radius-m); + display: flex; + align-items: center; } + +body:not(.is-mobile) .view-actions .view-action { + height: 26px; } + +.view-action.is-active { + color: var(--icon-color); + opacity: var(--icon-muted); } + +body:not(.is-mobile) .view-actions .view-action:last-child { + margin-left: -1px; } + +body:not(.minimal-focus-mode) .workspace-ribbon:not(.is-collapsed) ~ .mod-root .view-actions, +.minimal-focus-mode .workspace-ribbon:not(.is-collapsed) ~ .mod-root .view-header:hover .view-actions, +.workspace-ribbon.mod-left.is-collapsed ~ .mod-root .view-header:hover .view-actions, +.mod-right.is-collapsed ~ .mod-root .view-header:hover .view-actions, +.view-action.is-active:hover { + opacity: 1; + transition: opacity 0.25s ease-in-out; } + +.view-content { + height: calc(100% - var(--header-height)); } + +/* Window frame */ +body:not(.hider-frameless):not(.is-fullscreen):not(.is-mobile) { + --titlebar-height:28px; + padding-top: var(--titlebar-height) !important; } + +body:not(.hider-frameless):not(.is-fullscreen):not(.is-mobile) .titlebar { + background: var(--background-secondary); + border-bottom: var(--border-width) solid var(--background-divider); + height: var(--titlebar-height) !important; + top: 0 !important; + padding-top: 0 !important; } + +body.hider-frameless .titlebar { + border-bottom: none; } + +.mod-windows .titlebar-button:hover { + background-color: var(--background-primary-alt); } + +.mod-windows .titlebar-button.mod-close:hover { + background-color: var(--background-modifier-error); } + +.mod-windows .mod-close:hover svg { + fill: white !important; + stroke: white !important; } + +.titlebar-button-container { + height: var(--titlebar-height); + top: 0; + display: flex; + align-items: center; } + +.titlebar:hover .titlebar-button-container.mod-left { + opacity: 1; } + +.is-focused .titlebar-text { + color: var(--text-normal); } + +.titlebar-text { + font-weight: 600; + color: var(--text-faint); + letter-spacing: inherit; } + +body:not(.window-title-on) .titlebar-text { + display: none; } + +.titlebar-button:hover { + opacity: 1; + transition: opacity 100ms ease-out; } + +.titlebar-button { + opacity: 0.5; + cursor: var(--cursor); + color: var(--text-muted); + padding: 2px 4px; + border-radius: 3px; + line-height: 1; + display: flex; } + +.titlebar-button:hover { + background-color: var(--background-tertiary); } + +.titlebar-button-container.mod-left .titlebar-button { + margin-right: 5px; } + +.titlebar-button-container.mod-right .titlebar-button { + margin-left: 0; + border-radius: 0; + height: 100%; + align-items: center; + padding: 2px 15px; } + +/* Workspace */ +/* Empty state */ +.empty-state { + background-color: var(--background-primary); + text-align: center; } + +.workspace-leaf-content[data-type="empty"] .view-header, +.empty-state-title { + display: none; } + +.empty-state-action-list { + color: var(--text-normal); + font-size: var(--font-adaptive-normal); } + +/* Empty side pane */ +.pane-empty { + text-align: center; + color: var(--text-faint); + font-size: var(--font-adaptive-small); } + +.workspace-split.mod-root { + background-color: var(--background-primary); } + +.workspace-split.mod-vertical > .workspace-split { + padding: 0; } + +.workspace-split .workspace-tabs { + background: var(--background-primary); } + +.workspace-split:not(.mod-right-split) .workspace-tabs { + background: var(--background-secondary); } + +.workspace-split.mod-root > .workspace-leaf:first-of-type .workspace-leaf-content, +.workspace-split.mod-root > .workspace-leaf:last-of-type .workspace-leaf-content { + border-top-right-radius: 0px; + border-top-left-radius: 0px; } + +/* Resize handles */ +.workspace-split.mod-root.mod-horizontal .workspace-leaf-resize-handle, +.workspace-split.mod-root.mod-vertical .workspace-leaf-resize-handle { + border-width: 1px; } + +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle { + height: 3px; + background: transparent; + border-bottom: var(--border-width-alt) solid var(--background-divider); } + +.workspace-split.mod-right-split > .workspace-leaf-resize-handle { + background: transparent; + border-left: var(--border-width-alt) solid var(--background-divider); + width: 3px !important; } + +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle { + border-right: var(--border-width) solid var(--background-divider); + width: 4px !important; + background: transparent; } + +.workspace-split.mod-right-split > .workspace-leaf-resize-handle:hover, +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle:hover, +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle:hover, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle:hover { + border-color: var(--background-modifier-border-hover); + transition: border-color 0.1s ease-in-out 0.05s, border-width 0.1s ease-in-out 0.05s; + border-width: 2px; } + +.workspace-split.mod-right-split > .workspace-leaf-resize-handle:active, +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle:active, +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle:active, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle:active { + border-color: var(--background-modifier-border-focus); + border-width: 2px; } + +.workspace-tab-container-before, +.workspace-tab-container-after { + width: 0; } + +.workspace-leaf { + border-left: 0px; } + +.workspace-tabs .workspace-leaf, +.workspace-tabs .workspace-leaf.mod-active { + border: none; } + +.mod-horizontal .workspace-leaf { + border-bottom: 0px; + background-color: transparent; + box-shadow: none !important; } + +.workspace-split.mod-right-split .workspace-tabs .workspace-leaf { + border-radius: 0; } + +/* Effects on non-active panels */ +.workspace-tab-container-inner { + background: transparent; + border-radius: 0; + width: 100%; + max-width: 100%; + margin: 0 auto; + padding-left: 5px; } + +.workspace-tabs .workspace-tab-header-container { + border: none; } + +.workspace-sidedock-empty-state + .workspace-tabs .workspace-tab-header-container { + border-bottom: var(--border-width) solid var(--background-divider); } + +.mod-right-split .workspace-tabs .nav-buttons-container { + z-index: 1; } + +.workspace-tab-header.is-before-active .workspace-tab-header-inner, +.workspace-tab-header.is-active, +.workspace-tab-header.is-after-active, +.workspace-tab-header.is-after-active .workspace-tab-header-inner, +.workspace-tab-header.is-before-active, +.workspace-tab-header.is-after-active { + background: transparent; } + +.workspace-tabs { + border: 0; + padding-right: 0; + font-size: 100%; } + +.workspace-tab-container-inner { + padding-left: 6px; } + +.workspace-tab-header-inner { + padding: 0px 0px 0 2px; } + +.workspace-tab-header-container { + height: var(--header-height); + padding: 0; + align-items: center; + background-color: transparent; } + +.workspace-tab-header-container { + border-bottom: var(--border-width) solid var(--background-divider); } + +/* Components */ +/* Audio files */ +.theme-dark audio { + filter: none; } + +.theme-dark audio::-webkit-media-controls-play-button, +.theme-dark audio::-internal-media-controls-overflow-button, +.theme-dark audio::-webkit-media-controls-timeline, +.theme-dark audio::-webkit-media-controls-volume-control-container, +.theme-dark audio::-webkit-media-controls-current-time-display, +.theme-dark audio::-webkit-media-controls-time-remaining-display, +.theme-dark audio::-internal-media-controls-overflow-button { + filter: invert(1); } + +audio { + height: 36px; + border-radius: 4px; } + +audio::-webkit-media-controls-enclosure { + border: 1px solid var(--background-modifier-border); + background-color: var(--background-secondary); } + +audio::-webkit-media-controls-current-time-display { + color: var(--text-normal); + font-family: var(--font-interface); + font-size: var(--font-adaptive-small); + text-shadow: none; } + +audio::-webkit-media-controls-time-remaining-display { + color: var(--text-muted); + font-family: var(--font-interface); + font-size: var(--font-adaptive-small); + text-shadow: none; } + +audio::-webkit-media-controls-panel { + padding: 2px 1.5px; } + +audio::-webkit-media-controls input[pseudo="-internal-media-controls-overflow-button" i]:enabled:hover::-internal-media-controls-button-hover-background { + background-color: transparent; } + +/* Buttons */ +button { + cursor: var(--cursor); } + +button, +.setting-item-control button { + font-family: var(--font-interface); + font-size: var(--font-inputs); + font-weight: 400; + border-radius: var(--radius-m); } + +button:active, +button:focus { + -webkit-appearance: none; + border-color: var(--background-modifier-border-hover); } + +body:not(.is-mobile) button:active, +body:not(.is-mobile) button:focus { + box-shadow: 0 0 0px 2px var(--background-modifier-border-hover); } + +.modal.mod-settings button:not(.mod-cta):not(.mod-warning), +.modal button:not(.mod-warning), +.modal.mod-settings button:not(.mod-warning) { + background-color: var(--interactive-normal); + color: var(--text-normal); + border: 1px solid var(--background-modifier-border); + box-shadow: 0 1px 1px 0px var(--btn-shadow-color); + cursor: var(--cursor); + height: var(--input-height); + line-height: 0; + white-space: nowrap; + transition: background-color 0.2s ease-out, border-color 0.2s ease-out; } + +button.mod-warning { + border: 1px solid var(--background-modifier-error); + color: var(--text-error); + box-shadow: 0 1px 1px 0px var(--btn-shadow-color); + transition: background-color 0.2s ease-out; } + +button.mod-warning:hover { + border: 1px solid var(--background-modifier-error); + color: var(--text-error); + box-shadow: 0 2px 3px 0px var(--btn-shadow-color); + transition: background-color 0.2s ease-out; } + +button:hover, +.modal button:not(.mod-warning):hover, +.modal.mod-settings button:not(.mod-warning):hover { + background-color: var(--interactive-normal); + border-color: var(--background-modifier-border-hover); + box-shadow: 0 2px 3px 0px var(--btn-shadow-color); + transition: background-color 0.2s ease-out, border-color 0.2s ease-out; } + +.is-mobile button.copy-code-button { + width: auto; + margin-right: 4px; } + +/* Dropdowns */ +.dropdown, +body .addChoiceBox #addChoiceTypeSelector { + font-family: var(--font-interface); + font-size: var(--font-inputs); } + +.dropdown, +select { + box-shadow: 0 1px 1px 0px var(--btn-shadow-color); + background-color: var(--interactive-normal); + border-color: var(--background-modifier-border); + transition: border-color 0.1s linear; + height: var(--input-height); + font-family: var(--font-interface); + border-radius: var(--radius-m); } + +.dropdown { + background-image: url("data:image/svg+xml;charset=US-ASCII,<%2Fsvg>"); } + +.theme-dark .dropdown { + background-image: url("data:image/svg+xml;charset=US-ASCII,<%2Fsvg>"); } + +.dropdown:hover, +select:hover { + background-color: var(--interactive-normal); + box-shadow: 0 2px 3px 0px var(--btn-shadow-color); + border-color: var(--background-modifier-border-hover); + transition: all 0.1s linear; } + +.dropdown:focus, +.dropdown:active, +select:focus, +select:active { + -webkit-appearance: none; + border-color: var(--background-modifier-border-hover); } + +body:not(.is-mobile) .dropdown:focus, +body:not(.is-mobile) .dropdown:active, +body:not(.is-mobile) select:focus, +body:not(.is-mobile) select:active { + box-shadow: 0 0 0px 2px var(--background-modifier-border-hover); } + +/* Input fields */ +textarea, +input[type='text'], +input[type='search'], +input[type='email'], +input[type='password'], +input[type='number'] { + font-family: var(--font-interface); + font-size: var(--font-inputs); } + +textarea { + padding: 5px 10px; + transition: box-shadow 0.1s linear; + -webkit-appearance: none; + line-height: 1.3; } + +input[type='text'], +input[type='search'], +input[type='email'], +input[type='password'], +input[type='number'] { + padding: 5px 10px; + -webkit-appearance: none; + transition: box-shadow 0.1s linear; + height: var(--input-height); } + +textarea:hover, +input:hover { + border-color: var(--background-modifier-border-hover); + transition: border-color 0.1s linear, box-shadow 0.1s linear; } + +textarea:active, +textarea:focus, +input[type='text']:active, +input[type='search']:active, +input[type='email']:active, +input[type='password']:active, +input[type='number']:active, +input[type='text']:focus, +input[type='search']:focus, +input[type='email']:focus, +input[type='password']:focus, +input[type='number']:focus { + -webkit-appearance: none; + border-color: var(--background-modifier-border-hover); } + +body:not(.is-mobile) textarea:active, +body:not(.is-mobile) textarea:focus, +body:not(.is-mobile) .dropdown:focus, +body:not(.is-mobile) .dropdown:active, +body:not(.is-mobile) select:focus, +body:not(.is-mobile) select:active, +body:not(.is-mobile) input:focus { + box-shadow: 0 0 0px 2px var(--background-modifier-border-hover); + transition: border-color 0.1s linear, box-shadow 0.1s linear; } + +/* Progress bars */ +.theme-light { + --progress-outline:rgba(0,0,0,0.05); } + +.theme-dark { + --progress-outline:rgba(255,255,255,0.04); } + +.markdown-source-view.is-live-preview progress, +.markdown-preview-view progress { + -webkit-writing-mode: horizontal-tb; + writing-mode: horizontal-tb; + appearance: none; + box-sizing: border-box; + display: inline-block; + height: 5px; + margin-bottom: 4px; + width: 220px; + max-width: 100%; + overflow: hidden; + border-radius: 0px; + border: 0; + vertical-align: -0.2rem; } + .markdown-source-view.is-live-preview progress[value]::-webkit-progress-bar, + .markdown-preview-view progress[value]::-webkit-progress-bar { + background-color: var(--background-tertiary); + box-shadow: inset 0px 0px 0px var(--border-width) var(--progress-outline); + border-radius: 5px; + overflow: hidden; } + .markdown-source-view.is-live-preview progress[value]::-webkit-progress-value, + .markdown-preview-view progress[value]::-webkit-progress-value { + background-color: var(--text-accent); + overflow: hidden; } + .markdown-source-view.is-live-preview progress[value^='1']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value^='2']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value^='3']::-webkit-progress-value, + .markdown-preview-view progress[value^='1']::-webkit-progress-value, + .markdown-preview-view progress[value^='2']::-webkit-progress-value, + .markdown-preview-view progress[value^='3']::-webkit-progress-value { + background-color: var(--red); } + .markdown-source-view.is-live-preview progress[value^='4']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value^='5']::-webkit-progress-value, + .markdown-preview-view progress[value^='4']::-webkit-progress-value, + .markdown-preview-view progress[value^='5']::-webkit-progress-value { + background-color: var(--orange); } + .markdown-source-view.is-live-preview progress[value^='6']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value^='7']::-webkit-progress-value, + .markdown-preview-view progress[value^='6']::-webkit-progress-value, + .markdown-preview-view progress[value^='7']::-webkit-progress-value { + background-color: var(--yellow); } + .markdown-source-view.is-live-preview progress[value^='8']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value^='9']::-webkit-progress-value, + .markdown-preview-view progress[value^='8']::-webkit-progress-value, + .markdown-preview-view progress[value^='9']::-webkit-progress-value { + background-color: var(--green); } + .markdown-source-view.is-live-preview progress[value='1']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='100']::-webkit-progress-value, + .markdown-preview-view progress[value='1']::-webkit-progress-value, + .markdown-preview-view progress[value='100']::-webkit-progress-value { + background-color: var(--text-accent); } + .markdown-source-view.is-live-preview progress[value='0']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='2']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='3']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='4']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='5']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='6']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='7']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='8']::-webkit-progress-value, .markdown-source-view.is-live-preview progress[value='9']::-webkit-progress-value, + .markdown-preview-view progress[value='0']::-webkit-progress-value, + .markdown-preview-view progress[value='2']::-webkit-progress-value, + .markdown-preview-view progress[value='3']::-webkit-progress-value, + .markdown-preview-view progress[value='4']::-webkit-progress-value, + .markdown-preview-view progress[value='5']::-webkit-progress-value, + .markdown-preview-view progress[value='6']::-webkit-progress-value, + .markdown-preview-view progress[value='7']::-webkit-progress-value, + .markdown-preview-view progress[value='8']::-webkit-progress-value, + .markdown-preview-view progress[value='9']::-webkit-progress-value { + background-color: var(--red); } + +/* Range slider input */ +input[type=range] { + background-color: var(--background-modifier-border-hover); + height: 2px; + padding: 0 0px; + -webkit-appearance: none; + cursor: default; + margin: 0; + border-radius: 0px; } + +body:not(.is-mobile) input[type=range]:focus { + box-shadow: none; } + +input[type=range]::-webkit-slider-runnable-track { + background: var(--background-modifier-border-hover); + height: 2px; + margin-top: 0px; } + +input[type=range]::-webkit-slider-thumb { + background: white; + border: 1px solid var(--background-modifier-border-hover); + height: 18px; + width: 18px; + border-radius: 16px; + margin-top: -5px; + transition: all 0.1s linear; + cursor: default; + box-shadow: 0 1px 1px 0px rgba(0, 0, 0, 0.05), 0 2px 4px 0px rgba(0, 0, 0, 0.1); } + +input[type=range]::-webkit-slider-thumb:hover, +input[type=range]::-webkit-slider-thumb:active { + background: white; + border-width: 1; + border: 1px solid var(--background-modifier-border-focus); + box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.05), 0 2px 3px 0px rgba(0, 0, 0, 0.2); + transition: all 0.1s linear; } + +body:not(.is-mobile) input[type=range]:focus::-webkit-slider-thumb { + box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.05), 0 2px 3px 0px rgba(0, 0, 0, 0.2); } + +/* Toggle switches */ +.checkbox-container { + background-color: var(--background-modifier-border-hover); + box-shadow: inset 0 0px 1px 0px rgba(0, 0, 0, 0.2); + border: none; + width: 40px; + height: 22px; + cursor: var(--cursor); } + .checkbox-container.is-enabled { + border-color: var(--interactive-accent); } + .checkbox-container.is-enabled:after { + transform: translate3d(20px, 0, 0); } + .checkbox-container:after { + background: white; + border: none; + margin: 2px 0 0 0; + height: 18px; + width: 18px; + border-radius: 26px; + transform: translate3d(2px, 0, 0); + box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.1); + transition: all 0.1s linear; } + .checkbox-container:hover:after { + box-shadow: 0 2px 3px 0px rgba(0, 0, 0, 0.1); + transition: all 0.1s linear; } + +/* Minimal features */ +/* Active line highlight */ +.active-line-on .cm-line.cm-active, +.active-line-on .markdown-source-view.mod-cm6.is-live-preview .HyperMD-quote.cm-active { + background-color: var(--active-line-bg); + box-shadow: -25vw 0px var(--active-line-bg), 25vw 0 var(--active-line-bg); } + +.borders-low { + --border-width:0px; + --border-width-alt:1px; } + +.borders-none { + --border-width:0px; + --border-width-alt:0px; } + +/* Title borders */ +body.borders-title .workspace-leaf .workspace-leaf-content:not([data-type='empty']):not([data-type='map']):not([data-type='graph']):not([data-type='localgraph']) .view-header, +body.borders-title .workspace-split.mod-root .workspace-leaf:first-of-type:last-of-type .workspace-leaf-content:not([data-type='map']):not([data-type='graph']):not([data-type='empty']):not([data-type='localgraph']) .view-header { + border-bottom: var(--border-width) solid var(--background-divider); } + +body.borders-title .workspace-ribbon.mod-left.is-collapsed { + border-right: var(--border-width) solid var(--background-divider); } + +body:not(.is-fullscreen).mod-macos.hider-frameless.borders-title .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-container { + border: none; } + +/* MIT License | Copyright (c) Stephan Ango (@kepano) + +Cards snippet for Obsidian + +author: @kepano +version: 1.1.0 + +Support my work: +https://github.com/sponsors/kepano + +*/ +:root { + --cards-min-width:180px; + --cards-max-width:1fr; + --cards-mobile-width:120px; + --cards-image-height:400px; + --cards-padding:1.2em; + --cards-image-fit:contain; + --cards-background:transparent; + --cards-border-width:1px; } + +@media (max-width: 400pt) { + :root { + --cards-min-width:var(--cards-mobile-width); } } +/* Make the grid and basic cards */ +.cards.table-100 table.dataview tbody, +.table-100 .cards table.dataview tbody { + padding: 0.25rem 0.75rem; } + +.cards .el-pre + .el-lang-dataview .table-view-thead { + padding-top: 8px; } + +.cards table.dataview tbody { + clear: both; + padding: 0.5rem 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(var(--cards-min-width), var(--cards-max-width))); + grid-column-gap: 0.75rem; + grid-row-gap: 0.75rem; } + +.cards table.dataview > tbody > tr { + background-color: var(--cards-background); + border: var(--cards-border-width) solid var(--background-modifier-border); + display: flex; + flex-direction: column; + margin: 0; + padding: 0 0 calc(var(--cards-padding)/3) 0; + border-radius: 6px; + overflow: hidden; + transition: box-shadow 0.15s linear; } + +.cards table.dataview > tbody > tr:hover { + border: var(--cards-border-width) solid var(--background-modifier-border-hover); + box-shadow: 0 4px 6px 0px rgba(0, 0, 0, 0.05), 0 1px 3px 1px rgba(0, 0, 0, 0.025); + transition: box-shadow 0.15s linear; } + +/* Styling elements inside cards */ +.markdown-source-view.mod-cm6.cards .dataview.table-view-table > tbody > tr > td, +.trim-cols .cards table.dataview tbody > tr > td { + white-space: normal; } + +.markdown-source-view.mod-cm6.cards .dataview.table-view-table > tbody > tr > td, +.cards table.dataview tbody > tr > td { + border-bottom: none; + padding: 0 !important; + line-height: 1.2; + width: calc(100% - var(--cards-padding)); + margin: 0 auto; + overflow: visible !important; + max-width: 100%; + display: flex; } + +.cards table.dataview tbody > tr > td .el-p { + display: block; + width: 100%; } + +.cards table.dataview tbody > tr > td:first-child { + font-weight: var(--bold-weight); } + +.cards table.dataview tbody > tr > td:first-child a { + padding: 0 0 calc(var(--cards-padding)/3); + display: block; } + +.cards table.dataview tbody > tr > td:not(:first-child) { + font-size: 90%; + color: var(--text-muted); } + +@media (max-width: 400pt) { + .cards table.dataview tbody > tr > td:not(:first-child) { + font-size: 80%; } } +/* Helpers */ +.cards-cover.cards table.dataview tbody > tr > td img { + object-fit: cover; } + +.cards-16-9.cards table.dataview tbody > tr > td img { + aspect-ratio: 16/9; } + +.cards-1-1.cards table.dataview tbody > tr > td img { + aspect-ratio: 1/1; } + +.cards-2-1.cards table.dataview tbody > tr > td img { + aspect-ratio: 2/1; } + +.cards-2-3.cards table.dataview tbody > tr > td img { + aspect-ratio: 2/3; } + +.cards-align-bottom.cards table.dataview tbody > tr > td:last-child { + align-items: flex-end; + flex-grow: 1; } + +.cards-cols-1 table.dataview tbody { + grid-template-columns: repeat(1, minmax(0, 1fr)); } + +.cards-cols-2 table.dataview tbody { + grid-template-columns: repeat(2, minmax(0, 1fr)); } + +@media (min-width: 400pt) { + .cards-cols-3 table.dataview tbody { + grid-template-columns: repeat(3, minmax(0, 1fr)); } + + .cards-cols-4 table.dataview tbody { + grid-template-columns: repeat(4, minmax(0, 1fr)); } + + .cards-cols-5 table.dataview tbody { + grid-template-columns: repeat(5, minmax(0, 1fr)); } + + .cards-cols-6 table.dataview tbody { + grid-template-columns: repeat(6, minmax(0, 1fr)); } + + .cards-cols-7 table.dataview tbody { + grid-template-columns: repeat(7, minmax(0, 1fr)); } + + .cards-cols-8 table.dataview tbody { + grid-template-columns: repeat(8, minmax(0, 1fr)); } } +/* Card content */ +/* Paragraphs */ +.cards table.dataview tbody > tr > td > *:not(.el-embed-image) { + padding: calc(var(--cards-padding)/3) 0; } + +.cards table.dataview tbody > tr > td:not(:last-child):not(:first-child) > .el-p:not(.el-embed-image) { + border-bottom: 1px solid var(--background-modifier-border); + width: 100%; } + +/* Links */ +.cards table.dataview tbody > tr > td a { + text-decoration: none; } + +.links-int-on .cards table.dataview tbody > tr > td a { + text-decoration: none; } + +/* Buttons */ +.cards table.dataview tbody > tr > td > button { + width: 100%; + margin: calc(var(--cards-padding)/2) 0; } + +.cards table.dataview tbody > tr > td:last-child > button { + margin-bottom: calc(var(--cards-padding)/6); } + +/* Lists */ +.cards table.dataview tbody > tr > td > ul { + width: 100%; + padding: 0.25em 0 !important; + margin: 0 auto !important; } + +.cards table.dataview tbody > tr > td:not(:last-child) > ul { + border-bottom: 1px solid var(--background-modifier-border); } + +/* Images */ +.cards table.dataview tbody > tr > td .el-embed-image { + background-color: var(--background-secondary); + display: block; + margin: 0 calc(var(--cards-padding)/-2) 0 calc(var(--cards-padding)/-2); + width: calc(100% + var(--cards-padding)); } + +.cards table.dataview tbody > tr > td img { + width: 100%; + object-fit: var(--cards-image-fit); + max-height: var(--cards-image-height); + background-color: var(--background-secondary); + vertical-align: bottom; } + +/* ------------------- */ +/* Block button */ +.markdown-source-view.mod-cm6.cards .edit-block-button { + top: 0px; } + +/* ------------------- */ +/* Sorting */ +.cards.table-100 table.dataview thead > tr, +.table-100 .cards table.dataview thead > tr { + right: 0.75rem; } + +.table-100 .cards table.dataview thead:before, +.cards.table-100 table.dataview thead:before { + margin-right: 0.75rem; } + +.cards table.dataview thead { + user-select: none; + width: 180px; + display: block; + float: right; + position: relative; + text-align: right; + height: 24px; + padding-bottom: 4px; } + +.cards table.dataview thead:before { + content: ''; + position: absolute; + right: 0; + top: 0; + height: var(--icon-size); + background-repeat: no-repeat; + cursor: var(--cursor); + text-align: right; + padding: 4px 10px; + margin-bottom: 2px; + border-radius: 5px; + font-weight: 500; + font-size: var(--font-adaptive-small); } + +.cards table.dataview thead:before { + opacity: 0.25; + background-position: center center; + background-size: var(--icon-size); + background-image: url('data:image/svg+xml;utf8,'); } + +.theme-light .cards table.dataview thead:before { + background-image: url('data:image/svg+xml;utf8,'); } + +.cards table.dataview thead:hover:before { + opacity: 0.5; } + +.cards table.dataview thead > tr { + top: 0; + position: absolute; + display: none; + z-index: 9; + border: 1px solid var(--background-modifier-border); + background-color: var(--background-secondary); + box-shadow: 0 2px 8px var(--background-modifier-box-shadow); + padding: 6px; + border-radius: 6px; + flex-direction: column; + margin: 26px 0 0 0; + width: 100%; } + +.cards table.dataview thead:hover > tr { + display: flex; } + +.cards table.dataview thead > tr > th { + display: block; + padding: 3px 30px 3px 6px !important; + border-radius: 5px; + width: 100%; + font-weight: 400; + color: var(--text-muted); + cursor: var(--cursor); + border: none; + font-size: var(--font-adaptive-small); } + +.cards table.dataview thead > tr > th[sortable-style="sortable-asc"], +.cards table.dataview thead > tr > th[sortable-style="sortable-desc"] { + color: var(--text-normal); } + +.cards table.dataview thead > tr > th:hover { + color: var(--text-normal); + background-color: var(--background-tertiary); } + +/* Checklist icons */ +.cm-formatting.cm-formatting-task.cm-property { + font-family: var(--font-monospace); + font-size: 90%; } + +input[data-task=">"]:checked, +input[data-task="!"]:checked, +input[data-task="-"]:checked, +input[data-task="<"]:checked, +input[data-task="l"]:checked, +input[data-task="*"]:checked, +input[data-task="I"]:checked, +input[data-task="p"]:checked, +input[data-task="f"]:checked, +input[data-task="k"]:checked, +input[data-task="u"]:checked, +input[data-task="w"]:checked, +input[data-task="c"]:checked, +input[data-task="d"]:checked, +input[data-task="b"]:checked, +li[data-task=">"] > input:checked, +li[data-task="!"] > input:checked, +li[data-task="-"] > input:checked, +li[data-task="<"] > input:checked, +li[data-task="l"] > input:checked, +li[data-task="*"] > input:checked, +li[data-task="I"] > input:checked, +li[data-task="p"] > input:checked, +li[data-task="f"] > input:checked, +li[data-task="k"] > input:checked, +li[data-task="u"] > input:checked, +li[data-task="d"] > input:checked, +li[data-task="w"] > input:checked, +li[data-task="c"] > input:checked, +li[data-task="b"] > input:checked, +li[data-task=">"] > p > input:checked, +li[data-task="!"] > p > input:checked, +li[data-task="-"] > p > input:checked, +li[data-task="<"] > p > input:checked, +li[data-task="l"] > p > input:checked, +li[data-task="*"] > p > input:checked, +li[data-task="I"] > p > input:checked, +li[data-task="p"] > p > input:checked, +li[data-task="f"] > p > input:checked, +li[data-task="k"] > p > input:checked, +li[data-task="u"] > p > input:checked, +li[data-task="d"] > p > input:checked, +li[data-task="w"] > p > input:checked, +li[data-task="c"] > p > input:checked, +li[data-task="b"] > p > input:checked { + border: none; + border-radius: 0; + background-image: none; + background-color: currentColor; + -webkit-mask-size: var(--checkbox-icon); + -webkit-mask-position: 50% 50%; } + +/* [>] Forwarded */ +input[data-task=">"]:checked, +li[data-task=">"] > input:checked, +li[data-task=">"] > p > input:checked { + color: var(--text-faint); + transform: rotate(90deg); + -webkit-mask-position: 50% 100%; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z' /%3E%3C/svg%3E"); } + +/* [<] Schedule */ +input[data-task="<"]:checked, +li[data-task="<"] > input:checked, +li[data-task="<"] > p > input:checked { + color: var(--text-faint); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z' clip-rule='evenodd' /%3E%3C/svg%3E"); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [?] Question */ +input[data-task="?"]:checked, +li[data-task="?"] > input:checked, +li[data-task="?"] > p > input:checked { + background-color: var(--yellow); + border-color: var(--yellow); + background-position: 50% 50%; + background-size: 200% 90%; + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"%3E%3Cpath fill="white" fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215c0 1.344-.665 2.288-1.79 2.973c-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712c1.03-.632 1.397-1.135 1.397-2.028c0-.979-.758-1.698-1.926-1.698c-1.009 0-1.71.529-1.938 1.402c-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09c0-.618-.473-1.092-1.095-1.092c-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z"%2F%3E%3C%2Fsvg%3E'); } +.theme-dark input[data-task="?"]:checked, +.theme-dark li[data-task="?"] > input:checked, +.theme-dark li[data-task="?"] > p > input:checked { + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"%3E%3Cpath fill="black" fill-opacity="0.8" fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215c0 1.344-.665 2.288-1.79 2.973c-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712c1.03-.632 1.397-1.135 1.397-2.028c0-.979-.758-1.698-1.926-1.698c-1.009 0-1.71.529-1.938 1.402c-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09c0-.618-.473-1.092-1.095-1.092c-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z"%2F%3E%3C%2Fsvg%3E'); } + +/* [/] Incomplete */ +input[data-task="/"]:checked, +li[data-task="/"] > input:checked, +li[data-task="/"] > p > input:checked { + background-image: none; + background-color: transparent; + position: relative; + overflow: hidden; } + input[data-task="/"]:checked:after, + li[data-task="/"] > input:checked:after, + li[data-task="/"] > p > input:checked:after { + content: " "; + display: block; + position: absolute; + background-color: var(--background-modifier-accent); + width: calc(50% - 0.5px); + height: 100%; } + +/* [!] Important */ +input[data-task="!"]:checked, +li[data-task="!"] > input:checked, +li[data-task="!"] > p > input:checked { + color: var(--orange); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* ["] Quote */ +input[data-task="“"]:checked, +li[data-task="“"] > input:checked, +li[data-task="“"] > p > input:checked, +input[data-task="\""]:checked, +li[data-task="\""] > input:checked, +li[data-task="\""] > p > input:checked { + background-position: 50% 50%; + background-color: var(--cyan); + border-color: var(--cyan); + background-size: 75%; + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"%3E%3Cpath fill="white" d="M6.5 10c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.318.142-.686.238-1.028.466c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.945c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 6.5 10zm11 0c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.317.143-.686.238-1.028.467c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.944c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 17.5 10z"%2F%3E%3C%2Fsvg%3E'); } +.theme-dark input[data-task="“"]:checked, +.theme-dark li[data-task="“"] > input:checked, +.theme-dark li[data-task="“"] > p > input:checked, +.theme-dark input[data-task="\""]:checked, +.theme-dark li[data-task="\""] > input:checked, +.theme-dark li[data-task="\""] > p > input:checked { + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"%3E%3Cpath fill="black" fill-opacity="0.7" d="M6.5 10c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.318.142-.686.238-1.028.466c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.945c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 6.5 10zm11 0c-.223 0-.437.034-.65.065c.069-.232.14-.468.254-.68c.114-.308.292-.575.469-.844c.148-.291.409-.488.601-.737c.201-.242.475-.403.692-.604c.213-.21.492-.315.714-.463c.232-.133.434-.28.65-.35l.539-.222l.474-.197l-.485-1.938l-.597.144c-.191.048-.424.104-.689.171c-.271.05-.56.187-.882.312c-.317.143-.686.238-1.028.467c-.344.218-.741.4-1.091.692c-.339.301-.748.562-1.05.944c-.33.358-.656.734-.909 1.162c-.293.408-.492.856-.702 1.299c-.19.443-.343.896-.468 1.336c-.237.882-.343 1.72-.384 2.437c-.034.718-.014 1.315.028 1.747c.015.204.043.402.063.539l.025.168l.026-.006A4.5 4.5 0 1 0 17.5 10z"%2F%3E%3C%2Fsvg%3E'); } + +/* [-] Canceled */ +input[data-task="-"]:checked, +li[data-task="-"] > input:checked, +li[data-task="-"] > p > input:checked { + color: var(--text-faint); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +body:not(.tasks) .markdown-source-view.mod-cm6 .HyperMD-task-line[data-task]:is([data-task="-"]), +body:not(.tasks) .markdown-preview-view ul li[data-task="-"].task-list-item.is-checked, +body:not(.tasks) li[data-task="-"].task-list-item.is-checked { + color: var(--text-faint); + text-decoration: line-through solid var(--text-faint) 1px; } + +/* [*] Star */ +input[data-task="*"]:checked, +li[data-task="*"] > input:checked, +li[data-task="*"] > p > input:checked { + color: var(--yellow); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z' /%3E%3C/svg%3E"); } + +/* [l] Location */ +input[data-task="l"]:checked, +li[data-task="l"] > input:checked, +li[data-task="l"] > p > input:checked { + color: var(--red); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [i] Info */ +input[data-task="i"]:checked, +li[data-task="i"] > input:checked, +li[data-task="i"] > p > input:checked { + background-color: var(--blue); + border-color: var(--blue); + background-position: 50%; + background-size: 100%; + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512"%3E%3Cpath fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="40" d="M196 220h64v172"%2F%3E%3Cpath fill="none" stroke="white" stroke-linecap="round" stroke-miterlimit="10" stroke-width="40" d="M187 396h138"%2F%3E%3Cpath fill="white" d="M256 160a32 32 0 1 1 32-32a32 32 0 0 1-32 32Z"%2F%3E%3C%2Fsvg%3E'); } +.theme-dark input[data-task="i"]:checked, +.theme-dark li[data-task="i"] > input:checked, +.theme-dark li[data-task="i"] > p > input:checked { + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512"%3E%3Cpath fill="none" stroke="black" stroke-opacity="0.8" stroke-linecap="round" stroke-linejoin="round" stroke-width="40" d="M196 220h64v172"%2F%3E%3Cpath fill="none" stroke="black" stroke-opacity="0.8" stroke-linecap="round" stroke-miterlimit="10" stroke-width="40" d="M187 396h138"%2F%3E%3Cpath fill="black" fill-opacity="0.8" d="M256 160a32 32 0 1 1 32-32a32 32 0 0 1-32 32Z"%2F%3E%3C%2Fsvg%3E'); } + +/* [S] Amount/savings/money */ +input[data-task="S"]:checked, +li[data-task="S"] > input:checked, +li[data-task="S"] > p > input:checked { + border-color: var(--green); + background-color: var(--green); + background-size: 100%; + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 48 48"%3E%3Cpath fill="white" fill-rule="evenodd" d="M26 8a2 2 0 1 0-4 0v2a8 8 0 1 0 0 16v8a4.002 4.002 0 0 1-3.773-2.666a2 2 0 0 0-3.771 1.332A8.003 8.003 0 0 0 22 38v2a2 2 0 1 0 4 0v-2a8 8 0 1 0 0-16v-8a4.002 4.002 0 0 1 3.773 2.666a2 2 0 0 0 3.771-1.332A8.003 8.003 0 0 0 26 10V8Zm-4 6a4 4 0 0 0 0 8v-8Zm4 12v8a4 4 0 0 0 0-8Z" clip-rule="evenodd"%2F%3E%3C%2Fsvg%3E'); } +.theme-dark input[data-task="S"]:checked, +.theme-dark li[data-task="S"] > input:checked, +.theme-dark li[data-task="S"] > p > input:checked { + background-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 48 48"%3E%3Cpath fill-opacity="0.8" fill="black" fill-rule="evenodd" d="M26 8a2 2 0 1 0-4 0v2a8 8 0 1 0 0 16v8a4.002 4.002 0 0 1-3.773-2.666a2 2 0 0 0-3.771 1.332A8.003 8.003 0 0 0 22 38v2a2 2 0 1 0 4 0v-2a8 8 0 1 0 0-16v-8a4.002 4.002 0 0 1 3.773 2.666a2 2 0 0 0 3.771-1.332A8.003 8.003 0 0 0 26 10V8Zm-4 6a4 4 0 0 0 0 8v-8Zm4 12v8a4 4 0 0 0 0-8Z" clip-rule="evenodd"%2F%3E%3C%2Fsvg%3E'); } + +/* [I] Idea/lightbulb */ +input[data-task="I"]:checked, +li[data-task="I"] > input:checked, +li[data-task="I"] > p > input:checked { + color: var(--yellow); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M11 3a1 1 0 10-2 0v1a1 1 0 102 0V3zM15.657 5.757a1 1 0 00-1.414-1.414l-.707.707a1 1 0 001.414 1.414l.707-.707zM18 10a1 1 0 01-1 1h-1a1 1 0 110-2h1a1 1 0 011 1zM5.05 6.464A1 1 0 106.464 5.05l-.707-.707a1 1 0 00-1.414 1.414l.707.707zM5 10a1 1 0 01-1 1H3a1 1 0 110-2h1a1 1 0 011 1zM8 16v-1h4v1a2 2 0 11-4 0zM12 14c.015-.34.208-.646.477-.859a4 4 0 10-4.954 0c.27.213.462.519.476.859h4.002z' /%3E%3C/svg%3E"); } + +/* [f] Fire */ +input[data-task="f"]:checked, +li[data-task="f"] > input:checked, +li[data-task="f"] > p > input:checked { + color: var(--red); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [k] Key */ +input[data-task="k"]:checked, +li[data-task="k"] > input:checked, +li[data-task="k"] > p > input:checked { + color: var(--yellow); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [u] Up */ +input[data-task="u"]:checked, +li[data-task="u"] > input:checked, +li[data-task="u"] > p > input:checked { + color: var(--green); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [d] Down */ +input[data-task="d"]:checked, +li[data-task="d"] > input:checked, +li[data-task="d"] > p > input:checked { + color: var(--red); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12 13a1 1 0 100 2h5a1 1 0 001-1V9a1 1 0 10-2 0v2.586l-4.293-4.293a1 1 0 00-1.414 0L8 9.586 3.707 5.293a1 1 0 00-1.414 1.414l5 5a1 1 0 001.414 0L11 9.414 14.586 13H12z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [w] Win */ +input[data-task="w"]:checked, +li[data-task="w"] > input:checked, +li[data-task="w"] > p > input:checked { + color: var(--purple); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M6 3a1 1 0 011-1h.01a1 1 0 010 2H7a1 1 0 01-1-1zm2 3a1 1 0 00-2 0v1a2 2 0 00-2 2v1a2 2 0 00-2 2v.683a3.7 3.7 0 011.055.485 1.704 1.704 0 001.89 0 3.704 3.704 0 014.11 0 1.704 1.704 0 001.89 0 3.704 3.704 0 014.11 0 1.704 1.704 0 001.89 0A3.7 3.7 0 0118 12.683V12a2 2 0 00-2-2V9a2 2 0 00-2-2V6a1 1 0 10-2 0v1h-1V6a1 1 0 10-2 0v1H8V6zm10 8.868a3.704 3.704 0 01-4.055-.036 1.704 1.704 0 00-1.89 0 3.704 3.704 0 01-4.11 0 1.704 1.704 0 00-1.89 0A3.704 3.704 0 012 14.868V17a1 1 0 001 1h14a1 1 0 001-1v-2.132zM9 3a1 1 0 011-1h.01a1 1 0 110 2H10a1 1 0 01-1-1zm3 0a1 1 0 011-1h.01a1 1 0 110 2H13a1 1 0 01-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + +/* [p] Pros */ +input[data-task="p"]:checked, +li[data-task="p"] > input:checked, +li[data-task="p"] > p > input:checked { + color: var(--green); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z' /%3E%3C/svg%3E"); } + +/* [c] Cons */ +input[data-task="c"]:checked, +li[data-task="c"] > input:checked, +li[data-task="c"] > p > input:checked { + color: var(--orange); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.105-1.79l-.05-.025A4 4 0 0011.055 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z' /%3E%3C/svg%3E"); } + +/* [b] Bookmark */ +input[data-task="b"]:checked, +li[data-task="b"] > input:checked, +li[data-task="b"] > p > input:checked { + color: var(--orange); + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z' /%3E%3C/svg%3E"); } + +/* Colorful active states */ +.colorful-active { + --sp1:var(--bg1); } + +.colorful-active .nav-file-title.is-active, +.colorful-active #calendar-container .active, +.colorful-active #calendar-container .active.today, +.colorful-active #calendar-container .active:hover, +.colorful-active #calendar-container .day:active, +.colorful-active .vertical-tab-nav-item.is-active, +.colorful-active .nav-file-title.is-being-dragged, +.colorful-active .nav-folder-title.is-being-dragged, +body.colorful-active:not(.is-grabbing) .nav-file-title.is-being-dragged:hover, +body.colorful-active:not(.is-grabbing) .nav-folder-title.is-being-dragged:hover, +body.colorful-active:not(.is-grabbing) .nav-file-title.is-active:hover, +.colorful-active .menu-item.selected:not(.is-disabled):not(.is-label), +.colorful-active .menu-item:hover, +.colorful-active .menu-item:hover:not(.is-disabled):not(.is-label) { + background-color: var(--ax3); + color: var(--sp1); } + +.colorful-active #calendar-container .day:active .dot, +.colorful-active #calendar-container .active .dot, +.colorful-active #calendar-container .today.active .dot { + fill: var(--sp1); } + +.colorful-active .menu-item.selected:not(.is-disabled):not(.is-label) .menu-item-icon, +.colorful-active .menu-item:hover .menu-item-icon { + color: var(--sp1); } + +.colorful-active .nav-file-title-content.is-being-renamed { + color: var(--text-normal); } + +.is-mobile.colorful-active .nav-file-title.is-active { + box-shadow: 0 0 0px 2px var(--ax3); } + +/* +.colorful-active .suggestion-container .suggestion-item:hover, +.colorful-active .modal-container .suggestion-item.is-selected { +}*/ +/* Colorful headings */ +body.colorful-headings { + --h1-color:var(--red); + --h2-color:var(--orange); + --h3-color:var(--yellow); + --h4-color:var(--green); + --h5-color:var(--blue); + --h6-color:var(--purple); } + +/* Icons + +Thank you to Matthew Meyers and Chetachi Ezikeuzor */ +.is-mobile .tree-item-self .collapse-icon { + width: 20px; } + +body:not(.minimal-icons-off) .view-action svg, +body:not(.minimal-icons-off) .workspace-tab-header-inner-icon svg, +body:not(.minimal-icons-off) .nav-action-button svg, +body:not(.minimal-icons-off) .graph-controls-button svg { + width: var(--icon-size); + height: var(--icon-size); } + +body:not(.minimal-icons-off) .menu-item-icon svg { + width: 16px; + height: 16px; } + +body:not(.minimal-icons-off) .workspace-ribbon-collapse-btn svg { + width: var(--icon-size); + height: var(--icon-size); } + +body:not(.minimal-icons-off) svg.any-key, +body:not(.minimal-icons-off) svg.blocks, +body:not(.minimal-icons-off) svg.bar-graph, +body:not(.minimal-icons-off) svg.breadcrumbs-trail-icon, +body:not(.minimal-icons-off) svg.audio-file, +body:not(.minimal-icons-off) svg.bold-glyph, +body:not(.minimal-icons-off) svg.italic-glyph, +body:not(.minimal-icons-off) svg.bracket-glyph, +body:not(.minimal-icons-off) svg.broken-link, +body:not(.minimal-icons-off) svg.bullet-list-glyph, +body:not(.minimal-icons-off) svg.bullet-list, +body:not(.minimal-icons-off) svg.calendar-day, +body:not(.minimal-icons-off) svg.calendar-with-checkmark, +body:not(.minimal-icons-off) svg.check-in-circle, +body:not(.minimal-icons-off) svg.check-small, +body:not(.minimal-icons-off) svg.checkbox-glyph, +body:not(.minimal-icons-off) svg.checkmark, +body:not(.minimal-icons-off) svg.clock, +body:not(.minimal-icons-off) svg.cloud, +body:not(.minimal-icons-off) svg.code-glyph, +body:not(.minimal-icons-off) svg.create-new, +body:not(.minimal-icons-off) svg.cross-in-box, +body:not(.minimal-icons-off) svg.cross, +body:not(.minimal-icons-off) svg.crossed-star, +body:not(.minimal-icons-off) svg.dice, +body:not(.minimal-icons-off) svg.disk, +body:not(.minimal-icons-off) svg.document, +body:not(.minimal-icons-off) svg.documents, +body:not(.minimal-icons-off) svg.dot-network, +body:not(.minimal-icons-off) svg.double-down-arrow-glyph, +body:not(.minimal-icons-off) svg.double-up-arrow-glyph, +body:not(.minimal-icons-off) svg.down-arrow-with-tail, +body:not(.minimal-icons-off) svg.down-chevron-glyph, +body:not(.minimal-icons-off) svg.enter, +body:not(.minimal-icons-off) svg.exit-fullscreen, +body:not(.minimal-icons-off) svg.expand-vertically, +body:not(.minimal-icons-off) svg.excalidraw-icon, +body:not(.minimal-icons-off) svg.filled-pin, +body:not(.minimal-icons-off) svg.folder, +body:not(.minimal-icons-off) svg.fullscreen, +body:not(.minimal-icons-off) svg.gear, +body:not(.minimal-icons-off) svg.globe, +body:not(.minimal-icons-off) svg.hashtag, +body:not(.minimal-icons-off) svg.heading-glyph, +body:not(.minimal-icons-off) svg.go-to-file, +body:not(.minimal-icons-off) svg.help .widget-icon, +body:not(.minimal-icons-off) svg.help, +body:not(.minimal-icons-off) svg.highlight-glyph, +body:not(.minimal-icons-off) svg.horizontal-split, +body:not(.minimal-icons-off) svg.image-file, +body:not(.minimal-icons-off) svg.image-glyph, +body:not(.minimal-icons-off) svg.indent-glyph, +body:not(.minimal-icons-off) svg.info, +body:not(.minimal-icons-off) svg.install, +body:not(.minimal-icons-off) svg.keyboard-glyph, +body:not(.minimal-icons-off) svg.ledger, +body:not(.minimal-icons-off) svg.left-arrow-with-tail, +body:not(.minimal-icons-off) svg.left-arrow, +body:not(.minimal-icons-off) svg.left-chevron-glyph, +body:not(.minimal-icons-off) svg.lines-of-text, +body:not(.minimal-icons-off) svg.link-glyph, +body:not(.minimal-icons-off) svg.link, +body:not(.minimal-icons-off) svg.magnifying-glass, +body:not(.minimal-icons-off) svg.microphone-filled, +body:not(.minimal-icons-off) svg.microphone, +body:not(.minimal-icons-off) svg.minus-with-circle, +body:not(.minimal-icons-off) svg.note-glyph, +body:not(.minimal-icons-off) svg.number-list-glyph, +body:not(.minimal-icons-off) svg.open-vault, +body:not(.minimal-icons-off) svg.pane-layout, +body:not(.minimal-icons-off) svg.paper-plane, +body:not(.minimal-icons-off) svg.paused, +body:not(.minimal-icons-off) svg.pencil, +body:not(.minimal-icons-off) svg.pencil_icon, +body:not(.minimal-icons-off) svg.pin, +body:not(.minimal-icons-off) svg.plus-with-circle, +body:not(.minimal-icons-off) svg.popup-open, +body:not(.minimal-icons-off) svg.presentation, +body:not(.minimal-icons-off) svg.price-tag-glyph, +body:not(.minimal-icons-off) svg.quote-glyph, +body:not(.minimal-icons-off) svg.redo-glyph, +body:not(.minimal-icons-off) svg.reset, +body:not(.minimal-icons-off) svg.right-arrow-with-tail, +body:not(.minimal-icons-off) svg.right-arrow, +body:not(.minimal-icons-off) svg.right-chevron-glyph, +body:not(.minimal-icons-off) svg.right-triangle, +body:not(.minimal-icons-off) svg.run-command, +body:not(.minimal-icons-off) svg.search, +body:not(.minimal-icons-off) svg.ScriptEngine, +body:not(.minimal-icons-off) svg.sheets-in-box, +body:not(.minimal-icons-off) svg.spreadsheet, +body:not(.minimal-icons-off) svg.stacked-levels, +body:not(.minimal-icons-off) svg.star-list, +body:not(.minimal-icons-off) svg.star, +body:not(.minimal-icons-off) svg.strikethrough-glyph, +body:not(.minimal-icons-off) svg.switch, +body:not(.minimal-icons-off) svg.sync-small, +body:not(.minimal-icons-off) svg.sync, +body:not(.minimal-icons-off) svg.tag-glyph, +body:not(.minimal-icons-off) svg.three-horizontal-bars, +body:not(.minimal-icons-off) svg.trash, +body:not(.minimal-icons-off) svg.undo-glyph, +body:not(.minimal-icons-off) svg.unindent-glyph, +body:not(.minimal-icons-off) svg.up-and-down-arrows, +body:not(.minimal-icons-off) svg.up-arrow-with-tail, +body:not(.minimal-icons-off) svg.up-chevron-glyph, +body:not(.minimal-icons-off) svg.vault, +body:not(.minimal-icons-off) svg.vertical-split, +body:not(.minimal-icons-off) svg.vertical-three-dots, +body:not(.minimal-icons-off) svg.wrench-screwdriver-glyph, +body:not(.minimal-icons-off) svg.clock-glyph, +body:not(.minimal-icons-off) svg.command-glyph, +body:not(.minimal-icons-off) svg.add-note-glyph, +body:not(.minimal-icons-off) svg.calendar-glyph, +body:not(.minimal-icons-off) svg.duplicate-glyph, +body:not(.minimal-icons-off) svg.file-explorer-glyph, +body:not(.minimal-icons-off) svg.graph-glyph, +body:not(.minimal-icons-off) svg.import-glyph, +body:not(.minimal-icons-off) svg.languages, +body:not(.minimal-icons-off) svg.links-coming-in, +body:not(.minimal-icons-off) svg.links-going-out, +body:not(.minimal-icons-off) svg.merge-files-glyph, +body:not(.minimal-icons-off) svg.merge-files, +body:not(.minimal-icons-off) svg.open-elsewhere-glyph, +body:not(.minimal-icons-off) svg.obsidian-leaflet-plugin-icon-map, +body:not(.minimal-icons-off) svg.paper-plane-glyph, +body:not(.minimal-icons-off) svg.paste-text, +body:not(.minimal-icons-off) svg.paste, +body:not(.minimal-icons-off) svg.percent-sign-glyph, +body:not(.minimal-icons-off) svg.play-audio-glyph, +body:not(.minimal-icons-off) svg.plus-minus-glyph, +body:not(.minimal-icons-off) svg.presentation-glyph, +body:not(.minimal-icons-off) svg.question-mark-glyph, +body:not(.minimal-icons-off) svg.reading-glasses, +body:not(.minimal-icons-off) svg.restore-file-glyph, +body:not(.minimal-icons-off) svg.scissors-glyph, +body:not(.minimal-icons-off) svg.scissors, +body:not(.minimal-icons-off) svg.search-glyph, +body:not(.minimal-icons-off) svg.select-all-text, +body:not(.minimal-icons-off) svg.split, +body:not(.minimal-icons-off) svg.star-glyph, +body:not(.minimal-icons-off) svg.stop-audio-glyph, +body:not(.minimal-icons-off) svg.sweep, +body:not(.minimal-icons-off) svg.two-blank-pages, +body:not(.minimal-icons-off) svg.tomorrow-glyph, +body:not(.minimal-icons-off) svg.yesterday-glyph, +body:not(.minimal-icons-off) svg.workspace-glyph, +body:not(.minimal-icons-off) svg.box-glyph, +body:not(.minimal-icons-off) svg.wand, +body:not(.minimal-icons-off) svg.longform, +body:not(.minimal-icons-off) svg.changelog { + background-color: currentColor; } + +body:not(.minimal-icons-off) svg.any-key > path, +body:not(.minimal-icons-off) svg.blocks > path, +body:not(.minimal-icons-off) svg.bar-graph > path, +body:not(.minimal-icons-off) svg.breadcrumbs-trail-icon > path, +body:not(.minimal-icons-off) svg.audio-file > path, +body:not(.minimal-icons-off) svg.bold-glyph > path, +body:not(.minimal-icons-off) svg.italic-glyph > path, +body:not(.minimal-icons-off) svg.bracket-glyph > path, +body:not(.minimal-icons-off) svg.broken-link > path, +body:not(.minimal-icons-off) svg.bullet-list-glyph > path, +body:not(.minimal-icons-off) svg.bullet-list > path, +body:not(.minimal-icons-off) svg.calendar-day > path, +body:not(.minimal-icons-off) svg.calendar-with-checkmark > path, +body:not(.minimal-icons-off) svg.check-in-circle > path, +body:not(.minimal-icons-off) svg.check-small > path, +body:not(.minimal-icons-off) svg.checkbox-glyph > path, +body:not(.minimal-icons-off) svg.checkmark > path, +body:not(.minimal-icons-off) svg.clock > path, +body:not(.minimal-icons-off) svg.cloud > path, +body:not(.minimal-icons-off) svg.code-glyph > path, +body:not(.minimal-icons-off) svg.command-glyph > path, +body:not(.minimal-icons-off) svg.create-new > path, +body:not(.minimal-icons-off) svg.cross-in-box > path, +body:not(.minimal-icons-off) svg.cross > path, +body:not(.minimal-icons-off) svg.crossed-star > path, +body:not(.minimal-icons-off) svg.dice > path, +body:not(.minimal-icons-off) svg.disk > path, +body:not(.minimal-icons-off) svg.document > path, +body:not(.minimal-icons-off) svg.documents > path, +body:not(.minimal-icons-off) svg.dot-network > path, +body:not(.minimal-icons-off) svg.double-down-arrow-glyph > path, +body:not(.minimal-icons-off) svg.double-up-arrow-glyph > path, +body:not(.minimal-icons-off) svg.down-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.down-chevron-glyph > path, +body:not(.minimal-icons-off) svg.enter > path, +body:not(.minimal-icons-off) svg.exit-fullscreen > path, +body:not(.minimal-icons-off) svg.expand-vertically > path, +body:not(.minimal-icons-off) svg.excalidraw-icon path, +body:not(.minimal-icons-off) svg.filled-pin > path, +body:not(.minimal-icons-off) svg.folder > path, +body:not(.minimal-icons-off) svg.fullscreen > path, +body:not(.minimal-icons-off) svg.gear > path, +body:not(.minimal-icons-off) svg.hashtag > path, +body:not(.minimal-icons-off) svg.heading-glyph > path, +body:not(.minimal-icons-off) svg.globe > path, +body:not(.minimal-icons-off) svg.go-to-file > path, +body:not(.minimal-icons-off) svg.help .widget-icon > path, +body:not(.minimal-icons-off) svg.help > path, +body:not(.minimal-icons-off) svg.highlight-glyph > path, +body:not(.minimal-icons-off) svg.horizontal-split > path, +body:not(.minimal-icons-off) svg.image-file > path, +body:not(.minimal-icons-off) svg.image-glyph > path, +body:not(.minimal-icons-off) svg.indent-glyph > path, +body:not(.minimal-icons-off) svg.info > path, +body:not(.minimal-icons-off) svg.install > path, +body:not(.minimal-icons-off) svg.keyboard-glyph > path, +body:not(.minimal-icons-off) svg.left-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.left-arrow > path, +body:not(.minimal-icons-off) svg.left-chevron-glyph > path, +body:not(.minimal-icons-off) svg.lines-of-text > path, +body:not(.minimal-icons-off) svg.link-glyph > path, +body:not(.minimal-icons-off) svg.link > path, +body:not(.minimal-icons-off) svg.magnifying-glass > path, +body:not(.minimal-icons-off) svg.microphone-filled > path, +body:not(.minimal-icons-off) svg.microphone > path, +body:not(.minimal-icons-off) svg.minus-with-circle > path, +body:not(.minimal-icons-off) svg.note-glyph > path, +body:not(.minimal-icons-off) svg.number-list-glyph > path, +body:not(.minimal-icons-off) svg.obsidian-leaflet-plugin-icon-map > path, +body:not(.minimal-icons-off) svg.open-vault > path, +body:not(.minimal-icons-off) svg.pane-layout > path, +body:not(.minimal-icons-off) svg.paper-plane > path, +body:not(.minimal-icons-off) svg.paused > path, +body:not(.minimal-icons-off) svg.pencil > path, +body:not(.minimal-icons-off) svg.pencil_icon > path, +body:not(.minimal-icons-off) svg.pin > path, +body:not(.minimal-icons-off) svg.plus-with-circle > path, +body:not(.minimal-icons-off) svg.popup-open > path, +body:not(.minimal-icons-off) svg.presentation > path, +body:not(.minimal-icons-off) svg.price-tag-glyph > path, +body:not(.minimal-icons-off) svg.quote-glyph > path, +body:not(.minimal-icons-off) svg.redo-glyph > path, +body:not(.minimal-icons-off) svg.reset > path, +body:not(.minimal-icons-off) svg.reading-glasses > path, +body:not(.minimal-icons-off) svg.right-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.right-arrow > path, +body:not(.minimal-icons-off) svg.right-chevron-glyph > path, +body:not(.minimal-icons-off) svg.right-triangle > path, +body:not(.minimal-icons-off) svg.run-command > path, +body:not(.minimal-icons-off) svg.ScriptEngine > path, +body:not(.minimal-icons-off) svg.search > path, +body:not(.minimal-icons-off) svg.sheets-in-box > path, +body:not(.minimal-icons-off) svg.spreadsheet > path, +body:not(.minimal-icons-off) svg.stacked-levels > path, +body:not(.minimal-icons-off) svg.star-list > path, +body:not(.minimal-icons-off) svg.star > path, +body:not(.minimal-icons-off) svg.strikethrough-glyph > path, +body:not(.minimal-icons-off) svg.switch > path, +body:not(.minimal-icons-off) svg.sync-small > path, +body:not(.minimal-icons-off) svg.sync > path, +body:not(.minimal-icons-off) svg.tag-glyph > path, +body:not(.minimal-icons-off) svg.three-horizontal-bars > path, +body:not(.minimal-icons-off) svg.trash > path, +body:not(.minimal-icons-off) svg.undo-glyph > path, +body:not(.minimal-icons-off) svg.unindent-glyph > path, +body:not(.minimal-icons-off) svg.up-and-down-arrows > path, +body:not(.minimal-icons-off) svg.up-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.up-chevron-glyph > path, +body:not(.minimal-icons-off) svg.vault > path, +body:not(.minimal-icons-off) svg.vertical-split > path, +body:not(.minimal-icons-off) svg.vertical-three-dots > path, +body:not(.minimal-icons-off) svg.wrench-screwdriver-glyph > path, +body:not(.minimal-icons-off) svg.clock-glyph > path, +body:not(.minimal-icons-off) svg.add-note-glyph > path, +body:not(.minimal-icons-off) svg.calendar-glyph > path, +body:not(.minimal-icons-off) svg.duplicate-glyph > path, +body:not(.minimal-icons-off) svg.file-explorer-glyph > path, +body:not(.minimal-icons-off) svg.graph-glyph > path, +body:not(.minimal-icons-off) svg.import-glyph > path, +body:not(.minimal-icons-off) svg.languages > path, +body:not(.minimal-icons-off) svg.links-coming-in > path, +body:not(.minimal-icons-off) svg.links-going-out > path, +body:not(.minimal-icons-off) svg.merge-files > path, +body:not(.minimal-icons-off) svg.open-elsewhere-glyph > path, +body:not(.minimal-icons-off) svg.paper-plane-glyph > path, +body:not(.minimal-icons-off) svg.paste-text > path, +body:not(.minimal-icons-off) svg.paste > path, +body:not(.minimal-icons-off) svg.percent-sign-glyph > path, +body:not(.minimal-icons-off) svg.play-audio-glyph > path, +body:not(.minimal-icons-off) svg.plus-minus-glyph > path, +body:not(.minimal-icons-off) svg.presentation-glyph > path, +body:not(.minimal-icons-off) svg.question-mark-glyph > path, +body:not(.minimal-icons-off) svg.restore-file-glyph > path, +body:not(.minimal-icons-off) svg.scissors-glyph > path, +body:not(.minimal-icons-off) svg.scissors > path, +body:not(.minimal-icons-off) svg.search-glyph > path, +body:not(.minimal-icons-off) svg.select-all-text > path, +body:not(.minimal-icons-off) svg.split > path, +body:not(.minimal-icons-off) svg.star-glyph > path, +body:not(.minimal-icons-off) svg.stop-audio-glyph > path, +body:not(.minimal-icons-off) svg.sweep > path, +body:not(.minimal-icons-off) svg.two-blank-pages > path, +body:not(.minimal-icons-off) svg.tomorrow-glyph > path, +body:not(.minimal-icons-off) svg.yesterday-glyph > path, +body:not(.minimal-icons-off) svg.workspace-glyph > path, +body:not(.minimal-icons-off) svg.box-glyph > path, +body:not(.minimal-icons-off) svg.wand > path, +body:not(.minimal-icons-off) svg.longform > path, +body:not(.minimal-icons-off) svg.changelog > path { + display: none; } + +body:not(.minimal-icons-off) svg.any-key { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.audio-file { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.bar-graph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.breadcrumbs-trail-icon { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.blocks { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.bold-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.italic-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.bracket-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.broken-link { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.bullet-list-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.bullet-list { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.calendar-with-checkmark { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.check-in-circle { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.check-small { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.checkbox-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.checkmark { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.clock { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.clock-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.cloud { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.code-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.cross-in-box { + -webkit-mask-image: url("data:image/svg+xml,"); } + +body:not(.minimal-icons-off) svg.cross { + -webkit-mask-image: url("data:image/svg+xml,"); + width: var(--icon-size); + height: var(--icon-size); } + +body:not(.minimal-icons-off) svg.crossed-star { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.dice { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.disk { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.document { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) .nav-action-button[aria-label="New note"] svg.document, +body:not(.minimal-icons-off) .workspace-leaf-content[data-type="file-explorer"] .nav-action-button:first-child svg.document, +body:not(.minimal-icons-off) svg.create-new { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.documents { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.dot-network { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.double-down-arrow-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.double-up-arrow-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.down-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.down-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.enter { + transform: translate(-2px); + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.excalidraw-icon { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.expand-vertically { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.filled-pin { + transform: rotate(45deg); + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.folder { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) .workspace-tab-header[data-type="file-explorer"] svg.folder, +body:not(.minimal-icons-off) .workspace-tab-header[aria-label="File explorer"] svg.folder { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) .nav-action-button[aria-label="New folder"] svg.folder, +body:not(.minimal-icons-off) .workspace-leaf-content[data-type="file-explorer"] .nav-action-button:nth-child(2) svg.folder { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.fullscreen { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.ScriptEngine, +body:not(.minimal-icons-off) svg.gear { + -webkit-mask-image: url("data:image/svg+xml,"); } + +body:not(.minimal-icons-off) svg.globe { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.hashtag { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.heading-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.go-to-file { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.help .widget-icon, +body:not(.minimal-icons-off) svg.help { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.highlight-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.horizontal-split { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.image-file { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.image-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.indent-glyph { + -webkit-mask-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="24" height="24" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"%3E%3Cg fill="black"%3E%3Cpath d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm.646 2.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L4.293 8L2.646 6.354a.5.5 0 0 1 0-.708zM7 6.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm-5 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E'); } + +body:not(.minimal-icons-off) svg.info { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.install { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.keyboard-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.left-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.left-arrow { + -webkit-mask-image: url("data:image/svg+xml,"); } + +body:not(.minimal-icons-off) svg.left-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.reading-glasses, +body:not(.minimal-icons-off) svg.lines-of-text { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.ledger { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.link-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); + transform: rotate(90deg); } + +body:not(.minimal-icons-off) svg.link { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); + transform: rotate(90deg); } + +body:not(.minimal-icons-off) svg.magnifying-glass { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.microphone-filled { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.microphone { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.minus-with-circle { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.note-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.number-list-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.open-vault { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.obsidian-leaflet-plugin-icon-map { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.pane-layout { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.paper-plane { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.paused { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +/* Text Generator plugin */ +body:not(.minimal-icons-off) svg.pencil_icon { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.pencil { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.pin { + transform: rotate(45deg); + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.plus-with-circle { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.popup-open { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.presentation { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.price-tag-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.quote-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) .workspace-tab-header[data-type="dictionary-view"] svg.quote-glyph { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' /%3E%3C/svg%3E"); } + +body:not(.minimal-icons-off) svg.redo-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.reset { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.right-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.right-arrow { + -webkit-mask-image: url("data:image/svg+xml,"); } + +body:not(.minimal-icons-off) svg.right-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.right-triangle { + color: var(--text-faint); + background-color: var(--text-faint); + height: 12px; + width: 12px; + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.command-glyph, +body:not(.minimal-icons-off) svg.run-command { + -webkit-mask-image: url("data:image/svg+xml,"); } + +body:not(.minimal-icons-off) svg.search { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.sheets-in-box { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.spreadsheet { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.stacked-levels { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.star-list { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.star { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.strikethrough-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.switch { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.sync-small { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.sync { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.tag-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body.is-mobile:not(.minimal-icons-off) .view-header-icon svg.three-horizontal-bars { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 26 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.18555 18.8857H9.87207V1.91309H8.18555V18.8857ZM6.1123 6.2207C6.27702 6.2207 6.42025 6.15983 6.54199 6.03809C6.66374 5.90918 6.72461 5.76953 6.72461 5.61914C6.72461 5.45443 6.66374 5.31478 6.54199 5.2002C6.42025 5.07845 6.27702 5.01758 6.1123 5.01758H3.81348C3.64876 5.01758 3.50553 5.07845 3.38379 5.2002C3.26204 5.31478 3.20117 5.45443 3.20117 5.61914C3.20117 5.76953 3.26204 5.90918 3.38379 6.03809C3.50553 6.15983 3.64876 6.2207 3.81348 6.2207H6.1123ZM6.1123 9.00293C6.27702 9.00293 6.42025 8.94206 6.54199 8.82031C6.66374 8.69857 6.72461 8.55534 6.72461 8.39062C6.72461 8.23307 6.66374 8.09701 6.54199 7.98242C6.42025 7.86068 6.27702 7.7998 6.1123 7.7998H3.81348C3.64876 7.7998 3.50553 7.86068 3.38379 7.98242C3.26204 8.09701 3.20117 8.23307 3.20117 8.39062C3.20117 8.55534 3.26204 8.69857 3.38379 8.82031C3.50553 8.94206 3.64876 9.00293 3.81348 9.00293H6.1123ZM6.1123 11.7744C6.27702 11.7744 6.42025 11.7171 6.54199 11.6025C6.66374 11.4808 6.72461 11.3411 6.72461 11.1836C6.72461 11.0189 6.66374 10.8792 6.54199 10.7646C6.42025 10.6429 6.27702 10.582 6.1123 10.582H3.81348C3.64876 10.582 3.50553 10.6429 3.38379 10.7646C3.26204 10.8792 3.20117 11.0189 3.20117 11.1836C3.20117 11.3411 3.26204 11.4808 3.38379 11.6025C3.50553 11.7171 3.64876 11.7744 3.81348 11.7744H6.1123ZM3.37305 20.2822H21.957C23.0885 20.2822 23.9336 20.0029 24.4922 19.4443C25.0508 18.8929 25.3301 18.0622 25.3301 16.9521V3.83594C25.3301 2.72591 25.0508 1.89518 24.4922 1.34375C23.9336 0.785156 23.0885 0.505859 21.957 0.505859H3.37305C2.2487 0.505859 1.40365 0.785156 0.837891 1.34375C0.279297 1.89518 0 2.72591 0 3.83594V16.9521C0 18.0622 0.279297 18.8929 0.837891 19.4443C1.40365 20.0029 2.2487 20.2822 3.37305 20.2822ZM3.39453 18.5527C2.85742 18.5527 2.44564 18.4131 2.15918 18.1338C1.87272 17.8473 1.72949 17.4248 1.72949 16.8662V3.92188C1.72949 3.36328 1.87272 2.94434 2.15918 2.66504C2.44564 2.37858 2.85742 2.23535 3.39453 2.23535H21.9355C22.4655 2.23535 22.8737 2.37858 23.1602 2.66504C23.4538 2.94434 23.6006 3.36328 23.6006 3.92188V16.8662C23.6006 17.4248 23.4538 17.8473 23.1602 18.1338C22.8737 18.4131 22.4655 18.5527 21.9355 18.5527H3.39453Z' fill='black'/%3E%3C/svg%3E%0A"); } + +body:not(.minimal-icons-off) svg.three-horizontal-bars { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.trash { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.undo-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.unindent-glyph { + -webkit-mask-image: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="24" height="24" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"%3E%3Cg fill="black"%3E%3Cpath d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm10.646 2.146a.5.5 0 0 1 .708.708L11.707 8l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708l2-2zM2 6.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E'); } + +body:not(.minimal-icons-off) svg.up-and-down-arrows { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.up-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.up-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.vault { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.vertical-split { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.vertical-three-dots { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.wrench-screwdriver-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.add-note-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.calendar-day { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.calendar-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.duplicate-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.file-explorer-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.graph-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.import-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.languages { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.links-coming-in { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.links-going-out { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.merge-files { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.open-elsewhere-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.paper-plane-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.paste-text { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.paste { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.percent-sign-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.play-audio-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.plus-minus-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.presentation-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.question-mark-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.restore-file-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.scissors-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.scissors { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.search-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.select-all-text { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.split { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.star-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.stop-audio-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.sweep { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.two-blank-pages { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.tomorrow-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.yesterday-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.workspace-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.box-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.wand { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.longform { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +body:not(.minimal-icons-off) svg.changelog { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); } + +/* Fancy cursor */ +.fancy-cursor .CodeMirror-cursor { + border: none; + border-left: 2px solid var(--text-accent); } + +.cm-fat-cursor .CodeMirror-cursor { + background-color: var(--text-accent); + opacity: 0.5; + width: 5px; } + +.cm-animate-fat-cursor { + background-color: var(--text-accent); + opacity: 0.5; + width: 5px; } + +/* Reset to default for iOS */ +body .markdown-source-view.mod-cm6 .cm-content { + caret-color: unset; } + +/* Live Preview */ +body.fancy-cursor .markdown-source-view.mod-cm6 .cm-content, +body.fancy-cursor .mod-cm6 .cm-line { + caret-color: var(--text-accent); } + +/* Prompt */ +.fancy-cursor input.prompt-input { + caret-color: var(--text-accent); } + +.nav-folder-children .nav-folder-children { + margin-left: 18px; + padding-left: 0; } + +body:not(.sidebar-lines-off) .nav-folder-children .nav-folder-children { + border-left: 1px solid var(--background-modifier-border); } + +.nav-folder-title { + margin-left: 6px; } + +.nav-file { + margin-left: 10px; } + +.mod-root > .nav-folder-children > .nav-file { + margin-left: 12px; } + +/* Focus mode */ +/* MIT License | Copyright (c) Stephan Ango (@kepano) */ +/* Hide app ribbon */ +.workspace-ribbon.mod-left { + border-left: 0; + transition: none; } + +.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed { + border-color: transparent; + background-color: var(--background-primary); } + +.minimal-focus-mode .workspace-ribbon.mod-left { + background-color: var(--background-secondary); + transition: background-color 0s linear 0s; } + +.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed, +.minimal-focus-mode .workspace-ribbon.is-collapsed .workspace-ribbon-collapse-btn { + opacity: 0; + transition: opacity 0.1s ease-in-out 0.1s, background-color 0.1s linear 0.1s; } + +.minimal-focus-mode .workspace-ribbon.mod-left.is-collapsed:hover, +.minimal-focus-mode .workspace-ribbon.is-collapsed:hover .workspace-ribbon-collapse-btn { + opacity: 1; } + +.is-right-sidedock-collapsed .workspace-split.mod-right-split { + margin-right: 0px; } + +body.minimal-focus-mode.borders-title .workspace-ribbon.mod-left.is-collapsed { + border-right: none; } + +/* Collapse header bar */ +body.minimal-focus-mode.borders-title .workspace-leaf .workspace-leaf-content:not([data-type='empty']):not([data-type='map']):not([data-type='graph']):not([data-type='localgraph']) .view-header, +body.minimal-focus-mode.borders-title .workspace-split.mod-root .workspace-leaf:first-of-type:last-of-type .workspace-leaf-content:not([data-type='empty']):not([data-type='map']):not([data-type='graph']):not([data-type='localgraph']) .view-header { + border-bottom: var(--border-width) solid transparent; } + +body.minimal-focus-mode.borders-title .workspace-leaf .workspace-leaf-content:not([data-type=graph]):not([data-type=localgraph]) .view-header:focus-within, +body.minimal-focus-mode.borders-title .workspace-split.mod-root .workspace-leaf:first-of-type:last-of-type .workspace-leaf-content:not([data-type=graph]):not([data-type=empty]):not([data-type=localgraph]) .view-header:focus-within, +body.minimal-focus-mode.borders-title .workspace-leaf .workspace-leaf-content:not([data-type=graph]):not([data-type=localgraph]) .view-header:hover, +body.minimal-focus-mode.borders-title .workspace-split.mod-root .workspace-leaf:first-of-type:last-of-type .workspace-leaf-content:not([data-type=graph]):not([data-type=empty]):not([data-type=localgraph]) .view-header:hover { + border-bottom: var(--border-width) solid var(--background-divider); } + +body:not(.plugin-sliding-panes-rotate-header) .app-container .workspace-split.mod-root > .workspace-leaf .view-header { + transition: height linear 0.1s; } + +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .app-container .workspace-split.mod-root > .workspace-leaf .view-header { + height: 0em; + transition: all linear 0.1s; } + +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .view-header::after { + width: 100%; + content: " "; + background-color: transparent; + height: 20px; + position: absolute; + z-index: -9; + top: 0; } + +body.minimal-focus-mode .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header-icon, +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .view-header-icon, +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .view-header-title, +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .view-actions { + opacity: 0; + transition: all linear 0.1s; } + +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-root .workspace-leaf .view-header:hover, +body.minimal-focus-mode:not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-root .workspace-leaf .view-header:focus-within { + height: calc(var(--header-height) + 2px); + transition: all linear 0.1s; } + +body.minimal-focus-mode .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header:hover .view-header-icon, +body.minimal-focus-mode .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header:focus-within .view-header-icon, +body.minimal-focus-mode.show-grabber .view-header:hover .view-header-icon, +body.minimal-focus-mode.show-grabber .view-header:focus-within .view-header-icon { + opacity: var(--icon-muted); } + +body.minimal-focus-mode .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header:hover .view-header-icon:hover, +body.minimal-focus-mode .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header:focus-within .view-header-icon:hover, +body.minimal-focus-mode .view-header:hover .view-header-icon:hover, +body.minimal-focus-mode .view-header:focus-within .view-header-icon:hover, +body.minimal-focus-mode .view-header:hover .view-actions, +body.minimal-focus-mode .view-header:focus-within .view-actions, +body.minimal-focus-mode .view-header:hover .view-header-title, +body.minimal-focus-mode .view-header:focus-within .view-header-title { + opacity: 1; + transition: all linear 0.1s; } + +.minimal-focus-mode .view-content { + height: 100%; } + +/* Hide status bar */ +.status-bar { + transition: opacity 0.2s ease-in-out; } + +.minimal-focus-mode:not(.minimal-status-off) .status-bar { + opacity: 0; } + +.minimal-focus-mode .status-bar:hover { + opacity: 1; + transition: opacity 0.2s ease-in-out; } + +/* Full width media */ +.full-width-media .markdown-preview-view .image-embed img:not(.emoji):not([width]), +.full-width-media .image-embed img:not(.emoji):not([width]), +.full-width-media .markdown-preview-view audio, +.full-width-media .markdown-preview-view video { + width: 100%; } + +/* Table helper classes for alternate styles */ +/* MIT License | Copyright (c) Stephan Ango (@kepano) */ +.table-small table:not(.calendar) { + --table-font-size:85%; } + +.table-tiny table:not(.calendar) { + --table-font-size:75%; } + +.markdown-source-view.mod-cm6 th, +.markdown-source-view.mod-cm6 td, +.markdown-preview-view .table-view-table > thead > tr > th, +table:not(.calendar) thead > tr > th, +table:not(.calendar) tbody > tr > td, +.table-view-table .tag, +.table-view-table a.tag { + font-size: var(--table-font-size); } + +.row-hover th:first-child, +.row-hover th:first-child, +.row-alt.markdown-source-view.mod-cm6 th:first-child, +.row-alt.markdown-source-view.mod-cm6 td:first-child, +.row-alt table:not(.calendar) th:first-child, +.row-alt table:not(.calendar) tbody > tr > td:first-child, +.table-lines.markdown-source-view.mod-cm6 th:first-child, +.table-lines.markdown-source-view.mod-cm6 td:first-child, +.table-lines table:not(.calendar) thead > tr > th:first-child, +.table-lines table:not(.calendar) tbody > tr > td:first-child { + padding-left: 10px; } + +.row-alt table:not(.calendar) tbody > tr:nth-child(odd), +.col-alt table:not(.calendar) tr > th:nth-child(2n+2), +.col-alt table:not(.calendar) tr > td:nth-child(2n+2) { + padding-left: 10px; + background: var(--background-table-rows); } + +.table-tabular table:not(.calendar) { + font-variant-numeric: tabular-nums; } + +.table-lines table:not(.calendar), +.table-lines .table-view-table { + border: 1px solid var(--background-modifier-border); } + +.table-lines table:not(.calendar) .table-view-table thead > tr > th, +.table-lines table:not(.calendar) .table-view-table > tbody > tr > td { + border-right: 1px solid var(--background-modifier-border); + border-bottom: 1px solid var(--background-modifier-border); + padding: 4px 10px; } + +.table-nowrap thead > tr > th, +.table-nowrap tbody > tr > td { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } + +.trim-cols .markdown-preview-view.table-wrap .table-view-table > tbody > tr > td, +.trim-cols .markdown-preview-view.table-wrap .table-view-table > thead > tr > th, +.trim-cols .markdown-source-view.mod-cm6.table-wrap .table-view-table > tbody > tr > td, +.trim-cols .markdown-source-view.mod-cm6.table-wrap .table-view-table > thead > tr > th, +.table-nowrap .table-wrap thead > tr > th, +.table-nowrap .table-wrap tbody > tr > td { + white-space: normal; + overflow: auto; } + +.table-numbers table:not(.calendar) { + counter-reset: section; } + +.table-numbers table:not(.calendar) > thead > tr > th:first-child::before { + content: " "; + padding-right: 0.5em; + display: inline-block; + min-width: 2em; } + +.table-numbers table:not(.calendar) > tbody > tr > td:first-child::before { + counter-increment: section; + content: counter(section) " "; + text-align: center; + padding-right: 0.5em; + display: inline-block; + min-width: 2em; + color: var(--text-faint); + font-variant-numeric: tabular-nums; } + +.row-highlight table:not(.calendar) tbody > tr:hover td { + background-color: var(--background-table-rows); } + +.row-lines table:not(.calendar) tbody > tr > td, +.row-lines .table-view-table > tbody > tr > td { + border-bottom: 1px solid var(--background-modifier-border); } + +.row-lines table:not(.calendar) tbody > tr:last-child > td { + border-bottom: none; } + +.col-lines table:not(.calendar) tbody > tr > td:not(:last-child), +.col-lines .table-view-table thead > tr > th:not(:last-child), +.col-lines .table-view-table > tbody > tr > td:not(:last-child) { + border-right: 1px solid var(--background-modifier-border); } + +/* Highlight rows on hover */ +.row-hover { + --row-color-hover: + hsla( + var(--accent-h), + 50%, + 80%, + 20% + ); } + +.theme-dark.row-hover { + --row-color-hover: + hsla( + var(--accent-h), + 30%, + 40%, + 20% + ); } + +.row-hover tr:hover td { + background-color: var(--row-color-hover); } + +/* Dark mode images */ +/* MIT License | Copyright (c) Stephan Ango (@kepano) */ +.theme-dark .markdown-source-view img, +.theme-dark .markdown-preview-view img { + opacity: var(--image-muted); + transition: opacity 0.25s linear; } + +.theme-dark .print-preview img, +.theme-dark .markdown-source-view img:hover, +.theme-dark .markdown-preview-view img:hover { + opacity: 1; + transition: opacity 0.25s linear; } + +/* Invert */ +.theme-dark img[src$="#invert"], +.theme-dark div[src$="#invert"] img, +.theme-dark span[src$="#invert"] img { + filter: invert(1) hue-rotate(180deg); + mix-blend-mode: screen; } + +.theme-dark div[src$="#invert"] { + background-color: var(--background-primary); } + +.theme-light img[src$="#invertW"], +.theme-light div[src$="#invertW"] img, +.theme-light span[src$="invertW"] img { + filter: invert(1) hue-rotate(180deg); } + +/* Circle */ +img[src$="#circle"], +span[src$="#circle"] img, +span[src$="#round"] img { + border-radius: 50%; + aspect-ratio: 1/1; } + +/* Outline */ +img[src$="#outline"], +span[src$="#outline"] img { + border: 1px solid var(--ui1); } + +/* Interface */ +img[src$="#interface"], +span[src$="#interface"] img { + border: 1px solid var(--ui1); + box-shadow: 0px 0.5px 0.9px rgba(0, 0, 0, 0.021), 0px 1.3px 2.5px rgba(0, 0, 0, 0.03), 0px 3px 6px rgba(0, 0, 0, 0.039), 0px 10px 20px rgba(0, 0, 0, 0.06); + margin-top: 10px; + margin-bottom: 15px; + border-radius: var(--radius-m); } + +/* MIT License | Copyright (c) Stephan Ango (@kepano) + +Image Grid snippet for Obsidian + +author: @kepano +version: 3.0.0 + +Support my work: +https://github.com/sponsors/kepano + +*/ +/* Requires Contextual Typography 2.2.1+ */ +div:not(.el-embed-image) + .el-embed-image { + margin-top: 1rem; } + +.el-embed-image { + margin-top: 0.5rem; } + +.contextual-typography .markdown-preview-section > .el-embed-image > p { + margin-block-start: 0; + margin-block-end: 0; } + +.img-grid .markdown-preview-section .el-embed-image img:not(.emoji):not([width]), +.img-grid .markdown-preview-section video { + width: 100%; } + +.img-grid .markdown-preview-section > .el-embed-image > p { + display: grid; + grid-column-gap: 0.5rem; + grid-row-gap: 0; + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } + +.img-grid .markdown-preview-section > .el-embed-image > p > br { + display: none; } + +.img-grid .markdown-preview-section > .el-embed-image > p > img { + object-fit: cover; + align-self: stretch; } + +.img-grid .markdown-preview-section > .el-embed-image > p > .internal-embed img { + object-fit: cover; + height: 100%; } + +.img-grid .img-grid-ratio .markdown-preview-section > .el-embed-image > p > .internal-embed img, +.img-grid.img-grid-ratio .markdown-preview-section > .el-embed-image > p > .internal-embed img { + object-fit: contain; + height: 100%; + align-self: center; } + +@media (max-width: 400pt) { + .el-embed-image { + margin-top: 0.25rem; } + + .img-grid .markdown-preview-section > .el-embed-image > p { + grid-column-gap: 0.25rem; } } +/* Image zoom */ +/* MIT License | Copyright (c) Stephan Ango (@kepano) */ +body:not(.zoom-off) .view-content img { + max-width: 100%; + cursor: zoom-in; } + +body:not(.zoom-off) .view-content img:active { + cursor: zoom-out; } + +body:not(.is-mobile):not(.zoom-off) .view-content .markdown-preview-view img[referrerpolicy='no-referrer']:active, +body:not(.is-mobile):not(.zoom-off) .view-content .image-embed:active { + aspect-ratio: unset; + cursor: zoom-out; + display: block; + z-index: 200; + position: fixed; + max-height: calc(100% + 1px); + max-width: 100%; + height: calc(100% + 1px); + width: 100%; + object-fit: contain; + margin: -0.5px auto 0 !important; + text-align: center; + padding: 0; + left: 0; + right: 0; + bottom: 0; } + +body:not(.is-mobile):not(.zoom-off) .view-content .markdown-preview-view img[referrerpolicy='no-referrer']:active { + background-color: var(--background-primary); + padding: 10px; } + +body:not(.is-mobile):not(.zoom-off) .view-content .image-embed:active:after { + background-color: var(--background-primary); + opacity: 0.9; + content: " "; + height: calc(100% + 1px); + width: 100%; + position: fixed; + left: 0; + right: 1px; + z-index: 0; } + +body:not(.is-mobile):not(.zoom-off) .view-content .image-embed:active img { + aspect-ratio: unset; + top: 50%; + z-index: 99; + transform: translateY(-50%); + padding: 0; + margin: 0 auto; + width: calc(100% - 20px); + max-height: 95vh; + object-fit: contain; + left: 0; + right: 0; + bottom: 0; + position: absolute; + opacity: 1; } + +/* MIT License | Copyright (c) Stephan Ango (@kepano) + +Labeled Nav snippet for Obsidian + +author: @kepano +version: 1.2.0 + +Support my work: +https://github.com/sponsors/kepano + +*/ +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-container { + height: auto; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-container-inner { + flex-direction: column; + padding: 8px 8px 4px 8px; + background-color: transparent; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header { + padding: 0; + margin-bottom: 2px; + border: none; + height: auto; + opacity: 0.75; } + .labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header.is-active, .labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header:hover { + opacity: 1; + background-color: transparent; } + .labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header .workspace-tab-header-inner { + padding: 0; + box-shadow: none; + border: none; + border-radius: 6px; } + .labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header .workspace-tab-header-inner-icon { + border-radius: 6px; + padding: 5px 6px; + margin: 0; + height: 26px; + width: 100%; + opacity: 1; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header .workspace-tab-header-inner-icon:hover { + background-color: var(--background-tertiary); } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-inner-icon { + font-size: var(--font-small); + font-weight: 500; + display: flex; + align-items: center; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header:hover .workspace-tab-header-inner-icon, +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header.is-active .workspace-tab-header-inner-icon { + color: var(--icon-color-active); } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-inner-icon svg { + margin-right: 6px; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-container { + border: none; + padding: 0; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header .workspace-tab-header-inner-icon:after { + content: "Plugin"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="backlink"] .workspace-tab-header-inner-icon:after { + content: "Backlinks"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="calendar"] .workspace-tab-header-inner-icon:after { + content: "Calendar"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="dictionary-view"] .workspace-tab-header-inner-icon:after { + content: "Dictionary"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="localgraph"] .workspace-tab-header-inner-icon:after, +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="graph"] .workspace-tab-header-inner-icon:after { + content: "Graph"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="markdown"] .workspace-tab-header-inner-icon:after { + content: "Note"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="file-explorer"] .workspace-tab-header-inner-icon:after { + content: "Notes"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="outgoing-link"] .workspace-tab-header-inner-icon:after { + content: "Outlinks"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="outline"] .workspace-tab-header-inner-icon:after { + content: "Outline"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="recent-files"] .workspace-tab-header-inner-icon:after { + content: "Recent"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="reminder-list"] .workspace-tab-header-inner-icon:after { + content: "Reminders"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="search"] .workspace-tab-header-inner-icon:after { + content: "Search"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="starred"] .workspace-tab-header-inner-icon:after { + content: "Starred"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="style-settings"] .workspace-tab-header-inner-icon:after { + content: "Style"; } +.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header[data-type="tag"] .workspace-tab-header-inner-icon:after { + content: "Tags"; } + +/* MIT License | Copyright (c) Stephan Ango (@kepano) + +Layout Control snippet for Obsidian + +author: @kepano +version: 2.0.0 + +Support my work: +https://github.com/sponsors/kepano + +*/ +/* Requires Contextual Typography 2.2.1+ */ +/* Switch to flexbox */ +.contextual-typography .markdown-reading-view > .markdown-preview-view { + padding-top: 15px; } + +.contextual-typography .markdown-preview-view.markdown-preview-view.is-readable-line-width .markdown-preview-sizer { + display: flex; + flex-direction: column; + width: 100%; + max-width: 100%; + padding-left: 0; + padding-top: 0; } + +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-preview-sizer { + align-items: center; + padding-left: 0; } + +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > div { + width: var(--max-width); } + +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > div { + margin-left: auto; + margin-right: auto; + max-width: var(--max-width); + width: var(--line-width-adaptive); } + +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-embed .markdown-preview-sizer > div { + max-width: 100%; } + +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-table, +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-dataview, +.contextual-typography .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-dataviewjs { + width: 100%; + max-width: 100%; + overflow-x: auto; } + +.el-lang-dataviewjs .block-language-dataviewjs .contains-task-list, +.el-lang-dataview .block-language-dataview .contains-task-list { + max-width: 100%; } + +.is-readable-line-width .el-table table, +.is-readable-line-width .el-lang-dataview .dataview.table-view-table, +.is-readable-line-width .el-lang-dataviewjs .dataview.table-view-table { + width: var(--max-width); + max-width: var(--line-width-adaptive); + margin: 0 auto 0.5rem; } + +.markdown-embed .el-table table, +.markdown-embed .el-lang-dataview .dataview.table-view-table { + width: 100%; } + +/* Dataview and tables */ +.table-100 .el-table table, +.table-100 .el-lang-dataviewjs .dataview.table-view-table, +.table-100 .el-lang-dataview .dataview.table-view-table { + max-width: 100% !important; + width: 100% !important; } + +.markdown-preview-view.table-100.is-readable-line-width .el-table table, +.markdown-preview-view.table-100.is-readable-line-width .el-lang-dataview .dataview.table-view-table, +.markdown-preview-view.table-100.is-readable-line-width .el-lang-dataviewjs .dataview.table-view-table { + max-width: 100% !important; + width: 100% !important; } + +.table-max .el-table table, +.table-max .el-lang-dataview .dataview.table-view-table, +.table-max .el-lang-dataviewjs .dataview.table-view-table { + max-width: 100% !important; } + +.markdown-preview-view.table-max .el-table table, +.markdown-preview-view.table-max .el-lang-dataview .dataview.table-view-table +.markdown-preview-view.table-max .el-lang-dataviewjs .dataview.table-view-table { + max-width: 100% !important; } + +.table-wide .markdown-preview-view.is-readable-line-width .el-table table, +.markdown-preview-view.is-readable-line-width.table-wide .el-table table, +.table-wide .markdown-preview-view.is-readable-line-width .el-lang-dataview .dataview.table-view-table, +.markdown-preview-view.is-readable-line-width.table-wide .el-lang-dataview .dataview.table-view-table, +.table-wide .markdown-preview-view.is-readable-line-width .el-lang-dataviewjs .dataview.table-view-table, +.markdown-preview-view.is-readable-line-width.table-wide .el-lang-dataviewjs .dataview.table-view-table { + max-width: var(--line-width-wide) !important; } + +.table-100 table th:first-child, +.table-100 table td:first-child, +.table-100 .dataview.table-view-table th:first-child, +.table-100 .dataview.table-view-table td:first-child, +.table-100 .markdown-source-view.mod-cm6 td:first-child, +.table-100 .markdown-source-view.mod-cm6 th:first-child { + padding-left: 20px; } + +.table-100 table th:last-child, +.table-100 table td:last-child, +.table-100 .dataview.table-view-table th:last-child, +.table-100 .dataview.table-view-table td:last-child { + padding-right: 20px; } + +/* Maps, images and iframes */ +.contextual-typography.chart-max .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-chart, +.contextual-typography .markdown-preview-view.is-readable-line-width.chart-max .markdown-preview-sizer > .el-lang-chart, +.contextual-typography.map-max .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-leaflet, +.contextual-typography .markdown-preview-view.is-readable-line-width.map-max .markdown-preview-sizer > .el-lang-leaflet, +.contextual-typography.iframe-max .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-iframe, +.contextual-typography .markdown-preview-view.is-readable-line-width.iframe-max .markdown-preview-sizer > .el-iframe, +.contextual-typography.img-max .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-embed-image, +.contextual-typography .markdown-preview-view.is-readable-line-width.img-max .markdown-preview-sizer > .el-embed-image { + width: 100%; } + +.contextual-typography.chart-wide .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-chart, +.contextual-typography .markdown-preview-view.is-readable-line-width.chart-wide .markdown-preview-sizer > .el-lang-chart, +.contextual-typography.map-wide .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-leaflet, +.contextual-typography .markdown-preview-view.is-readable-line-width.map-wide .markdown-preview-sizer > .el-lang-leaflet, +.contextual-typography.iframe-wide .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-iframe, +.contextual-typography .markdown-preview-view.is-readable-line-width.iframe-wide .markdown-preview-sizer > .el-iframe, +.contextual-typography.img-wide .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-embed-image, +.contextual-typography .markdown-preview-view.is-readable-line-width.img-wide .markdown-preview-sizer > .el-embed-image { + width: var(--line-width-wide); } + +.contextual-typography.chart-100 .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-chart, +.contextual-typography .markdown-preview-view.is-readable-line-width.chart-100 .markdown-preview-sizer > .el-lang-chart, +.contextual-typography.map-100 .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-leaflet, +.contextual-typography .markdown-preview-view.is-readable-line-width.map-100 .markdown-preview-sizer > .el-lang-leaflet, +.contextual-typography.iframe-100 .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-iframe, +.contextual-typography .markdown-preview-view.iframe-100 .markdown-preview-sizer > .el-iframe, +.contextual-typography.img-100 .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-embed-image, +.contextual-typography .markdown-preview-view.img-100 .markdown-preview-sizer > .el-embed-image { + width: 100%; + max-width: 100%; } + +.is-readable-line-width .el-table table, +.is-readable-line-width .el-lang-dataview .dataview.table-view-table, +.is-readable-line-width .el-lang-dataviewjs .dataview.table-view-table { + max-width: calc(var(--line-width-adaptive) - var(--folding-offset)); } + +.embed-strict .el-embed-page p, +.map-100 .el-lang-leaflet, +.map-max .el-lang-leaflet, +.map-wide .el-lang-leaflet, +.chart-100 .el-lang-chart, +.chart-max .el-lang-chart, +.chart-wide .el-lang-chart, +.table-100 .el-lang-dataview, +.table-max .el-lang-dataview, +.table-wide .el-lang-dataview, +.table-100 .el-lang-dataviewjs, +.table-max .el-lang-dataviewjs, +.table-wide .el-lang-dataviewjs, +.table-100 .el-table, +.table-max .el-table, +.table-wide .el-table, +.iframe-100 .el-iframe, +.iframe-max .el-iframe, +.iframe-wide .el-iframe, +.img-100 .el-embed-image, +.img-max .el-embed-image, +.img-wide .el-embed-image { + --folding-offset:0px; } + +/* Live Preview */ +.chart-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-chart, +.chart-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-chart, +.map-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-leaflet, +.map-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-leaflet, +.table-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-table-widget > table, +.table-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-table-widget > table, +.table-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataview, +.table-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataview, +.table-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataviewjs, +.table-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataviewjs, +.table-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block.cm-table-widget > div:not(.edit-block-button), +.table-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block.cm-table-widget > div:not(.edit-block-button), +.img-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > .image-embed, +.img-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > .image-embed, +.img-max.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > img, +.img-max .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > img { + width: var(--max-width) !important; + max-width: var(--max-width) !important; + transform: none !important; + padding-left: 0; + margin: 0 auto !important; } + +.chart-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-chart, +.chart-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-chart, +.map-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-leaflet, +.map-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-leaflet, +.table-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-table-widget > table, +.table-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-table-widget > table, +.table-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataview, +.table-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataview, +.table-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataviewjs, +.table-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataviewjs, +.table-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block.cm-table-widget > div:not(.edit-block-button), +.table-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block.cm-table-widget > div:not(.edit-block-button), +.img-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > .image-embed, +.img-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > .image-embed, +.img-wide.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > img, +.img-wide .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > img { + width: var(--line-width-wide) !important; + max-width: var(--max-width); + transform: none !important; + padding-left: 0; + margin: 0 auto !important; } + +.chart-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-chart, +.chart-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-chart, +.map-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-leaflet, +.map-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .block-language-leaflet, +.table-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width table, +.table-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width table, +.table-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataview, +.table-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataview, +.table-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataviewjs, +.table-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block > .block-language-dataviewjs, +.table-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block.cm-table-widget > div:not(.edit-block-button), +.table-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block.cm-table-widget > div:not(.edit-block-button), +.img-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > .image-embed, +.img-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > .image-embed, +.img-100.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > img, +.img-100 .markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-content > img { + width: 100% !important; + max-width: 100% !important; + transform: none !important; + margin: 0 auto !important; + padding-left: 0; } + +/* Mobile */ +@media (max-width: 400pt) { + .markdown-preview-view .el-table th:first-child, + .markdown-preview-view .el-table td:first-child, + .markdown-preview-view .el-lang-dataview th:first-child, + .markdown-preview-view .el-lang-dataview td:first-child + .markdown-preview-view .el-lang-dataviewjs th:first-child, + .markdown-preview-view .el-lang-dataviewjs td:first-child { + padding-left: 6vw; } + + .markdown-preview-view .el-table th:last-child, + .markdown-preview-view .el-table td:last-child, + .markdown-preview-view .el-lang-dataview th:last-child, + .markdown-preview-view .el-lang-dataview td:last-child, + .markdown-preview-view .el-lang-dataviewjs th:last-child, + .markdown-preview-view .el-lang-dataviewjs td:last-child { + padding-right: 6vw; } + + .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-table, + .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-dataview + .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-dataviewjs { + padding-left: 0; + padding-right: 0; } + + .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-table, + .markdown-preview-view .table-view-table table, + .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-dataview + .markdown-preview-view.is-readable-line-width .markdown-preview-sizer > .el-lang-dataviewjs { + width: 100%; } } +/* Custom line width with folding offset */ +@media (max-width: 400pt) { + .is-mobile { + --folding-offset:0px; } } +/* Nudge titlebar */ +body:not(.title-align-center):not(.title-align-left):not(.plugin-sliding-panes-rotate-header) .view-header-title { + padding-left: var(--folding-offset); } + +.markdown-source-view.wide, +.markdown-preview-view.wide { + --line-width-adaptive:var(--line-width-wide); } + +.markdown-source-view.max, +.markdown-preview-view.max { + --line-width-adaptive:300em; + --line-width-wide:300em; } + +/* With readable line width */ +.markdown-preview-view.is-readable-line-width .markdown-preview-sizer { + max-width: var(--max-width); + width: var(--line-width-adaptive); + padding-left: 0; } + +.markdown-source-view.is-readable-line-width .CodeMirror { + padding-left: 0; + padding-right: 0; + margin: 0 auto 0 auto; + width: var(--line-width-adaptive); + max-width: var(--max-width); } + +/* Readable line width off */ +.markdown-reading-view .markdown-preview-view:not(.is-readable-line-width) > .markdown-preview-sizer { + max-width: var(--max-width); + margin: 0 auto; + padding-left: var(--folding-offset); } + +.is-mobile .markdown-source-view.mod-cm6 .cm-gutters { + padding-right: 0; } + +/* Requires Minimal plugin 5.2.1+ */ +.minimal-readable-off .view-header-title-container { + width: var(--max-width); } + +/* Max width for readable-line length off */ +.markdown-source-view.mod-cm6:not(.is-readable-line-width) .cm-contentContainer { + max-width: var(--max-width); + margin: 0 0 0 calc(50% - var(--max-width)/2) !important; + padding-left: var(--folding-offset); } + +.markdown-source-view.mod-cm6 .cm-content > .cm-embed-block[contenteditable=false] { + overflow-x: auto; } + +/* Folding offset */ +.markdown-preview-view.is-readable-line-width .markdown-preview-sizer > div, +.markdown-preview-view.is-readable-line-width .markdown-preview-sizer > div[data-block-language="dataview"], +.markdown-preview-view.is-readable-line-width .markdown-preview-sizer > div[data-block-language="dataviewjs"] { + padding-left: var(--folding-offset); } + +.internal-embed > .markdown-embed, +.popover:not(.hover-editor) { + --folding-offset:0; } + +/* Live Preview */ +.markdown-source-view.mod-cm6.is-line-wrap.is-readable-line-width .cm-content { + max-width: 100%; } + +.markdown-source-view.mod-cm6.is-line-wrap.is-readable-line-width .cm-line:not(.HyperMD-table-row) { + max-width: calc(var(--max-width) - var(--folding-offset)); } + +/* Fill the width of the parent block for nested elements */ +.is-live-preview.is-readable-line-width.embed-strict .internal-embed .markdown-preview-sizer, +.is-readable-line-width .block-language-dataview table.dataview, +.is-readable-line-width .block-language-dataviewjs table.dataview, +.is-live-preview.is-readable-line-width .cm-embed-block table.dataview, +.markdown-source-view.is-live-preview.is-readable-line-width table.NLT__table, +.markdown-preview-view.is-readable-line-width .dataview.result-group .contains-task-list { + width: 100%; + max-width: 100%; + transform: none; + margin-left: auto !important; } + +/* Remove margins when nested */ +.markdown-source-view.mod-cm6.is-readable-line-width .cm-line > .internal-embed, +.markdown-source-view.mod-cm6.is-readable-line-width .cm-line.HyperMD-list-line .internal-embed.image-embed { + margin-left: 0 !important; } + +/* Line width for Live Preview / Editor mode + Gets complicated. + -------------------------------------------*/ +/* Nudge everything slightly to the left to make space for folding and gutters */ +/* This is the big daddy rule for most editor content line types */ +.markdown-source-view.mod-cm6.is-readable-line-width { + /* Don't force width for images that have a width */ } + .markdown-source-view.mod-cm6.is-readable-line-width .internal-embed, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-content > .image-embed, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-line, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-line.HyperMD-quote, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-line.HyperMD-codeblock, + .markdown-source-view.mod-cm6.is-readable-line-width .embedded-backlinks, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-embed-block.cm-callout > .callout, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-html-embed, + .markdown-source-view.mod-cm6.is-readable-line-width .cm-content > img:not([width]), + .markdown-source-view.mod-cm6.is-readable-line-width table { + width: calc(var(--line-width-adaptive) - var(--folding-offset)); + max-width: calc(var(--max-width) - var(--folding-offset)); + margin-right: auto; + margin-left: max(calc(50% + var(--folding-offset) - var(--line-width-adaptive)/2), calc(50% + var(--folding-offset) - var(--max-width)/2)) !important; } + .markdown-source-view.mod-cm6.is-readable-line-width .cm-line > .cm-html-embed { + --folding-offset:0; } + .markdown-source-view.mod-cm6.is-readable-line-width .cm-content > img[width] { + max-width: var(--max-width); + margin-left: max(calc(50% + var(--folding-offset) - var(--line-width-adaptive)/2), calc(50% + var(--folding-offset) - var(--max-width)/2)) !important; } + +.markdown-source-view.mod-cm6.is-readable-line-width .mod-empty, +.markdown-source-view.mod-cm6.is-readable-line-width .cm-embed-block > div, +.markdown-source-view.mod-cm6.is-readable-line-width .cm-embed-block > mjx-container { + width: calc(var(--line-width-adaptive) - var(--folding-offset)); + max-width: calc(var(--max-width) - var(--folding-offset)); + margin-right: auto; + margin-left: max(calc(50% + var(--folding-offset) - var(--line-width-adaptive)/2), calc(50% + var(--folding-offset) - var(--max-width)/2)) !important; } + +/* For lists adding an extra offset value in Edit mode */ +/* Needs .is-line-wrap to override default styling */ +.markdown-source-view.mod-cm6.is-readable-line-width.is-line-wrap .HyperMD-list-line { + width: calc(var(--line-width-adaptive) - var(--folding-offset) - var(--list-edit-offset)); + max-width: calc(var(--max-width) - var(--folding-offset) - var(--list-edit-offset)); + margin-right: auto; + margin-left: max(calc(50% + var(--list-edit-offset) + var(--folding-offset) - var(--line-width-adaptive)/2), calc(50% + var(--list-edit-offset) + var(--folding-offset) - var(--max-width)/2)) !important; } + +/* Dataview lists/checklists + A nightmare mainly because there is no selector that indicates + a list is present inside the dataview block + -------------------------------------------*/ +/* Normal block width */ +/* ------------------ */ +body:not(.table-100):not(.table-max):not(.table-wide) .is-live-preview.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.list-view-ul, +body:not(.table-100):not(.table-max):not(.table-wide) .is-live-preview.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview > h4, +body:not(.table-100):not(.table-max):not(.table-wide) .is-live-preview.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.result-group > .contains-task-list, +body:not(.table-100):not(.table-max):not(.table-wide) .is-live-preview.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.dataview-container > .contains-task-list { + max-width: 100%; + margin-right: auto; + margin-left: auto; + transform: none; } +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.list-view-ul, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.dataview-container > .contains-task-list, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataviewjs > p, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataviewjs > h1, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataviewjs > h2, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataviewjs > h3, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataviewjs > h4, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataviewjs h4, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataview > h4, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .block-language-dataview h4, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.result-group, +body:not(.table-100):not(.table-max):not(.table-wide) .markdown-preview-view.is-readable-line-width:not(.table-100):not(.table-max):not(.table-wide) .dataview.dataview-error { + width: calc(var(--line-width-adaptive) - var(--folding-offset)); + max-width: var(--max-width); + margin-right: auto; + margin-left: auto; } + +/* Wider block widths */ +/* ------------------ */ +.is-live-preview.is-readable-line-width .dataview.list-view-ul, +.is-live-preview.is-readable-line-width .dataview > h4, +.is-live-preview.is-readable-line-width .block-language-dataviewjs h4, +.is-live-preview.is-readable-line-width .dataview .contains-task-list, +.is-live-preview.is-readable-line-width .dataview.dataview-container .contains-task-list { + --folding-offset:10px; + width: calc(var(--line-width-adaptive) - var(--folding-offset)); + max-width: calc(100% - var(--folding-offset)); + transform: translateX(calc(var(--folding-offset)/2)); + margin-right: auto; + margin-left: auto; } + +.table-100 .is-live-preview.is-readable-line-width .dataview.list-view-ul, +.table-100 .is-live-preview.is-readable-line-width .dataview > h4, +.table-100 .is-live-preview.is-readable-line-width .dataview .contains-task-list, +.table-100.is-live-preview.is-readable-line-width .dataview.list-view-ul, +.table-100.is-live-preview.is-readable-line-width .dataview > h4, +.table-100.is-live-preview.is-readable-line-width .dataview .contains-task-list { + max-width: calc(var(--max-width) - var(--folding-offset)); } + +.markdown-preview-view.is-readable-line-width .dataview.list-view-ul, +.markdown-preview-view.is-readable-line-width .dataview .contains-task-list, +.markdown-preview-view.is-readable-line-width .block-language-dataviewjs > p, +.markdown-preview-view.is-readable-line-width .block-language-dataviewjs > h1, +.markdown-preview-view.is-readable-line-width .block-language-dataviewjs > h2, +.markdown-preview-view.is-readable-line-width .block-language-dataviewjs > h3, +.markdown-preview-view.is-readable-line-width .block-language-dataviewjs > h4, +.markdown-preview-view.is-readable-line-width .block-language-dataviewjs h4, +.markdown-preview-view.is-readable-line-width .block-language-dataview > h4, +.markdown-preview-view.is-readable-line-width .block-language-dataview h4, +.markdown-preview-view.is-readable-line-width .dataview.result-group, +.markdown-preview-view.is-readable-line-width .dataview.dataview-error { + --folding-offset:10px; + width: calc(var(--line-width-adaptive) - var(--folding-offset)); + max-width: calc(var(--max-width) - var(--folding-offset)); + margin-left: auto; + margin-right: max(calc(50% - var(--line-width-adaptive)/2), calc(50% - var(--max-width)/2)); } + +/* Links and underline handling*/ +body:not(.links-int-on) a[href*="obsidian://"], +body:not(.links-int-on) .markdown-preview-view .internal-link, +body:not(.links-ext-on) .external-link, +body:not(.links-ext-on) .cm-link .cm-underline, +body:not(.links-ext-on) .cm-s-obsidian span.cm-url, +body:not(.links-int-on) .cm-hmd-internal-link .cm-underline, +body:not(.links-int-on) a.internal-link, +body:not(.links-int-on) .cm-s-obsidian span.cm-hmd-internal-link:hover { + text-decoration: none; } + +.links-int-on .is-live-preview .cm-hmd-internal-link, +.links-int-on .markdown-preview-view .internal-link, +.links-int-on .cm-s-obsidian span.cm-hmd-internal-link, +.markdown-preview-view .internal-link { + text-decoration: underline; } + +.links-ext-on .external-link, +.external-link { + background-position-y: center; + text-decoration: underline; } + +/* Scroll indicator for sidebar containers */ +body:not(.is-translucent):not(.is-mobile) .mod-left-split .item-list, +body:not(.is-translucent):not(.is-mobile) .mod-left-split .nav-files-container, +body:not(.is-translucent):not(.is-mobile) .mod-left-split .workspace-leaf-content[data-type='search'] .search-result-container, +body:not(.is-translucent):not(.is-mobile) .mod-left-split .tag-container, +body:not(.is-translucent):not(.is-mobile) .mod-left-split .outgoing-link-pane, +body:not(.is-translucent):not(.is-mobile) .mod-left-split .backlink-pane { + background: linear-gradient(var(--background-secondary) 10%, rgba(255, 255, 255, 0)) center top, linear-gradient(var(--background-modifier-border) 100%, rgba(0, 0, 0, 0)) center top; + background-repeat: no-repeat; + background-size: 100% 40px, 91% var(--border-width); + background-attachment: local, scroll; } + +body:not(.is-mobile) .mod-right-split .item-list, +body:not(.is-mobile) .mod-right-split .nav-files-container, +body:not(.is-mobile) .mod-right-split .workspace-leaf-content[data-type='search'] .search-result-container, +body:not(.is-mobile) .mod-right-split .tag-container, +body:not(.is-mobile) .mod-right-split .outgoing-link-pane, +body:not(.is-mobile) .mod-right-split .backlink-pane { + background: linear-gradient(var(--background-primary) 10%, rgba(255, 255, 255, 0)) center top, linear-gradient(var(--background-modifier-border) 100%, rgba(0, 0, 0, 0)) center top; + background-repeat: no-repeat; + background-size: 100% 40px, 91% var(--border-width); + background-attachment: local, scroll; } + +/* Sidebar documents */ +.mod-left-split .markdown-preview-sizer > div, +.mod-left-split .cm-contentContainer { + padding-left: 0 !important; + max-width: 100% !important; } + +.workspace > .workspace-split:not(.mod-root) .CodeMirror, +.workspace > .workspace-split:not(.mod-root) .cm-scroller, +.workspace > .workspace-split:not(.mod-root) .markdown-preview-view { + font-size: var(--font-adaptive-small); + line-height: 1.25; } +.workspace > .workspace-split:not(.mod-root) .workspace-leaf-content[data-type=markdown] .markdown-preview-view { + padding: 0 15px; } +.workspace > .workspace-split:not(.mod-root) .workspace-leaf-content[data-type=markdown] .markdown-embed .markdown-preview-view { + padding: 0; } +.workspace > .workspace-split:not(.mod-root) .CodeMirror, +.workspace > .workspace-split:not(.mod-root) .markdown-preview-section, +.workspace > .workspace-split:not(.mod-root) .markdown-preview-sizer { + max-width: 100%; + padding: 0; + width: auto; } +.workspace > .workspace-split:not(.mod-root) .cm-editor { + --folding-offset: 0px; } + +.minimal-folding .workspace > .workspace-split:not(.mod-root) .workspace-leaf-content[data-type=markdown] .allow-fold-headings.markdown-preview-view .markdown-preview-sizer, +.minimal-folding .workspace > .workspace-split:not(.mod-root) .workspace-leaf-content[data-type=markdown] .allow-fold-lists.markdown-preview-view .markdown-preview-sizer { + padding-left: 0; } + +/* Hide embed styling for sidebar documents */ +.workspace > .workspace-split:not(.mod-root) .internal-embed .markdown-embed { + border: none; + padding: 0; } + +.workspace > .workspace-split:not(.mod-root) .CodeMirror-sizer { + padding-left: 10px; } + +/* Hidden tabs +Needs some work + +.mod-right-split { + .workspace-tab-header-container:not(:hover) { + height:0; + opacity:0; + z-index:999; + width:100%; + transition:height 0.1s linear, opacity 0.1s linear; + &::after { + width:100%; + content:" "; + background-color:transparent; + height:20px; + position:absolute; + z-index:99; + top:0; + } + } +} +.workspace-tab-header-container { + transition:height 0.1s linear, opacity 0.1s linear; +} + */ +/* Underline */ +.tab-style-2 .workspace-tab-header-container .workspace-tab-header { + flex-grow: 1; + height: var(--header-height); } + +.tab-style-2 .workspace-tab-container-inner { + padding: 0; } + +.tab-style-2 .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner { + justify-content: center; + align-items: center; + border-bottom: 1px solid var(--background-divider); + border-radius: 0; + transition: none; } + +.tab-style-2 .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner:hover { + background-color: var(--bg3); } + +.tab-style-2 .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner { + border-bottom: 2px solid var(--ax3); + padding-top: 1px; + color: var(--ax3); } + +.tab-style-2 .workspace-tab-header-inner-icon:hover { + background-color: transparent; } + +/* Wide */ +.tab-style-3 .workspace-sidedock-empty-state + .workspace-tabs .workspace-tab-header-container, +.tab-style-3 .mod-right-split .workspace-sidedock-empty-state + .workspace-tabs .workspace-tab-header-container { + border-bottom: none; } + +.tab-style-3 .workspace-tab-header-container { + padding-left: 7px; + padding-right: 7px; + border: none; } + +.tab-style-3 .workspace-tab-header-container .workspace-tab-header { + flex-grow: 1; } + +.tab-style-3 .workspace-tab-container-inner { + padding: 3px; + background: var(--bg3); + border-radius: 6px; } + +.tab-style-3 .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner { + justify-content: center; + align-items: center; + transition: none; + border: 1px solid transparent; } + +.tab-style-3 .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner:hover { + background-color: transparent; } + +.tab-style-3:not(.minimal-dark-tonal) .mod-left-split .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner { + background: var(--bg2); } + +.tab-style-3 .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner { + background: var(--bg1); + box-shadow: 0px 1px 1px 0 rgba(0, 0, 0, 0.1); + border-radius: 4px; } + +.tab-style-3.labeled-nav .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner { + background-color: transparent; } + +.tab-style-3 .workspace-tab-header-inner-icon { + height: 18px; + padding: 0; } + +.tab-style-3 .workspace-tab-header-inner-icon:hover { + background-color: transparent; } + +/* Index */ +.tab-style-4 .workspace-sidedock-empty-state + .workspace-tabs .workspace-tab-header-container { + border: none; } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header { + flex-grow: 1; + height: var(--header-height); } + +.tab-style-4 .workspace-tab-container-inner { + background-color: var(--background-secondary); + padding: 0; } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner { + justify-content: center; + align-items: center; + border-bottom: none; + border-radius: 0; + transition: none; + border-top: 1px solid transparent; } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header { + border-bottom: 1px solid var(--background-modifier-border); + opacity: 1; } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header .workspace-tab-header-inner-icon { + opacity: var(--icon-muted); } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner-icon { + opacity: 1; } + +.tab-style-4.hider-frameless:not(.labeled-nav) .mod-left-split > .workspace-tabs:nth-child(3) .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner { + border-top: 1px solid var(--background-modifier-border); } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header.is-active { + border-bottom: 1px solid transparent; } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header.is-active { + background-color: var(--background-primary); + border-radius: 0; } + +.tab-style-4 .mod-left-split .workspace-tab-header-container .workspace-tab-header.is-active { + background-color: var(--background-secondary); } + +.tab-style-4 .workspace-tab-header-container .workspace-tab-header.is-active .workspace-tab-header-inner { + box-shadow: 1px 0 var(--background-modifier-border), -1px 0 var(--background-modifier-border); + border-bottom: none; } + +.tab-style-4 .workspace-tab-header-inner-icon:hover { + background-color: transparent; } + +/* Translucent sidebars */ +:root { + --bg-translucency-light:0.7; + --bg-translucency-dark:0.85; } + +.theme-light.frosted-sidebar.is-translucent, +.theme-dark.frosted-sidebar.is-translucent { + --opacity-translucency:1; } + +.is-translucent.frosted-sidebar:not(.hider-ribbon) .workspace-ribbon.mod-left, +.is-translucent.frosted-sidebar .workspace-split:not(.mod-right-split) .workspace-tabs { + background: transparent; } + +.is-translucent.frosted-sidebar:not(.hider-ribbon) .workspace-ribbon.mod-left:after { + background: var(--background-secondary); + opacity: var(--bg-translucency-light); + top: 0px; + left: 0px; + content: ""; + height: 120%; + position: fixed; + width: 42px; + z-index: -10; } + +.is-translucent.frosted-sidebar .mod-left-split .workspace-tabs:after { + background: var(--background-secondary); + opacity: var(--bg-translucency-light); + top: -50px; + content: ""; + height: 120%; + position: fixed; + width: 120%; + z-index: -10; } + +.theme-dark.is-translucent.frosted-sidebar:not(.hider-ribbon) .workspace-ribbon.mod-left:after, +.theme-dark.is-translucent.frosted-sidebar .workspace-split:not(.mod-right-split) .workspace-tabs:after { + opacity: var(--bg-translucency-dark); } + +.theme-light.is-translucent.frosted-sidebar.minimal-light-white .workspace-split:not(.mod-right-split) .workspace-tabs:after { + background: white; } + +.theme-dark.is-translucent.frosted-sidebar.minimal-dark-black .workspace-split:not(.mod-right-split) .workspace-tabs:after { + background: black; } + +.is-translucent .status-bar { + margin: 0; } + +/* Turn off file name trimming */ +.full-file-names .tree-item-inner, +.full-file-names .nav-file-title-content, +.full-file-names .search-result-file-title, +.nav-file-title-content.is-being-renamed { + text-overflow: unset; + white-space: normal; + line-height: 1.35; } + +.full-file-names .nav-file-title { + margin-bottom: 3px; } + +/* Underline headings */ +.theme-light, +.theme-dark { + --h1l:var(--ui1); + --h2l:var(--ui1); + --h3l:var(--ui1); + --h4l:var(--ui1); + --h5l:var(--ui1); + --h6l:var(--ui1); } + +.h1-l .markdown-reading-view h1:not(.embedded-note-title), +.h1-l .mod-cm6 .cm-editor .HyperMD-header-1 { + border-bottom: 1px solid var(--h1l); + padding-bottom: 0.4em; + margin-block-end: 0.6em; } + +.h2-l .markdown-reading-view h2, +.h2-l .mod-cm6 .cm-editor .HyperMD-header-2 { + border-bottom: 1px solid var(--h2l); + padding-bottom: 0.4em; + margin-block-end: 0.6em; } + +.h3-l .markdown-reading-view h3, +.h3-l .mod-cm6 .cm-editor .HyperMD-header-3 { + border-bottom: 1px solid var(--h3l); + padding-bottom: 0.4em; + margin-block-end: 0.6em; } + +.h4-l .markdown-reading-view h4, +.h4-l .mod-cm6 .cm-editor .HyperMD-header-4 { + border-bottom: 1px solid var(--h4l); + padding-bottom: 0.4em; + margin-block-end: 0.6em; } + +.h5-l .markdown-reading-view h5, +.h5-l .mod-cm6 .cm-editor .HyperMD-header-5 { + border-bottom: 1px solid var(--h5l); + padding-bottom: 0.4em; + margin-block-end: 0.6em; } + +.h6-l .markdown-reading-view h6, +.h6-l .mod-cm6 .cm-editor .HyperMD-header-6 { + border-bottom: 1px solid var(--h6l); + padding-bottom: 0.4em; + margin-block-end: 0.6em; } + +/* Mobile */ +/* Mobile styling +/* MIT License | Copyright (c) Stephan Ango (@kepano) */ +/* Needs cleanup +-------------------------------------------------------------------------------- */ +.is-mobile { + --font-settings-title:18px; + --font-settings:16px; + --font-settings-small:13px; + --input-height:38px; + --radius-m:8px; } + +@media (min-width: 400pt) { + .is-mobile { + --input-height:36px; + --radius-m:6px; } } +.hider-tooltips .follow-link-popover { + display: none; } + +.is-mobile .follow-link-popover { + font-family: var(--font-interface); } + +/* Padding reset */ +body.is-mobile { + padding: 0 !important; } + +.is-mobile { + /* Folding on mobile */ } + .is-mobile .titlebar { + height: 0 !important; + padding: 0 !important; + position: relative !important; + border-bottom: none; } + .is-mobile .safe-area-top-cover { + background-color: transparent; } + .is-mobile .horizontal-main-container { + background-color: var(--background-primary); } + .is-mobile .workspace { + border-radius: 0 !important; + transform: none !important; } + .is-mobile .workspace-drawer:not(.is-pinned) { + width: 100vw; + max-width: 360pt; + border: none; + box-shadow: 0 5px 50px 5px rgba(0, 0, 0, 0.05); } + .is-mobile .workspace-drawer.mod-left.is-pinned { + max-width: var(--mobile-left-sidebar-width); + min-width: 150pt; } + .is-mobile .workspace-drawer.mod-right.is-pinned { + max-width: var(--mobile-right-sidebar-width); + min-width: 150pt; } + .is-mobile .workspace-drawer.mod-right.is-pinned { + border-right: none; } + .is-mobile .workspace-leaf-content[data-type=starred] .item-list { + padding-left: 5px; } + .is-mobile .workspace-drawer-tab-container > * { + padding: 0; } + .is-mobile .workspace-drawer-tab-option-item-title, + .is-mobile .workspace-drawer-active-tab-title { + font-size: var(--font-adaptive-small); } + .is-mobile .workspace-drawer-tab-option-item:hover .workspace-drawer-tab-option-item-title, + .is-mobile .workspace-drawer-active-tab-header:hover .workspace-drawer-active-tab-title { + color: var(--text-normal); } + .is-mobile .workspace-drawer-active-tab-header:hover .workspace-drawer-active-tab-back-icon { + color: var(--text-normal); } + .is-mobile .nav-file-title, + .is-mobile .nav-folder-title, + .is-mobile .outline, + .is-mobile .tree-item-self, + .is-mobile .tag-container, + .is-mobile .tag-pane-tag { + font-size: var(--font-adaptive-small); + line-height: 1.5; + margin-bottom: 4px; } + .is-mobile .backlink-pane > .tree-item-self, + .is-mobile .outgoing-link-pane > .tree-item-self { + font-size: var(--font-adaptive-smallest); } + .is-mobile .tree-item-flair { + font-size: var(--font-adaptive-small); } + .is-mobile .nav-files-container { + padding: 5px 5px 5px 5px; } + .is-mobile .search-result-container { + padding-bottom: 20px; } + .is-mobile .search-result-file-match-replace-button { + background-color: var(--background-tertiary); + color: var(--text-normal); } + .is-mobile .search-result-file-matches, + .is-mobile .search-result-file-title { + font-size: var(--font-adaptive-small); } + .is-mobile .cm-editor .cm-foldGutter .cm-gutterElement { + cursor: var(--cursor); } + .is-mobile .cm-editor .cm-foldPlaceholder { + background: transparent; + border-color: transparent; } + .is-mobile .empty-state-action { + border-radius: var(--radius-m); + font-size: var(--font-adaptive-small); } + .is-mobile .workspace-drawer-header { + padding: 20px 10px 0 25px; } + .is-mobile .workspace-drawer-header-name { + font-weight: var(--bold-weight); + color: var(--text-normal); + font-size: 1.125em; } + .is-mobile .workspace-drawer-header-info { + color: var(--text-faint); + font-size: var(--font-adaptive-small); + margin-bottom: 0; } + .is-mobile .mod-left .workspace-drawer-header-info, + .is-mobile .is-mobile.hider-status .workspace-drawer-header-info { + display: none; } + .is-mobile .workspace-drawer-active-tab-header { + margin: 2px 12px 2px; + padding: 8px 0 8px 8px; } + .is-mobile .workspace-leaf-content .item-list, + .is-mobile .tag-container, + .is-mobile .backlink-pane { + padding-top: 10px; } + .is-mobile .outgoing-link-pane, + .is-mobile .backlink-pane { + padding-left: 10px; } + +/* Workspace */ +.workspace-drawer.mod-left .workspace-drawer-inner { + padding-left: 0; } + +.is-mobile .side-dock-ribbon { + background: var(--background-secondary); + border-right: 1px solid var(--background-modifier-border); + z-index: 3; + flex-direction: column; + width: 70px; + padding: 15px 0; + margin-right: 0px; } + +body:not(.is-ios).is-mobile .workspace-drawer-ribbon { + padding: 20px 5px; } + +.is-ios .is-pinned .side-dock-ribbon { + padding: 30px 0 20px 0; } + +body.is-mobile.hider-frameless:not(.hider-ribbon) .side-dock-actions { + padding-top: 5px; } + +.is-mobile .side-dock-actions, .is-mobile .side-dock-settings { + flex-direction: column; + border-radius: 15px; } + +.is-mobile .mod-left .workspace-drawer-header, +.is-mobile .mod-left .workspace-drawer-tab-container { + margin-left: 70px; } + +.is-mobile .side-dock-ribbon .side-dock-ribbon-action { + padding: 9px 5px 2px 5px; + margin: 0 12px 4px; + height: 40px; } + +.is-mobile .side-dock-ribbon .side-dock-ribbon-action svg { + width: 22px; + height: 22px; } + +.is-mobile .workspace-drawer-active-tab-container { + z-index: 2; + background-color: var(--background-primary); } + +.is-mobile .side-dock-actions, +.is-mobile .side-dock-settings { + display: flex; + align-content: center; + justify-content: center; + padding: 0; } + +.is-mobile .workspace-drawer.mod-left:not(.is-pinned) { + border-right: none; } + +.is-mobile .nav-buttons-container { + padding: 0 0 10px 15px; } + +/* Inputs */ +.is-mobile input[type='text'] { + font-size: 14px; + height: var(--input-height); } + +.is-mobile .setting-item-control .search-input-container input { + display: inline-block; + width: 100%; + margin-bottom: 0; } + +.is-mobile .search-input-container input, +.is-mobile .search-input-container input:hover, +.is-mobile .search-input-container input:focus, +.is-mobile .search-input-container input[type='text'], +.is-mobile .workspace-leaf-content[data-type='search'] .search-input-container input { + -webkit-appearance: none; + border-radius: 6px; + height: 36px; + padding: 6px 20px 6px 34px; + font-size: 14px; } + +.is-mobile .search-input-container input::placeholder { + font-size: 14px; } + +.is-mobile .workspace-drawer { + border-width: var(--border-width); } + +.is-mobile .workspace-drawer-inner, +.is-mobile .workspace-drawer-active-tab-container { + background-color: var(--background-secondary); } + +.workspace-drawer-active-tab-icon { + display: none; } + +.is-ios .is-pinned .workspace-drawer-ribbon { + padding: 30px 0 20px 0; } + +.is-ios .workspace-drawer.is-pinned .workspace-drawer-header { + padding-top: 26px; } + +.is-mobile .workspace-split.mod-root { + background-color: var(--background-primary); } + +.is-ios .mod-root .workspace-leaf { + padding-top: 20px; } + +.is-ios .mod-root .workspace-split.mod-horizontal .workspace-leaf:not(:first-of-type) { + padding-top: 0; } + +.is-mobile.minimal-focus-mode .view-actions { + opacity: 1; } + +.is-mobile .workspace-drawer-tab-options { + padding-top: 10px; } + +.is-mobile .workspace-drawer-tab-option-item { + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; + margin: 0 10px; + padding: 8px 10px; + border-radius: var(--radius-m); } + +.is-mobile .workspace-drawer-header-icon { + align-self: start; } + +body.is-mobile:not(.minimal-icons-off) .workspace-drawer-header-icon svg, +body.is-mobile:not(.minimal-icons-off) .nav-action-button svg, +body.is-mobile:not(.minimal-icons-off) .view-action svg { + width: 22px; + height: 22px; } + +.is-mobile.hider-search-suggestions .search-input-suggest-button { + display: none; } + +.is-mobile .search-input-clear-button { + right: 6px; } + +.is-mobile .search-input-clear-button:before { + height: 16px; + width: 16px; } + +.is-mobile .view-header-title { + font-size: var(--title-size); } + +.is-mobile .view-header-title:-webkit-autofill:focus { + font-family: var(--font-interface); + color: red; } + +.is-mobile .view-header-icon { + padding: 16px 6px 16px 7px; + margin-left: 4px; } + +.is-mobile .mod-root .view-header-icon, +.is-mobile .mod-left.is-pinned + .mod-root .view-header-icon { + display: none; } + +.is-mobile .view-action { + padding: 5px 5px 4px; } + +.is-mobile .workspace-leaf-content:not([data-type='search']) .nav-buttons-container { + border-bottom: var(--border-width) solid var(--background-modifier-border); } + +.is-mobile .workspace-leaf-content[data-type='search'] .nav-action-button, +.is-mobile .nav-action-button, +.is-mobile .workspace-drawer-header-icon { + padding: 4px 7px 0 !important; + margin: 5px 2px 2px 0; + text-align: center; + height: 32px; + cursor: var(--cursor); } + +.is-mobile .nav-file-title.is-active { + box-shadow: 0 0 0px 2px var(--background-tertiary); } + +.pull-down-action { + top: 0; + left: 0; + right: 0; + width: 100%; + margin: 0 auto; + padding: 50px 0 20px; + text-align: center; + border-radius: 0; + border: none; + box-shadow: 0 5px 200px var(--background-modifier-box-shadow); } + +.pull-out-action { + top: 0; + height: 100vh; + padding: 30px 10px; + background: transparent; + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; } + +.is-mobile .markdown-preview-view pre { + overflow-x: scroll; } + +.is-mobile .view-header-icon .three-horizontal-bars { + opacity: 0; } + +.is-mobile.plugin-sliding-panes .view-header-title { + mask-image: unset; + -webkit-mask-image: unset; } + +.is-mobile.plugin-sliding-panes-rotate-header .view-header-title { + line-height: 1.2; } + +.is-mobile .workspace-drawer-header-name-text { + white-space: nowrap; + margin-right: 10px; } + +/* --------------- */ +/* Phone */ +@media (max-width: 400pt) { + .is-mobile .view-header-icon { + display: none; } + + /* Disable hover backgrounds on phone */ + .is-mobile .view-action:hover, + .is-mobile .nav-action-button:hover, + .side-dock-ribbon .side-dock-ribbon-action:hover, + .is-mobile .workspace-leaf-content[data-type='search'] .nav-action-button.is-active:hover, + .is-mobile .workspace-leaf-content[data-type='backlink'] .nav-action-button.is-active:hover, + .is-mobile .workspace-drawer-tab-option-item:hover, + .is-mobile .workspace-drawer-header-icon:hover { + background: transparent; } + + .is-mobile .mod-left .workspace-drawer-header-icon { + display: none; } + + .is-ios .workspace-drawer .workspace-drawer-header { + padding-top: 45px; } + + .is-ios .mod-root .workspace-leaf { + padding-top: 40px; } + + .is-mobile .mod-right .workspace-drawer-header div:nth-child(2) { + display: none; } + + .is-mobile .workspace .workspace-drawer-backdrop { + margin-top: -40px; + height: calc(100vh + 50px); + z-index: 9; } + + .is-ios .workspace-drawer-ribbon { + padding: 50px 0 30px 0; } + + .is-mobile .view-header-title-container { + margin-left: 0; } + + .is-mobile .view-header-title { + max-width: calc(100vw - 90px); + padding-right: 20px; + padding-left: calc(50% - var(--max-width)/2 + var(--folding-offset)) !important; + font-size: var(--font-settings-title); + letter-spacing: -0.015em; } + + .is-mobile .workspace-drawer-header-name-text { + font-size: var(--font-settings-title); + letter-spacing: -0.015em; } + + .is-mobile .view-header { + border-bottom: var(--border-width) solid var(--background-modifier-border) !important; } + + .is-mobile .installed-plugins-container { + max-width: 100%; + overflow: hidden; } + + .is-mobile .setting-item-info { + flex: 1 1 auto; } + + .is-mobile .kanban-plugin__board-settings-modal .setting-item-control, + .is-mobile .setting-item-control { + flex: 1 0 auto; + margin-right: 0; + min-width: auto; } + + .is-mobile .checkbox-container { + flex: 1 0 40px; + max-width: 40px; } + + .is-mobile .setting-item-description { + word-break: break-word; + white-space: pre-line; } + + .is-mobile .view-action { + padding: 0 4px 0 4px; + height: 22px; } + + .is-mobile .frontmatter-container .tag, + .is-mobile .cm-s-obsidian span.cm-hashtag, + .is-mobile .tag { + font-size: var(--font-adaptive-smaller); } + + .is-mobile .setting-item-control select, + .is-mobile .setting-item-control input, + .is-mobile .setting-item-control button { + margin-bottom: 5px; } + + .is-mobile .setting-item-control input[type="range"] { + margin-bottom: 10px; } } +/* --------------- */ +/* Tablet */ +@media (min-width: 400pt) { + .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header-icon { + opacity: var(--icon-muted); + display: flex; } + + .mod-left:not(.is-pinned) + .mod-root > div:first-of-type .view-header-icon:hover, + .mod-left:not(.is-pinned) + .mod-root .view-header-icon .three-horizontal-bars { + opacity: 1; } + + .mod-left:not(.is-pinned) + .mod-root .view-header-icon:hover { + background-color: var(--background-tertiary); } + + .is-mobile.is-ios .safe-area-top-cover { + background-color: transparent; } + + .is-mobile .view-action { + padding: 5px 6px 4px; } + + .is-mobile .mod-left:not(.is-pinned) + .mod-root .workspace-leaf:first-of-type .view-header-title-container { + max-width: calc(100% - 102px); } + + /* Animations */ + .is-mobile .menu, + .is-mobile .suggestion-container, + .is-mobile .modal, + .is-mobile .prompt { + transition: unset !important; + transform: unset !important; + animation: unset !important; } + + .is-mobile .community-plugin-search .setting-item { + padding-top: 10px; } + + .is-mobile .setting-item:not(.mod-toggle):not(.setting-item-heading) { + flex-direction: row; + align-items: center; } + + .is-mobile button, + .is-mobile .setting-item-control select, + .is-mobile .setting-item-control input, + .is-mobile .setting-item-control button { + width: auto; } + + .is-mobile .workspace-drawer:not(.is-pinned) { + margin: 30px 16px 0; + height: calc(100vh - 48px); + border-radius: 15px; } + + .is-mobile .setting-item:not(.mod-toggle):not(.setting-item-heading) .setting-item-control { + width: auto; + margin-top: 0; } + + .is-mobile .markdown-preview-view ol > li.task-list-item .collapse-indicator, + .is-mobile .markdown-preview-view ul > li.task-list-item .collapse-indicator { + margin-left: -2.5em; + margin-top: 0.1em; } + + .pull-down-action { + width: 400px; + top: 15px; + padding: 15px; + border-radius: 15px; } } +/* iOS style modals */ +:root { + --ios-radius:10px; + --ios-input-radius:8px; + --ios-shadow:0 5px 100px rgba(0,0,0,0.15); + --ios-muted:#8e8e93; } + +.theme-light { + --ios-blue:#007aff; + --ios-red:#ff3c2f; + --ios-bg-translucent:rgba(255,255,255,0.85); + --ios-bg:white; + --ios-border:rgba(0,0,0,0.1); } + +.theme-dark { + --ios-blue:#0b84ff; + --ios-red:#ff453a; + --ios-bg-translucent:rgba(44,44,46,0.85); + --ios-bg:#2c2c2e; + --ios-border:rgba(255,255,255,0.15); } + +.is-ios { + --text-error:#ff453a; + /* + .mod-confirmation .modal { + width:400px; + max-width:95vw; + overflow:visible; + background-color:rgba(0,0,0,0.07); + padding:0; + border-radius:var(--ios-radius); + box-shadow:var(--ios-shadow); + .modal-title { + text-align:center; + display:none; + } + .modal-content { + border-radius:var(--ios-radius) var(--ios-radius) 0 0; + background-color:var(--ios-bg-translucent); + backdrop-filter:blur(2px); + -webkit-backdrop-filter:blur(2px); + font-size:13px; + margin:0; + text-align:center; + color:var(--ios-muted); + padding:15px; + p { + margin-block-start:0; + margin-block-end:0; + } + } + .setting-item { + margin-top: 15px; + border-top: 0; + flex-direction: column; + .setting-item-info { + padding-bottom:5px; + } + .setting-item-control { + margin:0; + flex-direction: column; + button { + backdrop-filter: none; + -webkit-backdrop-filter: none; + background: transparent; + padding: 20px 0 10px; + border-top: 0; + } + } + } + button { + background-color:var(--ios-bg-translucent); + backdrop-filter:blur(2px); + -webkit-backdrop-filter:blur(2px); + margin:0; + border:none; + height:auto; + padding:28px 0; + line-height:0; + box-shadow:none; + color:var(--ios-blue); + font-weight:400; + border-radius:0; + font-size:18px; + border-top:1px solid var(--ios-border); + } + button:hover { + background-color:transparent; + border:none; + box-shadow:none; + border-top:1px solid var(--ios-border); + } + .modal-button-container { + gap:0; + } + .modal-button-container>.mod-warning:nth-last-child(3), + button.mod-warning { + border-top:1px solid var(--ios-border); + background-color:var(--ios-bg-translucent); + backdrop-filter:blur(2px); + -webkit-backdrop-filter:blur(2px); + color:var(--ios-red); + font-weight:400; + text-decoration:none; + } + .modal-button-container>button:last-child { + border-top:none; + margin-top:10px; + font-weight:600; + border-radius:var(--ios-radius); + background-color:var(--ios-bg); + } + .modal-button-container>button:nth-last-child(2), + .modal-button-container>.mod-warning:nth-last-child(2) { + border-bottom-left-radius:var(--ios-radius); + border-bottom-right-radius:var(--ios-radius); + } + .modal-button-container>button:last-child:hover { + background-color:var(--ios-bg-translucent); + } + } */ } + .is-ios .search-input-container input, + .is-ios .workspace-leaf-content[data-type='search'] .search-input-container input, + .is-ios .document-search-container input[type='text'] { + border-radius: var(--ios-input-radius); + border: 0px; + background-color: var(--background-tertiary); } + .is-ios .search-input-container input:active, .is-ios .search-input-container input:hover, .is-ios .search-input-container input:focus, + .is-ios .workspace-leaf-content[data-type='search'] .search-input-container input:active, + .is-ios .workspace-leaf-content[data-type='search'] .search-input-container input:hover, + .is-ios .workspace-leaf-content[data-type='search'] .search-input-container input:focus, + .is-ios .document-search-container input[type='text']:active, + .is-ios .document-search-container input[type='text']:hover, + .is-ios .document-search-container input[type='text']:focus { + border-radius: var(--ios-input-radius); + border: 0px; + background-color: var(--background-tertiary); } + .is-ios .search-input-container input::placeholder, + .is-ios .workspace-leaf-content[data-type='search'] .search-input-container input::placeholder, + .is-ios .document-search-container input[type='text']::placeholder { + color: var(--text-muted); } + +/* iPad tablet */ +@media (min-width: 400pt) { + .is-ios .mobile-toolbar { + height: 70px; } + .is-ios .mobile-toolbar-options-container { + margin: 0 auto; + display: inline-flex; + width: auto; } } +.mobile-toolbar-off .mobile-toolbar { + display: none; } + +.mobile-toolbar { + width: 100%; + display: flex; + overflow: scroll; + background-color: var(--background-primary); + border-top: 1px solid var(--background-modifier-border); } + +@media (min-width: 400pt) { + .mobile-toolbar-option { + border-radius: 8px; + margin: 6px 0; } + + .mobile-toolbar-option:hover { + background-color: var(--background-tertiary); } } +/* Core plugins */ +/* Backlink pane */ +.outgoing-link-pane, +.backlink-pane { + padding-bottom: 30px; } + +.outgoing-link-pane .search-result-container, +.backlink-pane .search-result-container { + padding: 5px 5px 5px 5px; + margin-left: 0; } + +.outgoing-link-pane .search-result-file-title, +.backlink-pane .search-result-file-title { + padding-left: 15px; } + +.outgoing-link-pane .tree-item-icon, +.outgoing-link-pane > .tree-item-self .collapse-icon, +.backlink-pane > .tree-item-self .collapse-icon { + display: none; } + +.tree-item-self.outgoing-link-item { + padding: 0; + margin-left: 5px; } + +.outgoing-link-pane > .tree-item-self:hover, +.outgoing-link-pane > .tree-item-self, +.backlink-pane > .tree-item-self:hover, +.backlink-pane > .tree-item-self { + padding-left: 15px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: var(--font-adaptive-smallest); + font-weight: 500; + padding: 5px 7px 5px 10px; + background: transparent; } + +.outgoing-link-pane > .tree-item-self.is-collapsed, +.backlink-pane > .tree-item-self.is-collapsed { + color: var(--text-faint); } + +.outgoing-link-pane .search-result-file-match { + padding: 5px 0; + border: 0; } + +.outgoing-link-pane .search-result-file-match-destination-file { + background: transparent; } + +.search-result-file-match:hover .search-result-file-match-destination-file:hover { + background: transparent; + color: var(--text-normal); } + +/* Graphs */ +.theme-dark, +.theme-light { + --node:var(--text-muted); + --node-focused:var(--text-accent); + --node-tag:var(--red); + --node-attachment:var(--yellow); + --node-unresolved:var(--text-faint); } + +/* Fill color for nodes */ +.graph-view.color-fill { + color: var(--node); } + +/* Fill color for current local node */ +.graph-view.color-fill-focused { + color: var(--node-focused); } + +/* Fill color for nodes on hover */ +.graph-view.color-fill-highlight { + color: var(--node-focused); } + +/* Stroke color for nodes */ +.graph-view.color-circle { + color: var(--node-focused); } + +/* Line color */ +.graph-view.color-line { + color: var(--background-modifier-border); } + +/* Line color on hover */ +.graph-view.color-line-highlight { + color: var(--node-focused); } + +/* Text color */ +.graph-view.color-text { + color: var(--text-normal); } + +/* Tag nodes */ +.theme-dark .graph-view.color-fill-tag, +.theme-light .graph-view.color-fill-tag { + color: var(--node-tag); } + +.theme-dark .graph-view.color-fill-attachment, +.theme-light .graph-view.color-fill-attachment { + color: var(--node-attachment); } + +.theme-dark .graph-view.color-fill-unresolved, +.theme-light .graph-view.color-fill-unresolved { + color: var(--node-unresolved); } + +/* Full bleed (takes up full height) */ +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-root .workspace-leaf-content[data-type='localgraph'] .view-header, +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-root .workspace-leaf-content[data-type='graph'] .view-header { + position: fixed; + background: transparent !important; + width: 100%; } + +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='localgraph'] .view-content, +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='graph'] .view-content { + height: 100%; } + +body:not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='localgraph'] .view-header-title, +body:not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='graph'] .view-header-title { + display: none; } + +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='localgraph'] .view-actions, +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='graph'] .view-actions { + background: transparent; } + +.mod-root .workspace-leaf-content[data-type='localgraph'] .graph-controls, +.mod-root .workspace-leaf-content[data-type='graph'] .graph-controls { + top: 32px; } + +/* Graph controls */ +.graph-controls.is-close { + padding: 6px; + left: 0; + top: 0; } + +.graph-controls-button { + cursor: var(--cursor); } + +.graph-control-section .tree-item-children { + padding-bottom: 15px; } + +.graph-control-section-header { + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: var(--font-adaptive-smallest); + color: var(--text-muted); } + +.graph-control-section-header:hover { + color: var(--text-normal); } + +.graph-controls .search-input-container { + width: 100%; } + +.setting-item.mod-search-setting.has-term-changed .graph-control-search-button, +.graph-controls .graph-control-search-button { + display: none; } + +.graph-controls .setting-item { + padding: 4px 0 0 0; } + +.graph-controls .setting-item-name { + font-size: var(--font-adaptive-small); } + +.graph-controls { + background: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + min-width: 240px; + left: 6px; + margin-top: 6px; + margin-bottom: 0; + padding: 10px 12px 10px 2px; + border-radius: var(--radius-m); } + +.graph-controls input[type='text'], .graph-controls input[type='range'] { + font-size: var(--font-adaptive-small); } + +.graph-controls .mod-cta { + width: 100%; + font-size: var(--font-adaptive-small); + padding: 5px; + margin: 0; } + +.graph-controls-button.mod-animate { + margin-top: 5px; } + +.mod-left-split .graph-controls { + background: var(--background-secondary); } + +.local-graph-jumps-slider-container, +.workspace-split.mod-left-split .local-graph-jumps-slider-container, +.workspace-split.mod-right-split .local-graph-jumps-slider-container, +.workspace-fake-target-overlay .local-graph-jumps-slider-container { + background: transparent; + opacity: 0.6; + padding: 0; + left: 12px; + transition: opacity 0.2s linear; + height: auto; } + +.mod-root .local-graph-jumps-slider-container { + right: 0; + left: 0; + width: var(--line-width-adaptive); + max-width: var(--max-width); + margin: 0 auto; + top: 30px; } + +.workspace-split.mod-left-split .local-graph-jumps-slider-container:hover, +.workspace-split.mod-right-split .local-graph-jumps-slider-container:hover, +.workspace-fake-target-overlay .local-graph-jumps-slider-container:hover, +.local-graph-jumps-slider-container:hover { + opacity: 0.8; + transition: opacity 0.2s linear; } + +/* Outline */ +.outline { + padding: 15px 10px 20px 0; + font-size: var(--font-adaptive-small); } + +.outline .pane-empty { + font-size: var(--font-adaptive-small); + color: var(--text-faint); + padding: 0 0 0 15px; + width: 100%; } + +.outline .tree-item-self { + cursor: var(--cursor); + line-height: 1.4; + margin-bottom: 4px; + font-size: var(--font-adaptive-small); + padding-left: 15px; } + +.tree-item-collapse { + opacity: 1; + left: -5px; + color: var(--text-faint); } + +.outline .tree-item-inner:hover { + color: var(--text-normal); } + +.tree-item-self.is-clickable:hover .tree-item-collapse { + color: var(--text-normal); } + +.outline > .tree-item > .tree-item-self .right-triangle { + opacity: 0; } + +/* Page Preview aka Popovers */ +.theme-dark.minimal-dark-black .popover { + background: var(--bg2); } + +.popover, +.popover.hover-popover { + min-height: 40px; + box-shadow: 0 20px 40px var(--background-modifier-box-shadow); + pointer-events: auto !important; + border: 1px solid var(--background-modifier-border); } + +.popover.hover-popover { + width: 400px; + max-height: 40vh; } + +.popover.hover-popover .markdown-embed { + padding: 0; } + +.popover .markdown-embed-link { + display: none; } + +.popover .markdown-embed .markdown-preview-view { + padding: 10px 20px 30px; } + +.popover.hover-popover .markdown-embed .markdown-embed-content { + max-height: none; } + +.popover.hover-popover.mod-empty { + padding: 20px 20px 20px 20px; + color: var(--text-muted); } + +.popover.hover-popover .markdown-preview-view .table-view-table, +.popover.hover-popover .markdown-embed .markdown-preview-view { + font-size: 1.05em; } + +.popover.hover-popover .markdown-embed h1, +.popover.hover-popover .markdown-embed h2, +.popover.hover-popover .markdown-embed h3, +.popover.hover-popover .markdown-embed h4 { + margin-top: 1rem; } + +/* Prompt */ +/* Used for command palette and quick switcher */ +.prompt { + box-shadow: var(--shadow-m); + padding-bottom: 0; + border: 1px solid var(--modal-border); } + +body:not(.hider-scrollbars) .prompt { + padding-right: 0px; } + +body:not(.hider-scrollbars) .prompt-results { + padding-right: 10px; } + +input.prompt-input { + border: 0; + background: var(--background-primary); + box-shadow: none !important; + padding-left: 10px; + height: 40px; + line-height: 4; + font-size: var(--font-adaptive-normal); } + input.prompt-input:hover { + border: 0; + background: var(--background-primary); + padding-left: 10px; + line-height: 4; } + +.prompt-results { + padding-bottom: 0; } + .prompt-results .suggestion-item:last-child, + .prompt-results .suggestion-empty { + margin-bottom: 10px; } + +.prompt-instructions { + color: var(--text-muted); } + +.prompt-instruction-command { + font-weight: 600; } + +/* +.suggestion-prefix { + font-weight:500; +}*/ +/* In Editor autocomplete */ +.suggestion-container { + box-shadow: 0 5px 40px rgba(0, 0, 0, 0.2); + padding: 0 6px; + border-radius: 8px; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border-hover); } + .suggestion-container .suggestion-item { + font-size: calc(var(--font-adaptive-normal) * .9) !important; + cursor: var(--cursor); + padding: 4px 10px 4px 10px; + border-radius: 4px; } + .suggestion-container .suggestion-item:first-child { + margin-top: 6px; } + .suggestion-container .suggestion-item:last-child { + margin-bottom: 6px; } + +.is-mobile .suggestion-container .suggestion-item:first-child { + margin-top: 0; } +.is-mobile .suggestion-container .suggestion-item:last-child { + margin-bottom: 10px; } + +.suggestion-hotkey { + margin-top: 0.25em; } + +.suggestion-flair { + left: auto; + right: 8px; + opacity: 0.25; } + +.prompt-results .suggestion-flair .filled-pin { + display: none; } + +.prompt-results .suggestion-item { + padding: 5px 8px 5px 10px; } + +/* +.prompt .prompt-results { + .suggestion-item { + display:flex; + align-items:center; + .suggestion-prefix { + white-space:pre; + } + .suggestion-content { + white-space:pre; + overflow:hidden; + text-overflow:ellipsis; + flex-grow:1; + padding-right:1em; + } + .suggestion-hotkey { + white-space:pre; + margin-top:0; + } + .suggestion-hotkey:not(:last-child) { + margin:0 5px 0 0; + } + } +} +*/ +.modal-container .suggestion-item.is-selected { + border-radius: var(--radius-m); + background: var(--background-tertiary); } + +.suggestion-item.is-selected { + background: var(--background-tertiary); } + +.suggestion-item, +.suggestion-empty { + font-size: var(--font-adaptive-normal); + cursor: var(--cursor); } + +/* Mobile */ +.is-mobile { + /* Tablet */ + /* Phone */ } + .is-mobile .prompt, + .is-mobile .suggestion-container { + width: 100%; + max-width: 100%; + border: none; + padding: 10px 10px 0 10px; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; } + .is-mobile .suggestion-container { + left: 0; + right: 0; + margin: 0 auto; + border: none; } + .is-mobile .suggestion-item { + font-size: var(--font-adaptive-normal); + padding-left: 10px; + letter-spacing: 0.001px; } + .is-mobile .prompt-results .suggestion-flair { + display: none; } + .is-mobile input[type='text'].prompt-input, + .is-mobile input[type='text'].prompt-input:hover { + line-height: 2; + padding: 8px; + height: 4.5ex; + font-size: var(--font-adaptive-normal); } + @media (min-width: 400pt) { + .is-mobile .modal-container .prompt { + opacity: 1 !important; } + .is-mobile .prompt { + max-width: 600px; + max-height: 600px; + bottom: auto !important; + border-radius: 15px; + top: 100px !important; } + .is-mobile .suggestion-container { + max-width: 600px; + max-height: 600px; + border-radius: 15px; + bottom: 80px; + border: 1px solid var(--background-modifier-border); } + .is-mobile .modal-container .suggestion-item { + padding: 8px 5px 8px 8px; + border-radius: var(--radius-m); } + .is-mobile .suggestion-flair { + right: 0; + left: auto; + position: absolute; + padding: 10px; } } + @media (max-width: 400pt) { + .is-mobile .suggestion-hotkey { + display: none; } + .is-mobile .suggestion-flair { + right: 0; + left: auto; + position: absolute; + padding: 5px 5px 0 0; } + .is-mobile .suggestion-container { + max-height: 200px; + border-top: 1px solid var(--background-modifier-border); + border-radius: 0; + padding-top: 0; + box-shadow: none; } + .is-mobile .prompt { + border-radius: 0; + border: none; + padding-top: 5px; + padding-bottom: 0; + max-height: calc(100vh - 120px); + top: 120px; } + .is-mobile .suggestion-container .suggestion { + padding-top: 10px; } } + +/* Publish */ +.modal.mod-publish { + max-width: 600px; + padding-left: 0; + padding-right: 0; + padding-bottom: 0; } + +.modal.mod-publish .modal-title { + padding-left: 20px; + padding-bottom: 10px; } + +.mod-publish .modal-content { + padding-left: 20px; + padding-right: 20px; } + +.mod-publish p { + font-size: var(--font-small); } + +.mod-publish .tree-item-flair { + display: unset; } + +.file-tree .mod-new .tree-item-flair, +.file-tree .mod-deleted .tree-item-flair, +.file-tree .mod-to-delete .tree-item-flair, +.file-tree .mod-changed .tree-item-flair { + background: transparent; } + +.file-tree .mod-deleted .tree-item-flair, +.file-tree .mod-to-delete .tree-item-flair { + color: var(--pink); } + +.file-tree .mod-new .tree-item-flair { + color: var(--green); } + +.file-tree .mod-changed .tree-item-flair { + color: var(--yellow); } + +.mod-publish .button-container, +.modal.mod-publish .modal-button-container { + margin-top: 0px; + padding: 10px; + border-top: 1px solid var(--background-modifier-border); + bottom: 0px; + background-color: var(--background-primary); + position: absolute; + width: 100%; + margin-left: -20px; + text-align: center; } + +.publish-changes-info { + padding: 0 0 15px; + margin-bottom: 0; + border-bottom: 1px solid var(--background-modifier-border); } + +.modal.mod-publish .modal-content .publish-sections-container { + max-height: none; + height: auto; + padding: 10px 20px 30px 0; + margin-top: 10px; + margin-right: -20px; + margin-bottom: 80px; } + +.publish-site-settings-container { + max-height: none; + height: auto; + margin-right: -20px; + margin-bottom: 80px; + overflow-x: hidden; } + +.publish-section-header { + padding-bottom: 15px; + border-width: 1px; } + +.password-item { + padding-left: 0; + padding-right: 0; } + +.publish-section-header-text { + font-weight: 600; + color: var(--text-normal); + cursor: var(--cursor); } + +.publish-section-header-text, +.publish-section-header-toggle-collapsed-button, +.publish-section-header-action, +.file-tree-item-header { + cursor: var(--cursor); } + +.publish-section-header-text:hover, +.publish-section-header-toggle-collapsed-button:hover, +.publish-section-header-action:hover { + color: var(--text-normal); + cursor: var(--cursor); } + +.mod-publish .u-pop { + color: var(--text-normal); } + +.publish-section-header-toggle-collapsed-button { + padding: 7px 0 0 3px; + width: 18px; } + +.mod-publish .file-tree-item { + margin-left: 20px; } + +.mod-publish .file-tree-item { + padding: 0; + margin-bottom: 2px; + font-size: var(--font-small); } + +.mod-publish .file-tree-item-checkbox { + filter: hue-rotate(0); } + +.mod-publish .file-tree-item.mod-deleted .flair, +.mod-publish .file-tree-item.mod-to-delete .flair { + background: transparent; + color: #ff3c00; + font-weight: 500; } + +.mod-publish .file-tree-item.mod-new .flair { + background: transparent; + font-weight: 500; + color: #13c152; } + +.mod-publish .site-list-item { + padding-left: 0; + padding-right: 0; } + +.is-mobile { + /* Mobile publish */ + /* Phone */ } + .is-mobile .mod-publish .modal-content { + display: unset; + padding: 10px 10px 10px; + margin-bottom: 120px; + overflow-x: hidden; } + .is-mobile .mod-publish .button-container, + .is-mobile .modal.mod-publish .modal-button-container { + padding: 10px 15px 30px; + margin-left: 0px; + left: 0; } + .is-mobile .modal.mod-publish .modal-title { + padding: 10px 20px; + margin: 0 -10px; + border-bottom: 1px solid var(--background-modifier-border); } + .is-mobile .publish-site-settings-container { + margin-right: 0; + padding: 0; } + .is-mobile .modal.mod-publish .modal-content .publish-sections-container { + margin-right: 0; + padding-right: 0; } + @media (max-width: 400pt) { + .is-mobile .publish-section-header, + .is-mobile .publish-changes-info { + flex-wrap: wrap; + border: none; } + .is-mobile .publish-changes-info .publish-changes-add-linked-btn { + flex-basis: 100%; + margin-top: 10px; } + .is-mobile .publish-section-header-text { + flex-basis: 100%; + margin-bottom: 10px; + margin-left: 20px; + margin-top: -8px; } + .is-mobile .publish-section { + background: var(--background-secondary); + border-radius: 10px; + padding: 12px 12px 1px; } + .is-mobile .publish-changes-switch-site { + flex-grow: 0; + margin-right: 10px; } } + +/* Search */ +.search-result-container.mod-global-search .search-empty-state { + padding-left: 15px; } + +.search-result-file-match { + cursor: var(--cursor) !important; + width: auto; + left: 0; } + +.search-result-file-match:hover { + background: transparent; } + +.search-result-container:before { + height: 1px; } + +.search-result-file-match-replace-button { + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + color: var(--text-muted); + opacity: 1; + top: auto; + right: 18px; + bottom: 1px; + font-weight: 500; + font-size: var(--font-adaptive-smaller); } + +.search-result-hover-button:hover { + background-color: var(--background-tertiary); + color: var(--text-muted); } + +.search-result-file-match-replace-button:hover { + background-color: var(--background-modifier-border); + color: var(--text-normal); } + +.search-result-container.is-loading:before { + background-color: var(--background-modifier-accent); } + +.search-result { + margin-bottom: 0; } + +.search-result-count { + opacity: 1; + color: var(--text-faint); + padding: 0 0 0 5px; } + +.search-result-file-match:before { + top: 0; } + +.search-result-file-match:not(:first-child) { + margin-top: 0px; } + +.search-result-file-match { + margin-top: 0; + margin-bottom: 0; + padding-top: 6px; + padding-bottom: 5px; } + +.search-result-file-matched-text { + background-color: var(--text-selection); } + +.search-input-container input, +.search-input-container input:hover, +.search-input-container input:focus { + font-size: var(--font-adaptive-small); + padding: 5px 28px 5px 10px; + background-color: var(--background-modifier-form-field); } + +.search-input-container { + width: calc(100% - 20px); + margin: 0 0 8px 10px; } + +.workspace-leaf-content .setting-item { + padding: 5px 0; + border: none; } + +.workspace-leaf-content .setting-item-control { + flex-shrink: 0; + flex: 1; } + +.search-input-clear-button { + background: transparent; + border-radius: 50%; + color: var(--text-muted); + cursor: var(--cursor); + top: 0px; + right: 2px; + bottom: 0px; + line-height: 0; + height: calc(var(--input-height) - 2px); + width: 28px; + margin: auto; + padding: 0 0; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + transition: color 0.2s ease-in-out; } + +.search-input-clear-button:hover { + color: var(--text-normal); + transition: color 0.2s ease-in-out; } + +.search-input-clear-button:active { + color: var(--text-normal); + transition: color 0.2s ease-in-out; } + +.search-input-clear-button:before { + content: ''; + height: 13px; + width: 13px; + display: block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + -webkit-mask-repeat: no-repeat; } + +.search-input { + max-width: 100%; + margin-left: 0; + width: 500px; } + +input.search-input:focus { + border-color: var(--background-modifier-border); } + +.workspace-leaf-content[data-type='search'] .search-result-file-matches { + padding-left: 0; } + +.search-empty-state { + font-size: var(--font-adaptive-small); + color: var(--text-faint); + padding-left: 5px; + margin: 0; } + +.search-result-container { + padding: 5px 10px 50px 5px; } + +.search-result-file-title { + line-height: 1.3; + padding: 4px 4px 4px 20px; + vertical-align: middle; + cursor: var(--cursor) !important; } + +.tree-item-inner, +.search-result-file-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } + +.search-result-collapse-indicator { + left: 0px; } + +.search-result-file-match { + padding-right: 15px; } + +.search-result-file-match:before { + height: 0.5px; } + +.search-result-file-matches { + font-size: var(--font-adaptive-smaller); + line-height: 1.3; + margin: 3px 0 8px 0px; + padding: 0 0 2px 0; + color: var(--text-muted); + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + border-radius: var(--radius-m); } + +.search-result:last-child .search-result-file-matches { + border: 1px solid var(--background-modifier-border); } + +.search-result-hover-button.mod-top { + top: 4px; + right: 4px; } + +.search-result-hover-button.mod-bottom { + bottom: 0px; + right: 4px; } + +.search-info-container { + font-size: var(--font-adaptive-smaller); + color: var(--text-faint); + padding-top: 5px; + padding-bottom: 5px; } + +.search-info-more-matches { + font-size: var(--font-adaptive-smaller); + padding-top: 4px; + padding-bottom: 4px; + color: var(--text-normal); } + +.side-dock-collapsible-section-header-indicator { + display: none; } + +.search-result-file-title:hover { + color: var(--text-normal); + background: transparent; } + +.workspace-leaf-content .search-input, +.workspace-leaf-content .search-input:hover, +.workspace-leaf-content .search-input:focus { + font-size: var(--font-adaptive-small); + padding: 7px 10px; + height: 28px; + border-radius: var(--radius-m); + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + transition: border-color 0.1s ease-in-out; } + +.workspace-leaf-content .search-input:hover { + border-color: var(--background-modifier-border-hover); + transition: border-color 0.1s ease-in-out; } + +.workspace-leaf-content .search-input:focus { + background: var(--background-primary); + border-color: var(--background-modifier-border-focus); + transition: all 0.1s ease-in-out; } + +.search-input-container input::placeholder { + color: var(--text-faint); + font-size: var(--font-adaptive-small); } + +.workspace-split.mod-root .workspace-split.mod-vertical .workspace-leaf-content { + padding-right: 0; } + +.workspace-split.mod-horizontal.mod-right-split { + width: 0; } + +.workspace-split.mod-vertical > .workspace-leaf { + padding-right: 1px; } + +.workspace-leaf-content[data-type=starred] .item-list { + padding-top: 5px; } + +.workspace-leaf-content .view-content { + padding: 0; } + +.workspace-split.mod-right-split .view-content { + padding: 0; + background-color: var(--background-primary); } + +/* Sync */ +/* Sync Log */ +.modal.mod-sync-log { + padding: 20px 0 0 0; } + +.modal.mod-sync-log .modal-title { + padding: 0 20px; } + +.modal.mod-sync-log .modal-content { + padding: 0px; + display: flex; + flex-direction: column; } + +.modal.mod-sync-log .modal-button-container { + border-top: 1px solid var(--background-modifier-border); + padding: 15px; + background-color: var(--background-primary); + margin: 0; } + +.modal.mod-sync-log .sync-log-container { + padding: 16px 20px; + background-color: var(--background-secondary); + flex-grow: 1; + font-size: var(--font-adaptive-small); } + +.sync-log-container .list-item { + padding-left: 0; } + +.modal.mod-sync-log .setting-item.mod-toggle { + padding: 20px; } + +.sync-history-content { + font-size: var(--font-adaptive-small); + border: none; + padding: 20px 40px 20px 20px; + border-radius: 0; } + +body .sync-history-content-container textarea.sync-history-content:active, +body .sync-history-content-container textarea.sync-history-content:focus { + box-shadow: none; } + +/* Sync history */ +.modal.mod-sync-history .modal-content { + padding: 0; } + +.sync-history-content-empty { + padding: 5px 20px; + color: var(--text-muted); + font-size: var(--font-adaptive-small); } + +.sync-history-content-container { + padding: 0; + height: auto; + border-left: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); } + +.sync-history-content-buttons.u-center-text { + text-align: center; + padding: 10px; + margin: 0; + border-top: 1px solid var(--background-modifier-border); } + +.sync-history-content-container .modal-button-container { + margin: 0; + padding: 10px 5px; + border-top: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); + text-align: center; } + +.sync-history-list { + min-width: 220px; } + +.sync-history-list-container { + min-width: 220px; + flex-basis: 230px; + max-height: none; + overflow-y: scroll; + background-color: var(--background-secondary); } + +.sync-history-list { + padding: 10px 10px 0 10px; + overflow: unset; + background-color: var(--background-secondary); } + +.sync-history-list .search-input-container { + width: 100%; + margin: 0; } + +.sync-history-load-more-button { + font-size: var(--font-adaptive-small); + cursor: var(--cursor); + margin: 0px 10px 10px; + border-radius: var(--radius-m); } + +.sync-history-load-more-button:hover { + background-color: var(--background-tertiary); } + +.sync-history-list-item { + border-radius: var(--radius-m); + padding: 4px 8px; + margin-bottom: 4px; + font-size: var(--font-adaptive-small); + cursor: var(--cursor); } + +.sync-history-list-item.is-active, .sync-history-list-item:hover { + background-color: var(--background-tertiary); } + +/* Mobile */ +.is-mobile .sync-status-icon { + margin-top: 2px; } +.is-mobile .sync-history-list { + padding: 10px; + background-color: var(--background-primary); } +.is-mobile .sync-history-list-item { + font-size: var(--font-adaptive-small); + padding: 8px 10px; } +.is-mobile .sync-history-content-container .modal-button-container { + padding: 5px 10px 30px 10px; } +.is-mobile .sync-history-content { + outline: none; + -webkit-appearance: none; + border: 0; + background-color: var(--background-secondary); } +.is-mobile .modal.mod-sync-log .mod-toggle, .is-mobile .modal.mod-sync-log .modal-button-container { + flex: 0; } + +/* --------------- */ +/* Phone */ +@media (max-width: 400pt) { + .is-mobile .modal.mod-sync-log { + width: 100vw; + height: 100vh; + max-height: calc(100vh - 32px); + box-shadow: 0 -32px 0 32px var(--background-primary); + bottom: 0; + padding-bottom: 10px; } } +/* Community plugins */ +/* Banner plugin */ +/* +.markdown-source-view.mod-cm6 .cm-line.has-banner { + width:100% !important; + max-width:100% !important; + transform:none !important; + + .cm-fold-indicator, + .cm-def.cm-hmd-frontmatter { + margin-left:max( + calc(50% + var(--folding-offset) - var(--line-width-adaptive)/2), + calc(50% - var(--max-width)/2) + var(--folding-offset)) !important; + } + .obsidian-banner-icon { + width:calc(var(--line-width-adaptive) - var(--folding-offset)); + max-width:var(--max-width); + margin-left:auto; + margin-right:auto; + transform:translateX(calc(var(--folding-offset)/2)); + } +} */ +.obsidian-banner.solid { + border-bottom: var(--border-width) solid var(--background-divider); } + +.contextual-typography .markdown-preview-view div.has-banner-icon.obsidian-banner-wrapper { + overflow: visible; } + +.theme-dark .markdown-preview-view img.emoji { + opacity: 1; } + +/* Breadcrumbs plugin +body .BC-trail { + border-width: 0 0 1px 0; + border-radius: 0; +} +*/ +/* Buttons plugin */ +body.theme-dark .button-default, +body.theme-light .button-default { + border: none; + box-shadow: none; + height: var(--input-height); + background: var(--background-tertiary); + color: var(--text-normal); + font-size: revert; + font-weight: 500; + transform: none; + transition: all 0.1s linear; + padding: 0 20px; } + +body.theme-dark .button-default:hover, +body.theme-light .button-default:hover { + border: none; + background: var(--background-modifier-border-hover); + box-shadow: none; + transform: none; + transition: all 0.1s linear; } + +body.theme-light .button-default:focus, +body.theme-light .button-default:active, +body.theme-dark .button-default:focus, +body.theme-dark .button-default:active { + box-shadow: none; } + +body .button-default.blue { + background-color: var(--blue) !important; } + +.button-default.red { + background-color: var(--red) !important; } + +.button-default.green { + background-color: var(--green) !important; } + +.button-default.yellow { + background-color: var(--yellow) !important; } + +.button-default.purple { + background-color: var(--purple) !important; } + +/* Calendar plugin */ +.workspace-leaf-content[data-type='calendar'] .view-content { + padding: 5px 0 0 0; } + +#calendar-container { + padding: 0 15px 5px; + --color-background-day-empty:var(--background-secondary-alt); + --color-background-day-active:var(--background-tertiary); + --color-background-day-hover:var(--background-tertiary); + --color-dot:var(--text-faint); + --color-text-title:var(--text-normal); + --color-text-heading:var(--text-muted); + --color-text-day:var(--text-normal); + --color-text-today:var(--text-normal); + --color-arrow:var(--text-faint); + --color-background-day-empty:transparent; } + +#calendar-container .table { + border-collapse: separate; + table-layout: fixed; } + +#calendar-container h2 { + font-weight: 400; + font-size: var(--h2); } + +.mod-root #calendar-container { + width: var(--line-width-adaptive); + max-width: var(--max-width); + margin: 0 auto; + padding: 0; } + +#calendar-container .arrow { + cursor: var(--cursor); + width: 22px; + border-radius: 4px; + padding: 3px 7px; } + +#calendar-container .arrow svg { + width: 12px; + height: 12px; + color: var(--text-faint); + opacity: 0.7; } + +#calendar-container .arrow:hover { + fill: var(--text-muted); + color: var(--text-muted); + background-color: var(--background-tertiary); } + +#calendar-container .arrow:hover svg { + color: var(--text-muted); + opacity: 1; } + +#calendar-container tr th { + padding: 2px 0 4px; + font-weight: 500; + letter-spacing: 0.1em; + font-size: var(--font-adaptive-smallest); } + +#calendar-container tr td { + padding: 2px 0 0 0; + border-radius: var(--radius-m); + cursor: var(--cursor); + border: 1px solid transparent; + transition: none; } + +#calendar-container .nav { + padding: 0; + margin: 10px 5px 10px 5px; } + +#calendar-container .dot { + margin: 0; } + +#calendar-container .year, +#calendar-container .month, +#calendar-container .title { + font-size: var(--font-adaptive-normal); + font-weight: 400; + color: var(--text-normal); } + +#calendar-container .today.active, +#calendar-container .today { + color: var(--text-accent); + font-weight: 600; } + +#calendar-container .today.active .dot, +#calendar-container .today .dot { + fill: var(--text-accent); } + +#calendar-container .active .task { + stroke: var(--text-faint); } + +#calendar-container .active { + color: var(--text-normal); } + +#calendar-container .reset-button { + text-transform: none; + letter-spacing: 0; + font-size: var(--font-adaptive-small); + font-weight: 500; + color: var(--text-muted); + border-radius: 4px; + margin: 0; + padding: 2px 8px; } + +#calendar-container .reset-button:hover { + color: var(--text-normal); + background-color: var(--background-tertiary); } + +#calendar-container .reset-button, +#calendar-container .day { + cursor: var(--cursor); } + +#calendar-container .day.adjacent-month { + color: var(--text-faint); + opacity: 1; } + +#calendar-container .day { + padding: 2px 4px 4px; + font-size: calc(var(--font-adaptive-normal) - 2px); } + +#calendar-container .active, +#calendar-container .active.today, +#calendar-container .week-num:hover, +#calendar-container .day:hover { + background-color: var(--color-background-day-active); } + +#calendar-container .active .dot { + fill: var(--text-faint); } + +#calendar-container .active .task { + stroke: var(--text-faint); } + +/* Charts */ +.block-language-chart canvas, +.block-language-dataviewjs canvas { + margin: 1em 0; } + +.theme-light, +.theme-dark { + --chart-color-1:var(--blue); + --chart-color-2:var(--red); + --chart-color-3:var(--yellow); + --chart-color-4:var(--green); + --chart-color-5:var(--orange); + --chart-color-6:var(--purple); + --chart-color-7:var(--cyan); + --chart-color-8:var(--pink); } + +/* Checklist plugin */ +.checklist-plugin-main .group .classic, +.checklist-plugin-main .group .compact, +.checklist-plugin-main .group svg, +.checklist-plugin-main .group .page { + cursor: var(--cursor); } + +.workspace .view-content .checklist-plugin-main { + padding: 10px 10px 15px 15px; + --todoList-togglePadding--compact:2px; + --todoList-listItemMargin--compact:2px; } + +.checklist-plugin-main .title { + font-weight: 400; + color: var(--text-muted); + font-size: var(--font-adaptive-small); } + +.checklist-plugin-main .group svg { + fill: var(--text-faint); } + +.checklist-plugin-main .group svg:hover { + fill: var(--text-normal); } + +.checklist-plugin-main .group .title:hover { + color: var(--text-normal); } + +.checklist-plugin-main .group:not(:last-child) { + border-bottom: 1px solid var(--background-modifier-border); } + +.checklist-plugin-main .group { + padding: 0 0 2px 0; } + +.checklist-plugin-main .group .classic:last-child, +.checklist-plugin-main .group .compact:last-child { + margin-bottom: 10px; } + +.checklist-plugin-main .group .classic, +.checklist-plugin-main .group .compact { + font-size: var(--font-adaptive-small); } + +.checklist-plugin-main .group .classic, +.checklist-plugin-main .group .compact { + background: transparent; + border-radius: 0; + margin: 1px auto; + padding: 0; } + +.checklist-plugin-main .group .classic .content { + padding: 0; } + +.checklist-plugin-main .group .classic:hover, +.checklist-plugin-main .group .compact:hover { + background: transparent; } + +.markdown-preview-view.checklist-plugin-main ul > li:not(.task-list-item)::before { + display: none; } + +.checklist-plugin-main .group .compact > .toggle .checked { + background: var(--text-accent); + top: -1px; + left: -1px; + height: 18px; + width: 18px; } + +.checklist-plugin-main .compact .toggle:hover { + opacity: 1 !important; } + +.checklist-plugin-main .group .count { + font-size: var(--font-adaptive-smaller); + padding: 0; + background: transparent; + font-weight: 400; + color: var(--text-faint); } + +.checklist-plugin-main .group .group-header:hover .count { + color: var(--text-muted); } + +.checklist-plugin-main .group .checkbox { + border: 1px solid var(--background-modifier-border-hover); + min-height: 18px; + min-width: 18px; + height: 18px; + width: 18px; } + +.checklist-plugin-main .group .checkbox:hover { + border: 1px solid var(--background-modifier-border-focus); } + +.checklist-plugin-main button:active, +.checklist-plugin-main button:focus, +.checklist-plugin-main button:hover { + box-shadow: none !important; } + +.checklist-plugin-main button.collapse { + padding: 0; } + +body:not(.is-mobile) .checklist-plugin-main button.collapse svg { + width: 18px; + height: 18px; } + +/* Checklist plugin mobile */ +.is-mobile .checklist-plugin-main .group-header .title { + flex-grow: 1; + flex-shrink: 0; } + +.is-mobile .checklist-plugin-main button { + width: auto; } + +.is-mobile .checklist-plugin-main.markdown-preview-view ul { + padding-inline-start: 0; } + +.is-mobile .workspace .view-content .checklist-plugin-main { + padding-bottom: 50px; } + +/* cMenu plugin */ +body #cMenuModalBar { + box-shadow: 0px 2px 20px var(--shadow-color); } + +body #cMenuModalBar .cMenuCommandItem { + cursor: var(--cursor); } + +body #cMenuModalBar button.cMenuCommandItem:hover { + background-color: var(--background-tertiary); } + +/* Contextual Typography */ +.el-hr hr { + margin: 1rem 0; } + +.el-p + .el-h1, +.el-p + .el-h2 { + margin-top: 0.75rem; } + +.el-hr + .el-h1, +.el-hr + .el-h2, +.el-h1 + .el-h1, +.el-h1 + .el-h2, +.el-h2 + .el-h2 { + margin-top: 0rem; } + +.el-ol + .el-lang-dataview, +.el-ul + .el-lang-dataview, +.el-p:not(.el-lang-dataview) + .el-lang-dataview, +.el-ol + .el-lang-dataviewjs, +.el-ul + .el-lang-dataviewjs, +.el-p:not(.el-lang-dataviewjs) + .el-lang-dataviewjs, +.el-ol + .el-table, +.el-ul + .el-table, +.el-p + .el-table, +.el-lang-dataviewjs + .el-p, +.el-lang-dataview + .el-p { + margin-top: var(--spacing-p); } + +.el-div + .el-h1, +.el-pre + .el-h1, +.el-lang-leaflet, +.el-lang-leaflet + *, +.el-iframe + .el-p, +.el-p + .el-iframe, +.el-p:not(.el-embed-image) + .el-embed-image, +.el-embed-image + .el-p:not(.el-embed-image) { + margin-top: 1rem; } + +/* Dataview plugin */ +/*body .table-view-table > thead > tr > th, +.markdown-preview-view .table-view-table { + font-size:calc(var(--font-adaptive-normal) - 1px); +}*/ +body .table-view-table > thead > tr > th, +.markdown-preview-view .table-view-table > thead > tr > th { + font-weight: 400; + font-size: var(--table-font-size); + color: var(--text-muted); + border-bottom: 1px solid var(--background-modifier-border); + cursor: var(--cursor); } + +table.dataview ul.dataview-ul { + list-style: none; + padding-inline-start: 0; + margin-block-start: 0em !important; + margin-block-end: 0em !important; } + +.markdown-source-view.mod-cm6 .table-view-table > tbody > tr > td, +.markdown-preview-view .table-view-table > tbody > tr > td { + max-width: var(--max-col-width); } + +body .dataview.small-text { + color: var(--text-faint); } + +/* Remove hover effect */ +body .dataview.task-list-item:hover, +body .dataview.task-list-basic-item:hover, +body .table-view-table > tbody > tr:hover { + background-color: transparent; + box-shadow: none; } + +body .dataview-error { + margin-top: 16px; + background-color: transparent; } + +.markdown-source-view.mod-cm6 .cm-content .dataview.dataview-error, +.dataview.dataview-error { + color: var(--text-muted); } + +/* New error box as of 2022-05 */ +body div.dataview-error-box { + min-height: 0; + border: none; + background-color: transparent; + font-size: var(--table-font-size); + border-radius: var(--radius-m); + padding: 15px 0; } + body div.dataview-error-box p { + margin-block-start: 0; + margin-block-end: 0; + color: var(--text-faint); } + +.markdown-source-view div.dataview-error-box { + margin-top: 15px; } + +/* Trim columns feature */ +.trim-cols .markdown-source-view.mod-cm6 .table-view-table > tbody > tr > td, +.trim-cols .markdown-preview-view .table-view-table > tbody > tr > td, +.trim-cols .markdown-source-view.mod-cm6 .table-view-table > thead > tr > th { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } + +/* Lists */ +ul .dataview .task-list-item:hover, +ul .dataview .task-list-basic-item:hover { + background-color: transparent; + box-shadow: none; } + +body .dataview.result-group { + padding-left: 0; } + +/* Inline fields */ +body .dataview.inline-field-key, +body .dataview.inline-field-value, +body .dataview .inline-field-standalone-value { + font-family: var(--font-text); + font-size: calc(var(--font-adaptive-normal) - 2px); + background: transparent; + color: var(--text-muted); } + +body .dataview.inline-field-key { + padding: 0; } + +body .dataview .inline-field-standalone-value { + padding: 0; } + +body .dataview.inline-field-key::after { + margin-left: 3px; + content: "|"; + color: var(--background-modifier-border); } + +body .dataview.inline-field-value { + padding: 0 1px 0 3px; } + +/* Calendar */ +.markdown-preview-view .block-language-dataview table.calendar th { + border: none; + cursor: default; + background-image: none; } + +.markdown-preview-view .block-language-dataview table.calendar .day { + font-size: var(--font-adaptive-small); } + +/* Dictionary plugin */ +.workspace-leaf-content .view-content.dictionary-view-content { + padding: 0; } + +div[data-type="dictionary-view"] .contents { + padding-bottom: 2rem; } + +div[data-type="dictionary-view"] .results > .container { + background-color: transparent; + margin-top: 0; + max-width: none; + padding: 0 10px; } + +div[data-type="dictionary-view"] .error, +div[data-type="dictionary-view"] .errorDescription { + text-align: left; + font-size: var(--font-adaptive-small); + padding: 10px 12px 0; + margin: 0; } + +div[data-type="dictionary-view"] .results > .container h3 { + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + font-size: var(--font-adaptive-smallest); + font-weight: 500; + padding: 5px 7px 0px 2px; + margin-bottom: 6px; } + +div[data-type="dictionary-view"] .container .main { + border-radius: 0; + background-color: transparent; + font-size: var(--font-adaptive-smaller); + line-height: 1.3; + color: var(--text-muted); + padding: 5px 0 0; } + +div[data-type="dictionary-view"] .main .definition { + padding: 10px; + border: 1px solid var(--background-modifier-border); + border-radius: 5px; + margin: 10px 0 5px; + background-color: var(--background-primary); } + +div[data-type="dictionary-view"] .main .definition:last-child { + border: 1px solid var(--background-modifier-border); } + +div[data-type="dictionary-view"] .main .synonyms { + padding: 10px 0 0; } + +div[data-type="dictionary-view"] .main .synonyms p { + margin: 0; } + +div[data-type="dictionary-view"] .main .definition > blockquote { + margin: 0; } + +div[data-type="dictionary-view"] .main .label { + color: var(--text-normal); + margin-bottom: 2px; + font-size: var(--font-adaptive-smaller); + font-weight: 500; } + +div[data-type="dictionary-view"] .main .mark { + color: var(--text-normal); + background-color: var(--text-selection); + box-shadow: none; } + +div[data-type="dictionary-view"] .main > .opener { + font-size: var(--font-adaptive-small); + color: var(--text-normal); + padding-left: 5px; } + +/* Excalidraw Plugin */ +body .excalidraw, +body .excalidraw.theme--dark { + --color-primary-light:var(--text-selection); + --color-primary:var(--interactive-accent); + --color-primary-chubb:var(--interactive-accent-hover); + --color-primary-darker:var(--interactive-accent-hover); + --color-primary-darkest:var(--interactive-accent-hover); + --ui-font:var(--font-interface); + --island-bg-color:var(--background-secondary); + --button-gray-1:var(--background-tertiary); + --button-gray-2:var(--background-tertiary); + --focus-highlight-color:var(--background-modifier-border-focus); + --default-bg-color:var(--background-primary); + --input-border-color:var(--background-modifier-border); + --link-color:var(--text-accent); + --overlay-bg-color:rgba(255, 255, 255, 0.88); + --text-primary-color:var(--text-normal); } + +.workspace-leaf-content[data-type=excalidraw] .view-header .view-header-title-container { + width: auto; } + +body .excalidraw .App-toolbar-container .ToolIcon_type_floating:not(.is-mobile) .ToolIcon__icon { + box-shadow: none; } + +body .excalidraw button, +body .excalidraw .buttonList label { + cursor: var(--cursor); } + +body .excalidraw .Dialog__title { + font-variant: normal; } + +body .excalidraw .reset-zoom-button, +body .excalidraw .HintViewer { + color: var(--text-muted); + font-size: var(--font-small); } + +body .excalidraw .reset-zoom-button { + padding-left: 1em; + padding-right: 1em; } + +body .excalidraw .HintViewer > span { + background-color: transparent; } + +body .excalidraw button:hover { + box-shadow: none; } + +body .excalidraw .Island { + box-shadow: none; + border: 1px solid var(--background-modifier-border); } + +body .excalidraw .ToolIcon { + cursor: var(--cursor); + font-family: var(--font-interface); + background-color: transparent; } + +body .excalidraw label.ToolIcon { + cursor: var(--cursor); + background-color: transparent; } + +/* Electron Window Tweaker */ +:root { + --ewt-traffic-light-y:0px; } + +/* Embedded Note Titles plugin */ +.contextual-typography .markdown-preview-view h1.embedded-note-title { + margin-block-start: 0; + margin-block-end: 0; } + +.embedded-note-titles .markdown-preview-view > h1 { + padding-left: var(--folding-offset) !important; } + +.embedded-note-titles .is-readable-line-width.markdown-preview-view > h1 { + max-width: var(--max-width) !important; + width: var(--line-width-adaptive) !important; } + +.mod-cm6 .cm-editor h1.cm-line.embedded-note-title { + padding-top: var(--embedded-note-title-padding-top); + padding-bottom: var(--embedded-note-title-padding-bottom); } + +/* Attempting focus mode + embedded note titles + +.embedded-note-titles.minimal-focus-mode .markdown-preview-view > h1 { + padding-top:var(--header-height); +} +.embedded-note-titles.minimal-focus-mode .workspace-split.mod-root > .workspace-leaf:first-of-type:last-of-type .CodeMirror-scroller { + margin-top:calc(var(--header-height) - 10px); +}*/ +.embedded-note-titles .CodeMirror-scroll > h1 { + /* ...edit mode styles... */ } + +.embedded-note-titles .is-readable-line-width .CodeMirror-scroll > h1 { + /* ...edit mode styles with readable line width enabled... */ } + +/* Git plugin */ +.git-view-body .opener { + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: var(--font-adaptive-smallest); + font-weight: 500; + padding: 5px 7px 5px 10px; + margin-bottom: 6px; } + +.git-view-body .file-view .opener { + text-transform: none; + letter-spacing: normal; + font-size: var(--font-adaptive-smallest); + font-weight: normal; + padding: initial; + margin-bottom: 0px; } + +.git-view-body .file-view .opener .collapse-icon { + display: flex !important; + margin-left: -7px; } + +.git-view-body { + margin-top: 6px; } + +.git-view-body .file-view { + margin-left: 4px; } + +.git-view-body .file-view main:hover { + color: var(--text-normal); } + +.git-view-body .file-view .tools .type { + display: none !important; } + +.git-view-body .file-view .tools { + opacity: 0; + transition: opacity .1s; } + +.git-view-body .file-view main:hover > .tools { + opacity: 1; } + +.git-view-body .staged { + margin-bottom: 12px; } + +.git-view-body .opener.open { + color: var(--text-normal); } + +div[data-type="git-view"] .search-input-container { + margin-left: 0; + width: 100%; } + +.git-view-body .opener .collapse-icon { + display: none !important; } + +.git-view-body main { + background-color: var(--background-primary) !important; + width: initial !important; } + +.git-view-body .file-view > main:not(.topLevel) { + margin-left: 7px; } + +div[data-type="git-view"] .commit-msg { + min-height: 2.5em !important; + height: 2.5em !important; + padding: 6.5px 8px !important; } + +div[data-type="git-view"] .search-input-clear-button { + bottom: 5.5px; } + +/* Hider plugin */ +/* Frameless mode */ +body.hider-frameless:not(.is-mobile) .workspace-split.mod-left-split > .workspace-tabs { + padding-top: var(--top-left-padding-y); + transition: padding-top 0.2s linear; } + +/* Include support for Electron Window Tweaker */ +body.mod-macos.hider-frameless:not(.is-fullscreen):not(.is-mobile) .workspace-split.mod-left-split > .workspace-tabs:nth-child(3) { + padding-top: calc(var(--top-left-padding-y) + var(--ewt-traffic-light-y)); + transition: padding-top 0.2s linear; } + +body.mod-macos.hider-frameless:not(.hider-ribbon):not(.is-fullscreen):not(.is-mobile) .workspace-ribbon .side-dock-actions { + padding-top: calc(var(--top-left-padding-y) + var(--ewt-traffic-light-y)); } + +.hider-frameless:not(.is-mobile) .workspace-split.mod-right-split > .workspace-tabs, +.hider-frameless:not(.is-mobile) .workspace-split.mod-root .view-header { + padding-top: 0px; } + +.hider-frameless:not(.is-mobile) .workspace-split.mod-right-split > .workspace-tabs ~ .workspace-tabs, +.hider-frameless:not(.is-mobile) .workspace-split.mod-left-split > .workspace-tabs ~ .workspace-tabs { + padding-top: 0px; } + +.hider-frameless.is-fullscreen:not(.is-mobile) .workspace-split.mod-left-split > .workspace-tabs, +.hider-frameless.is-fullscreen:not(.is-mobile) .workspace-split.mod-root .view-header { + padding-top: 0px; } + +/* Adjustments to title bar for traffic light icons */ +:root { + --traffic-x-space:0px; } + +/* Frameless + no ribbon */ +.mod-macos.hider-ribbon.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-left-split.is-collapsed + .mod-root .workspace-leaf:first-of-type { + --traffic-x-space:64px; } + +/* Frameless + popout */ +.mod-macos.is-popout-window.hider-ribbon.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) .mod-root .workspace-leaf:first-of-type { + --traffic-x-space:64px; } + +/* Frameless */ +.mod-macos.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-left-split.is-collapsed + .mod-root .workspace-leaf:first-of-type { + --traffic-x-space:22px; } + +/* Remove ribbon border on Mac when frameless */ +.mod-macos.hider-frameless .workspace-ribbon { + border: none; } + +/* --------------- */ +/* App ribbon moved to the bottom edge */ +.hider-ribbon:not(.is-mobile) .workspace-ribbon-collapse-btn { + display: none; } + +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-right { + pointer-events: none; } + +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-left { + position: absolute; + border-right: 0px; + margin: 0; + height: var(--header-height); + overflow: visible; + flex-basis: 0; + bottom: 0; + top: auto; + display: flex !important; + flex-direction: row; + z-index: 17; + opacity: 0; + transition: opacity 0.25s ease-in-out; + filter: drop-shadow(2px 10px 30px rgba(0, 0, 0, 0.2)); } + +.hider-ribbon:not(.is-mobile) .side-dock-actions, +.hider-ribbon:not(.is-mobile) .side-dock-settings { + display: flex; + border-top: var(--border-width) solid var(--background-modifier-border); + background: var(--background-secondary); + margin: 0; + position: relative; } + +.hider-ribbon:not(.is-mobile) .side-dock-actions { + padding-left: 5px; } + +.hider-ribbon:not(.is-mobile) .side-dock-settings { + border-right: var(--border-width) solid var(--background-modifier-border); + border-top-right-radius: 5px; + padding-right: 10px; } + +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-left .side-dock-ribbon-action { + display: flex; + padding: 4px; + margin: 6px 0px 5px 7px; } + +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-left:hover { + opacity: 1; + transition: opacity 0.25s ease-in-out; } + +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-left .workspace-ribbon-collapse-btn { + opacity: 0; } + +.hider-ribbon:not(.is-mobile) .workspace-split.mod-left-split { + margin: 0; } + +.hider-ribbon:not(.is-mobile) .workspace-leaf-content .item-list { + padding-bottom: 40px; } + +.hider-ribbon .workspace-ribbon { + padding: 0; } + +/* Hover Editor */ +.popover.hover-editor { + --folding-offset:10px; } + +.theme-light, +.theme-dark { + --he-title-bar-inactive-bg:var(--background-secondary); + --he-title-bar-inactive-pinned-bg:var(--background-secondary); + --he-title-bar-active-pinned-bg:var(--background-secondary); + --he-title-bar-active-bg:var(--background-secondary); + --he-title-bar-inactive-fg:var(--text-muted); + --he-title-bar-active-fg:var(--text-normal); + --he-title-bar-font-size:14px; } + +.theme-light { + --popover-shadow: + 0px 2.7px 3.1px rgba(0, 0, 0, 0.032), + 0px 5.9px 8.7px rgba(0, 0, 0, 0.052), + 0px 10.4px 18.1px rgba(0, 0, 0, 0.071), + 0px 20px 40px rgba(0, 0, 0, 0.11) ; } + +.theme-dark { + --popover-shadow: + 0px 2.7px 3.1px rgba(0, 0, 0, 0.081), + 0px 5.9px 8.7px rgba(0, 0, 0, 0.131), + 0px 10.4px 18.1px rgba(0, 0, 0, 0.18), + 0px 20px 40px rgba(0, 0, 0, 0.28) ; } + +.popover.hover-editor:not(.snap-to-viewport) { + --max-width:92%; } + .popover.hover-editor:not(.snap-to-viewport) .markdown-preview-view, + .popover.hover-editor:not(.snap-to-viewport) .markdown-source-view .cm-content { + font-size: 90%; } + +body .popover.hover-editor:not(.is-loaded) { + box-shadow: var(--popover-shadow); } + body .popover.hover-editor:not(.is-loaded) .markdown-preview-view { + padding: 15px 0 0 0; } + body .popover.hover-editor:not(.is-loaded) .view-content { + height: 100%; + background-color: var(--background-primary); } + body .popover.hover-editor:not(.is-loaded) .view-actions { + height: auto; } + body .popover.hover-editor:not(.is-loaded) .popover-content { + border: 1px solid var(--background-modifier-border-hover); } + body .popover.hover-editor:not(.is-loaded) .popover-titlebar { + padding: 0 4px; } + body .popover.hover-editor:not(.is-loaded) .popover-titlebar .popover-title { + padding-left: 4px; + letter-spacing: -.02em; + font-weight: var(--title-weight); } + body .popover.hover-editor:not(.is-loaded) .markdown-embed { + height: auto; + font-size: unset; + line-height: unset; } + body .popover.hover-editor:not(.is-loaded) .markdown-embed .markdown-preview-view { + padding: 0; } + body .popover.hover-editor:not(.is-loaded).show-navbar .popover-titlebar { + border-bottom: var(--border-width) solid var(--background-modifier-border); } + body .popover.hover-editor:not(.is-loaded) .popover-action, + body .popover.hover-editor:not(.is-loaded) .popover-header-icon { + cursor: var(--cursor); + margin: 4px 0; + padding: 4px 3px; + border-radius: var(--radius-m); + color: var(--icon-color); } + body .popover.hover-editor:not(.is-loaded) .popover-action.mod-pin-popover, + body .popover.hover-editor:not(.is-loaded) .popover-header-icon.mod-pin-popover { + padding: 4px 2px; } + body .popover.hover-editor:not(.is-loaded) .popover-action svg, + body .popover.hover-editor:not(.is-loaded) .popover-header-icon svg { + opacity: var(--icon-muted); } + body .popover.hover-editor:not(.is-loaded) .popover-action:hover, + body .popover.hover-editor:not(.is-loaded) .popover-header-icon:hover { + background-color: var(--background-tertiary); + color: var(--icon-color-hover); } + body .popover.hover-editor:not(.is-loaded) .popover-action:hover svg, + body .popover.hover-editor:not(.is-loaded) .popover-header-icon:hover svg { + opacity: 1; + transition: opacity 0.1s ease-in-out; } + body .popover.hover-editor:not(.is-loaded) .popover-action.is-active, + body .popover.hover-editor:not(.is-loaded) .popover-header-icon.is-active { + color: var(--icon-color); } + +/* Kanban plugin */ +body .kanban-plugin__markdown-preview-view { + font-family: var(----text); } + +body .kanban-plugin { + --interactive-accent:var(--text-selection); + --interactive-accent-hover:var(--background-tertiary); + --text-on-accent:var(--text-normal); + background-color: var(--background-primary); } + +body .kanban-plugin__board > div { + margin: 0 auto; } + +body .kanban-plugin__checkbox-label { + font-size: var(--font-adaptive-small); + color: var(--text-muted); } + +body .kanban-plugin__item-markdown ul { + margin: 0; } + +body .kanban-plugin__item-content-wrapper { + box-shadow: none; } + +body .kanban-plugin__grow-wrap > textarea, +body .kanban-plugin__grow-wrap::after { + padding: 0; + border: 0; + border-radius: 0; } + +body:not(.is-mobile) .kanban-plugin__grow-wrap > textarea:focus { + box-shadow: none; } + +body .kanban-plugin__markdown-preview-view, +body .kanban-plugin__grow-wrap > textarea, +body .kanban-plugin__grow-wrap::after, +body .kanban-plugin__item-title p { + font-size: calc(var(--font-adaptive-normal) - 2px); + line-height: 1.3; } + +.kanban-plugin__item-input-actions button, +.kanban-plugin__lane-input-actions button { + font-size: var(--font-adaptive-small); } + +body .kanban-plugin__item { + background-color: var(--background-primary); } + +.kanban-plugin__item-title-wrapper { + align-items: center; } + +body .kanban-plugin__lane-form-wrapper { + border: 1px solid var(--background-modifier-border); } + +body .kanban-plugin__lane-header-wrapper { + border-bottom: 0; } + +body .kanban-plugin__lane-title p, +body .kanban-plugin__lane-header-wrapper .kanban-plugin__grow-wrap > textarea, +body .kanban-plugin__lane-input-wrapper .kanban-plugin__grow-wrap > textarea { + background: transparent; + color: var(--text-normal); + font-size: calc(var(--font-adaptive-normal) - 2px); + font-weight: 500; } + +body .kanban-plugin__item-input-wrapper .kanban-plugin__grow-wrap > textarea { + padding: 0; + border-radius: 0; } + +body .kanban-plugin__item-form .kanban-plugin__grow-wrap { + padding: 6px 8px; + border-radius: 6px; + border: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); } + +body .kanban-plugin__item-input-wrapper .kanban-plugin__grow-wrap > textarea::placeholder { + color: var(--text-faint); } + +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled, +body .kanban-plugin__item .kanban-plugin__item-edit-archive-button, +body .kanban-plugin__item button.kanban-plugin__item-edit-button, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button, +.kanban-plugin__item-settings-actions > button, +.kanban-plugin__lane-action-wrapper > button { + background: transparent; + transition: color 0.1s ease-in-out; } + +body .kanban-plugin__item .kanban-plugin__item-edit-archive-button:hover, +body .kanban-plugin__item button.kanban-plugin__item-edit-button.is-enabled, +body .kanban-plugin__item button.kanban-plugin__item-edit-button:hover, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button:hover { + color: var(--text-normal); + transition: color 0.1s ease-in-out; + background: transparent; } + +body .kanban-plugin__new-lane-button-wrapper { + position: fixed; + bottom: 30px; } + +body .kanban-plugin__lane-items > .kanban-plugin__placeholder:only-child { + border: 1px dashed var(--background-modifier-border); + height: 2em; } + +body .kanban-plugin__item-postfix-button-wrapper { + align-self: flex-start; } + +body .kanban-plugin__item button.kanban-plugin__item-prefix-button.is-enabled, +body .kanban-plugin__item button.kanban-plugin__item-postfix-button.is-enabled, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled { + color: var(--text-muted); } + +body .kanban-plugin button { + box-shadow: none; + cursor: var(--cursor); } + +body .kanban-plugin__item button.kanban-plugin__item-prefix-button:hover, +body .kanban-plugin__item button.kanban-plugin__item-postfix-button:hover, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button:hover { + background-color: var(--background-tertiary); } + +body:not(.minimal-icons-off) .kanban-plugin svg.cross { + height: 14px; + width: 14px; } + +body .kanban-plugin__item-button-wrapper > button { + font-size: var(--font-adaptive-small); + color: var(--text-muted); + font-weight: 400; + background: transparent; + height: 32px; } + +body .kanban-plugin__item-button-wrapper > button:hover { + color: var(--text-normal); + background: var(--background-tertiary); } + +body .kanban-plugin__item-button-wrapper > button:focus { + box-shadow: none; } + +body .kanban-plugin__item-button-wrapper { + padding: 1px 6px 5px; + border-top: none; } + +body .kanban-plugin__lane-setting-wrapper > div:last-child { + border: none; + margin: 0; } + +body .kanban-plugin.something-is-dragging { + cursor: grabbing; + cursor: -webkit-grabbing; } + +body .kanban-plugin__item.is-dragging { + box-shadow: 0 5px 30px rgba(0, 0, 0, 0.15), 0 0 0 2px var(--text-selection); } + +body .kanban-plugin__lane.is-dragging { + box-shadow: 0 5px 30px rgba(0, 0, 0, 0.15); + border: 1px solid var(--background-modifier-border); } + +body .kanban-plugin__lane { + background: transparent; + padding: 0; + border: var(--border-width) solid transparent; } + +body { + --kanban-border:var(--border-width); } + +body.theme-dark, +body.minimal-dark-black.theme-dark, +body.minimal-dark-tonal.theme-dark, +body.minimal-light-white.theme-light, +body.minimal-light-tonal.theme-light { + --kanban-border:0px; } + +body .kanban-plugin__lane-items { + border: var(--kanban-border) solid var(--background-modifier-border); + border-bottom: none; + padding: 0 4px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + margin: 0; + background-color: var(--background-secondary); } + +body .kanban-plugin__item-input-wrapper { + border: 0; + padding-top: 1px; + flex-grow: 1; } + +body .kanban-plugin__item-form, +body .kanban-plugin__item-button-wrapper { + background-color: var(--background-secondary); + border: var(--kanban-border) solid var(--background-modifier-border); + border-top: none; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; } + +body .kanban-plugin__item-form { + padding: 0 4px 5px; } + +body .kanban-plugin__markdown-preview-view ol.contains-task-list .contains-task-list, +body .kanban-plugin__markdown-preview-view ul.contains-task-list .contains-task-list, +body .kanban-plugin__markdown-preview-view ul, .kanban-plugin__markdown-preview-view ol { + padding-inline-start: 1.8em !important; } + +@media (max-width: 400pt) { + .kanban-plugin__board { + flex-direction: column !important; } + + .kanban-plugin__lane { + width: 100% !important; + margin-bottom: 1rem !important; } } +/* Lapel */ +body .cm-heading-marker { + cursor: var(--cursor); + padding-left: 10px; } + +/* Leaflet plugin */ +.theme-light { + --leaflet-buttons:var(--bg1); + --leaflet-borders:rgba(0,0,0,0.1); } + +.theme-dark { + --leaflet-buttons:var(--bg2); + --leaflet-borders:rgba(255,255,255,0.1); } + +.leaflet-top { + transition: top 0.1s linear; } + +.mod-macos.minimal-focus-mode .mod-root .map-100 .markdown-preview-sizer.markdown-preview-section .el-lang-leaflet:nth-child(3) .leaflet-top { + top: calc(18px + var(--ewt-traffic-light-y)); + transition: top 0.1s linear; } + +body .leaflet-container { + background-color: var(--background-secondary); + font-family: var(--font-interface); } + +.map-100 .markdown-preview-sizer.markdown-preview-section .el-lang-leaflet:nth-child(3) { + margin-top: -16px; } + +.leaflet-control-attribution { + display: none; } + +.leaflet-popup-content { + margin: 10px; } + +.block-language-leaflet { + border-radius: var(--radius-m); + overflow: hidden; + border: var(--border-width) solid var(--background-modifier-border); } + +.map-wide .block-language-leaflet { + border-radius: var(--radius-l); } + +.map-max .block-language-leaflet { + border-radius: var(--radius-xl); } + +.workspace-leaf-content[data-type="obsidian-leaflet-map-view"] .block-language-leaflet { + border-radius: 0; + border: none; } + +.map-100 .block-language-leaflet { + border-radius: 0px; + border-left: none; + border-right: none; } + +/* Checkbox */ +.block-language-leaflet .leaflet-control-expandable-list .input-container .input-item > input { + appearance: none; } + +/* Buttons */ +body .block-language-leaflet .leaflet-bar.disabled > a { + background-color: transparent; + opacity: 0.3; } + +body .leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; } + +body .leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; } + +body .leaflet-control-layers-toggle { + border-radius: 4px; } + +body .leaflet-control-layers-toggle, +body .leaflet-control-layers-expanded, +body .block-language-leaflet .leaflet-control-has-actions .control-actions.expanded, +body .block-language-leaflet .leaflet-control-expandable, +body .block-language-leaflet .leaflet-distance-control, +body .leaflet-bar, +body .leaflet-bar a { + background-color: var(--leaflet-buttons); + color: var(--text-muted); + border: none; + user-select: none; } + +body .leaflet-bar a.leaflet-disabled, +body .leaflet-bar a.leaflet-disabled:hover { + background-color: var(--leaflet-buttons); + color: var(--text-faint); + opacity: 0.6; + cursor: not-allowed; } + +body .leaflet-control a { + cursor: var(--cursor); + color: var(--text-normal); } + +body .leaflet-bar a:hover { + background-color: var(--background-tertiary); + color: var(--text-normal); + border: none; } + +body .leaflet-touch .leaflet-control-layers { + background-color: var(--leaflet-buttons); } + +body .leaflet-touch .leaflet-control-layers, +body .leaflet-touch .leaflet-bar { + border-radius: 5px; + box-shadow: 2px 0 8px 0px rgba(0, 0, 0, 0.1); + border: 1px solid var(--ui1); } + +body .block-language-leaflet .leaflet-control-has-actions .control-actions { + box-shadow: 0; + border: 1px solid var(--ui1); } + +body .leaflet-control-expandable-list .leaflet-bar { + box-shadow: none; + border-radius: 0; } + +body .block-language-leaflet .leaflet-distance-control { + padding: 4px 10px; + height: auto; + cursor: var(--cursor) !important; } + +body .block-language-leaflet .leaflet-marker-link-popup > .leaflet-popup-content-wrapper > * { + font-size: var(--font-adaptive-small); + font-family: var(--font-interface); } + +body .block-language-leaflet .leaflet-marker-link-popup > .leaflet-popup-content-wrapper { + padding: 4px 10px !important; } + +.leaflet-marker-icon svg path { + stroke: var(--background-primary); + stroke-width: 18px; } + +/* Map View plugin */ +.map-view-marker-name { + font-weight: 400; } + +.workspace-leaf-content[data-type="map"] .graph-controls { + background-color: var(--background-primary); } + +/* Full bleed */ +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-split.mod-root .workspace-leaf-content[data-type='map'] .view-header { + position: fixed; + background: transparent !important; + width: 100%; + z-index: 99; } + +body:not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='map'] .view-header-title { + display: none; } + +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='map'] .view-actions { + background: transparent; } + +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='map'] .view-content { + height: 100%; } + +body:not(.is-mobile):not(.plugin-sliding-panes-rotate-header) .workspace-leaf-content[data-type='map'] .leaflet-top.leaflet-right { + top: var(--header-height); } + +/* Metatable */ +.obsidian-metatable { + --metatable-font-size:calc(var(--font-adaptive-normal) - 2px); + --metatable-font-family: var(--font-interface); + --metatable-background:transparent; + --metatable-foreground: var(--text-faint); + --metatable-key-background:transparent; + --metatable-key-border-width:0; + --metatable-key-border-color:transparent; + --metatable-value-background:transparent; + padding-bottom: 0.5rem; } + .obsidian-metatable::part(value), .obsidian-metatable::part(key) { + border-bottom: 0px solid var(--background-modifier-border); + padding: 0.1rem 0; + text-overflow: ellipsis; + overflow: hidden; } + .obsidian-metatable::part(key) { + font-weight: 400; + color: var(--tx3); + font-size: calc(var(--font-adaptive-normal) - 2px); } + .obsidian-metatable::part(value) { + font-size: calc(var(--font-adaptive-normal) - 2px); + color: var(--tx1); } + +/* NL tables */ +body .NLT__header-menu-header-container { + font-size: 85%; } + +body .NLT__button { + background: transparent; + box-shadow: none; + color: var(--text-muted); } + body .NLT__button:hover, body .NLT__button:active, body .NLT__button:focus { + background: transparent; + color: var(--text-normal); + box-shadow: none; } + +.NLT__app .NLT__button { + background: transparent; + border: 1px solid var(--background-modifier-border); + box-shadow: 0 0.5px 1px 0 var(--btn-shadow-color); + color: var(--text-muted); + padding: 2px 8px; } + .NLT__app .NLT__button:hover, .NLT__app .NLT__button:active, .NLT__app .NLT__button:focus { + background: transparent; + border-color: var(--background-modifier-border-hover); + color: var(--text-normal); + box-shadow: 0 0.5px 1px 0 var(--btn-shadow-color); } + +/* +.NLT__header-content { + position:relative; +} +th.NLT__selectable .NLT__header-content:after { + content:" "; + width:16px; + height:16px; + position:absolute; + z-index:999999; + top:50%; + transform:translateY(-50%); + display:inline-block; + -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor' %3E%3Cpath fill-rule='evenodd' d='M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z' clip-rule='evenodd' /%3E%3C/svg%3E"); + -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath d='M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z' /%3E%3C/svg%3E"); + -webkit-mask-size:16px 16px; + margin:0 0 0 6px; + background-color:var(--text-faint); +}*/ +.NLT__td:nth-last-child(2), +.NLT__th:nth-last-child(2) { + border-right: 0; } + +.NLT__app { + /* Remove Sortable plugin background icons */ } + .NLT__app .NLT__td:last-child, + .NLT__app .NLT__th:last-child { + padding-right: 0; } + .NLT__app .NLT__th { + background-image: none !important; } + .NLT__app th.NLT__selectable:hover { + background-color: transparent; + cursor: var(--cursor); } + +.NLT__menu .NLT__menu-container { + background-color: var(--background-secondary); } +.NLT__menu .NLT__header-menu-item { + font-size: var(--font-adaptive-small); } +.NLT__menu .NLT__header-menu { + padding: 6px 4px; } +.NLT__menu .NLT__drag-menu { + font-size: var(--font-adaptive-small); + padding: 6px 4px; } +.NLT__menu svg { + color: var(--text-faint); + margin-right: 6px; } +.NLT__menu .NLT__selected, +.NLT__menu .NLT__selectable:hover { + background: transparent; } +.NLT__menu .NLT__selected > .NLT__selectable { + background-color: var(--background-tertiary); } +.NLT__menu .NLT__selectable { + cursor: var(--cursor); } +.NLT__menu div.NLT__selectable { + min-width: 110px; + border-radius: var(--radius-m); + padding: 3px 8px 3px 4px; + margin: 1px 2px 1px; + cursor: var(--cursor); + height: auto; + line-height: 20px; } + .NLT__menu div.NLT__selectable:hover { + background-color: var(--background-tertiary); } +.NLT__menu .NLT__textarea { + font-size: var(--table-font-size); } + +.NLT__tfoot tr:hover td { + background-color: transparent; } + +/* Outliner plugin (pre Live Preview) */ +body.outliner-plugin-bls .CodeMirror-line .cm-hmd-list-indent::before { + background-image: linear-gradient(to right, var(--background-modifier-border) 1px, transparent 1px); + background-position-x: 2px; + background-size: var(--font-text-size) 1px; } + +body.outliner-plugin-bls .cm-s-obsidian span.cm-formatting-list { + letter-spacing: unset; } + +body.outliner-plugin-bls .cm-s-obsidian .HyperMD-list-line { + padding-top: 0; } + +body.outliner-plugin-bls .cm-s-obsidian span.cm-formatting-list-ul:before { + color: var(--text-faint); + margin-left: -3px; + margin-top: -5px; } + +body.outliner-plugin-bls.minimal-rel-edit .cm-hmd-list-indent > .cm-tab:after { + content: ""; + border-right: none; } + +body.outliner-plugin-bls .cm-s-obsidian span.cm-formatting-list-ul { + color: transparent !important; } + +body.outliner-plugin-bls .cm-s-obsidian:not(.is-live-preview) .cm-formatting-list-ul:before, +body.outliner-plugin-bls .cm-s-obsidian.is-live-preview .list-bullet:before { + color: var(--text-faint); } + +/* QuickAdd plugin */ +.modal .quickAddPrompt > h1, +.modal .quickAddYesNoPrompt h1 { + margin-top: 0; + text-align: left !important; + font-size: var(--h1); + font-weight: 600; } + +.modal .quickAddYesNoPrompt p { + text-align: left !important; } + +.modal .quickAddYesNoPrompt button { + font-size: var(--font-settings-small); } + +.modal .yesNoPromptButtonContainer { + font-size: var(--font-settings-small); + justify-content: flex-end; } + +.quickAddModal .modal-content { + padding: 20px 2px 5px; } + +div#quick-explorer { + display: flex; } + div#quick-explorer span.explorable { + align-items: center; + color: var(--text-muted); + display: flex; + font-size: var(--font-adaptive-smaller); + line-height: 16px; } + div#quick-explorer span.explorable:last-of-type { + font-size: var(--font-adaptive-smaller); } + div#quick-explorer span.explorable.selected, div#quick-explorer span.explorable:hover { + background-color: unset !important; } + div#quick-explorer span.explorable.selected .explorable-name, div#quick-explorer span.explorable:hover .explorable-name { + color: var(--text-normal); } + div#quick-explorer span.explorable.selected .explorable-separator, div#quick-explorer span.explorable:hover .explorable-separator { + color: var(--text-normal); } + div#quick-explorer .explorable-name { + padding: 0 4px; + border-radius: 4px; } + div#quick-explorer .explorable-separator::before { + content: "\00a0›" !important; + font-size: 1.3em; + font-weight: 400; + margin: 0px; } + +body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label):hover, body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label).selected { + background-color: var(--background-tertiary); + color: var(--text-normal); } + body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label):hover .menu-item-icon, body:not(.colorful-active) .qe-popup-menu .menu-item:not(.is-disabled):not(.is-label).selected .menu-item-icon { + color: var(--text-normal); } + +/* Obsidian Tabs plugin */ +.workspace-leaf-content[data-type="recent-files"] .view-content { + padding-top: 10px; } + +/* Reminder Plugin */ +.mod-root .workspace-leaf-content[data-type="reminder-list"] main { + max-width: var(--max-width); + margin: 0 auto; + padding: 0; } + +/* Popup */ +.modal .reminder-actions .later-select { + font-size: var(--font-settings-small); + vertical-align: bottom; + margin-left: 3px; } +.modal .reminder-actions .icon { + line-height: 1; } + +/* In sidebar */ +:not(.mod-root) .workspace-leaf-content[data-type="reminder-list"] main { + margin: 0 auto; + padding: 15px; } + :not(.mod-root) .workspace-leaf-content[data-type="reminder-list"] main .group-name { + font-weight: 500; + color: var(--text-muted); + font-size: var(--font-adaptive-small); + padding-bottom: 0.5em; + border-bottom: 1px solid var(--background-modifier-border); } + :not(.mod-root) .workspace-leaf-content[data-type="reminder-list"] main .reminder-group .reminder-list-item { + line-height: 1.3; + font-size: var(--font-adaptive-small); } + :not(.mod-root) .workspace-leaf-content[data-type="reminder-list"] main .reminder-group .no-reminders { + color: var(--text-faint); } + :not(.mod-root) .workspace-leaf-content[data-type="reminder-list"] main .reminder-group .reminder-time { + font-family: var(--font-text); + font-size: var(--font-adaptive-small); } + :not(.mod-root) .workspace-leaf-content[data-type="reminder-list"] main .reminder-group .reminder-file { + color: var(--text-faint); } + +/* Calendar picker */ +body .modal .dtchooser { + background-color: transparent; } + body .modal .dtchooser .reminder-calendar .year-month { + font-weight: 400; + font-size: var(--font-adaptive-normal); + padding-bottom: 10px; } + body .modal .dtchooser .reminder-calendar .year-month .month, + body .modal .dtchooser .reminder-calendar .year-month .year { + color: var(--text-normal); } + body .modal .dtchooser .reminder-calendar .year-month .month-nav:first-child { + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + body .modal .dtchooser .reminder-calendar .year-month .month-nav:last-child { + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E"); } + body .modal .dtchooser .reminder-calendar .year-month .month-nav { + -webkit-mask-size: 20px 20px; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: 50% 50%; + color: var(--text-faint); + cursor: var(--cursor); + border-radius: var(--radius-m); + padding: 0; + width: 30px; + display: inline-block; } + body .modal .dtchooser .reminder-calendar .year-month .month-nav:hover { + color: var(--text-muted); } + body .modal .dtchooser .reminder-calendar th { + padding: 0.5em 0; + font-size: var(--font-adaptive-smallest); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.1em; } + body .modal .dtchooser .reminder-calendar .calendar-date { + transition: background-color 0.1s ease-in; + padding: 0.3em 0; + border-radius: var(--radius-m); } + body .modal .dtchooser .reminder-calendar .calendar-date:hover, body .modal .dtchooser .reminder-calendar .calendar-date.is-selected { + transition: background-color 0.1s ease-in; + background-color: var(--background-tertiary) !important; } + body .modal .dtchooser .reminder-calendar .calendar-date.is-selected { + font-weight: var(--bold-weight); + color: var(--text-accent) !important; } + +/* Sliding Panes aka Andy Mode plugin */ +body.plugin-sliding-panes-rotate-header { + --header-width:40px; } + body.plugin-sliding-panes-rotate-header .view-header-title:before { + display: none; } + +body.plugin-sliding-panes .workspace-split.mod-root { + background-color: var(--background-primary); } +body.plugin-sliding-panes .mod-horizontal .workspace-leaf { + box-shadow: none !important; } +body.plugin-sliding-panes:not(.is-fullscreen) .workspace-split.is-collapsed ~ .workspace-split.mod-root .view-header { + transition: padding 0.1s ease; } +body.plugin-sliding-panes .view-header-title:before { + background: none; } +body.plugin-sliding-panes .view-header { + background: none; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf.mod-active > .workspace-leaf-content > .view-header { + border: none; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header { + border: none; + text-orientation: sideways; } + body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header .view-header-icon { + padding: 4px 1px; + margin: 5px 0 0 0; + left: 0; + width: 26px; } + body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header .view-actions { + padding-bottom: 33px; + margin-left: 0; + height: auto; } + body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header .view-action { + margin: 3px 0; + padding: 4px 1px; + width: 26px; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header > .view-header-title-container:before, +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .app-container .workspace > .mod-root > .workspace-leaf.mod-active > .workspace-leaf-content > .view-header > .view-header-title-container:before { + background: none !important; } +.workspace > .mod-root .view-header-title-container +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header.plugin-sliding-panes-header-alt .workspace > .mod-root .view-header-title { + margin-top: 0; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root .view-header-title-container { + margin-left: 0; + padding-top: 0; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .view-header-title-container { + position: static; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .app-container .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header > div { + margin-left: 0; + margin-right: 0; + bottom: 0; } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header.show-grabber .view-header-icon { + opacity: var(--icon-muted); } +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .view-header-icon:hover { + opacity: 1; } + +body:not(.plugin-sliding-panes-header-alt).plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header > .view-header-title-container > .view-header-title, +body:not(.plugin-sliding-panes-header-alt).plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-split > .workspace-leaf-content > .view-header > .view-header-title-container > .view-header-title { + padding-top: 5px; } + +body.plugin-sliding-panes-stacking .workspace > .mod-root > .workspace-leaf, +body.plugin-sliding-panes .workspace-split.mod-vertical > .workspace-leaf { + box-shadow: 0 0 0 1px var(--background-modifier-border), 1px 0px 15px 0px var(--shadow-color) !important; } + +body.is-mobile.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header .view-header-icon { + height: 30px; } + +/* Space for the hover ribbon in the bottom left */ +body.hider-ribbon.plugin-sliding-panes.plugin-sliding-panes-rotate-header .workspace > .mod-root > .workspace-leaf > .workspace-leaf-content > .view-header .view-actions { + padding-bottom: 50px; } + +body.plugin-sliding-panes.is-fullscreen .view-header-icon { + padding-top: 8px; } + +body.plugin-sliding-panes .mod-root .graph-controls { + top: 20px; + left: 30px; } + +/* Sortable plugin */ +body .markdown-preview-view th, +body .table-view-table > thead > tr > th, +body .markdown-source-view.mod-cm6 .dataview.table-view-table thead.table-view-thead tr th { + cursor: var(--cursor); + background-image: none; } + +/* Live preview */ +.markdown-source-view.mod-cm6 th { + background-repeat: no-repeat; + background-position: right; } + +/* Style Settings preferences */ +.style-settings-container[data-level="2"] { + background: var(--background-secondary); + border: 1px solid var(--ui1); + border-radius: 5px; + padding: 10px 20px; + margin: 2px 0 2px -20px; } + +.workspace-leaf-content[data-type="style-settings"] .view-content { + padding: 0 0 20px var(--folding-offset); } +.workspace-leaf-content[data-type="style-settings"] .view-content > div { + width: var(--line-width-adaptive); + max-width: var(--max-width); + margin: 0 auto; } +.workspace-leaf-content[data-type="style-settings"] .style-settings-heading[data-level="0"] .setting-item-name { + padding-left: 17px; } +.workspace-leaf-content[data-type="style-settings"] .setting-item { + max-width: 100%; + margin: 0 auto; } +.workspace-leaf-content[data-type="style-settings"] .setting-item-name { + position: relative; } +.workspace-leaf-content[data-type="style-settings"] .style-settings-collapse-indicator { + position: absolute; + left: 0; } + +.setting-item-heading.style-settings-heading, +.style-settings-container .style-settings-heading { + cursor: var(--cursor); } + +.modal.mod-settings .setting-item .pickr button.pcr-button { + box-shadow: none; + border-radius: 40px; + height: 24px; + width: 24px; } + +.setting-item .pickr .pcr-button:after, +.setting-item .pickr .pcr-button:before { + border-radius: 40px; + box-shadow: none; + border: none; } + +.setting-item.setting-item-heading.style-settings-heading.is-collapsed { + border-bottom: 1px solid var(--background-modifier-border); } + +.setting-item.setting-item-heading.style-settings-heading { + border: 0; + padding: 10px 0 5px; + margin-bottom: 0; } + +.mod-root .workspace-leaf-content[data-type="style-settings"] .style-settings-container .setting-item:not(.setting-item-heading) { + flex-direction: row; + align-items: center; + padding: 0.5em 0; } + +.workspace-split:not(.mod-root) .workspace-leaf-content[data-type="style-settings"] .setting-item-name { + font-size: var(--font-small); } + +.setting-item .style-settings-import, +.setting-item .style-settings-export { + text-decoration: none; + font-size: var(--font-settings-small); + font-weight: 500; + color: var(--text-muted); + margin: 0; + padding: 2px 8px; + border-radius: 5px; + cursor: var(--cursor); } + +.style-settings-import:hover, +.style-settings-export:hover { + background-color: var(--background-tertiary); + color: var(--text-normal); + cursor: var(--cursor); } + +.themed-color-wrapper > div + div { + margin-top: 0; + margin-left: 6px; } + +.theme-light .themed-color-wrapper > .theme-light { + background-color: transparent; } + +.theme-light .themed-color-wrapper > .theme-dark { + background-color: rgba(0, 0, 0, 0.8); } + +.theme-dark .themed-color-wrapper > .theme-dark { + background-color: transparent; } + +/* Obsidian Tabs plugin */ +body.plugin-tabs .mod-root.workspace-split.mod-vertical > div.workspace-leaf.stayopen .view-header, +body.plugin-tabs .mod-root.workspace-split.mod-vertical > .workspace-split.mod-vertical > div.workspace-leaf .view-header, .plugin-tabs .mod-root.workspace-split.mod-vertical > div.workspace-leaf.mod-active .view-header { + border: none; } + +/* Todoist Plugin Styles */ +body .todoist-query-title { + display: inline; + font-size: var(--h4); + font-variant: var(--h4-variant); + letter-spacing: 0.02em; + color: var(--h4-color); + font-weight: var(--h4-weight); + font-style: var(--h4-style); } + +body .is-live-preview .block-language-todoist { + padding-left: 0; } + +ul.todoist-task-list > li.task-list-item .task-list-item-checkbox { + margin: 0; } + +body .todoist-refresh-button { + display: inline; + float: right; + background: transparent; + padding: 5px 6px 0; + margin-right: 0px; } + +body .is-live-preview .todoist-refresh-button { + margin-right: 30px; } + +body .todoist-refresh-button:hover { + box-shadow: none; + background-color: var(--background-tertiary); } + +.todoist-refresh-button svg { + width: 15px; + height: 15px; + opacity: var(--icon-muted); } + +ul.todoist-task-list { + margin-left: -0.25em; } + +.is-live-preview ul.todoist-task-list { + padding-left: 0; + margin-left: 0.5em; + margin-block-start: 0; + margin-block-end: 0; } + +.contains-task-list.todoist-task-list .task-metadata { + font-size: var(--font-adaptive-small); + display: flex; + color: var(--text-muted); + justify-content: space-between; + margin-left: 0.1em; + margin-bottom: 0.25rem; } + +.is-live-preview .contains-task-list.todoist-task-list .task-metadata { + padding-left: calc(var(--checkbox-size) + 0.6em); } + +.todoist-task-list .task-date.task-overdue { + color: var(--orange); } + +body .todoist-p1 > input[type="checkbox"] { + border: 1px solid var(--red); } + +body .todoist-p1 > input[type="checkbox"]:hover { + opacity: 0.8; } + +body .todoist-p2 > input[type="checkbox"] { + border: 1px solid var(--yellow); } + +body .todoist-p2 > input[type="checkbox"]:hover { + opacity: 0.8; } + +body .todoist-p3 > input[type="checkbox"] { + border: 1px solid var(--blue); } + +body .todoist-p3 > input[type="checkbox"]:hover { + opacity: 0.8; } + +/* Tracker */ +body.theme-light { + --color-axis-label:var(--tx1); + --color-tick-label:var(--tx2); + --color-dot-fill:var(--ax1); + --color-line:var(--ui1); } + +.tracker-axis-label { + font-family: var(--font-interface); } + +.tracker-axis { + color: var(--ui2); } + +/* Color schemes */ +/* Atom */ +.theme-dark.minimal-atom-dark { + --red:#e16d76; + --orange:#d19a66; + --yellow:#cec167; + --green:#98c379; + --cyan:#58b6c2; + --blue:#62afef; + --purple:#c678de; + --pink:#e16d76; } + +.theme-light.minimal-atom-light { + --red:#e45749; + --orange:#b76b02; + --yellow:#c18302; + --green:#50a150; + --cyan:#0d97b3; + --blue:#62afef; + --purple:#a626a4; + --pink:#e45749; } + +.theme-light.minimal-atom-light { + --base-h:106; + --base-s:0%; + --base-l:98%; + --accent-h:209; + --accent-s:100%; + --accent-l:55%; + --bg1:#fafafa; + --bg2:#eaeaeb; + --bg3:#dbdbdc; + --ui1:#dbdbdc; + --ui2:#d8d8d9; + --tx1:#232324; + --tx2:#8e8e90; + --tx3:#a0a1a8; + --ax1:#1a92ff; + --ax3:#566de8; + --hl1:rgba(180,180,183,0.3); + --hl2:rgba(209,154,102,0.35); } + +.theme-light.minimal-atom-light.minimal-light-white { + --bg3:#eaeaeb; } + +.theme-light.minimal-atom-light.minimal-light-contrast .titlebar, +.theme-light.minimal-atom-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-atom-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-atom-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-atom-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-atom-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-atom-dark { + --base-h:220; + --base-s:12%; + --base-l:18%; + --accent-h:220; + --accent-s:86%; + --accent-l:65%; + --bg1:#282c34; + --bg2:#21252c; + --bg3:#3a3f4b; + --background-divider:#181a1f; + --tx1:#d8dae1; + --tx2:#898f9d; + --tx3:#5d6370; + --ax1:#578af2; + --ax3:#578af2; + --hl1:rgba(114,123,141,0.3); + --hl2:rgba(209,154,102,0.3); + --sp1:#fff; } + +.theme-dark.minimal-atom-dark.minimal-dark-black { + --base-d:5%; + --bg3:#282c34; + --background-divider:#282c34; } + +/* Dracula */ +.theme-dark.minimal-dracula-dark { + --red:#ff5555; + --yellow:#f1fa8c; + --green:#50fa7b; + --orange:#ffb86c; + --purple:#bd93f9; + --pink:#ff79c6; + --cyan:#8be9fd; + --blue:#6272a4; } + +.theme-light.minimal-dracula-light.minimal-light-contrast .titlebar, +.theme-light.minimal-dracula-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-dracula-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-dracula-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-dracula-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-dracula-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-dracula-dark { + --base-h:232; + --base-s:16%; + --base-l:19%; + --accent-h:265; + --accent-s:89%; + --accent-l:78%; + --bg1:#282a37; + --bg2:#21222c; + --ui2:#44475a; + --ui3:#6272a4; + --tx1:#f8f8f2; + --tx2:#949FBE; + --tx3:#6272a4; + --ax3:#ff79c6; + --hl1:rgba(134, 140, 170, 0.3); + --hl2:rgba(189, 147, 249, 0.35); } + +.theme-dark.minimal-dracula-dark.minimal-dark-black { + --ui1:#282a36; } + +/* Everforest */ +.theme-light.minimal-everforest-light { + --red:#f85552; + --orange:#f57d26; + --yellow:#dfa000; + --green:#8da101; + --purple:#df69ba; + --pink:#df69ba; + --cyan:#35a77c; + --blue:#7fbbb3; } + +.theme-dark.minimal-everforest-dark { + --red:#e67e80; + --orange:#e69875; + --yellow:#dbbc7f; + --green:#a7c080; + --purple:#d699b6; + --pink:#d699b6; + --cyan:#83c092; + --blue:#7fbbb3; } + +.theme-light.minimal-everforest-light { + --base-h:46; + --base-s:87%; + --base-l:94%; + --accent-h:81; + --accent-s:37%; + --accent-l:52%; + --bg1:#FDF7E3; + --bg2:#EEEAD5; + --bg3:rgba(206,207,182,.5); + --ui1:#dfdbc8; + --ui2:#bdc3af; + --ui3:#bdc3af; + --tx1:#5C6A72; + --tx2:#829181; + --tx3:#a6b0a0; + --ax1:#93b259; + --ax2:#738555; + --ax3:#93b259; + --hl1:rgba(198,214,152,.4); + --hl2:rgba(222,179,51,.3); } + +.theme-light.minimal-everforest-light.minimal-light-tonal { + --bg2:#EEEAD5; } + +.theme-light.minimal-everforest-light.minimal-light-white { + --bg3:#f3efda; + --ui1:#edead5; } + +.theme-light.minimal-everforest-light.minimal-light-contrast .titlebar, +.theme-light.minimal-everforest-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-everforest-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-everforest-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-everforest-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-everforest-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-everforest-dark { + --base-h:203; + --base-s:15%; + --base-l:23%; + --accent-h:81; + --accent-s:34%; + --accent-l:63%; + --bg1:#323D44; + --bg2:#2A343A; + --bg3:#414C54; + --bg3:rgba(78,91,100,0.5); + --ui1:#404c51; + --ui2:#4A555C; + --ui3:#525c62; + --tx1:#d3c6aa; + --tx2:#9da9a0; + --tx3:#7a8478; + --ax1:#A7C080; + --ax2:#c7cca3; + --ax3:#93b259; + --hl1:rgba(134,70,93,.5); + --hl2:rgba(147,185,96,.3); } + +.theme-dark.minimal-everforest-dark.minimal-dark-black { + --hl1:rgba(134,70,93,.4); + --ui1:#2b3339; } + +/* Gruvbox */ +.theme-dark.minimal-gruvbox-dark, +.theme-light.minimal-gruvbox-light { + --red:#cc241d; + --yellow:#d79921; + --green:#98971a; + --orange:#d65d0e; + --purple:#b16286; + --pink:#b16286; + --cyan:#689d6a; + --blue:#458588; } + +.theme-light.minimal-gruvbox-light { + --base-h:49; + --base-s:92%; + --base-l:89%; + --accent-h:24; + --accent-s:88%; + --accent-l:45%; + --bg1:#fcf2c7; + --bg2:#f2e6bd; + --bg3:#ebd9b3; + --ui1:#ebdbb2; + --ui2:#d5c4a1; + --ui3:#bdae93; + --tx1:#282828; + --tx2:#7c7065; + --tx3:#a89a85; + --ax1:#d65d0e; + --ax2:#af3a03; + --ax3:#d65d0d; + --hl1:rgba(192,165,125,.3); + --hl2:rgba(215,153,33,.4); } + +.theme-light.minimal-gruvbox-light.minimal-light-tonal { + --bg2:#fcf2c7; } + +.theme-light.minimal-gruvbox-light.minimal-light-white { + --bg3:#faf5d7; + --ui1:#f2e6bd; } + +.theme-light.minimal-gruvbox-light.minimal-light-contrast .titlebar, +.theme-light.minimal-gruvbox-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-gruvbox-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-gruvbox-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-gruvbox-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-gruvbox-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-gruvbox-dark { + --accent-h:24; + --accent-s:88%; + --accent-l:45%; + --bg1:#282828; + --bg2:#1e2021; + --bg3:#3d3836; + --bg3:rgba(62,57,55,0.5); + --ui1:#3c3836; + --ui2:#504945; + --ui3:#665c54; + --tx1:#fbf1c7; + --tx2:#bdae93; + --tx3:#7c6f64; + --ax1:#d65d0e; + --ax2:#fe8019; + --ax3:#d65d0e; + --hl1:rgba(173,149,139,0.3); + --hl2:rgba(215,153,33,.4); } + +.theme-dark.minimal-gruvbox-dark.minimal-dark-black { + --hl1:rgba(173,149,139,0.4); + --ui1:#282828; } + +/* macOS */ +.theme-dark.minimal-macos-dark, +.theme-light.minimal-macos-light { + --red:#ff3b31; + --yellow:#ffcc00; + --green:#2acd41; + --orange:#ff9502; + --purple:#b051de; + --pink:#ff2e55; + --cyan:#02c7be; + --blue:#027aff; } + +.theme-light.minimal-macos-light { + --base-h:106; + --base-s:0%; + --base-l:94%; + --accent-h:212; + --accent-s:100%; + --accent-l:50%; + --bg1:#fff; + --bg2:#f0f0f0; + --bg3:#d7d7d7; + --ui1:#e7e7e7; + --tx1:#454545; + --tx2:#808080; + --tx3:#b0b0b0; + --ax1:#027aff; + --ax2:#0463cc; + --ax3:#007bff; + --hl1:#b3d7ff; } + +.theme-light.minimal-macos-light.minimal-light-tonal { + --bg1:#f0f0f0; + --bg2:#f0f0f0; } + +.theme-light.minimal-macos-light.minimal-light-contrast .titlebar, +.theme-light.minimal-macos-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-macos-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-macos-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-macos-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-macos-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-macos-dark { + --base-h:106; + --base-s:0%; + --base-l:12%; + --accent-h:212; + --accent-s:100%; + --accent-l:50%; + --bg1:#1e1e1e; + --bg2:#282828; + --bg3:rgba(255,255,255,0.11); + --background-divider:#000; + --ui1:#373737; + --ui2:#515151; + --ui3:#595959; + --tx1:#dcdcdc; + --tx2:#8c8c8c; + --tx3:#686868; + --ax1:#027aff; + --ax2:#3f9bff; + --ax3:#007bff; + --hl1:rgba(98,169,252,0.5); + --sp1:#fff; } + +.theme-dark.minimal-macos-dark.minimal-dark-black { + --background-divider:#1e1e1e; } + +/* Nord */ +.theme-dark.minimal-nord-dark, +.theme-light.minimal-nord-light { + --red:#BF616A; + --yellow:#EBCB8B; + --green:#A3BE8C; + --orange:#D08770; + --purple:#B48EAD; + --pink:#B48EAD; + --cyan:#88C0D0; + --blue:#81A1C1; } + +.theme-light.minimal-nord-light { + --base-h:221; + --base-s:27%; + --base-l:94%; + --accent-h:213; + --accent-s:32%; + --accent-l:52%; + --bg1:#fff; + --bg2:#eceff4; + --bg3:rgba(157,174,206,0.25); + --ui1:#d8dee9; + --ui2:#BBCADC; + --ui3:#81a1c1; + --tx1:#2e3440; + --tx2:#7D8697; + --tx3:#ADB1B8; + --ax1:#5e81ac; + --ax2:#81a1c1; + --hl2:rgba(208, 135, 112, 0.35); } + +.theme-light.minimal-nord-light.minimal-light-contrast .titlebar, +.theme-light.minimal-nord-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-nord-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-nord-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-nord-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-nord-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-nord-dark { + --base-h:220; + --base-s:16%; + --base-l:22%; + --accent-h:213; + --accent-s:32%; + --accent-l:52%; + --bg1:#2e3440; + --bg2:#3b4252; + --bg3:rgba(135,152,190,0.15); + --ui1:#434c5e; + --ui2:#58647b; + --ui3:#5e81ac; + --tx1:#d8dee9; + --tx2:#9eafcc; + --tx3:#4c566a; + --ax3:#5e81ac; + --hl1:rgba(129,142,180,0.3); + --hl2:rgba(208, 135, 112, 0.35); } + +.theme-dark.minimal-nord-dark.minimal-dark-black { + --ui1:#2e3440; } + +/* Notion */ +.theme-light.minimal-notion-light { + --base-h:39; + --base-s:18%; + --base-d:96%; + --accent-h:197; + --accent-s:65%; + --accent-l:71%; + --bg2:#f7f6f4; + --bg3:#e8e7e4; + --ui1:#ededec; + --ui2:#dbdbda; + --ui3:#aaa9a5; + --tx1:#37352f; + --tx2:#72706c; + --tx3:#aaa9a5; + --ax1:#37352f; + --ax2:#000; + --ax3:#2eaadc; + --hl1:rgba(131,201,229,0.3); + --link-weight:500; } + +.theme-light.minimal-notion-light.minimal-light-contrast .titlebar, +.theme-light.minimal-notion-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-notion-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-notion-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-notion-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-notion-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-notion-dark { + --base-h:203; + --base-s:8%; + --base-d:20%; + --accent-h:197; + --accent-s:48%; + --accent-l:43%; + --bg1:#2f3437; + --bg2:#373c3f; + --bg3:#4b5053; + --ui1:#3e4245; + --ui2:#585d5f; + --ui3:#585d5f; + --tx1:#ebebeb; + --tx2:#909295; + --tx3:#585d5f; + --ax1:#ebebeb; + --ax2:#fff; + --ax3:#2eaadc; + --hl1:rgba(57,134,164,0.3); + --link-weight:500; } + +.theme-dark.minimal-notion-dark.minimal-dark-black { + --base-d:5%; + --bg3:#232729; + --ui1:#2f3437; } + +/* Solarized */ +.theme-dark.minimal-solarized-dark, +.theme-light.minimal-solarized-light { + --red:#dc322f; + --orange:#cb4b16; + --yellow:#b58900; + --green:#859900; + --cyan:#2aa198; + --blue:#268bd2; + --purple:#6c71c4; + --pink:#d33682; } + +.theme-light.minimal-solarized-light { + --base-h:44; + --base-s:87%; + --base-l:94%; + --accent-h:205; + --accent-s:70%; + --accent-l:48%; + --bg1:#fdf6e3; + --bg2:#eee8d5; + --bg3:rgba(0,0,0,0.062); + --ui1:#e9e1c8; + --ui2:#d0cab8; + --ui3:#d0cab8; + --tx1:#073642; + --tx2:#586e75; + --tx3:#ABB2AC; + --tx4:#586e75; + --ax1:#268bd2; + --hl1:rgba(202,197,182,0.3); + --hl2:rgba(203,75,22,0.3); } + +.theme-light.minimal-solarized-light.minimal-light-tonal { + --bg2:#fdf6e3; } + +.theme-light.minimal-solarized-light.minimal-light-contrast .titlebar, +.theme-light.minimal-solarized-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-solarized-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-solarized-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-solarized-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-solarized-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-solarized-dark { + --accent-h:205; + --accent-s:70%; + --accent-l:48%; + --base-h:193; + --base-s:98%; + --base-l:11%; + --bg1:#002b36; + --bg2:#073642; + --bg3:rgba(255,255,255,0.062); + --ui1:#19414B; + --ui2:#274850; + --ui3:#31535B; + --tx1:#93a1a1; + --tx2:#657b83; + --tx3:#31535B; + --tx4:#657b83; + --ax1:#268bd2; + --ax3:#268bd2; + --hl1:rgba(15,81,98,0.3); + --hl2:rgba(203, 75, 22, 0.35); } + +.theme-dark.minimal-solarized-dark.minimal-dark-black { + --hl1:rgba(15,81,98,0.55); + --ui1:#002b36; } + +/* Things */ +.theme-dark.minimal-things-dark, +.theme-light.minimal-things-light { + --red:#FF306C; + --yellow:#FFD500; + --green:#4BBF5E; + --orange:#ff9502; + --purple:#b051de; + --pink:#ff2e55; + --cyan:#49AEA4; } + +.theme-light.minimal-things-light { + --blue:#1b61c2; } + +.theme-dark.minimal-things-dark { + --blue:#4d95f7; } + +.theme-light.minimal-things-light { + --accent-h:215; + --accent-s:76%; + --accent-l:43%; + --bg1:white; + --bg2:#f5f6f8; + --bg3:rgba(162,177,187,0.25); + --ui1:#eef0f4; + --ui2:#D8DADD; + --ui3:#c1c3c6; + --tx1:#26272b; + --tx2:#7D7F84; + --tx3:#a9abb0; + --ax1:#1b61c2; + --ax2:#1C88DD; + --ax3:#1b61c2; + --hl1:#cae2ff; } + +.theme-light.minimal-things-light.minimal-light-tonal { + --ui1:#e6e8ec; } + +.theme-light.minimal-things-light.minimal-light-white { + --bg3:#f5f6f8; } + +.theme-light.minimal-things-light.minimal-light-contrast .titlebar, +.theme-light.minimal-things-light.minimal-light-contrast .workspace-fake-target-overlay.is-in-sidebar, +.theme-light.minimal-things-light.minimal-light-contrast .workspace-ribbon.mod-left:not(.is-collapsed), +.theme-light.minimal-things-light.minimal-light-contrast .mod-left-split, +.theme-light.minimal-things-light.minimal-light-contrast.minimal-status-off .status-bar, +.theme-light.minimal-things-light.minimal-light-contrast.is-mobile .workspace-drawer.mod-left, +.theme-dark.minimal-things-dark { + --base-h:218; + --base-s:9%; + --base-l:15%; + --accent-h:215; + --accent-s:91%; + --accent-l:64%; + --bg1:#24262a; + --bg2:#202225; + --bg3:#3d3f41; + --background-divider:#17191c; + --ui1:#3A3B3F; + --ui2:#45464a; + --ui3:#6c6e70; + --tx1:#fbfbfb; + --tx2:#CBCCCD; + --tx3:#6c6e70; + --ax1:#4d95f7; + --ax2:#79a9ec; + --ax3:#4d95f7; + --hl1:rgba(40,119,236,0.35); + --sp1:#fff; } + +.theme-dark.minimal-things-dark.minimal-dark-black { + --base-d:5%; + --bg3:#24262a; + --background-divider:#24262a; } +/* Plugin compatibility */ + +/* @plugins +core: +- backlink +- command-palette +- daily-notes +- file-explorer +- file-recovery +- global-search +- graph +- outgoing-link +- outline +- page-preview +- publish +- random-note +- starred +- switcher +- sync +- tag-pane +- word-count +community: +- buttons +- dataview +- calendar +- obsidian-charts +- obsidian-checklist-plugin +- obsidian-codemirror-options +- obsidian-dictionary-plugin +- obsidian-embedded-note-titles +- obsidian-excalidraw-plugin +- obsidian-git +- obsidian-hider +- obsidian-hover-editor +- obsidian-kanban +- obsidian-metatable +- obsidian-minimal-settings +- obsidian-outliner +- obsidian-system-dark-mode +- obsidian-style-settings +- quickadd +- sliding-panes-obsidian +- todoist-sync-plugin +*/ +/* @settings + +name: Minimal +id: minimal-style +settings: + - + id: instructions + title: Welcome 👋 + type: heading + level: 2 + collapsed: true + description: Use the Minimal Theme Settings plugin to access hotkeys, adjust features, select fonts, and choose from preset color schemes. Use the settings below for more granular customization. Visit minimal.guide for documentation. + - + id: interface + title: Interface colors + type: heading + level: 2 + collapsed: true + - + id: base + title: Base color + description: Defines all background and border colors unless overridden in more granular settings + type: variable-themed-color + format: hsl-split + default-light: '#' + default-dark: '#' + - + id: accent + title: Accent color + description: Defines link and checkbox colors unless overridden in more granular settings + type: variable-themed-color + format: hsl-split + default-light: '#' + default-dark: '#' + - + id: bg1 + title: Primary background + description: Background color for the main window + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: bg2 + title: Secondary background + description: Background color for left sidebar and menus + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: bg3 + title: Active background + description: Background color for hovered buttons and currently selected file + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: ui1 + title: Border color + type: variable-themed-color + description: For buttons, divider lines, and outlined elements + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: ui2 + title: Highlighted border color + description: Used when hovering over buttons, dividers, and outlined elements + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: ui3 + title: Active border color + description: Used when clicking buttons and outlined elements + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: extended-palette + title: Interface extended palette + type: heading + level: 2 + collapsed: true + - + id: red + title: Red + description: Extended palette colors are defaults used for progress bar status, syntax highlighting, colorful headings, and graph nodes + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: orange + title: Orange + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: yellow + title: Yellow + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: green + title: Green + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: cyan + title: Cyan + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: blue + title: Blue + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: purple + title: Purple + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: pink + title: Pink + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: active-line + title: Active line + type: heading + level: 2 + collapsed: true + - + id: active-line-on + title: Highlight active line + description: Adds a background to current line in editor + type: class-toggle + default: false + - + id: active-line-bg + title: Active line background + description: Using a low opacity color is recommended to avoid conflicting with highlights + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: blockquotes + title: Blockquotes + type: heading + level: 2 + collapsed: true + - + id: text-blockquote + title: Blockquotes text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: blockquote-size + title: Blockquotes font size + description: Accepts any CSS font-size value + type: variable-text + default: '' + - + id: blockquote-style + title: Blockquotes font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: code-blocks + title: Code blocks + type: heading + level: 2 + collapsed: true + - + id: text-code + title: Code text color + description: Color of code when syntax highlighting is not present + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: font-code + title: Code font size + description: Accepts any CSS font-size value + type: variable-text + default: 13px + - + id: embed-blocks + title: Embeds and transclusions + type: heading + level: 2 + collapsed: true + - + id: embed-strict + title: Use strict embed style globally + description: Transclusions appear seamlessly in the flow of text. Can be enabled per file using the embed-strict helper class + type: class-toggle + default: false + - + id: graphs + title: Graphs + type: heading + level: 2 + collapsed: true + - + id: node + title: Node color + description: Changing node colors requires closing and reopening graph panes or restarting Obsidian + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: node-focused + title: Active node color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: node-tag + title: Tag node color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: node-attachment + title: Attachment node color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: node-unresolved + title: Unresolved node color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: headings + title: Headings + type: heading + level: 2 + collapsed: true + - + id: level-1-headings + title: Level 1 Headings + type: heading + level: 3 + collapsed: true + - + id: h1-font + title: H1 font + description: Name of the font as it appears on your system + type: variable-text + default: '' + - + id: h1 + title: H1 font size + description: Accepts any CSS font-size value + type: variable-text + default: 1.125em + - + id: h1-weight + title: H1 font weight + type: variable-number-slider + default: 600 + min: 100 + max: 900 + step: 100 + - + id: h1-color + title: H1 text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: h1-variant + title: H1 font variant + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Small caps + value: small-caps + - + label: All small caps + value: all-small-caps + - + id: h1-style + title: H1 font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: h1-l + title: H1 divider line + description: Adds a border below the heading + type: class-toggle + default: false + - + id: level-2-headings + title: Level 2 Headings + type: heading + level: 3 + collapsed: true + - + id: h2-font + title: H2 font + description: Name of the font as it appears on your system + type: variable-text + default: '' + - + id: h2 + title: H2 font size + description: Accepts any CSS font-size value + type: variable-text + default: 1em + - + id: h2-weight + title: H2 font weight + type: variable-number-slider + default: 600 + min: 100 + max: 900 + step: 100 + - + id: h2-color + title: H2 text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: h2-variant + title: H2 font variant + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Small caps + value: small-caps + - + label: All small caps + value: all-small-caps + - + id: h2-style + title: H2 font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: h2-l + title: H2 divider line + description: Adds a border below the heading + type: class-toggle + default: false + - + id: level-3-headings + title: Level 3 Headings + type: heading + level: 3 + collapsed: true + - + id: h3-font + title: H3 font + description: Name of the font as it appears on your system + type: variable-text + default: '' + - + id: h3 + title: H3 font size + description: Accepts any CSS font-size value + type: variable-text + default: 1em + - + id: h3-weight + title: H3 font weight + type: variable-number-slider + default: 600 + min: 100 + max: 900 + step: 100 + - + id: h3-color + title: H3 text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: h3-variant + title: H3 font variant + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Small caps + value: small-caps + - + label: All small caps + value: all-small-caps + - + id: h3-style + title: H3 font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: h3-l + title: H3 divider line + description: Adds a border below the heading + type: class-toggle + default: false + - + id: level-4-headings + title: Level 4 Headings + type: heading + level: 3 + collapsed: true + - + id: h4-font + title: H4 font + description: Name of the font as it appears on your system + type: variable-text + default: '' + - + id: h4 + title: H4 font size + description: Accepts any CSS font-size value + type: variable-text + default: 0.9em + - + id: h4-weight + title: H4 font weight + type: variable-number-slider + default: 500 + min: 100 + max: 900 + step: 100 + - + id: h4-color + title: H4 text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: h4-variant + title: H4 font variant + type: variable-select + allowEmpty: false + default: small-caps + options: + - + label: Normal + value: normal + - + label: Small caps + value: small-caps + - + label: All small caps + value: all-small-caps + - + id: h4-style + title: H4 font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: h4-l + title: H4 divider line + description: Adds a border below the heading + type: class-toggle + default: false + - + id: level-5-headings + title: Level 5 Headings + type: heading + level: 3 + collapsed: true + - + id: h5-font + title: H5 font + description: Name of the font as it appears on your system + type: variable-text + default: '' + - + id: h5 + title: H5 font size + description: Accepts any CSS font-size value + type: variable-text + default: 0.85em + - + id: h5-weight + title: H5 font weight + type: variable-number-slider + default: 500 + min: 100 + max: 900 + step: 100 + - + id: h5-color + title: H5 text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: h5-variant + title: H5 font variant + type: variable-select + allowEmpty: false + default: small-caps + options: + - + label: Normal + value: normal + - + label: Small caps + value: small-caps + - + label: All small caps + value: all-small-caps + - + id: h5-style + title: H5 font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: h5-l + title: H5 divider line + description: Adds a border below the heading + type: class-toggle + default: false + - + id: level-6-headings + title: Level 6 Headings + type: heading + level: 3 + collapsed: true + - + id: h6-font + title: H6 font + description: Name of the font as it appears on your system + type: variable-text + default: '' + - + id: h6 + title: H6 font size + description: Accepts any CSS font-size value + type: variable-text + default: 0.85em + - + id: h6-weight + title: H6 font weight + type: variable-number-slider + default: 400 + min: 100 + max: 900 + step: 100 + - + id: h6-color + title: H6 text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: h6-variant + title: H6 font variant + type: variable-select + allowEmpty: false + default: small-caps + options: + - + label: Normal + value: normal + - + label: Small caps + value: small-caps + - + label: All small caps + value: all-small-caps + - + id: h6-style + title: H6 font style + type: variable-select + allowEmpty: false + default: normal + options: + - + label: Normal + value: normal + - + label: Italic + value: italic + - + id: h6-l + title: H6 divider line + type: class-toggle + description: Adds a border below the heading + default: false + - + id: icons + title: Icons + type: heading + level: 2 + collapsed: true + - + id: icon-muted + title: Icon opacity (inactive) + type: variable-number-slider + default: 0.5 + min: 0.25 + max: 1 + step: 0.05 + - + id: icon-color + title: Icon color + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: icon-color-hover + title: Icon color (hover) + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: icon-color-active + title: Icon color (active) + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: images + title: Images + type: heading + level: 2 + collapsed: true + - + id: image-muted + title: Image opacity in dark mode + description: Level of fading for images in dark mode. Hover over images to display at full brightness. + type: variable-number-slider + default: 0.7 + min: 0.25 + max: 1 + step: 0.05 + - + id: zoom-off + title: Disable image zoom + description: Turns off click + hold to zoom images + type: class-toggle + - + id: indentation-guides + title: Indentation guides + type: heading + level: 2 + collapsed: true + - + id: ig-adjust-reading + title: Horizontal adjustment in reading mode + type: variable-number-slider + default: -0.65 + min: -1.2 + max: 0 + step: 0.05 + format: em + - + id: ig-adjust-edit + title: Horizontal adjustment in edit mode + type: variable-number-slider + default: -1 + min: -10 + max: 10 + step: 1 + format: px + - + id: links + title: Links + type: heading + level: 2 + collapsed: true + - + id: ax1 + title: Link color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: ax2 + title: Link color (hovering) + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: link-weight + title: Link font weight + type: variable-number-slider + default: 400 + min: 100 + max: 900 + step: 100 + - + id: lists + title: Lists and tasks + type: heading + level: 2 + collapsed: true + - + id: ax3 + title: Checkbox color + description: Background color for completed tasks + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: checkbox-shape + title: Checkbox shape + type: class-select + allowEmpty: false + default: checkbox-circle + options: + - + label: Circle + value: checkbox-circle + - + label: Square + value: checkbox-square + - + id: minimal-strike-lists + title: Strike completed tasks + description: Adds strikethrough line and greyed text for completed tasks + type: class-toggle + default: false + - + id: list-spacing + title: List item spacing + description: Vertical space between list items in em units + type: variable-number-slider + default: 0.075 + min: 0 + max: 0.3 + step: 0.005 + format: em + - + id: list-indent + title: Nested list indentation + description: Horizontal space from left in em units + type: variable-number-slider + default: 2 + min: 1 + max: 3.5 + step: 0.1 + format: em + - + id: sidebars + title: Sidebars + type: heading + level: 2 + collapsed: true + - + id: tab-style + title: Tab style + description: See documentation for screenshots + type: class-select + allowEmpty: false + default: tab-style-1 + options: + - + label: Compact + value: tab-style-1 + - + label: Pill + value: tab-style-3 + - + label: Underlined + value: tab-style-2 + - + label: Index + value: tab-style-4 + - + id: sidebar-lines-off + title: Disable sidebar relationship lines + description: Turns off lines in file navigation + type: class-toggle + - + id: mobile-left-sidebar-width + title: Mobile left sidebar width + description: Maximum width for pinned left sidebar on mobile + type: variable-number + default: 280 + format: pt + - + id: mobile-right-sidebar-width + title: Mobile right sidebar width + description: Maximum width for pinned right sidebar on mobile + type: variable-number + default: 240 + format: pt + - + id: tables + title: Tables + type: heading + level: 2 + collapsed: true + - + id: table-font-size + title: Table font size + description: All of the following settings apply to all tables globally. To turn on these features on a per-note basis use helper classes. See documentation. + type: variable-text + default: 1em + - + id: row-lines + title: Row lines + description: Display borders between table rows globally + type: class-toggle + default: false + - + id: col-lines + title: Column lines + description: Display borders between table columns globally + type: class-toggle + default: false + - + id: table-lines + title: Cell lines + description: Display borders around all table cells globally + type: class-toggle + default: false + - + id: row-alt + title: Striped rows + description: Display striped background in alternating table rows globally + type: class-toggle + default: false + - + id: col-alt + title: Striped columns + description: Display striped background in alternating table columns globally + type: class-toggle + default: false + - + id: table-tabular + title: Tabular figures + description: Use fixed width numbers in tables globally + type: class-toggle + default: false + - + id: table-numbers + title: Row numbers + description: Display row numbers in tables globally + type: class-toggle + default: false + - + id: table-nowrap + title: Disable line wrap + description: Turn off line wrapping in table cells globally + type: class-toggle + default: false + - + id: row-hover + title: Highlight active row + description: Highlight rows on hover + type: class-toggle + default: false + - + id: row-color-hover + title: Active row background + description: Background color for hovered tables rows + type: variable-themed-color + format: hex + opacity: true + default-light: '#' + default-dark: '#' + - + id: tags + title: Tags + type: heading + level: 2 + collapsed: true + - + id: minimal-unstyled-tags + title: Plain tags + description: Tags will render as normal text, overrides settings below + type: class-toggle + default: false + - + id: tag-radius + title: Tag shape + type: variable-select + default: 14px + options: + - + label: Pill + value: 14px + - + label: Rounded + value: 4px + - + label: Square + value: 0px + - + id: tag-border-width + title: Tag border width + type: variable-select + default: 1px + options: + - + label: None + value: 0 + - + label: Thin + value: 1px + - + label: Thick + value: 2px + - + id: tag-color + title: Tag text color + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: tag-bg + title: Tag background color + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: tag-bg2 + title: Tag background color (hover) + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: text + title: Text + type: heading + level: 2 + collapsed: true + - + id: tx1 + title: Normal text color + type: variable-themed-color + description: Primary text color used by default across all elements + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: hl1 + title: Selected text background + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: hl2 + title: Highlighted text background + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: tx2 + title: Muted text color + description: Secondary text such as sidebar note titles and table headings + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: tx3 + title: Faint text color + description: tertiary text such as input placeholders, empty checkboxes, and disabled statuses + type: variable-themed-color + opacity: true + format: hex + default-light: '#' + default-dark: '#' + - + id: text-italic + title: Italic text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: text-bold + title: Bold text color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: bold-weight + title: Bold text weight + type: variable-number-slider + default: 600 + min: 100 + max: 900 + step: 100 + - + id: spacing-p + title: Paragraph spacing + description: Space between paragraphs in reading mode + type: variable-text + default: 0.75em + - + id: titlebar + title: Title bar + type: heading + level: 2 + collapsed: true + - + id: title-alignment + title: Title alignment + description: Position of the text within the title bar + type: class-select + allowEmpty: false + default: title-align-body + options: + - + label: Text body + value: title-align-body + - + label: Left + value: title-align-left + - + label: Center + value: title-align-center + - + id: show-grabber + title: Always show grabber icon + description: Make the dragging handle always visible in the top left corner of a pane + type: class-toggle + default: false + - + id: header-height + title: Title bar height + type: variable-text + default: 42px + - + id: title-size + title: Title font size + description: Accepts any CSS font-size value + type: variable-text + default: 1.1em + - + id: title-weight + title: Title font weight + type: variable-number-slider + default: 600 + min: 100 + max: 900 + step: 100 + - + id: title-color + title: Title text color (active) + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: title-color-inactive + title: Title text color (inactive) + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + - + id: translucency + title: Translucency + type: heading + level: 2 + collapsed: true + - + id: bg-translucency-light + title: Translucency (light mode) + description: Sidebar translucency in light mode. Requires turning on "Translucent window" in Appearance settings, and "Translucent sidebar" in Minimal settings. + type: variable-number-slider + default: 0.7 + min: 0 + max: 1 + step: 0.05 + - + id: bg-translucency-dark + title: Translucency (dark mode) + description: Sidebar translucency in dark mode + type: variable-number-slider + default: 0.85 + min: 0 + max: 1 + step: 0.05 + +*/ + +/* @settings +name: Minimal Cards +id: minimal-cards-style +settings: + - + id: cards-min-width + title: Card minimum width + type: variable-text + default: 180px + - + id: cards-max-width + title: Card maximum width + description: Default fills the available width, accepts valid CSS units + type: variable-text + default: 1fr + - + id: cards-mobile-width + title: Card minimum width on mobile + type: variable-text + default: 120px + - + id: cards-padding + title: Card padding + type: variable-text + default: 1.2em + - + id: cards-image-height + title: Card maximum image height + type: variable-text + default: 400px + - + id: cards-border-width + title: Card border width + type: variable-text + default: 1px + - + id: cards-background + title: Card background color + type: variable-themed-color + format: hex + default-light: '#' + default-dark: '#' + +*/ + +/* @settings +name: Minimal Mobile +id: minimal-mobile +settings: + - + id: mobile-toolbar-off + title: Disable toolbar + description: Turns off mobile toolbar + type: class-toggle +*/ + +/* @settings +name: Minimal Advanced Settings +id: minimal-advanced +settings: + - + id: window-title-on + title: Display window title + description: Display title in the window frame + type: class-toggle + - + id: styled-scrollbars + title: Styled scrollbars + description: Use styled scrollbars (replaces native scrollbars) + type: class-toggle + - + id: cursor + title: Cursor style + description: The cursor style for UI elements + type: variable-select + default: default + options: + - + label: Default + value: default + - + label: Pointer + value: pointer + - + label: Crosshair + value: crosshair + - + id: font-smaller + title: Smaller font size + description: Font size in px of smaller text + type: variable-number + default: 11 + format: px + - + id: font-smallest + title: Smallest font size + description: Font size in px of smallest text + type: variable-number + default: 10 + format: px + - + id: folding-offset + title: Folding offset + description: Width of the left margin used for folding indicators + type: variable-number-slider + default: 10 + min: 0 + max: 30 + step: 1 + format: px + +*/ diff --git a/docs/.obsidian/themes/Obsidian Nord.css b/docs/.obsidian/themes/Obsidian Nord.css new file mode 100644 index 0000000000..808ba086dd --- /dev/null +++ b/docs/.obsidian/themes/Obsidian Nord.css @@ -0,0 +1,564 @@ + +:root +{ + --dark0: #2e3440; + --dark1: #3b4252; + --dark2: #434c5e; + --dark3: #4c566a; + + --light0: #d8dee9; + --light1: #e5e9f0; + --light2: #eceff4; + --light3: #ffffff; + + --frost0: #8fbcbb; + --frost1: #88c0d0; + --frost2: #81a1c1; + --frost3: #5e81ac; + + --red: #bf616a; + --orange: #d08770; + --yellow: #ebcb8b; + --green: #a3be8c; + --purple: #b48ead; +} + +.theme-dark +{ + --background-primary: var(--dark0); + --background-primary-alt: var(--dark0); + --background-secondary: var(--dark1); + --background-secondary-alt: var(--dark2); + --text-normal: var(--light2); + --text-faint: var(--light0); + --text-muted: var(--light1); + --text-title-h1: var(--red); + --text-title-h2: var(--orange); + --text-title-h3: var(--yellow); + --text-title-h4: var(--green); + --text-title-h5: var(--purple); + --text-title-h6: var(--orange); + --text-link: var(--frost0); + --text-a: var(--frost3); + --text-a-hover: var(--frost2); + --text-mark: rgba(136, 192, 208, 0.3); /* frost1 */ + --pre-code: var(--dark1); + --text-highlight-bg: rgba(163, 190, 140, 0.3); /* green */ + --text-highlight-bg-active: var(--green); + --interactive-accent: var(--frost0); + --interactive-before: var(--dark3); + --background-modifier-border: var(--dark2); + --text-accent: var(--orange); + --interactive-accent-rgb: var(--orange); + --inline-code: var(--frost1); + --code-block: var(--frost1); + --vim-cursor: var(--orange); + --text-selection: var(--dark3); + --text-tag: var(--frost0); + --task-checkbox: var(--frost0); + --table-header: hsl(220, 16%, 16%); + --table-row-even: hsl(220, 16%, 20%); + --table-row-odd: hsl(220, 16%, 24%); + --table-hover: var(--dark3); +} +.theme-light +{ + --background-primary: var(--light3); + --background-primary-alt: var(--light3); + --background-secondary: var(--light2); + --background-secondary-alt: var(--light1); + --text-normal: var(--dark1); + --text-faint: var(--dark3); + --text-muted: var(--dark2); + --text-title-h1: var(--red); + --text-title-h2: var(--orange); + --text-title-h3: var(--yellow); + --text-title-h4: var(--green); + --text-title-h5: var(--purple); + --text-title-h6: var(--orange); + --text-link: var(--frost0); + --text-a: var(--frost3); + --text-a-hover: var(--frost1); + --text-mark: rgba(136, 192, 208, 0.3); /* frost1 */ + --pre-code: var(--light2); + --text-highlight-bg: rgba(235, 203, 139, 0.6); /* yellow */ + --text-highlight-bg-active: var(--yellow); + --interactive-accent: var(--frost0); + --interactive-before: var(--light0); + --background-modifier-border: var(--light1); + --text-accent: var(--orange); + --interactive-accent-rgb: var(--orange); + --inline-code: var(--frost1); + --code-block: var(--frost1); + --vim-cursor: var(--orange); + --text-selection: var(--light0); + --text-tag: var(--frost2); + --task-checkbox: var(--frost0); + --table-header: hsl(218, 27%, 48%); + --table-row-even: hsl(220, 16%, 94%); + --table-row-odd: hsl(220, 16%, 98%); + --table-hover: var(--light1); +} + +body { + --font-text-theme: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; + + --font-monospace-theme: 'Hack Nerd Font', 'Source Code Pro', ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; +} + +.theme-dark code[class*="language-"], +.theme-dark pre[class*="language-"], +.theme-light code[class*="language-"], +.theme-light pre[class*="language-"] +{ + text-shadow: none !important; + background-color: var(--pre-code) !important; +} + +.graph-view.color-circle, +.graph-view.color-fill-highlight, +.graph-view.color-line-highlight +{ + color: var(--interactive-accent-rgb) !important; +} +.graph-view.color-text +{ + color: var(--text-a-hover) !important; +} +/* +.graph-view.color-fill +{ + color: var(--background-secondary); +} +.graph-view.color-line +{ + color: var(--background-modifier-border); +} +*/ + +html, +body +{ + /* font-size: 16px !important; */ +} + +strong +{ + font-weight: 600 !important; +} + +a, +.cm-hmd-internal-link +{ + color: var(--text-a) !important; + text-decoration: none !important; +} + +a:hover, +.cm-hmd-internal-link:hover, +.cm-url +{ + color: var(--text-a-hover) !important; + text-decoration: none !important; +} + +a.tag, a.tag:hover { + color: var(--text-tag) !important; + background-color: var(--background-secondary-alt); + padding: 2px 4px; + border-radius: 4px; +} + +a.tag:hover { + text-decoration: underline !important; +} + +mark +{ + background-color: var(--text-mark); +} + +.titlebar { + background-color: var(--background-secondary-alt); +} + +.titlebar-inner { + color: var(--text-normal); +} + +.view-actions a +{ + color: var(--text-normal) !important; +} + +.view-actions a:hover +{ + color: var(--text-a) !important; +} + +.HyperMD-codeblock-bg +{ + background-color: var(--pre-code) !important; +} + +.HyperMD-codeblock +{ + line-height: 1.4em !important; + color: var(--code-block) !important; +} + +.HyperMD-codeblock-begin +{ + border-top-left-radius: 4px !important; + border-top-right-radius: 4px !important; +} + +.HyperMD-codeblock-end +{ + border-bottom-left-radius: 4px !important; + border-bottom-right-radius: 4px !important; +} + + +table { + border: 1px solid var(--background-secondary) !important; + border-collapse: collapse; +} + +th { + font-weight: 600 !important; + border: 0px !important; + text-align: left; + background-color: var(--table-header); + color: var(--frost0); +} + +td { + border-left: 0px !important; + border-right: 0px !important; + border-bottom: 1px solid var(--background-secondary) !important; +} + +tr:nth-child(even){ background-color: var(--table-row-even) } +tr:nth-child(odd){ background-color: var(--table-row-odd) } +tr:hover { background-color: var(--table-hover); } + +thead +{ + border-bottom: 2px solid var(--background-modifier-border) !important; +} + +.HyperMD-table-row +{ + line-height: normal !important; + padding-left: 4px !important; + padding-right: 4px !important; + /* background-color: var(--pre-code) !important; */ +} + +.HyperMD-table-row-0 +{ + /* padding-top: 4px !important; */ +} + +.CodeMirror-foldgutter-folded, +.is-collapsed .nav-folder-collapse-indicator +{ + color: var(--text-a) !important; +} + +.nav-file-tag +{ + color: var(--text-a) !important; +} + +.is-active .nav-file-title +{ + color: var(--text-a) !important; + background-color: var(--background-primary-alt) !important; +} + +.nav-file-title +{ + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; +} + +.HyperMD-list-line +{ + padding-top: 0 !important; +} + +.cm-hashtag-begin +{ + color: var(--text-tag) !important; + background-color: var(--background-secondary-alt); + padding: 2px 0 2px 4px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + text-decoration: none !important; +} + +.cm-hashtag-end +{ + color: var(--text-tag) !important; + background-color: var(--background-secondary-alt); + padding: 2px 4px 2px 0; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + text-decoration: none !important; +} + +.cm-hashtag-begin:hover, .cm-hashtag-end:hover +{ + text-decoration: underline !important; +} + +.search-result-file-matched-text +{ + color: var(--light3) !important; +} + +.markdown-preview-section pre code, +.markdown-preview-section code +{ + font-size: 0.9em !important; + background-color: var(--pre-code) !important; +} + +.markdown-preview-section pre code +{ + padding: 4px !important; + line-height: 1.4em !important; + display: block !important; + color: var(--code-block) !important; +} + +.markdown-preview-section code +{ + color: var(--inline-code) !important; +} + +.cm-s-obsidian, +.cm-inline-code +{ + -webkit-font-smoothing: auto !important; +} + +.cm-inline-code +{ + color: var(--inline-code) !important; + background-color: var(--pre-code) !important; + padding: 1px !important; +} + +.workspace-leaf-header-title +{ + font-weight: 600 !important; +} + +.side-dock-title +{ + padding-top: 15px !important; + font-size: 20px !important; +} + +.side-dock-ribbon-tab:hover, +.side-dock-ribbon-action:hover, +.side-dock-ribbon-action.is-active:hover, +.nav-action-button:hover, +.side-dock-collapse-btn:hover +{ + color: var(--text-a); +} + +.side-dock +{ + border-right: 0 !important; +} + +/* vertical resize-handle */ +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle, +.workspace-split.mod-right-split > .workspace-leaf-resize-handle +{ + width: 1px !important; + background-color: var(--background-secondary-alt); +} + +/* horizontal resize-handle */ +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle +{ + height: 1px !important; + background-color: var(--background-secondary-alt); +} + +/* Remove vertical split padding */ +.workspace-split.mod-root .workspace-split.mod-vertical .workspace-leaf-content, +.workspace-split.mod-vertical > .workspace-split, +.workspace-split.mod-vertical > .workspace-leaf, +.workspace-tabs +{ + padding-right: 0px; +} + +.markdown-embed-title +{ + font-weight: 600 !important; +} + +.markdown-embed +{ + padding-left: 10px !important; + padding-right: 10px !important; + margin-left: 10px !important; + margin-right: 10px !important; +} + +.cm-header-1.cm-link, +h1 a +{ + color: var(--text-title-h1) !important; +} + +.cm-header-2.cm-link, +h2 a +{ + color: var(--text-title-h2) !important; +} + +.cm-header-3.cm-link, +h3 a +{ + color: var(--text-title-h3) !important; +} +.cm-header-4.cm-link, +h4 a +{ + color: var(--text-title-h4) !important; +} +.cm-header-5.cm-link, +h5 a +{ + color: var(--text-title-h5) !important; +} +.cm-header-6.cm-link, +h6 a +{ + color: var(--text-title-h6) !important; +} + +.cm-header { + font-weight: 500 !important; +} + +.HyperMD-header-1, +.markdown-preview-section h1 +{ + font-weight: 500 !important; + font-size: 2.2em !important; + color: var(--text-title-h1) !important; +} + +.HyperMD-header-2, +.markdown-preview-section h2 +{ + font-weight: 500 !important; + font-size: 2.0em !important; + color: var(--text-title-h2) !important; +} + +.HyperMD-header-3, +.markdown-preview-section h3 +{ + font-weight: 500 !important; + font-size: 1.8em !important; + color: var(--text-title-h3) !important; +} + +.HyperMD-header-4, +.markdown-preview-section h4 +{ + font-weight: 500 !important; + font-size: 1.6em !important; + color: var(--text-title-h4) !important; +} + +.HyperMD-header-5, +.markdown-preview-section h5 +{ + font-weight: 500 !important; + font-size: 1.4em !important; + color: var(--text-title-h5) !important; +} + +.HyperMD-header-6, +.markdown-preview-section h6 +{ + font-weight: 500 !important; + font-size: 1.2em !important; + color: var(--text-title-h6) !important; +} + +.suggestion-item.is-selected +{ + background-color: var(--background-secondary); +} + +.empty-state-action:hover +{ + color: var(--interactive-accent); +} + +.checkbox-container +{ + background-color: var(--interactive-before); +} + +.checkbox-container:after +{ + background-color: var(--background-secondary-alt); +} + +.mod-cta +{ + color: var(--background-secondary-alt) !important; + font-weight: 600 !important; +} + +.mod-cta:hover +{ + background-color: var(--interactive-before) !important; + font-weight: 600 !important; +} + +.CodeMirror-cursor +{ + background-color: var(--vim-cursor) !important; + opacity: 60% !important; +} + +input.task-list-item-checkbox { + border: 1px solid var(--task-checkbox); + appearance: none; + -webkit-appearance: none; +} + +input.task-list-item-checkbox:checked { + background-color: var(--task-checkbox); + box-shadow: inset 0 0 0 2px var(--background-primary); +} + +.mermaid .note +{ + fill: var(--frost3) !important; +} + +.setting-item-control input[type="text"] { + color: var(--text-normal); +} +.setting-item-control input[type="text"]::placeholder { + color: var(--dark3); +} diff --git a/docs/.obsidian/themes/Things.css b/docs/.obsidian/themes/Things.css new file mode 100644 index 0000000000..0ae4ac4edb --- /dev/null +++ b/docs/.obsidian/themes/Things.css @@ -0,0 +1,6967 @@ +/*─────────────────────────────────────────────────────── +THINGS +Version 1.8.2 +Created by @colineckert + +Readme: +https://github.com/colineckert/obsidian-things + +Support my work: +https://www.buymeacoffee.com/colineckert + +Support @kepano +https://www.buymeacoffee.com/kepano + +---------------------------------------------------------------- + +MIT License + +Copyright (c) 2020-2021 Stephan Ango (@kepano) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------------------------------------------------------- + +For help and/or CSS snippets, thanks to: +- @kepano +- @chetachiezikeuzor + +────────────────────────────────────────────────────── */ + +@charset "UTF-8"; +:root { + /*---------------------------------------------------------------- + COLORS + ----------------------------------------------------------------*/ + + --base-h: 212; /* Base hue */ + --base-s: 15%; /* Base saturation */ + --base-d: 13%; /* Base lightness Dark Mode - 0 is black */ + --base-l: 97%; /* Base lightness Light Mode - 100 is white */ + --accent-h: 215; /* Accent hue */ + --accent-s: 75%; /* Accent saturation */ + --accent-d: 70%; /* Accent lightness Dark Mode */ + --accent-l: 60%; /* Accent lightness Light Mode */ + + --blue: #2e80f2; + --pink: #ff82b2; + --green: #3eb4bf; + --yellow: #e5b567; + --orange: #e87d3e; + --red: #e83e3e; + --purple: #9e86c8; + + --light-yellow-highlighter: #fff3a3a6; + --dark-yellow-highlighter: #dbce7e77; + --pink-highlighter: #ffb8eba6; + --red-highlighter: #db3e606e; + --blue-highlighter: #adccffa6; + --dark-blue-highlighter: #adccff5b; + --green-highlighter: #bbfabba6; + --purple-highlighter: #d2b3ffa6; + --orange-highlighter: #ffb86ca6; + --grey-highlighter: #cacfd9a6; + + /* Colors, sizes, weights, padding */ + + --h1-color: var(--text-normal); + --h2-color: var(--blue); + --h3-color: var(--blue); + --h4-color: var(--yellow); + --h5-color: var(--red); + --h6-color: var(--text-muted); + + --strong-color: var(--pink); + --em-color: var(--pink); + + --font-normal: 16px; + --font-small: 13px; + --font-smaller: 11px; + --font-smallest: 10px; + + --font-settings: 15px; + --font-settings-small: 13px; + --font-inputs: 14px; + + --h1: 1.5em; + --h2: 1.3em; + --h3: 1.1em; + --h4: 0.9em; + --h5: 0.85em; + --h6: 0.85em; + + --h1-weight: 700; + --h2-weight: 700; + --h3-weight: 600; + --h4-weight: 500; + --h5-weight: 500; + --h6-weight: 400; + + --normal-weight: 400; /* Switch to 300 if you want thinner default text */ + --bold-weight: 700; /* Switch to 700 if you want thicker bold text */ + --icon-muted: 0.4; + --line-width: 45rem; /* Maximum characters per line */ + --line-height: 1.5; + --border-width: 1px; + --border-width-alt: 1px; + --max-width: 90%; /* Amount of padding around the text, use 90% for narrower padding */ + --nested-padding: 3.5%; /* Amount of padding for quotes and transclusions */ + --input-height: 36px; + --list-indent: 2em; + + --font-todoist-title-size: 1em; + --font-todoist-metadata-size: small; + + --cursor: default; + --h4-transform: uppercase; +} + +/* Desktop fonts */ +body { + /* Font families */ + --font-text-theme: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Inter, Ubuntu, sans-serif; + --font-editor-theme: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Inter, Ubuntu, sans-serif; + --font-monospace-theme: 'JetBrains', Menlo, SFMono-Regular, Consolas, + 'Roboto Mono', monospace; + --font-interface-theme: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Inter, Ubuntu, sans-serif; + --font-editor: var(--font-editor-override), var(--font-text-override), + var(--font-editor-theme); + + /* Font sizes */ + --font-adaptive-normal: var(--font-text-size, var(--editor-font-size)); + --font-adaptive-small: var(--font-small); + --font-adaptive-smaller: var(--font-smaller); + --font-adaptive-smallest: var(--font-smallest); + --line-width-adaptive: var(--line-width); + --line-width-wide: calc(var(--line-width) + 12.5%); + --font-code: calc(var(--font-adaptive-normal) * 0.9); +} + +/* Phone font sizes */ +@media (max-width: 400pt) { + .is-mobile { + --font-adaptive-small: calc(var(--font-small) + 2px); + --font-adaptive-smaller: calc(var(--font-smaller) + 2px); + --font-adaptive-smallest: calc(var(--font-smallest) + 2px); + --max-width: 88%; + } +} +/* Tablet font sizes */ +@media (min-width: 400pt) { + .is-mobile { + --font-adaptive-small: calc(var(--font-small) + 3px); + --font-adaptive-smaller: calc(var(--font-smaller) + 2px); + --font-adaptive-smallest: calc(var(--font-smallest) + 2px); + --line-width-adaptive: calc(var(--line-width) + 6rem); + --max-width: 90%; + } +} + +/*---------------------------------------------------------------- + THEMES +---------------------------------------------------------------- */ + +.theme-light { + --text-normal: hsl(var(--base-h), var(--base-s), calc(var(--base-l) - 80%)); + --text-muted: hsl( + var(--base-h), + calc(var(--base-s) - 5%), + calc(var(--base-l) - 45%) + ); + --text-faint: hsl( + var(--base-h), + calc(var(--base-s) - 5%), + calc(var(--base-l) - 25%) + ); + + --text-accent: hsl(var(--accent-h), var(--accent-s), var(--accent-l)); + --text-accent-hover: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) - 10%) + ); + --text-on-accent: white; + --text-selection: hsla(var(--accent-h), 50%, calc(var(--base-l) - 20%), 30%); + --text-highlight-bg: var(--light-yellow-highlighter); + --text-highlight-bg-active: rgba(0, 0, 0, 0.1); + + --background-primary: white; + --background-primary-alt: hsl(var(--base-h), var(--base-s), var(--base-l)); + --background-secondary: hsl(var(--base-h), var(--base-s), var(--base-l)); + --background-secondary-alt: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 2%) + ); + --background-tertiary: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 7%) + ); + --background-modifier-border: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 4%) + ); + --background-modifier-border-hover: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 12%) + ); + --background-modifier-border-focus: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 20%) + ); + --background-modifier-form-field: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 6%) + ); + --background-modifier-form-field-highlighted: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 8%) + ); + --background-button: white; + + --background-transparent: hsla( + var(--base-h), + var(--base-s), + var(--base-l), + 0 + ); + /* --background-translucent: rgba(255, 255, 255, 0.85); */ + --background-translucent: hsla( + var(--base-h), + var(--base-s), + calc(var(--base-l) + 0%), + 0.8 + ); + --opacity-translucency: 1; + + --icon-color: var(--text-muted); + --icon-hex: 000; + + --background-match-highlight: hsla(var(--accent-h), 40%, 62%, 0.2); + --background-modifier-accent: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) + 10%) + ); + + --interactive-accent: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) + 10%) + ); + --interactive-accent-hover: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-l) - 0%) + ); + + --interactive-accent-rgb: 220, 220, 220; + + --quote-opening-modifier: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 10%) + ); + --background-modifier-cover: hsla( + var(--base-h), + var(--base-s), + calc(var(--base-l) - 5%), + 0.7 + ); + --shadow-color: rgba(0, 0, 0, 0.1); + + /* --tag-background-color: rgb(189, 225, 211); */ + --tag-background-color-l: #bde1d3; + /* --tag-font-color: rgb(29, 105, 75); */ + --tag-font-color-l: #1d694b; + + --code-color-l: #5c5c5c; + --code-color: var(--code-color-l); + --atom-gray-1: #383a42; + --atom-gray-2: #383a42; + --atom-red: #e75545; + --atom-green: #4ea24c; + --atom-blue: #3d74f6; + --atom-purple: #a625a4; + --atom-aqua: #0084bc; + --atom-yellow: #e35649; + --atom-orange: #986800; +} + +.theme-dark { + --text-normal: hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 70%)); + --text-muted: hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 45%)); + --text-faint: hsl(var(--base-h), var(--base-s), calc(var(--base-d) + 20%)); + + --text-accent: hsl(var(--accent-h), var(--accent-s), var(--accent-d)); + --text-accent-hover: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-d) + 12%) + ); + --text-on-accent: white; + --text-selection: hsla(var(--accent-h), 70%, 40%, 30%); + --text-highlight-bg: var(--dark-blue-highlighter); + --text-highlight-bg-active: rgba(255, 255, 255, 0.1); + + --background-primary: hsl(var(--base-h), var(--base-s), var(--base-d)); + --background-primary-alt: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) - 2%) + ); + --background-secondary: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) - 2%) + ); + --background-secondary-alt: hsl(var(--base-h), var(--base-s), var(--base-d)); + --background-tertiary: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 2%) + ); + --background-modifier-border: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 4%) + ); + --background-modifier-border-hover: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 10%) + ); + --background-modifier-border-focus: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 20%) + ); + --background-modifier-box-shadow: rgba(0, 0, 0, 0.3); + --background-button: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 2%) + ); + + --background-transparent: hsla( + var(--base-h), + var(--base-s), + var(--base-d), + 0 + ); + --background-translucent: hsla( + var(--base-h), + var(--base-s), + var(--base-d), + 0.8 + ); + --opacity-translucency: 1; + + --background-match-highlight: hsla(var(--accent-h), 40%, 62%, 0.2); + --background-modifier-accent: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-d) - 10%) + ); + + --icon-color: var(--text-muted); + --icon-hex: FFF; + --interactive-accent: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-d) - 20%) + ); + --interactive-accent-hover: hsl( + var(--accent-h), + var(--accent-s), + calc(var(--accent-d) - 15%) + ); + --quote-opening-modifier: hsl( + var(--base-h), + var(--base-s), + calc(var(--base-d) + 10%) + ); + --interactive-accent-rgb: 66, 66, 66; + + --background-modifier-cover: hsla( + var(--base-h), + var(--base-s), + calc(var(--base-d) - 12%), + 0.8 + ); + --shadow-color: rgba(0, 0, 0, 0.3); + + --tag-background-color-d: rgb(29, 105, 75); + --tag-font-color-d: var(--text-normal); + + --code-color-d: #a6a6a6; + --code-color: var(--code-color-d); + --atom-gray-1: #5c6370; + --atom-gray-2: #abb2bf; + --atom-red: #e06c75; + --atom-orange: #d19a66; + --atom-green: #98c379; + --atom-aqua: #56b6c2; + --atom-purple: #c678dd; + --atom-blue: #61afef; + --atom-yellow: #e5c07b; +} + +/* ---------------------------------------------------------------- +Desktop Styling +---------------------------------------------------------------- */ + +/* ---------------------- */ +/* Better Live Preview */ +/* ---------------------- */ + +.is-live-preview { + padding: 0 0.5em !important; +} + +/* Quote blocks */ +.markdown-source-view.mod-cm6.is-live-preview .HyperMD-quote { + border: 0 solid var(--quote-opening-modifier); + border-left-width: 2px; + background-color: var(--background-primary); +} + +/* Live Preview list bullets */ +body:not(.is-mobile) .markdown-source-view.mod-cm6 .list-bullet:after { + left: -3px; +} +.mod-cm6 .HyperMD-list-line .list-bullet::after, +.mod-cm6 span.list-bullet::after { + line-height: 0.95em; + font-size: 1.4em; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + vertical-align: middle; + color: var(--text-faint); +} + +.is-live-preview .HyperMD-header-2 { + border-bottom: 2px solid var(--background-modifier-border); + width: 100%; + padding-bottom: 2px; +} + +/* Temp fix to match Live Preview checkbox color */ +.is-live-preview input[type='checkbox']:checked { + background-color: #00a7c4 !important; +} + +/* Align checkboxes */ +.markdown-source-view.mod-cm6 .task-list-item-checkbox { + vertical-align: sub !important; +} + +/* Align collapse-indicators */ +.is-live-preview .collapse-indicator.collapse-icon { + top: 2px !important; +} + +.cm-strong, +strong { + font-weight: var(--bold-weight) !important; +} + +h1, +h2, +h3, +h4 { + letter-spacing: -0.02em; +} + +h2 { + border-bottom: 2px solid var(--background-modifier-border); + width: 100%; + padding-bottom: 2px; +} + +.popover, +.vertical-tab-content-container, +.workspace-leaf-content[data-type='markdown'] { + font-family: var(--text); +} + +body, +input, +button, +.markdown-preview-view, +.cm-s-obsidian .cm-formatting-hashtag, +.cm-s-obsidian { + font-size: var(--font-adaptive-normal); + font-weight: var(--normal-weight); + line-height: var(--line-height); + -webkit-font-smoothing: subpixel-antialiased; +} + +.markdown-source-view.mod-cm6 .cm-scroller, +.markdown-source-view, +.cm-s-obsidian .cm-formatting-hashtag, +.cm-s-obsidian, +.cm-s-obsidian span.cm-formatting-task { + font-family: var(--font-editor); + -webkit-font-smoothing: subpixel-antialiased; +} + +/* Ensure tags use text font */ +.markdown-source-view.mod-cm6.is-live-preview .cm-hashtag.cm-meta, +.markdown-source-view.mod-cm5 .cm-hashtag.cm-meta { + font-family: var(--font-text-theme); +} + +/* Use reading font in live preview */ +.lp-reading-font .markdown-source-view.mod-cm6.is-live-preview .cm-scroller { + font-family: var(--font-text); +} + +.cm-s-obsidian span.cm-formatting-task { + font-family: var(--font-monospace); /* Editor task is monospace */ + line-height: var(--line-height); +} +.cm-formatting-strong, +.cm-formatting-em, +.cm-formatting.cm-formatting-quote { + color: var(--text-faint) !important; + font-weight: var(--normal-weight); + opacity: 0.8; + letter-spacing: -0.02em; +} +.cm-formatting-header, +.cm-s-obsidian .cm-formatting-header.cm-header-1, +.cm-s-obsidian .cm-formatting-header.cm-header-2, +.cm-s-obsidian .cm-formatting-header.cm-header-3, +.cm-s-obsidian .cm-formatting-header.cm-header-4, +.cm-s-obsidian .cm-formatting-header.cm-header-5, +.cm-s-obsidian .cm-formatting-header.cm-header-6 { + color: var(--text-faint); + font-weight: var(--bold-weight); + opacity: 0.8; + letter-spacing: -0.02em; +} +.view-header-title, +.file-embed-title, +.markdown-embed-title { + letter-spacing: -0.02em; + text-align: left; + font-size: 1.125em; + padding: 10px; +} +.empty-state-title, +.markdown-preview-view h1, +.HyperMD-header-1 .cm-header-1, +.cm-s-obsidian .cm-header-1 { + letter-spacing: -0.02em; + line-height: 1.3; + font-size: var(--h1) !important; + color: var(--h1-color); + font-weight: var(--h1-weight) !important; +} +.markdown-preview-view h2, +.HyperMD-header-2 .cm-header-2, +.cm-s-obsidian .cm-header-2 { + letter-spacing: -0.02em; + line-height: 1.3; + font-size: var(--h2) !important; + color: var(--h2-color); + font-weight: var(--h2-weight) !important; +} +.markdown-preview-view h3, +.HyperMD-header-3 .cm-header-3, +.cm-s-obsidian .cm-header-3 { + letter-spacing: -0em; + line-height: 1.4; + font-size: var(--h3) !important; + color: var(--h3-color); + font-weight: var(--h3-weight) !important; +} +.markdown-preview-view h4, +.HyperMD-header-4 .cm-header-4, +.cm-s-obsidian .cm-header-4 { + letter-spacing: 0.02em; + font-size: var(--h4) !important; + color: var(--h4-color); + font-weight: var(--h4-weight) !important; + text-transform: var(--h4-transform); +} +.markdown-preview-view h5, +.HyperMD-header-5 .cm-header-5, +.cm-s-obsidian .cm-header-5 { + letter-spacing: 0.02em; + font-size: var(--h5) !important; + color: var(--h5-color); + font-weight: var(--h5-weight) !important; +} +.markdown-preview-view h6, +.HyperMD-header-6 .cm-header-6, +.cm-s-obsidian .cm-header-6 { + letter-spacing: 0.02em; + font-size: var(--h6) !important; + color: var(--h6-color); + font-weight: var(--h6-weight) !important; +} + +.markdown-preview-view mark { + margin: 0 -0.05em; + padding: 0.125em 0.15em; + border-radius: 0.2em; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} + +/* --------------- */ +/* Highlight styles */ +/* --------------- */ + +span.cm-highlight { + padding: 0.1em 0; + border-radius: 0.2em; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} + +span.cm-formatting-highlight { + /*margin: 0 0 0 -0.4em;*/ + padding-left: 0.15em; + padding-right: 0em; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} + +.cm-highlight + span.cm-formatting-highlight { + padding-left: 0em; + padding-right: 0.15em; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} + +/* --------------- */ +/* Tags */ +/* --------------- */ + +.theme-light .frontmatter-container .tag, +.theme-light a.tag { + background-color: var(--tag-background-color-l); + color: var(--tag-font-color-l); + font-size: var(--font-adaptive-small); + font-weight: 500; + padding: 3px 8px; + text-align: center; + text-decoration: none; + border-radius: 20px; +} +.theme-light a.tag:hover { + color: var(--text-normal); + border-color: var(--background-modifier-border-hover); +} +.theme-dark .frontmatter-container .tag, +.theme-dark a.tag { + background-color: var(--tag-background-color-d); + color: var(--tag-font-color-d); + font-size: var(--font-adaptive-small); + font-weight: 500; + padding: 3px 8px; + text-align: center; + text-decoration: none; + border-radius: 20px; +} +.theme-dark a.tag:hover { + color: var(--text-normal); + border-color: var(--background-modifier-border-hover); +} +.theme-light .cm-s-obsidian span.cm-hashtag { + background-color: var(--tag-background-color-l); + color: var(--tag-font-color-l); + font-size: var(--font-adaptive-small); + font-weight: 500; + text-align: center; + text-decoration: none; + padding-top: 3px; + padding-bottom: 3px; + border-left: none; + border-right: none; + cursor: text; +} +.theme-dark .cm-s-obsidian span.cm-hashtag { + background-color: var(--tag-background-color-d); + color: var(--tag-font-color-d); + font-size: var(--font-adaptive-small); + font-weight: 500; + text-align: center; + text-decoration: none; + padding-top: 3px; + padding-bottom: 3px; + border-left: none; + border-right: none; + cursor: text; +} +span.cm-hashtag.cm-hashtag-begin { + border-top-left-radius: 14px; + border-bottom-left-radius: 14px; + padding-left: 8px; + border-right: none; + border-left: 1px solid var(--background-modifier-border); +} +span.cm-hashtag.cm-hashtag-end { + border-top-right-radius: 14px; + border-bottom-right-radius: 14px; + border-left: none; + padding-right: 8px; + border-right: 1px solid var(--background-modifier-border); +} + +/* --------------- */ +/* Image zoom */ +/* --------------- */ + +/* Image cards */ +img { + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + background-color: var(--background-secondary); + /* Background color so PNGs with transparent backgrounds don't look weird */ +} + +.full-width-media .markdown-preview-view .image-embed img:not([width]), +.full-width-media .markdown-preview-view audio, +.full-width-media .markdown-preview-view video { + width: 100%; +} + +.view-content .markdown-preview-view img { + max-width: 100%; + cursor: zoom-in; +} + +body:not(.is-mobile) + .view-content + .markdown-preview-view + img[referrerpolicy='no-referrer']:active, +body:not(.is-mobile) .view-content .image-embed:active { + cursor: zoom-out; + display: block; + z-index: 100; + position: fixed; + max-height: calc(100% + 1px); + max-width: calc(100% - 20px); + height: calc(100% + 1px); + width: 100%; + object-fit: contain; + margin: -0.5px auto 0; + text-align: center; + padding: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--background-translucent); +} +body:not(.is-mobile) + .view-content + .markdown-preview-view + img[referrerpolicy='no-referrer']:active { + padding: 2.5%; +} +body:not(.is-mobile) + .view-content + .markdown-preview-view + .image-embed:active + img { + top: 50%; + transform: translateY(-50%); + padding: 0; + margin: 0 auto; + width: auto; + max-height: 95vh; + left: 0; + right: 0; + bottom: 0; + position: absolute; + opacity: 1; +} +.theme-dark span[src$='#invert'] img { + filter: invert(1) hue-rotate(180deg); + mix-blend-mode: screen; +} + +/* --------------- */ +/* Modals */ +/* --------------- */ + +.modal { + border: none; + background: var(--background-primary); + border-radius: 10px; + overflow: hidden; + padding: 20px 20px 10px; +} +.modal.mod-settings .vertical-tab-content-container { + border-left: 1px solid var(--background-modifier-border); + padding-bottom: 0; + padding-right: 0; +} +.modal.mod-settings, +.modal.mod-settings .vertical-tab-container { + max-width: 1000px; + width: 60vw; + min-height: 20vh; + width: 90vw; + height: 100vh; + max-height: 80vh; + overflow-y: hidden; + border: 1px solid var(--background-modifier-border) !important; +} +.modal.mod-settings .vertical-tab-content-container, +.modal.mod-settings .vertical-tab-header { + height: 80vh; +} +body .modal.mod-community-theme { + max-width: 1000px; + border: 1px solid var(--background-modifier-border); + overflow: hidden; +} +.modal.mod-community-theme { + padding: 0; +} +body:not(.is-mobile) .theme-list .community-theme { + width: 100%; + padding: 18px; + border: 1px solid var(--background-modifier-border); +} +.modal.mod-community-theme .modal-content { + padding: 30px; +} +.modal-title { + text-align: left; + font-size: var(--h2); + line-height: 1.4; + padding-bottom: 0; +} +.modal-content { + margin-top: 0px; + padding: 0; +} +.modal-content .u-center-text { + text-align: left; + font-size: 13px; +} +.community-plugin-name, +.modal.mod-settings .vertical-tab-content-container, +.setting-item-name { + font-size: var(--font-settings); + line-height: 1.4; +} +.community-plugin-downloads, +.community-plugin-item .community-plugin-author, +.community-plugin-item .community-plugin-desc, +.community-plugin-search-summary, +.setting-item-description { + font-size: var(--font-settings-small); + line-height: 1.4; + font-weight: 400; +} +.setting-item-description { + padding-top: 4px; +} +.setting-item-control button, +button { + font-size: var(--font-inputs); + font-weight: 400; +} +.modal button, +.modal button.mod-cta a, +button.mod-cta { + font-size: var(--font-settings-small); + margin-right: 3px; + margin-left: 3px; +} +.dropdown, +body .addChoiceBox #addChoiceTypeSelector { + font-size: var(--font-settings-small); +} +.progress-bar-message { + color: var(--text-faint); +} +input.prompt-input { + border: 0; + background: var(--background-primary); + box-shadow: none !important; + padding-left: 10px; + height: 40px; + line-height: 4; + font-size: var(--font-adaptive-normal); +} +input.prompt-input:hover { + border: 0; + background: var(--background-primary); + padding-left: 10px; + line-height: 4; +} +.suggestion-item { + cursor: var(--cursor); + padding-left: 10px; +} +.suggestion-flair { + left: auto; + right: 16px; + opacity: 0.25; +} +.prompt-results .suggestion-flair .filled-pin { + display: none; +} +.theme-light .modal-container .suggestion-item.is-selected { + border-radius: 6px; + background: var(--background-tertiary); +} +.theme-dark .modal-container .suggestion-item.is-selected { + border-radius: 6px; + background: var(--blue); +} +.menu-item { + margin-bottom: 1px; +} +.suggestion-item.is-selected, +.menu-item:hover:not(.is-disabled):not(.is-label), +.menu-item:hover { + background: var(--background-tertiary); +} +.suggestion-item, +.suggestion-empty { + font-size: var(--font-adaptive-normal); +} +.modal, +.prompt, +.suggestion-container { + box-shadow: 0 5px 30px rgba(0, 0, 0, 0.15); +} +.prompt-instructions { + color: var(--text-muted); + padding: 10px; +} +.prompt-instruction-command { + font-weight: 600; +} +.prompt { + padding-bottom: 0; +} +.prompt-results { + padding-bottom: 10px; +} +.menu { + padding: 6px; +} +.menu-item { + font-size: var(--font-adaptive-small); + border-radius: 5px; + padding: 2px 12px 3px 10px; + height: 26px; + cursor: var(--cursor); + line-height: 20px; +} +.menu-separator { + margin: 6px -5px; +} +.menu-item-icon svg { + width: 12px; + height: 12px; +} +.menu-item-icon { + width: 24px; +} + +/* --------------- */ +/* Sync */ +/* --------------- */ + +.sync-history-content { + font-size: var(--font-adaptive-small); + border: none; + padding: 20px 40px 20px 20px; +} +.sync-history-content-container { + padding: 0; +} +.sync-history-content-container .modal-button-container { + margin: 0; + padding: 10px 5px; + border-top: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); + text-align: center; +} +.sync-history-list-container { + flex-basis: 220px; +} +.sync-history-list { + padding: 10px; + border-right: 1px solid var(--background-modifier-border); + background-color: var(--background-secondary); +} +.sync-history-list-item { + border-radius: 4px; + padding: 4px 8px; + margin-bottom: 4px; + font-size: var(--font-adaptive-small); + cursor: var(--cursor); +} +.sync-history-list-item.is-active, +.sync-history-list-item:hover { + background-color: var(--background-tertiary); +} + +/* --------------- */ +/* YAML Front matter */ +/* --------------- */ + +.theme-dark pre.frontmatter[class*='language-yaml'], +.theme-light pre.frontmatter[class*='language-yaml'] { + padding: 0 0 0px 0; + background: transparent; + font-family: var(--text); + line-height: 1.2; + border-radius: 0; + border-bottom: 0px solid var(--background-modifier-border); +} +.markdown-preview-view .table-view-table > thead > tr > th { + border-color: var(--background-modifier-border); +} +.theme-dark .frontmatter .token, +.theme-light .frontmatter .token, +.markdown-preview-section .frontmatter code { + font-family: var(--text); +} + +.markdown-source-view .cm-s-obsidian .cm-hmd-frontmatter { + font-family: var(--font-monospace); +} + +/* --------------- */ +/* Drag ghost */ +/* --------------- */ + +body.is-dragging { + cursor: grabbing; + cursor: -webkit-grabbing; +} + +.workspace-drop-overlay:before, +.mod-drag, +.drag-ghost { + opacity: 100; + border-radius: 0 !important; +} +.mod-drag { + opacity: 0; + border: 2px solid var(--text-accent); + background-color: var(--background-primary); +} +.view-header.is-highlighted:after { + background-color: var(--text-selection); +} +.view-header.is-highlighted .view-actions { + background: transparent; +} + +/* --------------- */ +/* Workspace */ +/* --------------- */ + +.empty-state { + background-color: var(--background-primary); + text-align: center; +} +.workspace-split.mod-vertical > .workspace-split { + padding: 0; +} +.workspace-split .workspace-tabs { + background: var(--background-primary); +} +.workspace-split:not(.mod-right-split) .workspace-tabs { + background: var(--background-secondary); +} +.workspace-split.mod-root + > .workspace-leaf:first-of-type + .workspace-leaf-content, +.workspace-split.mod-root + > .workspace-leaf:last-of-type + .workspace-leaf-content { + border-top-right-radius: 0px; + border-top-left-radius: 0px; +} +.workspace-split.mod-root.mod-horizontal .workspace-leaf-resize-handle, +.workspace-split.mod-root.mod-vertical .workspace-leaf-resize-handle { + border-width: 1px; +} +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle { + height: 2px; + background: transparent; + border-bottom: var(--border-width-alt) solid var(--background-modifier-border); +} +.workspace-split.mod-right-split > .workspace-leaf-resize-handle { + background: transparent; + border-left: var(--border-width-alt) solid var(--background-modifier-border); + width: 3px !important; +} +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle { + border-right: var(--border-width) solid var(--background-modifier-border); + width: 2px !important; + background: transparent; +} +.workspace-split.mod-right-split > .workspace-leaf-resize-handle:hover, +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle:hover, +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle:hover, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle:hover { + border-color: var(--background-modifier-border-hover); + transition: border-color 0.1s ease-in-out 0.05s, + border-width 0.1s ease-in-out 0.05s; + border-width: 3px; +} +.workspace-split.mod-right-split > .workspace-leaf-resize-handle:active, +.workspace-split.mod-horizontal > * > .workspace-leaf-resize-handle:active, +.workspace-split.mod-vertical > * > .workspace-leaf-resize-handle:active, +.workspace-split.mod-left-split > .workspace-leaf-resize-handle:active { + border-color: var(--background-modifier-border-focus); + border-width: 3px; +} +.workspace-tab-container-before, +.workspace-tab-container-after { + width: 0; +} +.workspace-leaf { + border-left: 0px; +} +.mod-horizontal .workspace-leaf { + border-bottom: 0px; + background-color: transparent; + box-shadow: none !important; +} + +.workspace-tab-header.is-before-active .workspace-tab-header-inner, +.workspace-tab-header.is-active, +.workspace-tab-header.is-after-active, +.workspace-tab-header.is-after-active .workspace-tab-header-inner, +.workspace-tab-header.is-before-active, +.workspace-tab-header.is-after-active { + background: transparent; +} +.workspace-tabs { + border: 0; + padding-right: 0; + font-size: 100%; +} +.workspace-tab-header-container { + border: 0 !important; + height: 40px; + background-color: transparent; +} + +/* --------------- */ +/* Workspace Icons */ +/* --------------- */ + +.nav-action-button svg { + width: 25px; + height: 15px; +} +.workspace-ribbon-collapse-btn svg path { + stroke-width: 3px; +} +.nav-action-button svg path { + stroke-width: 2px; +} +.clickable-icon { + cursor: var(--cursor); +} +.view-header-icon, +.workspace-tab-header, +.nav-action-button, +.side-dock-ribbon-tab, +.view-action { + background: transparent; + color: var(--text-muted); + opacity: var(--icon-muted); + transition: opacity 0.1s ease-in-out; + cursor: var(--cursor); +} +.view-header-icon { + opacity: 0; +} +.is-mobile.show-mobile-hamburger .view-header-icon { + opacity: 1; + transform: scale(1.4); + padding: 0px 15px; + top: 5px; +} +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active, +.workspace-leaf-content[data-type='backlink'] .nav-action-button.is-active, +.workspace-leaf-content[data-type='tag'] .nav-action-button.is-active, +.workspace-tab-header.is-active, +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active { + background: transparent; + color: var(--text-muted); + opacity: 1; + transition: opacity 0.1s ease-in-out; +} +.view-action:hover, +.view-header-icon:hover, +.nav-action-button:hover, +.workspace-tab-header:hover, +.side-dock-ribbon-tab:hover, +.side-dock-ribbon-action:hover { + background: transparent; + color: var(--text-muted); + opacity: 1; + transition: opacity 0 ease-in-out; +} +.workspace-leaf-content[data-type='search'] .nav-action-button.is-active { + background: transparent; +} +.nav-action-button, +.workspace-leaf-content[data-type='search'] .nav-action-button, +.workspace-leaf-content[data-type='backlink'] .nav-action-button { + padding: 0 4px 0 8px; + margin: 0; +} + +/* --------------- */ +/* Workspace Tabs */ +/* --------------- */ + +.workspace-tab-header-container { + height: unset; + padding: 5px 10px 0px 10px; + margin: 5px 0; +} +.theme-light .workspace-tab-header.is-active { + box-shadow: 0px 0px 1px 1px inset var(--background-tertiary); + background-color: var(--background-primary); + border-radius: 6px; +} +.theme-dark .workspace-tab-header.is-active { + box-shadow: 0px 0px 0px 1px inset var(--background-secondary); + background-color: var(--background-tertiary); + border-radius: 6px; +} +.workspace-tab-container-before.is-before-active, +.workspace-tab-container-after.is-after-active, +.workspace-tab-header.is-before-active, +.workspace-tab-header.is-after-active { + background: transparent; +} + +/* --------------- */ +/* Workspace slider */ +/* --------------- */ + +.theme-light .workspace-tab-container-inner { + border-radius: 10px; + background-color: var(--background-secondary-alt) !important; + border: 1px solid var(--background-tertiary); + display: flex; + justify-content: center; + align-items: center; + stroke-width: 0; +} +.theme-dark .workspace-tab-container-inner { + border-radius: 10px; + background-color: var(--background-secondary) !important; + border: 1px solid var(--background-tertiary); + display: flex; + justify-content: center; + align-items: center; + stroke-width: 0; +} +.workspace-tab-header { + background-color: transparent; + border-radius: 10px !important; +} +.workspace-tab-header-inner { + padding: 6px 15px; +} +.workspace-tab-header-inner-icon { + display: flex; + justify-content: center; + align-items: center; +} + +/* --------------- */ +/* Window frame */ +/* --------------- */ + +body:not(.hider-frameless):not(.is-fullscreen):not(.is-mobile) { + --titlebar-height: 28px; + padding-top: var(--titlebar-height) !important; +} +body:not(.hider-frameless):not(.is-fullscreen):not(.is-mobile) .titlebar { + background: var(--background-secondary); + border-bottom: var(--border-width) solid var(--background-modifier-border); + height: var(--titlebar-height) !important; + top: 0 !important; + padding-top: 0 !important; +} +body.hider-frameless .titlebar { + border-bottom: none; +} +.mod-windows .titlebar-button:hover { + background-color: var(--background-primary-alt); +} +.mod-windows .titlebar-button.mod-close:hover { + background-color: var(--background-modifier-error); +} +.mod-windows .mod-close:hover svg { + fill: white !important; + stroke: white !important; +} + +.titlebar-button-container { + height: var(--titlebar-height); + top: 0; + display: flex; + align-items: center; +} +.titlebar:hover .titlebar-button-container.mod-left { + opacity: 1; +} +.titlebar-text { + display: none; + padding-top: 5px; + color: var(--text-faint); + letter-spacing: inherit; +} +.titlebar-button:hover { + opacity: 1; + transition: opacity 100ms ease-out; +} +.titlebar-button { + opacity: 1; + cursor: var(--cursor); + color: var(--text-muted); + padding: 2px 4px; + border-radius: 3px; + line-height: 1; + display: flex; +} +.titlebar-button:hover { + background-color: var(--background-tertiary); +} +.titlebar-button-container.mod-left .titlebar-button { + margin-right: 5px; +} +.titlebar-button-container.mod-right .titlebar-button { + margin-left: 0; + border-radius: 0; + height: 100%; + align-items: center; + padding: 2px 15px; +} + +/* --------------- */ +/* Title Bar */ +/* --------------- */ + +.view-actions { + margin-right: 10px; + z-index: 15; + background: var(--background-primary); +} +.view-header { + height: 40px; +} +.view-header-title { + padding: 0; +} +.workspace-leaf-header, +.view-header { + background-color: var(--background-primary) !important; + border: none !important; +} +.view-header-title-container:after { + display: none; +} + +/* --------------- */ +/* Full borders */ +/* --------------- */ + +body.full-borders .view-header { + border-bottom: 1px solid var(--background-modifier-border) !important; +} +body.full-borders .side-dock-ribbon { + border-right: 1px solid var(--background-modifier-border) !important; +} + +/* --------------- */ +/* Custom line width */ +/* --------------- */ + +.markdown-preview-view.is-readable-line-width .markdown-preview-sizer { + max-width: var(--max-width); + width: var(--line-width-adaptive); +} +.is-mobile .markdown-source-view.mod-cm6.is-readable-line-width .cm-content { + max-width: var(--line-width-adaptive); +} + +.markdown-source-view.is-readable-line-width .CodeMirror { + padding-left: 0; + padding-right: 0; + margin: 0 auto 0 auto; + width: var(--line-width-adaptive); + max-width: var(--max-width); +} +.view-header-title-container { + padding-left: 0; + padding-right: 0; + position: absolute; + max-width: var(--max-width); + width: var(--line-width-adaptive); + margin: 0 auto; + left: 0; + right: 0; +} + +/* --------------- */ +/* EDITOR MODE */ +/* --------------- */ + +/* Fancy cursor */ +/* .CodeMirror-cursor, +.cm-s-obsidian .cm-cursor { + border: none; + border-right: 2px solid var(--text-accent); +} */ +.markdown-source-view.mod-cm6, +.markdown-source-view.mod-cm5, +.markdown-source-view { + padding: 0; +} +.cm-s-obsidian .CodeMirror-code { + padding-right: 0; +} +.CodeMirror-lines { + padding-bottom: 170px; +} +.cm-s-obsidian pre.HyperMD-list-line { + padding-top: 0; +} +.workspace .markdown-preview-view { + padding: 0; +} +.workspace .markdown-preview-view .markdown-embed { + margin: 0; +} +.workspace .markdown-preview-view .markdown-embed-content { + max-height: none; +} +.markdown-embed-title, +.internal-embed .markdown-preview-section { + max-width: 100%; +} +.cm-s-obsidian .HyperMD-header, +.cm-s-obsidian pre.HyperMD-header { + /* Commenting to better align header and content */ + /* padding-left: 0 !important; */ + font-size: 1em !important; +} +.CodeMirror-linenumber { + font-size: var(--font-adaptive-small) !important; + font-feature-settings: 'tnum'; + color: var(--text-faint); + padding-top: 3px; +} +.cm-s-obsidian span.cm-url, +.cm-s-obsidian span.cm-url:hover { + color: var(--text-accent); +} +.cm-s-obsidian span.cm-link { + color: var(--text-muted); +} +.cm-s-obsidian span.cm-hmd-internal-link { + color: var(--text-accent) !important; +} +.cm-s-obsidian span.cm-formatting-link { + color: var(--text-faint) !important; +} + +/* Mermaid */ +.mermaid svg { + width: 100%; +} + +/* Transcluded notes and embeds */ + +.markdown-preview-view.is-readable-line-width + .markdown-embed + .markdown-preview-sizer { + max-width: 100%; + width: 100%; +} + +.markdown-embed h1:first-child { + margin-block-start: 0em; +} + +.markdown-preview-view .markdown-embed { + margin-top: var(--nested-padding); + padding: 0 calc(var(--nested-padding) / 2) 0 var(--nested-padding); +} +.markdown-embed-title { + /* Remove height to fix cutoff bug */ + /* height: 24px; */ + line-height: 18px; +} +.markdown-embed .markdown-preview-sizer:first-child ul { + margin-block-start: 2px; +} +.markdown-embed .markdown-preview-section:last-child p, +.markdown-embed .markdown-preview-section:last-child ul { + margin-block-end: 2px; +} +.internal-embed:not([src*='#^']) .markdown-embed-link { + left: 0; + width: 100%; +} +.markdown-embed-link, +.file-embed-link { + top: 0px; + right: 0; + text-align: right; +} +.file-embed-link svg, +.markdown-embed-link svg { + width: 20px; + opacity: 0; +} +.markdown-embed:hover .file-embed-link svg, +.markdown-embed:hover .markdown-embed-link svg { + opacity: 1; +} +.markdown-preview-view .markdown-embed-content > .markdown-preview-view { + max-height: none !important; +} +.markdown-embed .markdown-preview-view { + padding: 0; +} +.internal-embed .markdown-embed { + border: 0; + border-left: 2px solid var(--quote-opening-modifier); + border-radius: 0; +} + +/* Embedded Searches */ + +.markdown-preview-view .internal-query.is-embed { + border-top: none; + border-bottom: none; +} +.markdown-preview-view .internal-query.is-embed .internal-query-header { + justify-content: start; +} +.markdown-preview-view .internal-query.is-embed .internal-query-header-title { + font-weight: 500; + color: var(--text-normal); + font-size: var(--h2); +} +.internal-query.is-embed .search-result-file-matches { + border-bottom: 0; +} + +/* Editor Mode Footnotes */ + +.cm-s-obsidian span.cm-footref { + font-size: var(--font-adaptive-normal); +} +.cm-s-obsidian pre.HyperMD-footnote { + font-size: var(--font-adaptive-small); + padding-left: 20px; +} + +/* Editor Mode Tables */ + +.CodeMirror pre.HyperMD-table-row { + font-size: calc(var(--font-adaptive-normal) - 1px); + font-family: var(--font-monospace) !important; +} + +/* Editor Mode Lists */ + +.cm-formatting-list { + color: var(--text-faint) !important; +} +/* Editor Mode Quotes */ + +span.cm-formatting.cm-formatting-quote { + color: var(--text-faint) !important; +} + +/* --------------- */ +/* Internal search */ +/* --------------- */ + +.is-flashing { + border-radius: 2px; + box-shadow: 0 2px 0 8px var(--text-highlight-bg); + transition: all 0s ease-in-out; +} +.is-flashing .tag { + border-color: var(--text-highlight-bg-active); +} +.suggestion-container.mod-search-suggestion { + max-width: 280px; +} +.mod-search-suggestion .suggestion-item { + font-size: var(--font-adaptive-small); +} +.mod-search-suggestion .clickable-icon { + margin: 0; +} +.search-suggest-item.mod-group { + font-size: var(--font-adaptive-smaller); +} +.cm-s-obsidian span.obsidian-search-match-highlight { + background: inherit; + background: var(--text-highlight-bg); + padding-left: 0; + padding-right: 0; +} +.markdown-preview-view .search-highlight > div { + box-shadow: 0 0 0px 2px var(--text-normal); + border-radius: 2px; + background: transparent; +} +.markdown-preview-view .search-highlight > div { + opacity: 0.4; +} +.markdown-preview-view .search-highlight > div.is-active { + background: transparent; + border-radius: 2px; + opacity: 1; + mix-blend-mode: normal; + box-shadow: 0 0 0px 3px var(--text-accent); +} +.document-search-container.mod-replace-mode { + height: 90px; +} +.document-search-button, +.document-search-close-button { + cursor: var(--cursor); +} +.document-search-close-button:before { + font-weight: 200; +} +body .document-search-container { + margin-top: 12px; + padding: 0; + height: 38px; + background-color: var(--background-primary); + border-top: none; + width: 100%; +} +.markdown-reading-view.is-searching, +.markdown-source-view.is-replacing, +.markdown-source-view.is-searching { + flex-direction: column-reverse; +} +input.document-search-input, +input.document-replace-input { + margin-top: 2px; + font-size: var(--font-adaptive-small) !important; + border: 1px solid var(--background-modifier-border); + border-radius: 5px; + height: 28px !important; + background: var(--background-primary); + transition: border-color 0.1s ease-in-out; +} +input.document-search-input:hover, +input.document-replace-input:hover { + border: 1px solid var(--background-modifier-border-hover); + background: var(--background-primary); + transition: border-color 0.1s ease-in-out; +} +input.document-search-input:focus, +input.document-replace-input:focus { + border: 1px solid var(--background-modifier-border-focus); + background: var(--background-primary); + transition: all 0.1s ease-in-out; +} +.document-search-button { + font-size: var(--font-adaptive-small); +} + +/* --------------- */ +/* Sidebar documents */ +/* --------------- */ + +.workspace > .workspace-split:not(.mod-root) .CodeMirror, +.workspace > .workspace-split:not(.mod-root) .markdown-preview-view { + font-size: var(--font-adaptive-small); + line-height: 1.2; +} +.workspace + > .workspace-split:not(.mod-root) + .workspace-leaf-content[data-type='markdown'] + .markdown-preview-view { + padding: 0 15px; +} +.workspace + > .workspace-split:not(.mod-root) + .workspace-leaf-content[data-type='markdown'] + .markdown-embed + .markdown-preview-view { + padding: 0; +} +.workspace > .workspace-split:not(.mod-root) .CodeMirror, +.workspace > .workspace-split:not(.mod-root) .markdown-preview-section, +.workspace > .workspace-split:not(.mod-root) .markdown-preview-sizer { + max-width: 100%; + padding: 0; + width: auto; +} + +/* Hide embed styling for sidebar documents */ +.workspace > .workspace-split:not(.mod-root) .internal-embed .markdown-embed { + border: none; + padding: 0; +} + +.workspace > .workspace-split:not(.mod-root) .CodeMirror-sizer { + padding-left: 10px; +} + +/* --------------- */ +/* Turn off file name trimming */ +/* --------------- */ + +.full-file-names .tree-item-inner, +.full-file-names .nav-file-title-content, +.full-file-names .search-result-file-title { + text-overflow: unset; + white-space: normal; + line-height: 1.4; +} + +.full-file-names .nav-file-title { + margin-bottom: 3px; +} + +/* --------------- */ +/* Form inputs */ +/* --------------- */ + +input[type='email'], +input[type='number'], +input[type='password'], +input[type='search'], +/* input[type='text'], */ +textarea { + font-size: var(--font-inputs); +} +textarea { + padding: 5px 10px; + transition: all 0.1s linear; + line-height: 1.3; + -webkit-appearance: none; +} +input[type='text'], +input[type='search'], +input[type='email'], +input[type='password'], +input[type='number'] { + padding: 5px 10px; + transition: all 0.1s linear; + height: var(--input-height); + -webkit-appearance: none; +} +textarea:hover, +select:hover, +input:hover { + border-color: var(--background-modifier-border-hover); + transition: all 0.1s linear; +} +textarea:active, +textarea:focus, +button:active, +button:focus, +.dropdown:focus, +.dropdown:active, +select:focus, +select:active, +input[type='text']:active, +input[type='search']:active, +input[type='email']:active, +input[type='password']:active, +input[type='number']:active, +input[type='text']:focus, +input[type='search']:focus, +input[type='email']:focus, +input[type='password']:focus, +input[type='number']:focus { + -webkit-appearance: none; + border-color: var(--background-modifier-border-hover); +} +body:not(.is-mobile) textarea:active, +body:not(.is-mobile) textarea:focus, +body:not(.is-mobile) button:active, +body:not(.is-mobile) button:focus, +body:not(.is-mobile) .dropdown:focus, +body:not(.is-mobile) .dropdown:active, +body:not(.is-mobile) select:focus, +body:not(.is-mobile) select:active, +body:not(.is-mobile) input:focus { + box-shadow: 0 0 0px 2px var(--background-modifier-border-hover); +} +.modal.mod-settings button:not(.mod-cta):not(.mod-warning), +.modal button:not(.mod-warning), +.modal.mod-settings button:not(.mod-warning) { + background-color: var(--background-button); + color: var(--text-normal); + border: 1px solid var(--background-modifier-border); + box-shadow: 0 1px 1px 0px rgba(0, 0, 0, 0.05); + cursor: var(--cursor); + height: var(--input-height); + line-height: 0; + white-space: nowrap; +} +button:hover, +.modal button:not(.mod-warning):hover, +.modal.mod-settings button:not(.mod-warning):hover { + box-shadow: 0 2px 3px 0px rgba(0, 0, 0, 0.05); + background-color: var(--background-button); + border-color: var(--background-modifier-border-hover); +} +.dropdown, +select { + box-shadow: 0 1px 1px 0px rgba(0, 0, 0, 0.05); + background-color: var(--background-button); + border-color: var(--background-modifier-border); + transition: border-color 0.1s linear; +} +.dropdown:hover, +select:hover { + background-color: var(--background-button); + box-shadow: 0 2px 3px 0px rgba(0, 0, 0, 0.05); +} + +/* --------------- */ +/* Checkboxes */ +/* --------------- */ + +input[type='checkbox'] { + -webkit-appearance: none; + appearance: none; + border-radius: 30%; + border: 2px solid var(--background-modifier-border-hover); + padding: 0; +} +input[type='checkbox']:focus, +input[type='checkbox']:hover { + outline: 0; + border-color: var(--text-faint); +} +.checklist-plugin-main .group .compact > .toggle .checked, +.is-flashing input[type='checkbox']:checked, +input[type='checkbox']:checked { + background-color: var(--blue) !important; + /* border: 2px solid var(--blue); */ + border: none; + background-position: center; + background-size: 70%; + background-repeat: no-repeat; + background-image: url('data:image/svg+xml; utf8, '); +} +.markdown-preview-section > .contains-task-list { + padding-bottom: 0.5em; +} +.markdown-preview-view ul > li.task-list-item.is-checked, +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task='x'], +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task='X'] { + text-decoration: none; +} +.markdown-preview-view .task-list-item-checkbox { + width: 16px; + height: 16px; + position: relative; + top: 6px; + line-height: 0; + margin-left: -1.5em; + margin-right: 6px; + filter: none; +} +.markdown-preview-view ul > li.task-list-item { + text-indent: 0; + line-height: 1.4; +} +.markdown-preview-view .task-list-item { + padding-inline-start: 0; +} +.side-dock-plugin-panel-inner { + padding-right: 6px; + padding-left: 6px; +} + +/* --------------- */ +/* Toggle switches */ +/* --------------- */ + +.checkbox-container { + background-color: var(--background-modifier-border-hover); + box-shadow: inset 0 0px 1px 0px rgba(0, 0, 0, 0.2); + border: none; + width: 40px; + height: 24px; + cursor: var(--cursor); +} +.checkbox-container:after { + background: white; + border: none; + margin: 3px 0 0 0; + height: 18px; + width: 18px; + border-radius: 26px; + box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.1); + transition: all 0.1s linear; +} +.checkbox-container:hover:after { + box-shadow: 0 2px 3px 0px rgba(0, 0, 0, 0.1); + transition: all 0.1s linear; +} +.checkbox-container.is-enabled { + border-color: var(--interactive-accent); +} + +/* --------------- */ +/* File browser */ +/* --------------- */ + +.nav-header { + padding: 0; +} +.nav-buttons-container { + padding: 10px 5px 0px 5px; + margin-bottom: 0px !important; + justify-content: flex-start; + border: 0; +} +.nav-files-container { + overflow-x: hidden; + padding-bottom: 50px; + padding-left: 5px; +} +.nav-folder-title { + margin: 0 0 0 8px; + min-width: auto; + width: calc(100% - 16px); + padding: 0 10px 0 16px; + line-height: 1.5; + cursor: var(--cursor); +} +.nav-folder-children .nav-folder-children { + margin-left: 20px; + padding-left: 0; + border-left: 1px solid var(--background-modifier-border); +} +.nav-folder.mod-root > .nav-folder-title.is-being-dragged-over { + background-color: var(--text-selection); +} +.nav-folder-title.is-being-dragged-over { + background-color: var(--text-selection); + border-color: var(--text-selection); + border-radius: 6px; + border: 1px solid transparent; +} +.nav-folder-title-content { + padding: 0px 4px 1px 0; + font-weight: 600; +} +.nav-folder-collapse-indicator { + top: 1px; + margin-left: -10px; +} +.nav-file { + margin-left: 12px; + padding-right: 4px; +} +.workspace-leaf.mod-active .nav-folder.has-focus > .nav-folder-title, +.workspace-leaf.mod-active .nav-file.has-focus { + border: 1px solid transparent; +} +.nav-file-title { + width: calc(100% - 30px); + margin: 0 8px 0 -4px; + padding: 2px 2px; + border-width: 0; + line-height: 1.6; + border-color: var(--background-secondary); + border-radius: 6px; + cursor: var(--cursor); +} +.nav-file-title.is-being-dragged, +.nav-file-title.is-active, +body:not(.is-grabbing) .nav-file-title.is-active:hover { + background-color: var(--background-tertiary); + color: var(--text-normal); +} +.nav-file-title-content { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 5px; + vertical-align: middle; + cursor: var(--cursor); +} +.drop-indicator { + border-width: 1px; +} +.nav-file-icon { + margin: 1px 0 0 0; + vertical-align: bottom; + padding: 0 0 0 5px; +} +.workspace-leaf-content[data-type='starred'] .nav-file-title-content { + width: calc(100% - 15px); +} +body:not(.is-grabbing) .nav-file-title:hover .nav-folder-collapse-indicator, +body:not(.is-grabbing) .nav-folder-title:hover .nav-folder-collapse-indicator, +body:not(.is-grabbing) .nav-file-title:hover, +body:not(.is-grabbing) .nav-folder-title:hover { + background: transparent; +} + +/* Tooltip */ + +.tooltip { + font-size: var(--font-adaptive-small); + line-height: 1.2; + padding: 4px 8px; + border-radius: 4px; +} + +/* Sidebar font size */ +.nav-file-title, +.tree-item-self, +.nav-folder-title, +.is-collapsed .search-result-file-title, +.tag-pane-tag { + font-size: var(--font-adaptive-small); + color: var(--text-muted); +} +.search-result-file-title { + font-size: var(--font-adaptive-small); + color: var(--text-normal); + font-weight: var(--normal-weight); +} +.side-dock-collapsible-section-header { + font-size: var(--font-adaptive-small); + color: var(--text-muted); + cursor: var(--cursor); + margin-right: 0; + margin-left: 0; +} +.side-dock-collapsible-section-header:hover, +.side-dock-collapsible-section-header:not(.is-collapsed) { + color: var(--text-muted); + background: transparent; +} +.tree-view-item-self:hover .tree-view-item-collapse, +.collapsible-item-self.is-clickable:hover { + color: var(--text-muted); + background: transparent; + cursor: var(--cursor); +} +.collapsible-item-self.is-clickable { + cursor: var(--cursor); +} +.search-result-collapse-indicator svg, +.search-result-file-title:hover .search-result-collapse-indicator svg, +.side-dock-collapsible-section-header-indicator:hover svg, +.side-dock-collapsible-section-header:hover + .side-dock-collapsible-section-header-indicator + svg, +.markdown-preview-view .collapse-indicator svg, +.tree-view-item-collapse svg, +.is-collapsed .search-result-collapse-indicator svg, +.nav-folder-collapse-indicator svg, +.side-dock-collapsible-section-header-indicator svg, +.is-collapsed .side-dock-collapsible-section-header-indicator svg { + color: var(--text-faint); + cursor: var(--cursor); +} +.search-result-collapse-indicator, +.search-result-file-title:hover .search-result-collapse-indicator, +.side-dock-collapsible-section-header-indicator:hover, +.side-dock-collapsible-section-header:hover + .side-dock-collapsible-section-header-indicator, +.markdown-preview-view .collapse-indicator, +.tree-view-item-collapse, +.is-collapsed .search-result-collapse-indicator, +.nav-folder-collapse-indicator, +.side-dock-collapsible-section-header-indicator, +.is-collapsed .side-dock-collapsible-section-header-indicator { + color: var(--text-faint); + cursor: var(--cursor); +} +.nav-folder-title.is-being-dragged-over .nav-folder-collapse-indicator svg { + color: var(--text-normal); +} + +/* --------------- */ +/* Relationship lines */ +/* --------------- */ + +/* Relationship lines in Preview */ + +ul { + position: relative; +} +.markdown-preview-view ul ul::before { + content: ''; + border-right: 1px solid var(--background-modifier-border); + position: absolute; + left: -0.85em !important; + top: 0; + bottom: 0; +} +.markdown-preview-view ul.contains-task-list::before { + top: 5px; +} +.markdown-preview-view .task-list-item-checkbox { + margin-left: -1.3em; +} + +/* Relationship lines in Edit mode */ + +.cm-hmd-list-indent > .cm-tab { + display: inline-block; +} +.cm-hmd-list-indent > .cm-tab:after { + content: ' '; + display: block; + width: 1px; + position: absolute; + top: 1px; + border-right: 1px solid var(--background-modifier-border); + height: 100%; +} + +/* --------------- */ +/* Folding offset */ +/* --------------- */ + +/* Add padding to account for gutter in Edit mode when folding is on */ + +body:not(.plugin-sliding-panes-rotate-header) .view-header-title, +.allow-fold-headings.markdown-preview-view .markdown-preview-sizer, +.allow-fold-lists.markdown-preview-view .markdown-preview-sizer { + padding: 0 8px 0 16px; +} +.allow-fold-lists.markdown-preview-view + .markdown-embed + .markdown-preview-sizer { + padding-left: 0; +} +.is-mobile .markdown-source-view.mod-cm6.is-readable-line-width .cm-gutters, +.is-mobile .markdown-source-view.mod-cm6.is-readable-line-width .cm-content { + transform: translateX(-10px) !important; +} +.CodeMirror-sizer { + padding-right: 12px !important; +} + +/* Folding icons in Preview */ + +.markdown-preview-view .heading-collapse-indicator.collapse-indicator svg, +.markdown-preview-view ol > li .collapse-indicator svg, +.markdown-preview-view ul > li .collapse-indicator svg { + opacity: 0; +} + +h1:hover .heading-collapse-indicator.collapse-indicator svg, +h2:hover .heading-collapse-indicator.collapse-indicator svg, +h3:hover .heading-collapse-indicator.collapse-indicator svg, +h4:hover .heading-collapse-indicator.collapse-indicator svg, +h5:hover .heading-collapse-indicator.collapse-indicator svg, +.markdown-preview-view .is-collapsed .collapse-indicator svg, +.markdown-preview-view .collapse-indicator:hover svg { + opacity: 1; +} +.markdown-preview-view div.is-collapsed h1::after, +.markdown-preview-view div.is-collapsed h2::after, +.markdown-preview-view div.is-collapsed h3::after, +.markdown-preview-view div.is-collapsed h4::after, +.markdown-preview-view div.is-collapsed h5::after, +.markdown-preview-view ol .is-collapsed::after, +.markdown-preview-view ul .is-collapsed::after { + content: '...'; + padding: 5px; + color: var(--text-faint); +} +.markdown-preview-view ol > li.task-list-item .collapse-indicator, +.markdown-preview-view ul > li.task-list-item .collapse-indicator { + position: absolute; + margin-left: -42px; + margin-top: 5px; +} +.markdown-preview-view ol > li .collapse-indicator { + padding-right: 20px; +} +.markdown-preview-view .heading-collapse-indicator.collapse-indicator { + margin-left: -25px; + padding-right: 7px 8px 7px 0; +} +.markdown-preview-view .collapse-indicator { + position: absolute; + margin-left: -42px; + padding-bottom: 10px; + padding-top: 0px; +} +.markdown-preview-view ul > li:not(.task-list-item) .collapse-indicator { + padding-right: 20px; +} +.markdown-preview-view ul > li:not(.task-list-item)::marker { + font-size: 0.9em; +} +.markdown-preview-view ul > li:not(.task-list-item).is-collapsed::before { + background: var(--background-modifier-border); + box-shadow: 3px 0 0px 4px var(--background-modifier-border); +} +.list-collapse-indicator .collapse-indicator .collapse-icon { + opacity: 0; +} +.markdown-preview-view ul > li h1, +.markdown-preview-view ul > li h2, +.markdown-preview-view ul > li h3, +.markdown-preview-view ul > li h4 { + display: inline; +} + +/* Folding icons in Edit mode */ + +span[title='Fold line'], +span[title='Unfold line'] { + margin: 0 0 0 0; + padding: 0 0 1em 0; +} + +.CodeMirror-foldmarker { + color: var(--text-faint); + cursor: default; + margin-left: 5px; +} +.CodeMirror-foldgutter-folded { + cursor: var(--cursor); + margin-top: -3px; + transform: rotate(-90deg); +} +.CodeMirror-foldgutter-open { + cursor: var(--cursor); + margin-top: -1px; + width: 16px; + height: 20px; +} +span[title='Fold line'], +span[title='Unfold line'], +.CodeMirror-foldgutter-folded:after, +.CodeMirror-foldgutter-open:after { + background-repeat: no-repeat; + background-position: 50% 50%; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='8' height='8' class='right-triangle'%3E%3Cpath fill='currentColor' stroke='currentColor' d='M94.9,20.8c-1.4-2.5-4.1-4.1-7.1-4.1H12.2c-3,0-5.7,1.6-7.1,4.1c-1.3,2.4-1.2,5.2,0.2,7.6L43.1,88c1.5,2.3,4,3.7,6.9,3.7 s5.4-1.4,6.9-3.7l37.8-59.6C96.1,26,96.2,23.2,94.9,20.8L94.9,20.8z'%3E%3C/path%3E%3C/svg%3E"); + color: transparent; +} +span[title='Unfold line'] { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='8' height='8' class='right-triangle'%3E%3Cpath fill='currentColor' stroke='currentColor' transform='rotate(-90,50,50)' d='M94.9,20.8c-1.4-2.5-4.1-4.1-7.1-4.1H12.2c-3,0-5.7,1.6-7.1,4.1c-1.3,2.4-1.2,5.2,0.2,7.6L43.1,88c1.5,2.3,4,3.7,6.9,3.7 s5.4-1.4,6.9-3.7l37.8-59.6C96.1,26,96.2,23.2,94.9,20.8L94.9,20.8z'%3E%3C/path%3E%3C/svg%3E"); +} +.theme-dark span[title='Fold line'], +.theme-dark span[title='Unfold line'], +.theme-dark .CodeMirror-foldgutter-folded:after, +.theme-dark .CodeMirror-foldgutter-open:after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='8' height='8' class='right-triangle'%3E%3Cpath fill='%23FFFFFF' stroke='%23FFFFFF' d='M94.9,20.8c-1.4-2.5-4.1-4.1-7.1-4.1H12.2c-3,0-5.7,1.6-7.1,4.1c-1.3,2.4-1.2,5.2,0.2,7.6L43.1,88c1.5,2.3,4,3.7,6.9,3.7 s5.4-1.4,6.9-3.7l37.8-59.6C96.1,26,96.2,23.2,94.9,20.8L94.9,20.8z'%3E%3C/path%3E%3C/svg%3E"); +} +span[title='Fold line'], +.CodeMirror-foldgutter-open:after { + opacity: 0; +} +span[title='Fold line']:hover, +span[title='Unfold line'], +.CodeMirror-foldgutter-folded:after, +.CodeMirror-code > div:hover .CodeMirror-foldgutter-open:after { + opacity: 0.3; +} +span[title='Unfold line']:hover, +.CodeMirror-code > div:hover .CodeMirror-foldgutter-open:hover:after, +.CodeMirror-code > div:hover .CodeMirror-foldgutter-folded:hover:after { + opacity: 1; +} + +/* --------------- */ +/* Outline */ +/* --------------- */ + +.outline { + padding: 15px 10px 20px 5px; + font-size: var(--font-adaptive-small); +} +.outline .pane-empty { + font-size: var(--font-adaptive-small); + color: var(--text-faint); + padding: 0 0 0 15px; + width: 100%; +} +.outline .collapsible-item-self { + cursor: var(--cursor); + line-height: 1.4; + margin-bottom: 4px; + font-size: var(--font-adaptive-small); + padding-left: 15px; +} +.collapsible-item-collapse { + opacity: 1; + left: -5px; + color: var(--text-faint); +} +.outline .collapsible-item-inner:hover { + color: var(--text-normal); +} +.collapsible-item-self.is-clickable:hover .collapsible-item-collapse { + color: var(--text-normal); +} +.outline > .collapsible-item > .collapsible-item-self .right-triangle { + opacity: 0; +} + +/* --------------- */ +/* Search */ +/* --------------- */ + +.search-result-container.mod-global-search .search-empty-state { + padding-left: 15px; +} +.search-result-file-match { + cursor: var(--cursor) !important; +} +.search-result-file-match:hover { + color: var(--text-normal); + background: transparent; +} +.search-result-container:before { + height: 1px; +} +.search-result-container.is-loading:before { + background-color: var(--background-modifier-accent); +} +.search-result { + margin-bottom: 0; +} +.search-result-count { + opacity: 1; + color: var(--text-faint); + padding: 0 0 0 5px; +} +.search-result-file-match:before { + top: 0; +} +.search-result-file-match:not(:first-child) { + margin-top: 0px; +} +.search-result-file-match { + margin-top: 0; + margin-bottom: 0; + padding-top: 6px; + padding-bottom: 5px; +} +.search-input-container input, +.search-input-container input:hover, +.search-input-container input:focus { + font-size: var(--font-adaptive-small); + padding: 5px 10px; + background-color: var(--background-secondary); +} +.search-input-container { + width: calc(100% - 20px); + margin: 0 0 5px 10px; +} +/* .search-result-file-matched-text { + background-color: var(--text-selection); +} */ +.workspace-leaf-content .setting-item { + padding: 5px 0; + border: none; +} +.workspace-leaf-content .setting-item-control { + flex-shrink: 0; + flex: 1; +} +.search-input-clear-button { + cursor: var(--cursor); + top: 0px; + bottom: 0px; + border-radius: 15px; + line-height: 0px; + height: 15px; + width: 15px; + margin: auto; + padding: 6px 0 0 0; + text-align: center; + vertical-align: middle; + align-items: center; + color: var(--text-faint); +} +.search-input-clear-button:hover { + color: var(--text-normal); +} +.search-input-clear-button:before { + font-size: 22px; + font-weight: 200; +} +.search-input { + max-width: 100%; + margin-left: 0; + width: 500px; +} +input.search-input:focus { + border-color: var(--background-modifier-border); +} +.workspace-leaf-content[data-type='search'] .search-result-file-matches { + border-left: 0; + padding-left: 0; +} +.search-empty-state { + font-size: var(--font-adaptive-small); + color: var(--text-faint); + padding-left: 5px; + margin: 0; +} +.search-result-container { + padding: 5px 10px 50px 0px; +} +.search-result-file-title { + line-height: 1.3; + padding: 4px 4px 4px 24px; + vertical-align: middle; + cursor: var(--cursor) !important; +} +.tree-item-inner, +.search-result-file-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.search-result-collapse-indicator { + left: 0px; +} +.search-result-file-match:before { + height: 0.5px; +} +.search-result-file-matches { + font-size: var(--font-adaptive-small); + line-height: 1.4; + margin-bottom: 8px; + padding: 0 0 6px 0; + color: var(--text-muted); + border-bottom: 1px solid var(--background-modifier-border-focus); +} +.search-info-container { + font-size: var(--font-adaptive-smaller); + color: var(--text-faint); + padding-top: 5px; + padding-bottom: 5px; +} +.search-info-more-matches { + font-size: var(--font-adaptive-smaller); + padding-top: 4px; + padding-bottom: 4px; + color: var(--text-normal); +} +.side-dock-collapsible-section-header-indicator { + display: none; +} +.search-result-file-title:hover { + color: var(--text-normal); + background: transparent; +} +.workspace-leaf-content .search-input, +.workspace-leaf-content .search-input:hover, +.workspace-leaf-content .search-input:focus { + font-size: var(--font-adaptive-small); + padding: 7px 10px; + height: 28px; + border-radius: 5px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + transition: border-color 0.1s ease-in-out; +} +.workspace-leaf-content .search-input:hover { + border-color: var(--background-modifier-border-hover); + transition: border-color 0.1s ease-in-out; +} +.workspace-leaf-content .search-input:focus { + background: var(--background-primary); + border-color: var(--background-modifier-border-focus); + transition: all 0.1s ease-in-out; +} +.search-input-container input::placeholder { + color: var(--text-faint); + font-size: var(--font-adaptive-small); +} +.workspace-split.mod-root + .workspace-split.mod-vertical + .workspace-leaf-content { + padding-right: 0; +} +.workspace-split.mod-horizontal.mod-right-split { + width: 0; +} +.workspace-split.mod-vertical > .workspace-leaf { + padding-right: 1px; +} +.workspace-leaf-content[data-type='starred'] .item-list { + padding-top: 5px; +} +.workspace-leaf-content .view-content, +.workspace-split.mod-right-split .view-content { + padding: 0; +} + +/* --------------- */ +/* Nested items */ +/* --------------- */ + +.nav-folder-collapse-indicator, +.tree-item-self .collapse-icon { + color: var(--background-modifier-border-hover); +} +.tree-item-self .collapse-icon { + padding-left: 0; + width: 15px; + margin-left: -15px; +} +.outline .tree-item-self .collapse-icon { + margin-left: -20px; +} +.tag-container .collapse-icon { + margin-left: -20px; +} +.tree-item-self:hover .collapse-icon { + color: var(--text-normal); +} +.tree-item { + padding-left: 5px; +} +.tree-item-flair { + font-size: var(--font-adaptive-smaller); + right: 0; + background: transparent; + color: var(--text-faint); +} +.tree-item-flair-outer:after { + content: ''; +} +.tree-item-self.is-clickable { + cursor: var(--cursor); +} +.tree-item-self.is-clickable:hover { + background: transparent; +} +.tree-item-self:hover .tree-item-flair { + background: transparent; + color: var(--text-muted); +} +.tree-item-children { + margin-left: 5px; +} + +/* --------------- */ +/* Backlink pane */ +/* --------------- */ + +.outgoing-link-pane, +.backlink-pane { + padding-bottom: 30px; +} +.outgoing-link-pane .search-result-container, +.backlink-pane .search-result-container { + padding: 5px 5px 5px 5px; + margin-left: 0; +} +.outgoing-link-pane .search-result-file-title, +.backlink-pane .search-result-file-title { + padding-left: 15px; +} +.outgoing-link-pane .tree-item-icon, +.outgoing-link-pane > .tree-item-self .collapse-icon, +.backlink-pane > .tree-item-self .collapse-icon { + display: none; +} + +.tree-item-self.outgoing-link-item { + padding: 0; + margin-left: 5px; +} + +.outgoing-link-pane > .tree-item-self:hover, +.outgoing-link-pane > .tree-item-self, +.backlink-pane > .tree-item-self:hover, +.backlink-pane > .tree-item-self { + padding-left: 15px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: var(--font-adaptive-smaller); + font-weight: 500; + padding: 10px 7px 5px 10px; + background: transparent; +} + +.outgoing-link-pane > .tree-item-self.is-collapsed, +.backlink-pane > .tree-item-self.is-collapsed { + color: var(--text-faint); +} + +.outgoing-link-pane .search-result-file-match { + padding: 5px 0; + border: 0; +} +.outgoing-link-pane .search-result-file-match-destination-file { + background: transparent; +} +.search-result-file-match:hover + .search-result-file-match-destination-file:hover { + background: transparent; + color: var(--text-normal); +} + +/* --------------- */ +/* Tag pane */ +/* --------------- */ + +.tag-container { + padding: 10px 15px; +} +.tag-pane-tag-count { + margin-right: 10px; + color: var(--text-faint); +} +.pane-list-item-ending-flair { + background: transparent; +} +.tag-pane-tag { + padding: 2px 5px 2px 5px; + cursor: var(--cursor); +} +.tag-pane-tag:hover { + background: transparent; +} +.nav-file.is-active .nav-file-title:hover { + background: var(--background-tertiary) !important; +} +.nav-file.is-active > .nav-file-title { + background: var(--background-tertiary); +} + +/* --------------- */ +/* Status bar */ +/* --------------- */ + +.status-bar { + transition: color 0.2s linear; + color: var(--text-faint); + font-size: var(--font-adaptive-smaller); + border-top: var(--border-width) solid var(--background-divider); + line-height: 1; + max-height: 24px; +} +.minimal-status-off .status-bar { + background-color: var(--background-secondary); + border-width: var(--border-width); + padding: 2px 6px 4px; +} +.status-bar { + background-color: var(--background-primary); + z-index: 30; + border-top-left-radius: 5px; + width: auto; + position: absolute; + left: auto; + border: 0; + bottom: 0; + right: 0; + max-height: 26px; + padding: 2px 8px 6px 3px; +} +.sync-status-icon.mod-success, +.sync-status-icon.mod-working { + color: var(--text-faint); + cursor: var(--cursor); +} +.status-bar:hover, +.status-bar:hover .sync-status-icon.mod-success, +.status-bar:hover .sync-status-icon.mod-working { + color: var(--text-muted); + transition: color 0.2s linear; +} +.status-bar .plugin-sync:hover .sync-status-icon.mod-success, +.status-bar .plugin-sync:hover .sync-status-icon.mod-working { + color: var(--text-normal); +} +.status-bar-item-segment { + margin-right: 10px; +} +.status-bar-item { + cursor: var(--cursor) !important; +} +/* .status-bar-item.cMenu-statusbar-button:hover, +.status-bar-item.mod-clickable:hover, +.status-bar-item.plugin-sync:hover { + text-align: center; + background-color: var(--background-tertiary) !important; + border-radius: 4px; +} */ +.status-bar-item { + padding: 7px 4px; + margin: 0; +} +.status-bar-item, +.sync-status-icon { + display: flex; + align-items: center; +} +.status-bar-item.plugin-sync svg { + height: 15px; + width: 15px; +} + +/* --------------- */ +/* Workplace ribbon & sidedock icons */ +/* --------------- */ + +.workspace-ribbon { + flex: 0 0 42px; + padding-top: 7px; +} +.workspace-ribbon.mod-right { + right: 4px; + bottom: 0; + height: 32px; + padding-top: 6px; + position: absolute; + background: 0 0; + border: 0; +} +.workspace-ribbon-collapse-btn { + margin: 0; + padding: 5px 4px; + border-radius: 5px; +} +.mod-right .workspace-ribbon-collapse-btn { + background-color: var(--background-primary); +} +.mod-right .workspace-ribbon-collapse-btn:hover { + background-color: var(--background-tertiary); +} +.workspace-ribbon.mod-left .workspace-ribbon-collapse-btn, +.workspace-ribbon.mod-right .workspace-ribbon-collapse-btn { + opacity: 1; + position: fixed; + width: 26px; + display: flex; + align-items: center; + top: auto; + text-align: center; + bottom: 42px; + right: 15px; + z-index: 9; +} +.workspace-ribbon.mod-left .workspace-ribbon-collapse-btn { + left: 8px; +} +.side-dock-settings { + padding-bottom: 30px; +} +.workspace-ribbon-collapse-btn, +.view-action, +.side-dock-ribbon-tab, +.side-dock-ribbon-action { + cursor: var(--cursor); +} +.workspace-ribbon { + border-width: var(--border-width-alt); + border-color: var(--background-modifier-border); + background: var(--background-secondary); +} +.mod-right:not(.is-collapsed) ~ .workspace-split.mod-right-split { + margin-right: 0; +} +.side-dock-ribbon-action { + padding: 6px 0; +} +body.hider-frameless:not(.hider-ribbon):not(.is-fullscreen) .side-dock-actions { + padding-top: 24px; +} +body.hider-frameless:not(.hider-ribbon):not(.is-fullscreen) + .workspace-ribbon-collapse-btn { + margin: 0; + padding-top: 40px; +} +.workspace-ribbon.mod-right { + right: 7px; /* DO NOT CHANGE */ +} + +/* --------------- */ +/* Preview mode */ +/* --------------- */ + +.markdown-preview-view hr { + height: 1px; + border-width: 2px 0 0 0; +} +a[href*="obsidian://search"] +{ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='17' height='17' class='search'%3E%3Cpath fill='black' stroke='black' stroke-width='2' d='M42,6C23.2,6,8,21.2,8,40s15.2,34,34,34c7.4,0,14.3-2.4,19.9-6.4l26.3,26.3l5.6-5.6l-26-26.1c5.1-6,8.2-13.7,8.2-22.1 C76,21.2,60.8,6,42,6z M42,10c16.6,0,30,13.4,30,30S58.6,70,42,70S12,56.6,12,40S25.4,10,42,10z'%3E%3C/path%3E%3C/svg%3E"); +} +.theme-dark a[href*="obsidian://search"] +{ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='17' height='17' class='search'%3E%3Cpath fill='white' stroke='white' stroke-width='2' d='M42,6C23.2,6,8,21.2,8,40s15.2,34,34,34c7.4,0,14.3-2.4,19.9-6.4l26.3,26.3l5.6-5.6l-26-26.1c5.1-6,8.2-13.7,8.2-22.1 C76,21.2,60.8,6,42,6z M42,10c16.6,0,30,13.4,30,30S58.6,70,42,70S12,56.6,12,40S25.4,10,42,10z'%3E%3C/path%3E%3C/svg%3E"); +} + +/* Style settings to toggle link underlines */ +body:not(.links-int-on) a[href*="obsidian://"], +body:not(.links-int-on) .markdown-preview-view .internal-link, +body:not(.links-int-on) .markdown-source-view.mod-cm6 .cm-hmd-internal-link .cm-underline, +body:not(.links-ext-on) .external-link, +body:not(.links-ext-on) .markdown-source-view.mod-cm6 .cm-link .cm-underline { + text-decoration: none; +} + +.footnotes-list { + margin-block-start: -10px; + padding-inline-start: 20px; + font-size: var(--font-adaptive-small); +} +.footnotes-list p { + display: inline; + margin-block-end: 0; + margin-block-start: 0; +} +.footnote-ref a { + text-decoration: none; +} +.footnote-backref { + color: var(--text-faint); +} +iframe { + border: 0; +} +.markdown-preview-view .mod-highlighted { + transition: background-color 0.3s ease; + background-color: var(--text-selection); + color: inherit; +} + +/* Metadata */ + +.frontmatter-collapse-indicator.collapse-indicator { + display: none; +} +.frontmatter-container .tag { + font-size: var(--font-adaptive-smaller); +} +.frontmatter-container .frontmatter-alias { + color: var(--text-muted); +} +.frontmatter-container { + border: 1px solid var(--background-modifier-border); + font-size: 14px; + color: var(--text-muted); + padding: 6px 14px; + border-radius: 4px; + background-color: var(--background-primary-alt); + position: relative; + margin-top: 16px; +} + +/* Blockquotes */ + +.markdown-preview-view blockquote { + border-radius: 0; + border: solid var(--quote-opening-modifier); + border-width: 0px 0px 0px 2px; + background-color: transparent; + font-style: italic; + padding: 0 0 0 calc(var(--nested-padding) / 2); + margin-inline-start: var(--nested-padding); +} + +.cm-s-obsidian span.cm-quote { + font-style: italic; +} + +body:not(.default-font-color) .cm-s-obsidian span.cm-quote, +body:not(.default-font-color) .markdown-preview-view blockquote { + color: var(--green); +} + +/* --------------- +TEXT MARKINGS +--------------- */ + +/* Hashes */ + +span.cm-formatting { + color: var(--text-faint); +} + +/* Italics */ + +body:not(.default-font-color) em, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-1, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-2, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-3, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-4, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-5, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-6, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-1, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-2, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-3, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-4, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-5, +body:not(.default-font-color) .cm-s-obsidian .cm-em.cm-header.cm-header-6, +body:not(.default-font-color) .markdown-preview-section em, +body:not(.default-font-color) .cm-s-obsidian .cm-em { + font-style: italic; + color: var(--em-color); +} + +/* Bold */ + +body:not(.default-font-color) strong, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-1, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-2, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-3, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-4, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-5, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-6, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-1, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-2, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-3, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-4, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-5, +body:not(.default-font-color) .cm-s-obsidian .cm-strong.cm-header.cm-header-6, +body:not(.default-font-color) .cm-header.cm-header-3.cm-hmd-internal-link, +body:not(.default-font-color) .markdown-preview-section strong, +body:not(.default-font-color) .cm-s-obsidian .cm-strong { + color: var(--strong-color); +} + +/* Strikethrough */ + +del, +.cm-strikethrough { + text-decoration-color: var(--text-muted); + text-decoration-thickness: 2px !important; +} + +/* Tables */ + +.markdown-preview-view th { + font-weight: var(--bold-weight); + text-align: left; + border-top: none; +} +.markdown-preview-view th:last-child, +.markdown-preview-view td:last-child { + border-right: none; +} +.markdown-preview-view th:first-child, +.markdown-preview-view td:first-child { + border-left: none; + padding-left: 0; +} +.markdown-preview-view tr:last-child td { + border-bottom: none; +} + +/* Number Tables */ +.numbertable table { + counter-reset: section; +} +.numbertable table > tbody > tr > td:first-child::before { + counter-increment: section; + content: counter(section) '. '; +} + +/* Color rows */ +.color-rows tr:nth-child(even) { + background: var(--background-primary); +} +.color-rows tr:nth-child(odd) { + background: var(--background-secondary); +} + +/* Lists */ +ul { + padding-inline-start: var(--list-indent); +} +ol { + padding-inline-start: var(--list-indent); + margin-left: 0; + list-style: default; +} +.is-mobile ul > li:not(.task-list-item)::marker { + font-size: 0.8em; +} +.is-mobile .markdown-rendered ol, +.is-mobile .markdown-rendered ul { + padding-inline-start: var(--list-indent); +} +.is-mobile .markdown-rendered div > ol, +.is-mobile .markdown-rendered div > ul { + padding-inline-start: 2em; +} +.is-mobile .el-ol > ol, +.is-mobile .el-ul > ul { + margin-left: 0; +} +.cm-line:not(.HyperMD-codeblock) { + tab-size: var(--list-indent); +} +ul > li { + min-height: 1.4em; +} +ul > li::marker, +ol > li::marker { + color: var(--text-faint); +} +ol > li { + margin-left: 0em; +} + +/* --------------- */ +/* Code */ +/* --------------- */ + +.markdown-preview-view code { + color: var(--code-color); +} +.cm-inline-code { + color: var(--code-color) !important; +} +.theme-light :not(pre) > code[class*='language-'], +.theme-light pre[class*='language-'] { + background-color: var(--background-primary-alt); +} +.theme-light code[class*='language-'], +.theme-light pre[class*='language-'] { + text-shadow: none; +} +/* Horizontal scroll */ +code[class*='language-'], +pre[class*='language-'] { + text-align: left !important; + white-space: pre !important; + word-spacing: normal !important; + word-break: normal !important; + word-wrap: normal !important; + line-height: 1.5 !important; + -moz-tab-size: 4 !important; + -o-tab-size: 4 !important; + tab-size: 4 !important; + -webkit-hyphens: none !important; + -moz-hyphens: none !important; + -ms-hyphens: none !important; + hyphens: none !important; +} +pre[class*='language-'] { + overflow: auto !important; +} +/* ------------------ */ +pre .copy-code-button { + border-radius: 5px; + background-color: var(--background-secondary-alt); +} +pre .copy-code-button:hover { + background-color: var(--background-tertiary); +} +.markdown-preview-section .frontmatter code { + color: var(--text-muted); + font-size: var(--font-adaptive-small); +} +.cm-s-obsidian .hmd-fold-html-stub, +.cm-s-obsidian .hmd-fold-code-stub, +.cm-s-obsidian.CodeMirror .HyperMD-hover > .HyperMD-hover-content code, +.cm-s-obsidian .cm-formatting-hashtag, +.cm-s-obsidian .cm-inline-code, +.cm-s-obsidian .HyperMD-codeblock, +.cm-s-obsidian .HyperMD-hr, +.cm-s-obsidian .cm-hmd-frontmatter, +.cm-s-obsidian .cm-hmd-orgmode-markup, +.cm-s-obsidian .cm-formatting-code, +.cm-s-obsidian .cm-math, +.cm-s-obsidian span.hmd-fold-math-placeholder, +.cm-s-obsidian .CodeMirror-linewidget kbd, +.cm-s-obsidian .hmd-fold-html kbd .CodeMirror-code { + font-family: var(--font-monospace); +} +.cm-s-obsidian .cm-hmd-frontmatter { + font-size: var(--font-adaptive-small); + color: var(--text-muted); +} +.markdown-source-view.mod-cm6 .code-block-flair { + color: var(--text-muted); +} + +/* ------------------- */ +/* Atom coloring */ +/* Source: https://github.com/AGMStudio/prism-theme-one-dark */ +/* ------------------- */ + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: var(--atom-gray-1) !important; +} +.token.punctuation, +.cm-hmd-codeblock, +.cm-bracket { + color: var(--atom-gray-2) !important; +} +code[class*='language-'], +.token.selector, +.token.tag, +code .cm-property, +.cm-def { + color: var(--atom-red) !important; +} +.token.property, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.attr-name, +.token.deleted, +.cm-number { + color: var(--atom-orange) !important; +} +.token.string, +.token.char, +.token.attr-value, +.token.builtin, +.token.inserted, +.cm-hmd-codeblock.cm-string { + color: var(--atom-green) !important; +} +.token.operator, +.cm-operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: var(--atom-aqua) !important; +} +.token.atrule, +.token.keyword, +.cm-keyword { + color: var(--atom-purple) !important; +} +.token.function, +.token.macro.property, +.cm-variable { + color: var(--atom-blue) !important; +} +.token.class-name, +.cm-atom, +code .cm-tag, +.cm-type, +.theme-dark .cm-variable-2 { + color: var(--atom-yellow) !important; +} +.token.regex, +.token.important, +.token.variable { + color: var(--atom-purple) !important; +} +.token.important, +.token.bold { + font-weight: bold !important; +} +.token.italic { + font-style: italic !important; +} +.token.entity { + cursor: help !important; +} +pre.line-numbers { + position: relative !important; + padding-left: 3.8em !important; + counter-reset: linenumber !important; +} +pre.line-numbers > code { + position: relative !important; +} +.line-numbers .line-numbers-rows { + position: absolute !important; + pointer-events: none !important; + top: 0 !important; + font-size: 100% !important; + left: -3.8em !important; + width: 3em !important; + letter-spacing: -1px !important; + border-right: 0 !important; + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; +} +.line-numbers-rows > span { + pointer-events: none !important; + display: block !important; + counter-increment: linenumber !important; +} +.line-numbers-rows > span:before { + content: counter(linenumber) !important; + color: var(--syntax-gray-1) !important; + display: block !important; + padding-right: 0.8em !important; + text-align: right !important; +} +.cm-s-obsidian .HyperMD-codeblock { + line-height: 1.5 !important; +} +.markdown-source-view.mod-cm6.is-readable-line-width + .cm-editor + .HyperMD-codeblock.cm-line, +.mod-cm6 .cm-editor .HyperMD-codeblock.cm-line { + padding-left: 10px; + padding-right: 10px; +} +.markdown-source-view.mod-cm6 .code-block-flair { + font-size: var(--font-smaller); + padding: 5px 0; + color: var(--text-muted); +} + +/* --------------- */ +/* Popovers */ +/* --------------- */ + +.popover, +.popover.hover-popover { + min-height: 40px; + box-shadow: 0 20px 40px var(--background-modifier-box-shadow); + pointer-events: auto !important; + border: 1px solid var(--background-modifier-border); +} +.popover.hover-popover { + max-height: 40vh; +} +.popover .markdown-embed-link { + display: none; +} +.popover .markdown-embed .markdown-preview-view { + padding: 10px 20px 30px; +} +.popover.hover-popover .markdown-embed .markdown-embed-content { + max-height: none; +} +.popover.hover-popover.mod-empty { + padding: 20px 20px 20px 20px; + color: var(--text-muted); +} + +.popover.hover-popover .markdown-preview-view .table-view-table, +.popover.hover-popover .markdown-embed .markdown-preview-view { + font-size: 1.05em; +} + +.popover.hover-popover .markdown-embed h1, +.popover.hover-popover .markdown-embed h2, +.popover.hover-popover .markdown-embed h3, +.popover.hover-popover .markdown-embed h4 { + margin-top: 1rem; +} + +/* --------------- */ +/* Graphs */ + +/* Fill color for nodes */ +.graph-view.color-fill { + color: var(--text-muted); +} +/* Fill color for nodes on hover */ +.graph-view.color-fill-highlight { + color: var(--text-accent); +} +/* Stroke color for nodes */ +.graph-view.color-circle { + color: var(--text-accent); +} +/* Line color */ +.graph-view.color-line { + color: var(--background-modifier-border); +} +/* Line color on hover */ +.graph-view.color-line-highlight { + color: var(--text-accent); + border: 0; +} +/* Text color */ +.graph-view.color-text { + color: var(--text-normal); +} +.graph-view.color-fill-unresolved { + color: var(--text-faint); +} + +/* Full bleed (takes up full height) */ + +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='localgraph'] + .view-header, +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='graph'] + .view-header { + position: fixed; + background: transparent !important; + width: 100%; +} +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='localgraph'] + .view-content, +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='graph'] + .view-content { + height: 100%; +} +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='localgraph'] + .view-header-title, +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='graph'] + .view-header-title { + display: none; +} +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='localgraph'] + .view-actions, +body:not(.plugin-sliding-panes-rotate-header) + .workspace-leaf-content[data-type='graph'] + .view-actions { + background: transparent; +} +.mod-root .workspace-leaf-content[data-type='localgraph'] .graph-controls, +.mod-root .workspace-leaf-content[data-type='graph'] .graph-controls { + top: 30px; +} + +.mod-root .workspace-leaf-content[data-type='localgraph'] .graph-controls, +.mod-root .workspace-leaf-content[data-type='graph'] .graph-controls { + top: 30px; +} + +/* Graph controls */ + +.graph-control-section .tree-item-children { + padding-bottom: 15px; +} +.graph-control-section-header { + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: var(--font-adaptive-smallest); + color: var(--text-muted); +} +.graph-controls .search-input-container { + width: 100%; +} +.setting-item.mod-search-setting.has-term-changed .graph-control-search-button, +.graph-controls .graph-control-search-button { + display: none; +} +.graph-controls .setting-item-name { + font-size: var(--font-adaptive-small); +} +.graph-controls { + background: var(--background-primary); + border: none; + min-width: 240px; + left: 0; + top: 10px; + margin-bottom: 0; + padding: 10px 20px 10px 10px; + border-radius: 0; +} +.graph-controls input[type='text'], +.graph-controls input[type='range'] { + font-size: var(--font-adaptive-small); +} +.graph-controls .mod-cta { + width: 100%; + font-size: var(--font-adaptive-small); + padding: 5px; +} + +.mod-left-split .graph-controls { + background: var(--background-secondary); +} +input[type='range'] { + background-color: var(--background-modifier-border-hover); + height: 2px; + padding: 0 0px; + -webkit-appearance: none; + cursor: default; + margin: 0; + border-radius: 0px; +} +input[type='range']::-webkit-slider-runnable-track { + background: var(--background-modifier-border-hover); + height: 2px; + margin-top: 0px; +} +input[type='range']::-webkit-slider-thumb { + background: white; + border: 1px solid var(--background-modifier-border-hover); + height: 18px; + width: 18px; + border-radius: 16px; + margin-top: -5px; + transition: all 0.1s linear; + cursor: default; + box-shadow: 0 1px 1px 0px rgba(0, 0, 0, 0.05), + 0 2px 4px 0px rgba(0, 0, 0, 0.1); +} +input[type='range']::-webkit-slider-thumb:hover, +input[type='range']::-webkit-slider-thumb:active { + background: white; + border-width: 1; + border: 1px solid var(--background-modifier-border-focus); + box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.05), + 0 2px 3px 0px rgba(0, 0, 0, 0.2); + transition: all 0.1s linear; +} + +.local-graph-jumps-slider-container, +.workspace-split.mod-left-split .local-graph-jumps-slider-container, +.workspace-split.mod-right-split .local-graph-jumps-slider-container, +.workspace-fake-target-overlay .local-graph-jumps-slider-container { + background: transparent; + opacity: 0.6; + padding: 0; + left: 12px; + transition: opacity 0.2s linear; + height: auto; +} +.mod-root .local-graph-jumps-slider-container { + right: 0; + left: 0; + width: var(--line-width-adaptive); + max-width: var(--max-width); + margin: 0 auto; + top: 30px; +} +.workspace-split.mod-left-split .local-graph-jumps-slider-container:hover, +.workspace-split.mod-right-split .local-graph-jumps-slider-container:hover, +.workspace-fake-target-overlay .local-graph-jumps-slider-container:hover, +.local-graph-jumps-slider-container:hover { + opacity: 0.8; + transition: opacity 0.2s linear; +} + +/* --------------- */ +/* Settings */ +/* --------------- */ + +.horizontal-tab-content, +.vertical-tab-content { + background: var(--background-primary); + padding-bottom: 100px; +} +.vertical-tab-header, +.vertical-tab-content { + padding-bottom: 100px; +} +.plugin-list-plugins { + overflow: visible; +} +.community-theme-container, +.hotkey-settings-container { + height: auto; + overflow: visible; +} +.modal.mod-settings .vertical-tab-header { + background: var(--background-secondary); + padding-top: 5px; + padding-bottom: 25px; +} +.vertical-tab-header-group-title { + color: var(--text-faint); + font-size: 12px; + letter-spacing: 0.05em; + font-weight: var(--bold-weight); +} +.vertical-tab-nav-item { + padding: 4px 10px 4px 17px; + color: var(--text-muted); + border: none; + background: var(--background-secondary); + cursor: var(--cursor); + font-size: var(--font-small); + line-height: 1.4; +} +.vertical-tab-nav-item:hover, +.vertical-tab-nav-item.is-active { + color: var(--text-normal); +} +.setting-hotkey { + background-color: var(--background-modifier-border); + padding: 3px 10px 3px 10px; +} +.setting-hotkey.mod-empty { + background: transparent; +} +.dropdown { + border-color: var(--background-modifier-border); + background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'); +} +.theme-dark .dropdown { + background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'); +} + +/* --------------- */ +/* Publish */ +/* --------------- */ + +.modal.mod-publish { + max-width: 600px; + padding-left: 0; + padding-right: 0; + padding-bottom: 0; +} +.modal.mod-publish .modal-title { + padding-left: 20px; + padding-bottom: 10px; +} +.mod-publish .modal-content { + padding-left: 20px; + padding-right: 20px; +} +.mod-publish p { + font-size: var(--font-small); +} +.mod-publish .button-container, +.modal.mod-publish .modal-button-container { + margin-top: 0px; + padding: 10px; + border-top: 1px solid var(--background-modifier-border); + bottom: 0px; + background-color: var(--background-primary); + position: absolute; + width: 100%; + margin-left: -20px; + text-align: center; +} +.publish-changes-info { + padding: 0 0 15px; + margin-bottom: 0; + border-bottom: 1px solid var(--background-modifier-border); +} +.modal.mod-publish .modal-content .publish-sections-container { + max-height: none; + height: auto; + padding: 10px 20px 30px 0; + margin-top: 10px; + margin-right: -20px; + margin-bottom: 80px; +} +.publish-site-settings-container { + max-height: none; + height: auto; + margin-right: -20px; + margin-bottom: 80px; + overflow-x: hidden; +} +.publish-section-header { + padding-bottom: 15px; + border-width: 1px; +} +.password-item { + padding-left: 0; + padding-right: 0; +} +.publish-section-header-text { + font-weight: 600; + color: var(--text-normal); + cursor: var(--cursor); +} +.publish-section-header-text, +.publish-section-header-toggle-collapsed-button, +.publish-section-header-action, +.file-tree-item-header { + cursor: var(--cursor); +} +.publish-section-header-text:hover, +.publish-section-header-toggle-collapsed-button:hover, +.publish-section-header-action:hover { + color: var(--text-normal); + cursor: var(--cursor); +} +.mod-publish .u-pop { + color: var(--text-normal); +} +.publish-section-header-toggle-collapsed-button { + padding: 7px 0 0 3px; + width: 18px; +} +.mod-publish .file-tree-item { + margin-left: 20px; +} +.mod-publish .file-tree-item { + padding: 0; + margin-bottom: 2px; + font-size: var(--font-small); +} +.mod-publish .file-tree-item-checkbox { + filter: hue-rotate(0); +} +.mod-publish .file-tree-item.mod-deleted .flair, +.mod-publish .file-tree-item.mod-to-delete .flair { + background: transparent; + color: #ff3c00; + font-weight: 500; +} +.mod-publish .file-tree-item.mod-new .flair { + background: transparent; + font-weight: 500; + color: #13c152; +} +.mod-publish .site-list-item { + padding-left: 0; + padding-right: 0; +} + +/* --------------- */ +/* Scroll bars */ +/* --------------- */ + +::-webkit-scrollbar { + width: 7px !important; +} +::-webkit-scrollbar-track { + background-color: var(--background-primary); +} +::-webkit-scrollbar-thumb { + border-width: 0px 4px 6px 0px; + border-style: solid; + border-radius: 0 !important; + border-color: var(--background-primary); + background-color: var(--background-modifier-border); + min-height: 40px; +} +.modal .vertical-tab-header::-webkit-scrollbar-track, +.mod-left-split .workspace-tabs ::-webkit-scrollbar-track { + background-color: var(--background-secondary); +} +.modal .vertical-tab-header::-webkit-scrollbar-track-piece, +.mod-left-split .workspace-tabs ::-webkit-scrollbar-track-piece { + background-color: var(--background-secondary); +} +.modal .vertical-tab-header::-webkit-scrollbar-thumb, +.mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb { + border-color: var(--background-secondary); + background-color: var(--background-modifier-border); +} +.modal .vertical-tab-header::-webkit-scrollbar-thumb:hover, +.mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:hover, +::-webkit-scrollbar-thumb:hover { + background-color: var(--background-modifier-border-hover); +} +.modal .vertical-tab-header::-webkit-scrollbar-thumb:active, +.mod-left-split .workspace-tabs ::-webkit-scrollbar-thumb:active, +::-webkit-scrollbar-thumb:active { + background-color: var(--background-modifier-border-focus); +} + +/* -------------------------------------------------------------------------------- +Mobile styling +-------------------------------------------------------------------------------- */ + +.is-mobile { + --font-settings-title: 18px; + --font-settings: 16px; + --font-settings-small: 13px; + --input-height: 40px; +} +body.is-mobile { + padding: 0 !important; +} +.hider-tooltips .follow-link-popover { + display: none; +} +.is-mobile .workspace-drawer-tab-container > *, +body.is-mobile .view-header-title, +.is-mobile .allow-fold-headings.markdown-preview-view .markdown-preview-sizer, +.is-mobile .allow-fold-lists.markdown-preview-view .markdown-preview-sizer { + padding: 0; +} +.is-mobile .titlebar { + height: 0 !important; + padding: 0 !important; + position: relative !important; + border-bottom: none; +} +.is-mobile .horizontal-main-container { + background-color: var(--background-primary); +} +.is-mobile .safe-area-top-cover { + background-color: var(--background-primary); +} +.is-mobile .workspace { + border-radius: 0 !important; + transform: none !important; +} +.is-mobile .workspace-drawer:not(.is-pinned) { + width: 100vw; + max-width: 360pt; + border: none; + box-shadow: 0 5px 50px 5px rgba(0, 0, 0, 0.05); +} +.is-mobile .workspace-drawer.mod-left.is-pinned { + max-width: 280pt; +} +.is-mobile .workspace-drawer.mod-right.is-pinned { + max-width: 240pt; +} + +.is-mobile .workspace-drawer.mod-right.is-pinned { + border-right: none; +} +.is-mobile .workspace-leaf-content[data-type='starred'] .item-list { + padding-left: 5px; +} +.is-mobile .workspace-drawer-tab-option-item-title, +.is-mobile .workspace-drawer-active-tab-title { + font-size: var(--font-adaptive-small); +} +.is-mobile + .workspace-drawer-tab-option-item:hover + .workspace-drawer-tab-option-item-title, +.is-mobile + .workspace-drawer-active-tab-header:hover + .workspace-drawer-active-tab-title { + color: var(--text-normal); +} +.is-mobile + .workspace-drawer-active-tab-header:hover + .workspace-drawer-active-tab-back-icon { + color: var(--text-normal); +} +.is-mobile .nav-file-title, +.is-mobile .nav-folder-title, +.is-mobile .outline, +.is-mobile .tree-item-self, +.is-mobile .tag-container, +.is-mobile .tag-pane-tag { + font-size: var(--font-adaptive-small); + line-height: 1.5; + margin-bottom: 4px; +} +.is-mobile .backlink-pane > .tree-item-self, +.is-mobile .outgoing-link-pane > .tree-item-self { + font-size: var(--font-adaptive-smallest); +} +.is-mobile .tree-item-flair { + font-size: var(--font-adaptive-small); +} +.is-mobile .nav-files-container { + padding: 5px 5px 5px 5px; +} +.is-mobile .search-result-container { + padding-bottom: 20px; +} +.is-mobile .search-result-file-match-replace-button { + background-color: var(--background-tertiary); + color: var(--text-normal); +} +.is-mobile .search-result-file-matches, +.is-mobile .search-result-file-title { + font-size: var(--font-adaptive-small); +} + +/* Modal close button */ + +.modal-close-button { + top: 2px; + padding: 0; + cursor: var(--cursor); + font-size: 24px; + color: var(--text-faint); +} +.modal-close-button:hover { + color: var(--text-normal); +} +.modal-close-button:before { + font-family: Inter, sans-serif; + font-weight: 200; +} + +.is-mobile .modal-close-button { + display: block; + z-index: 2; + top: 0; + right: 12px; + padding: 4px; + font-size: 34px; + width: 34px; + height: 34px; +} +.is-mobile .modal-close-button:before { + font-weight: 300; + color: var(--text-muted); +} + +/* Folding on mobile */ + +.is-mobile .empty-state-action { + border-radius: 6px; + font-size: var(--font-adaptive-small); +} +.is-mobile .workspace-drawer-header { + padding: 5px 10px 0 20px; +} +body:not(.is-ios).is-mobile .workspace-drawer-ribbon { + padding: 5px; +} +.is-mobile .workspace-drawer-header-name { + font-weight: var(--bold-weight); + color: var(--text-normal); + font-size: 1.125em; + margin-top: 3px; +} +.is-mobile .workspace-drawer-header-info { + color: var(--text-faint); + font-size: var(--font-adaptive-smaller); + margin-bottom: 0; +} +.is-mobile .mod-left .workspace-drawer-header-info, +.is-mobile.hider-status .workspace-drawer-header-info { + display: none; +} +.is-mobile .workspace-drawer-active-tab-header { + margin: 2px 12px 2px; + padding: 8px 0 8px 8px; +} +.is-mobile .workspace-leaf-content .item-list, +.is-mobile .tag-container, +.is-mobile .backlink-pane { + padding-top: 10px; +} +.is-mobile .outgoing-link-pane, +.is-mobile .backlink-pane { + padding-left: 10px; +} +.workspace-drawer.mod-left .workspace-drawer-inner { + padding-left: 0; +} +.is-mobile .workspace-drawer-ribbon { + background: var(--background-secondary); + border-right: 1px solid var(--background-modifier-border); + z-index: 3; + flex-direction: column; + width: 70px; + padding: 15px 0; + margin-right: 0px; +} +.is-ios .is-pinned .workspace-drawer-ribbon { + padding: 30px 0 20px 0; +} +.is-mobile .side-dock-actions, +.is-mobile .side-dock-settings { + flex-direction: column; + border-radius: 15px; +} +.is-mobile .mod-left .workspace-drawer-header, +.is-mobile .mod-left .workspace-drawer-tab-container { + margin-left: 70px; +} +.is-mobile .workspace-drawer-ribbon .side-dock-ribbon-action { + padding: 9px 5px 2px 5px; + margin: 0 12px 4px; + border-radius: 8px; +} +.is-mobile .workspace-drawer-ribbon .side-dock-ribbon-action svg { + width: 22px; + height: 22px; +} +.is-mobile .workspace-drawer-ribbon .side-dock-ribbon-action:hover { + background-color: var(--background-tertiary); + box-shadow: 0 0 0px 1px var(--background-tertiary); +} +.is-mobile .workspace-drawer-active-tab-container { + z-index: 9999; + background-color: var(--background-primary); +} +.is-mobile .side-dock-actions, +.is-mobile .side-dock-settings { + display: flex; + align-content: center; + justify-content: center; + padding: 0; +} +.is-mobile .workspace-drawer.mod-left:not(.is-pinned) { + border-right: none; +} +.is-mobile .modal.mod-publish, +.is-mobile .modal.mod-community-plugin, +.is-mobile .modal.mod-settings { + width: 100vw; + max-height: 90vh; + padding: 0; +} +.is-mobile .vertical-tab-header-group:last-child, +.is-mobile .vertical-tab-content, +.is-mobile .minimal-donation { + padding-bottom: 70px !important; +} +.is-mobile .modal.mod-settings .vertical-tab-header:before { + content: 'Settings'; + font-weight: 600; + font-size: var(--font-settings); + position: sticky; + display: flex; + height: 54px; + margin-top: 8px; + align-items: center; + justify-content: center; + text-align: center; + border-bottom: 1px solid var(--background-modifier-border); + background: var(--background-primary); + left: 0; + top: 0; + right: 0; + z-index: 1; +} +.is-mobile .modal .vertical-tab-header-group-title { + padding: 15px 20px 10px 20px; + text-transform: uppercase; + letter-spacing: 0.05em; +} +.is-mobile .nav-buttons-container { + padding: 0 0 10px 15px; +} +.is-mobile + .workspace-leaf-content:not([data-type='search']) + .nav-buttons-container { + border-bottom: var(--border-width) solid var(--background-modifier-border); +} +.is-mobile input[type='text'] { + font-size: 14px; + height: var(--input-height); +} +.is-mobile .search-input-container input[type='text'] { + border-radius: 50px; + height: 40px; + padding: 10px 20px; + font-size: 14px; + -webkit-appearance: none; +} +.is-mobile .search-input-clear-button { + right: 15px; +} +.is-mobile .modal, +.is-mobile .prompt, +.is-mobile .suggestion-container { + width: 100%; + max-width: 100%; + padding: 10px; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; +} +.is-mobile .suggestion-container { + margin: 0 auto; + border: none; + left: 0; + right: 0; +} +.is-mobile .suggestion-item { + font-size: var(--font-adaptive-normal); + padding-left: 10px; + letter-spacing: 0.001px; +} +.is-mobile .prompt-results .suggestion-flair { + display: none; +} +.is-mobile input[type='text'].prompt-input, +.is-mobile input[type='text'].prompt-input:hover { + line-height: 2; + padding: 8px; + font-size: var(--font-adaptive-normal); +} +.is-mobile .search-input-container input::placeholder { + font-size: 14px; +} +.is-mobile .modal-setting-back-button { + padding: 20px; + background-color: var(--color-background); + box-shadow: none; +} +.is-mobile .hotkey-list-container .setting-command-hotkeys { + flex: unset; +} +.is-mobile + .markdown-preview-view + input[type='checkbox'].task-list-item-checkbox { + top: 6px; +} +.is-mobile .workspace-drawer { + border-width: var(--border-width); +} +.is-mobile .workspace-drawer-inner, +.is-mobile .workspace-drawer-active-tab-container { + background-color: var(--background-secondary); +} +.is-mobile .menu { + border: none; + width: 100%; + max-width: 100%; + left: 0 !important; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; +} +.is-ios .is-pinned .workspace-drawer-ribbon { + padding: 30px 0 20px 0; +} +.is-ios .workspace-drawer.is-pinned .workspace-drawer-header { + padding-top: 26px; +} +.is-mobile .workspace-split.mod-root { + background-color: var(--background-primary); +} +.is-ios .mod-root .workspace-leaf { + padding-top: 20px; +} +.is-ios + .mod-root + .workspace-split.mod-horizontal + .workspace-leaf:not(:first-of-type) { + padding-top: 0; +} +.is-mobile.focus-mode .view-actions { + opacity: 1; +} +.is-mobile .workspace-drawer-header-icon { + align-self: start; +} +.is-mobile .workspace-drawer-header-icon svg { + width: 22px; + height: 100%; +} +.is-mobile .workspace-drawer-tab-options { + padding-top: 10px; +} +.is-mobile .workspace-drawer-tab-option-item { + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; + margin: 0 10px; + padding: 8px 10px; + border-radius: 6px; +} +.is-mobile .nav-action-button svg { + width: 22px; + margin: 0; +} +.is-mobile .menu-item { + padding: 5px 10px; +} +.is-mobile .menu-item-icon { + margin-right: 10px; +} +.is-mobile .menu-item-icon svg { + width: 18px; + height: 18px; +} +.is-mobile .view-header-title { + font-size: 125%; +} +.is-mobile .view-action svg { + width: 22px; +} +.is-mobile .view-action { + padding: 5px 5px 4px; + margin: 0; + border-radius: 8px; +} +.is-mobile .workspace-leaf-content[data-type='search'] .nav-action-button, +.is-mobile .nav-action-button, +.is-mobile .workspace-drawer-header-icon { + padding: 5px 7px 0 !important; + margin: 5px 2px 2px 0; + text-align: center; + border-radius: 8px; + cursor: var(--cursor); +} +.is-mobile .nav-file-title.is-active { + box-shadow: 0 0 0px 3px var(--background-tertiary); +} +.pull-down-action { + top: 0; + left: 0; + right: 0; + width: 100%; + margin: 0 auto; + padding: 50px 0 20px; + text-align: center; + border-radius: 0; + border: none; + box-shadow: 0 5px 200px var(--background-modifier-box-shadow); +} +.is-mobile .menu-item.is-label { + color: var(--text-normal); + font-weight: var(--bold-weight); +} +.is-mobile .menu-item.is-label .menu-item-icon { + display: none; +} +.mobile-toolbar { + width: 100%; + text-align: center; + display: flex; + overflow: scroll; + background-color: var(--background-primary); + border-top: 1px solid var(--background-modifier-border); +} +.is-mobile .modal.mod-settings .vertical-tab-content-container { + border: 0; +} +.is-mobile .modal, +.is-mobile .modal-bg { + transition: none !important; + transform: none !important; +} +.is-mobile .document-search-container { + height: 56px; + padding: 10px 15px; +} +.is-mobile .document-search-container input[type='text'] { + width: auto; + margin: 0 5px 0 0; + height: 32px; + padding: 5px 7px; + border-radius: 6px; + border: 1px solid var(--background-modifier-border); + background-color: var(--background-primary); +} +.is-mobile .document-search-container button { + width: auto; + margin: 0px; + background: transparent; + font-size: 14px; + height: 32px; +} +.is-mobile .modal .vertical-tab-header-group:last-child, +.is-mobile .modal .vertical-tab-content { + padding-bottom: 70px !important; +} +.pull-out-action { + top: 0; + height: 100vh; + padding: 30px 10px; + background: transparent; + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; +} +.is-mobile .markdown-preview-view pre { + overflow-x: scroll; +} + +/* Sync */ + +.is-mobile .sync-history-list { + padding: 10px; + background-color: var(--background-primary); +} +.is-mobile .sync-history-list-item { + font-size: var(--font-adaptive-small); + padding: 8px 10px; +} +.is-mobile .sync-history-content-container .modal-button-container { + padding: 5px 10px 30px 10px; +} +.is-mobile .sync-history-content { + outline: none; + -webkit-appearance: none; + border: 0; + background-color: var(--background-secondary); +} +.is-mobile.show-mobile-hamburger .view-header-icon .three-horizontal-bars { + opacity: 1; +} +.is-mobile.show-mobile-hamburger .view-header .view-header-title-container { + left: 50px; +} +.is-mobile.plugin-sliding-panes .view-header-title { + mask-image: unset; + -webkit-mask-image: unset; +} +.is-mobile.plugin-sliding-panes-rotate-header .view-header-title { + line-height: 1.2; +} +.is-mobile .workspace-drawer-header-name-text { + white-space: nowrap; + margin-right: 10px; +} +.is-mobile .mod-community-theme .modal-title { + padding: 10px 20px; +} +.is-mobile .mod-publish .modal-content { + display: unset; + padding: 10px 10px 10px; + margin-bottom: 120px; + overflow-x: hidden; +} +.is-mobile .mod-publish .button-container, +.is-mobile .modal.mod-publish .modal-button-container { + padding: 10px 15px 30px; + margin-left: 0px; + left: 0; +} +.is-mobile .modal.mod-publish .modal-title { + padding: 10px 20px; + margin: 0 -10px; + border-bottom: 1px solid var(--background-modifier-border); +} +.is-mobile .publish-site-settings-container { + margin-right: 0; + padding: 0; +} +.is-mobile .modal.mod-publish .modal-content .publish-sections-container { + margin-right: 0; + padding-right: 0; +} + +/* --------------- */ +/* Phone styling */ +/* --------------- */ + +@media (max-width: 400pt) { + .is-mobile.show-mobile-hamburger .view-header-icon { + display: block; + } + .is-mobile .suggestion-hotkey { + display: none; + } + .is-mobile .modal, + .is-mobile .menu, + .is-mobile .prompt { + border-radius: 0; + } + .is-mobile .suggestion-flair { + right: 0; + left: auto; + position: absolute; + padding: 5px 5px 0 0; + } + .is-mobile .prompt { + border-radius: 0; + padding-top: 5px; + padding-bottom: 0; + max-height: calc(100vh - 120px); + top: 120px; + } + .is-mobile .suggestion-container { + max-height: 200px; + border-top: 1px solid var(--background-modifier-border); + border-radius: 0; + padding-top: 0; + box-shadow: none; + } + .is-mobile .suggestion-container .suggestion { + padding-top: 10px; + } + .workspace-drawer-header-icon .pin { + display: none; + } + /* + .is-mobile .markdown-source-view .cm-scroller > .cm-content { + margin-top:15px; + } */ + .is-ios .workspace-drawer .workspace-drawer-header { + padding-top: 40px; + } + .is-ios .mod-root .workspace-leaf { + padding-top: 40px; + } + .is-mobile .workspace .workspace-drawer-backdrop { + margin-top: -40px; + height: calc(100vh + 50px); + z-index: 9; + } + .is-mobile .modal .vertical-tab-header-group-title { + padding: 20px 20px 10px; + } + .is-mobile .modal .vertical-tab-nav-item { + padding: 3px 20px; + } + .is-ios .workspace-drawer-ribbon { + padding: 40px 0 20px 0; + } + .is-mobile .view-header-title { + max-width: 80vw; + } + .is-mobile .view-header-title { + padding-right: 20px; + font-size: 18px; + } + .is-mobile .workspace-drawer-header-name-text { + font-size: var(--font-settings-title); + letter-spacing: -0.015em; + } + .is-mobile .menu-item.is-label { + font-size: 18px; + } + .is-mobile .view-header { + border-bottom: var(--border-width) solid var(--background-modifier-border) !important; + } + .is-mobile .modal-setting-back-button { + border-bottom: 1px solid var(--background-modifier-border); + } + .is-mobile .installed-plugins-container { + max-width: 100%; + overflow: hidden; + } + .is-mobile .setting-item-info { + flex: 1 1 auto; + } + .is-mobile .kanban-plugin__board-settings-modal .setting-item-control, + .is-mobile .setting-item-control { + flex: 1 0 auto; + margin-right: 0; + min-width: auto; + } + .is-mobile .checkbox-container { + flex: 1 0 40px; + max-width: 40px; + } + .is-mobile .setting-item-description { + word-break: break-word; + white-space: pre-line; + } + .is-mobile .view-action { + padding: 3px 0 0 4px; + margin-top: -4px; + } + .is-mobile .menu { + padding-bottom: 30px; + } + .is-mobile .frontmatter-container .tag, + .is-mobile .cm-s-obsidian span.cm-hashtag, + .is-mobile .tag { + font-size: var(--font-adaptive-smaller); + } + .is-mobile .setting-item-control select, + .is-mobile .setting-item-control input, + .is-mobile .setting-item-control button { + margin-bottom: 5px; + } + .is-mobile .setting-item-control input[type='range'] { + margin-bottom: 10px; + } + .is-mobile .publish-section-header, + .is-mobile .publish-changes-info { + flex-wrap: wrap; + border: none; + } + .is-mobile .publish-changes-info .publish-changes-add-linked-btn { + flex-basis: 100%; + margin-top: 10px; + } + .is-mobile .publish-section-header-text { + flex-basis: 100%; + margin-bottom: 10px; + margin-left: 20px; + margin-top: -8px; + } + .is-mobile .publish-section { + background: var(--background-secondary); + border-radius: 10px; + padding: 12px 12px 1px; + } + .is-mobile .publish-changes-switch-site { + flex-grow: 0; + margin-right: 10px; + } +} + +/* ---------------- */ +/* Mobile toolbar button */ +/* ---------------- */ + +body.is-mobile:not(.floating-button-off):not(.advanced-toolbar) + .view-action:nth-last-of-type(5), +body.is-mobile:not(.floating-button-off):not(.advanced-toolbar) + .view-action:nth-last-of-type(4) { + color: white; + background-color: var(--blue); + opacity: 1; + top: calc(100vh - 90px); + display: flex; + padding: 5px; + position: fixed; + left: 87vw; + transform: translate(-40%, -18%); + justify-content: center; + align-items: center; + width: 53px; + height: 53px; + border-radius: 50% !important; + box-shadow: 0.9px 0.9px 3.6px rgba(0, 0, 0, 0.07), + 2.5px 2.4px 10px rgba(0, 0, 0, 0.1), 6px 5.7px 24.1px rgba(0, 0, 0, 0.13), + 20px 19px 80px rgba(0, 0, 0, 0.2); +} + +body.is-mobile:not(.floating-button-off).advanced-toolbar + .view-action:nth-last-of-type(5), +body.is-mobile:not(.floating-button-off).advanced-toolbar + .view-action:nth-last-of-type(4) { + color: white; + background-color: var(--blue); + opacity: 1; + position: fixed; + top: calc(100vh - 138px); + display: flex; + padding: 5px; + left: 87vw; + transform: translate(-40%, -18%); + justify-content: center; + align-items: center; + width: 53px; + height: 53px; + border-radius: 50% !important; + box-shadow: 0.9px 0.9px 3.6px rgba(0, 0, 0, 0.07), + 2.5px 2.4px 10px rgba(0, 0, 0, 0.1), 6px 5.7px 24.1px rgba(0, 0, 0, 0.13), + 20px 19px 80px rgba(0, 0, 0, 0.2); +} + +/* --------------- */ +/* Tablet styling */ +/* --------------- */ + +@media (min-width: 400pt) { + .mobile-toolbar-option { + border-radius: 8px; + margin: 6px 0; + } + .mobile-toolbar-option:hover { + background-color: var(--background-tertiary); + } + + .is-mobile.is-ios .safe-area-top-cover { + background-color: transparent; + } + .is-mobile .modal, + .is-mobile .modal-container .modal.mod-settings { + max-width: 800px; + transform: translateZ(0); + border-top-left-radius: 20px !important; + border-top-right-radius: 20px !important; + margin-bottom: -15px; + overflow: hidden; + } + .is-mobile .modal-container .modal.mod-settings .vertical-tabs-container { + transform: translateZ(0); + } + .is-mobile .view-action { + padding: 5px 5px 4px; + border-radius: 8px; + } + .is-mobile .view-action:hover, + .is-mobile .nav-action-button:hover, + .is-mobile + .workspace-leaf-content[data-type='search'] + .nav-action-button.is-active:hover, + .is-mobile + .workspace-leaf-content[data-type='backlink'] + .nav-action-button.is-active:hover, + .is-mobile .workspace-drawer-tab-option-item:hover, + .is-mobile .workspace-drawer-header-icon:hover { + background-color: var(--background-tertiary); + box-shadow: 0 0 0 2px var(--background-tertiary); + } + .is-mobile .prompt { + max-width: 600px; + max-height: 600px; + bottom: auto !important; + border-radius: 20px; + top: 100px !important; + } + .is-mobile .suggestion-container { + max-width: 600px; + max-height: 600px; + border-radius: 20px; + bottom: 80px; + border: 1px solid var(--background-modifier-border); + } + .is-mobile .modal-container .suggestion-item { + padding: 10px 5px 10px 10px; + border-radius: 8px; + } + .is-mobile .suggestion-flair { + right: 0; + left: auto; + position: absolute; + padding: 10px; + } + .is-mobile .menu { + top: 60px !important; + right: 0 !important; + bottom: auto; + left: auto; + margin: 0 auto; + width: 360px; + padding: 10px 10px 20px; + border-radius: 15px; + box-shadow: 0 0 100vh 100vh rgba(0, 0, 0, 0.5); + } + /* Animations */ + .is-mobile .menu, + .is-mobile .suggestion-container, + .is-mobile .modal, + .is-mobile .prompt { + transition: unset !important; + transform: unset !important; + animation: unset !important; + } + .is-mobile .modal-container .modal-bg { + opacity: 0.8 !important; + } + .is-mobile .modal-container .prompt { + opacity: 1 !important; + } + .is-mobile .menu .menu-item:hover { + background-color: var(--background-tertiary); + } + .is-mobile .setting-item:not(.mod-toggle):not(.setting-item-heading) { + flex-direction: row; + align-items: center; + } + .is-mobile .setting-item-control select, + .is-mobile .setting-item-control input, + .is-mobile .setting-item-control button { + width: auto; + } + .is-mobile .workspace-drawer:not(.is-pinned) { + margin: 30px 16px 0; + height: calc(100vh - 48px); + border-radius: 15px; + } + .is-mobile + .setting-item:not(.mod-toggle):not(.setting-item-heading) + .setting-item-control { + width: auto; + margin-top: 0; + } + .is-mobile .modal .search-input-container input { + width: 100%; + } + .pull-down-action { + width: 400px; + top: 15px; + padding: 15px; + border-radius: 15px; + } +} + +/*---------------------------------------------------------------- +PLUGINS +----------------------------------------------------------------*/ + +/* --------------- */ +/* Sliding Panes */ +/* --------------- */ + +body.plugin-sliding-panes-rotate-header { + --header-width: 40px; +} +body.plugin-sliding-panes-rotate-header .view-header-title:before { + display: none; +} +body.plugin-sliding-panes .workspace-split.mod-root { + background-color: var(--background-primary); +} +body.plugin-sliding-panes .mod-horizontal .workspace-leaf { + box-shadow: none !important; +} +body.plugin-sliding-panes:not(.is-fullscreen) + .workspace-split.is-collapsed + ~ .workspace-split.mod-root + .view-header { + transition: padding 0.1s ease; +} +body.plugin-sliding-panes .view-header-title:before { + background: 0 0; +} +body.plugin-sliding-panes .view-header { + background: 0 0; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf.mod-active + > .workspace-leaf-content + > .view-header { + border: none; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header { + border: none; + text-orientation: sideways; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + .view-header-icon { + padding: 4px 1px; + margin: 5px 0 0 0; + left: 0; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + .view-actions { + padding-bottom: 33px; + margin-left: 0; + height: auto; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + .view-action { + margin: 3px 0; + padding: 4px 1px; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .app-container + .workspace + > .mod-root + > .workspace-leaf.mod-active + > .workspace-leaf-content + > .view-header + > .view-header-title-container:before, +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + > .view-header-title-container:before { + background: 0 0 !important; +} +.workspace + > .mod-root + .view-header-title-container + body.plugin-sliding-panes.plugin-sliding-panes-rotate-header.plugin-sliding-panes-header-alt + .workspace + > .mod-root + .view-header-title { + margin-top: 0; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + .view-header-title-container { + margin-left: 0; + padding-top: 0; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .view-header-title-container { + position: static; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .app-container + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + > div { + margin-left: 0; + bottom: 0; +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header .view-header-icon { + opacity: var(--icon-muted); +} +body.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .view-header-icon:hover { + opacity: 1; +} +body.plugin-sliding-panes .workspace-split.mod-vertical > .workspace-leaf, +body.plugin-sliding-panes-stacking .workspace > .mod-root > .workspace-leaf { + box-shadow: 0 0 0 1px var(--background-modifier-border), + 1px 0 15px 0 var(--shadow-color) !important; +} +body.is-mobile.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + .view-header-icon { + height: 30px; +} +body.hider-ribbon.plugin-sliding-panes.plugin-sliding-panes-rotate-header + .workspace + > .mod-root + > .workspace-leaf + > .workspace-leaf-content + > .view-header + .view-actions { + padding-bottom: 50px; +} +body.plugin-sliding-panes.is-fullscreen .view-header-icon { + padding-top: 8px; +} +body.plugin-sliding-panes .mod-root .graph-controls { + top: 20px; + left: 30px; +} + +/* --------------- */ +/* Hider */ +/* --------------- */ + +.hider-ribbon:not(.is-mobile) .workspace-ribbon-collapse-btn { + display: none; +} +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-right { + pointer-events: none; +} +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-left { + position: absolute; + border-right: 0px; + margin: 0; + height: var(--header-height); + overflow: visible; + flex-basis: 0; + bottom: 0; + top: auto; + display: flex !important; + flex-direction: row; + z-index: 17; + opacity: 0; + transition: opacity 0.25s ease-in-out; + filter: drop-shadow(2px 10px 30px rgba(0, 0, 0, 0.2)); +} +.hider-ribbon:not(.is-mobile) .side-dock-actions, +.hider-ribbon:not(.is-mobile) .side-dock-settings { + display: flex; + border-top: var(--border-width) solid var(--background-modifier-border); + background: var(--background-secondary); + margin: 0; + position: relative; +} +.hider-ribbon:not(.is-mobile) .side-dock-actions { + padding-left: 5px; +} +.hider-ribbon:not(.is-mobile) .side-dock-settings { + border-right: var(--border-width) solid var(--background-modifier-border); + border-top-right-radius: 5px; + padding-right: 10px; +} +.hider-ribbon:not(.is-mobile) + .workspace-ribbon.mod-left + .side-dock-ribbon-action { + display: flex; + padding: 4px; + margin: 6px 0px 5px 10px; +} +.hider-ribbon:not(.is-mobile) .workspace-ribbon.mod-left:hover { + opacity: 1; + transition: opacity 0.25s ease-in-out; +} +.hider-ribbon:not(.is-mobile) + .workspace-ribbon.mod-left + .workspace-ribbon-collapse-btn { + border-top: 1px solid var(--background-modifier-border); +} +.hider-ribbon:not(.is-mobile) .workspace-split.mod-left-split { + margin: 0; +} +.hider-ribbon:not(.is-mobile) .workspace-leaf-content .item-list { + padding-bottom: 40px; +} +.hider-ribbon .workspace-ribbon { + padding: 0; +} + +/* --------------- */ +/* View Headers & Actions */ +/* --------------- */ + +.view-header { + align-items: center; +} +.view-actions { + margin-right: 0px; + margin-left: auto; + transition: opacity 0.25s ease-in-out; +} +.view-actions .view-action { + margin-right: 8px; +} +.view-action.is-active { + color: var(--text-faint); + opacity: 1; +} +.view-actions .view-action:last-child { + margin-left: 2px; +} + +/* Frameless mode on macOS only */ + +.hider-frameless:not(.is-mobile) + .workspace-split.mod-right-split + > .workspace-tabs, +.hider-frameless:not(.is-mobile) .workspace-split.mod-root .view-header { + padding-top: 2px; +} +.hider-frameless:not(.is-mobile) + .workspace-split.mod-left-split + > .workspace-tabs { + padding-top: 24px; +} +.hider-frameless:not(.is-mobile) + .workspace-split.mod-right-split + > .workspace-tabs + ~ .workspace-tabs, +.hider-frameless:not(.is-mobile) + .workspace-split.mod-left-split + > .workspace-tabs + ~ .workspace-tabs { + padding-top: 0px; +} +.hider-frameless.is-fullscreen:not(.is-mobile) + .workspace-split.mod-left-split + > .workspace-tabs, +.hider-frameless.is-fullscreen:not(.is-mobile) + .workspace-split.mod-root + .view-header { + padding-top: 0px; +} + +/* Title bar / traffic light icons */ +/* TODO: fix for Live Preview */ +.mod-macos.hider-frameless.hider-ribbon:not(.plugin-sliding-panes-rotate-header) { + --traffic-space: 80px; + --traffic-padding: 60px; +} +.mod-macos.hider-frameless:not(.plugin-sliding-panes-rotate-header) { + --traffic-space: 55px; + --traffic-padding: 20px; +} +.mod-macos.hider-frameless.hider-ribbon:not(.plugin-sliding-panes-rotate-header) { + --traffic-space: 95px; + --traffic-padding: 60px; +} +.mod-macos.hider-frameless:not(.plugin-sliding-panes-rotate-header) { + --traffic-space: 65px; + --traffic-padding: 20px; +} +.mod-macos.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) + .workspace-split.mod-left-split.is-collapsed + + .mod-root + .workspace-leaf:first-of-type + .workspace-leaf-content:not([data-type='graph']) + .view-header-icon { + margin-left: var(--traffic-padding); +} + +body:not(.plugin-sliding-panes-rotate-header) + .app-container + .workspace-split.mod-root + > .workspace-leaf + .view-header { + transition: height linear 0.1s; +} + +:root { + --traffic-x-space: 0px; +} +.mod-macos.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) + .workspace-split.mod-left-split.is-collapsed + + .mod-root + .workspace-leaf:first-of-type + .view-header-title-container { + max-width: calc(100% - (var(--traffic-x-space) * 2) - 30px); +} +.mod-macos.is-popout-window.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) + .mod-root + .workspace-leaf:first-of-type + .view-header-title-container { + max-width: calc(100% - (var(--traffic-x-space) * 2) - 30px); +} +.mod-macos.hider-ribbon.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) + .workspace-split.mod-left-split.is-collapsed + + .mod-root + .workspace-leaf:first-of-type { + --traffic-x-space: 64px; +} +.mod-macos.is-popout-window.hider-ribbon.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) + .mod-root + .workspace-leaf:first-of-type { + --traffic-x-space: 64px; +} +.mod-macos.hider-frameless:not(.is-fullscreen):not(.plugin-sliding-panes-rotate-header) + .workspace-split.mod-left-split.is-collapsed + + .mod-root + .workspace-leaf:first-of-type { + --traffic-x-space: 22px; +} +.mod-macos.hider-frameless .workspace-ribbon { + border: none; +} + +/* --------------- */ +/* Calendar */ +/* --------------- */ + +.workspace-leaf-content[data-type='calendar'] .view-content { + padding: 5px 0 0 0; +} +#calendar-container { + padding: 5px 15px; + --color-background-day-empty: var(--background-secondary-alt); + --color-background-day-active: var(--background-tertiary); + --color-background-day-hover: var(--background-tertiary); + --color-dot: var(--text-faint); + --color-text-title: var(--text-normal); + --color-text-heading: var(--text-muted); + --color-text-day: var(--text-normal); + --color-text-today: var(--text-normal); + --color-arrow: var(--text-faint); + --color-background-day-empty: transparent; +} +#calendar-container .table { + border-collapse: separate; + table-layout: fixed; +} +#calendar-container h2 { + font-size: var(--h2); + font-weight: 400; +} +.mod-root #calendar-container { + width: var(--line-width-adaptive); + max-width: var(--max-width); + margin: 0 auto; + padding: 0; +} +#calendar-container h2 .arrow { + color: var(--text-faint); + cursor: var(--cursor); +} +#calendar-container .arrow:hover { + fill: var(--text-muted); + color: var(--text-muted); +} +#calendar-container tr th { + padding: 2px 0; + font-weight: 500; +} +#calendar-container tr td { + padding: 2px 0 0; + border-radius: 4px; + cursor: var(--cursor); + border: 2px solid transparent; + transition: none; +} +#calendar-container .nav { + padding: 0; + margin: 10px 5px 10px 5px; +} +#calendar-container .dot { + margin: 0; +} +#calendar-container .arrow { + cursor: var(--cursor); +} +#calendar-container .arrow:hover svg { + color: var(--text-muted); +} +#calendar-container .reset-button { + font-size: var(--font-adaptive-smaller); +} +#calendar-container .reset-button:hover { + color: var(--text-normal); +} +#calendar-container .title { + font-size: var(--h1); +} + +#calendar-container .month, +#calendar-container .title { + font-size: var(--font-adaptive-normal); + font-weight: 600; +} +#calendar-container .today { + color: var(--text-accent); + font-weight: 600; +} +#calendar-container .today .dot { + fill: var(--text-accent); +} +#calendar-container .active .task { + stroke: var(--text-faint); +} +#calendar-container .active { + color: var(--text-normal); +} + +#calendar-container .reset-button, +#calendar-container .day { + cursor: var(--cursor); +} +#calendar-container .active, +#calendar-container .active.today, +#calendar-container .week-num:hover, +#calendar-container .day:hover { + background-color: var(--color-background-day-active); +} +#calendar-container .active .dot { + fill: var(--text-faint); +} +#calendar-container .active .task { + stroke: var(--text-faint); +} +#calendar-container .year { + color: var(--text-normal); +} + +/* --------------- */ +/* Kanban */ +/* --------------- */ + +body .kanban-plugin__markdown-preview-view { + font-family: var(--text); +} + +body .workspace-leaf-content[data-type='kanban'] .view-header-title-container { + text-align: center; +} +body .kanban-plugin { + --interactive-accent: var(--text-selection); + --interactive-accent-hover: var(--background-tertiary); + --text-on-accent: var(--text-normal); + background-color: var(--background-primary); +} +body .kanban-plugin__board > div { + margin: 0 auto; +} +body .kanban-plugin__checkbox-label { + font-size: var(--font-adaptive-small); + color: var(--text-muted); +} +body .kanban-plugin__item-markdown ul { + margin: 0; +} +body .kanban-plugin__item-content-wrapper { + box-shadow: none; +} +body .kanban-plugin__grow-wrap > textarea, +body .kanban-plugin__grow-wrap::after { + padding: 0; + border: 0; +} +body .kanban-plugin__grow-wrap > textarea, +body .kanban-plugin__grow-wrap::after, +body .kanban-plugin__item-title p { + font-size: calc(var(--preview-font-size) - 2px); +} +body:not(.is-mobile) .kanban-plugin__grow-wrap > textarea:focus { + box-shadow: none; +} +.kanban-plugin__item-input-actions button, +.kanban-plugin__lane-input-actions button { + font-size: var(--font-adaptive-small); +} +body .kanban-plugin__item { + background-color: var(--background-primary); +} +body .kanban-plugin__lane-header-wrapper { + border-bottom: 0; +} +body .kanban-plugin__lane-header-wrapper .kanban-plugin__grow-wrap > textarea, +body .kanban-plugin__lane-input-wrapper .kanban-plugin__grow-wrap > textarea { + background: transparent; + color: var(--text-normal); + font-size: 0.875rem; + font-weight: 600; +} +body .kanban-plugin__item-input-wrapper { + border: 0; +} +body .kanban-plugin__item-input-wrapper .kanban-plugin__grow-wrap > textarea { + padding: 6px 8px; + border: 1px solid var(--background-modifier-border); +} +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled, +body .kanban-plugin__item .kanban-plugin__item-edit-archive-button, +body .kanban-plugin__item button.kanban-plugin__item-edit-button, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button, +.kanban-plugin__item-settings-actions > button, +.kanban-plugin__lane-action-wrapper > button { + background: transparent; + transition: color 0.1s ease-in-out; +} +body .kanban-plugin__item .kanban-plugin__item-edit-archive-button:hover, +body .kanban-plugin__item button.kanban-plugin__item-edit-button.is-enabled, +body .kanban-plugin__item button.kanban-plugin__item-edit-button:hover, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button.is-enabled, +body .kanban-plugin__lane button.kanban-plugin__lane-settings-button:hover { + color: var(--text-normal); + transition: color 0.1s ease-in-out; + background: transparent; +} +body .kanban-plugin__new-lane-button-wrapper { + position: fixed; + bottom: 30px; +} +body .kanban-plugin button { + box-shadow: none; + cursor: var(--cursor); +} +body .kanban-plugin__item-button-wrapper > button { + font-size: var(--font-adaptive-small); + color: var(--text-muted); + background: transparent; +} +body .kanban-plugin__item-button-wrapper > button:hover { + color: var(--text-normal); + background: var(--background-tertiary); +} +body .kanban-plugin__item-button-wrapper { + padding-top: 5px; + border-top: none; +} + +body .kanban-plugin__lane-setting-wrapper > div:last-child { + border: none; + margin: 0; +} + +body .kanban-plugin__item.is-dragging { + box-shadow: 0 5px 30px rgba(0, 0, 0, 0.15), 0 0 0 2px var(--text-selection); +} +body .kanban-plugin__lane.is-dragging { + box-shadow: 0 5px 30px rgba(0, 0, 0, 0.15); + border: 1px solid var(--background-modifier-border); +} + +body .kanban-plugin__lane { + background: var(--background-secondary); + padding: 0; + border-radius: 8px; + border: 1px solid transparent; +} +body .kanban-plugin__lane-items { + padding-bottom: 0; + margin: 0; + background-color: var(--background-secondary); +} + +body + .kanban-plugin__markdown-preview-view + ol.contains-task-list + .contains-task-list, +body + .kanban-plugin__markdown-preview-view + ul.contains-task-list + .contains-task-list, +body .kanban-plugin__markdown-preview-view ul, +.kanban-plugin__markdown-preview-view ol { + padding-inline-start: 24px !important; +} + +@media (max-width: 400pt) { + .kanban-plugin__board { + flex-direction: column !important; + } + + .kanban-plugin__lane { + width: 100% !important; + margin-bottom: 1rem !important; + } +} + +/* --------------- */ +/* Todoist */ +/* --------------- */ + +.todoist-query-title { + display: inline !important; +} +.todoist-refresh-spin { + animation: spin 1s linear infinite; +} +.todoist-refresh-button { + display: inline; + float: right; + margin-left: 8px; + padding: 3px 10px; +} +.todoist-refresh-button:hover { + background-color: var(--background-tertiary); +} +@-webkit-keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + } +} + +/* READER VIEW */ + +.markdown-preview-view + ul + > li.task-list-item + .todoist-p1 + > input[type='checkbox'] { + border: 1px solid #ff757f !important; + background-color: rgba(255, 117, 127, 0.25) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p1 + > input[type='checkbox']:hover { + background-color: rgba(255, 117, 127, 0.5) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p2 + > input[type='checkbox'] { + border: 1px solid #ffc777 !important; + background-color: rgba(255, 199, 119, 0.25) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p2 + > input[type='checkbox']:hover { + background-color: rgba(255, 199, 119, 0.5) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p3 + > input[type='checkbox'] { + border: 1px solid #65bcff !important; + background-color: rgba(101, 188, 255, 0.25) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p3 + > input[type='checkbox']:hover { + background-color: rgba(101, 188, 255, 0.5) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p4 + > input[type='checkbox'] { + border: 1px solid #b4c2f0 !important; + background-color: rgba(180, 194, 240, 0.25) !important; +} +.markdown-preview-view + ul + > li.task-list-item + .todoist-p4 + > input[type='checkbox']:hover { + background-color: rgba(180, 194, 240, 0.5) !important; +} + +/* LIVE PREVIEW */ + +.is-live-preview ul > li.task-list-item .todoist-p1 > input[type='checkbox'] { + border: 1px solid #ff75c6 !important; + background-color: rgba(255, 117, 221, 0.25) !important; +} +.is-live-preview + ul + > li.task-list-item + .todoist-p1 + > input[type='checkbox']:hover { + background-color: rgba(255, 117, 193, 0.5) !important; +} +.is-live-preview ul > li.task-list-item .todoist-p2 > input[type='checkbox'] { + border: 1px solid #ffa3a3 !important; + background-color: rgba(255, 139, 119, 0.25) !important; +} +.is-live-preview + ul + > li.task-list-item + .todoist-p2 + > input[type='checkbox']:hover { + background-color: rgba(255, 154, 154, 0.5) !important; +} +.is-live-preview ul > li.task-list-item .todoist-p3 > input[type='checkbox'] { + border: 1px solid #35bfff !important; + background-color: rgba(67, 233, 255, 0.308) !important; +} +.is-live-preview + ul + > li.task-list-item + .todoist-p3 + > input[type='checkbox']:hover { + background-color: rgba(53, 223, 253, 0.5) !important; +} +.is-live-preview ul > li.task-list-item .todoist-p4 > input[type='checkbox'] { + border: 1px solid #89c6ffd5 !important; + background-color: rgba(150, 170, 179, 0.192) !important; +} +.is-live-preview + ul + > li.task-list-item + .todoist-p4 + > input[type='checkbox']:hover { + background-color: rgba(166, 182, 194, 0.418) !important; +} + +.task-metadata { + font-size: var(--font-todoist-metadata-size); + color: #7a88cf; + margin-left: unset !important; +} +.task-metadata > * { + margin-right: 30px; +} +.task-date.task-overdue { + color: rgba(255, 152, 164, 0.75) !important; +} +.task-calendar-icon, +.task-project-icon, +.task-labels-icon { + vertical-align: middle; + height: 17px; + width: 17px; +} +.todoist-project .todoist-project { + margin-left: 20px; +} +.todoist-section { + margin-left: 20px; +} +.todoist-project .todoist-project-title { + font-weight: 700; + margin-block-end: 0px; +} +.todoist-section .todoist-section-title { + font-size: var(--font-todoist-title-size); + color: #7a88cf; + font-weight: 700; + margin-block-end: 0px; +} +.todoist-error { + border: 1px solid #ff98a4; + background-color: rgba(255, 152, 164, 0.05); + padding: 1em 1em; + margin: 1em 0px; +} +.todoist-error p { + margin: 0 0 1em 0; + font-weight: 600; +} +.todoist-error code { + background-color: unset !important; + padding: unset !important; + margin: unset !important; +} +.todoist-success { + border: 1px solid #c3e88d !important; + background-color: rgba(195, 232, 141, 0.05); + padding: 1em 1em !important; + margin: 1em 0px; +} +.todoist-success p { + margin: 0; + font-weight: 600; +} +.priority-container .priority-1 { + color: #ff98a4; +} +.priority-container .priority-2 { + color: #ffc777; +} +.priority-container .priority-3 { + color: #65bcff; +} +.priority-container .priority-4 { + color: #b4c2f0; +} + +/* --------------- */ +/* Checklist */ +/* --------------- */ + +.checklist-plugin-main .group .classic, +.checklist-plugin-main .group .compact, +.checklist-plugin-main .group svg, +.checklist-plugin-main .group .page { + cursor: var(--cursor); +} +.workspace .view-content .checklist-plugin-main { + padding: 10px 10px 15px 15px; + --todoList-togglePadding--compact: 2px; + --todoList-listItemMargin--compact: 2px; +} +.checklist-plugin-main .title { + font-weight: 400; + color: var(--text-muted); + font-size: var(--font-adaptive-small); +} +.checklist-plugin-main .group svg { + fill: var(--text-faint); +} +.checklist-plugin-main .group svg:hover { + fill: var(--text-normal); +} +.checklist-plugin-main .group .title:hover { + color: var(--text-normal); +} +.checklist-plugin-main .group:not(:last-child) { + border-bottom: 1px solid var(--background-modifier-border); +} +.checklist-plugin-main .group { + padding: 0 0 4px 0; +} +.checklist-plugin-main .group .classic:last-child, +.checklist-plugin-main .group .compact:last-child { + margin-bottom: 10px; +} +.checklist-plugin-main .group .classic, +.checklist-plugin-main .group .compact { + font-size: var(--font-adaptive-small) !important; +} +.checklist-plugin-main .content { + font-size: var(--font-adaptive-small) !important; +} +.checklist-plugin-main .group .classic, +.checklist-plugin-main .group .compact { + background: transparent; + border-radius: 0; + margin: 1px auto; + padding: 0; +} +.checklist-plugin-main .group .classic .content { + padding: 0; +} +.checklist-plugin-main .group .classic:hover, +.checklist-plugin-main .group .compact:hover { + background: transparent; +} +.markdown-preview-view.checklist-plugin-main + ul + > li:not(.task-list-item)::before { + display: none; +} +.checklist-plugin-main .group .compact > .toggle .checked { + background: var(--text-accent); + top: -1px; + left: -1px; + height: 18px; + width: 18px; +} +.checklist-plugin-main .compact .toggle:hover { + opacity: 1 !important; +} +.checklist-plugin-main .group .count { + font-size: var(--font-adaptive-smaller); + background: transparent; + font-weight: 400; + color: var(--text-faint); +} +.checklist-plugin-main .group .group-header:hover .count { + color: var(--text-muted); +} +.checklist-plugin-main .group .checkbox { + border: 2px solid var(--background-modifier-border-focus); + min-height: 18px; + min-width: 18px; + height: 18px; + width: 18px; + border-radius: 30%; +} + +.checklist-plugin-main .group .checkbox:hover { + border: 2px solid var(--background-modifier-border-focus); +} + +.checklist-plugin-main .toggle:hover { + box-shadow: none; +} + +.checklist-plugin-main .container .search { + font-size: var(--font-adaptive-small) !important; + border: 1px solid var(--background-modifier-border) !important; +} + +.checklist-plugin-main .container .settings-container > svg { + width: 100%; +} + +.checklist-plugin-main .checkbox .checked { + border-radius: 30% !important; + background-color: var(--background-modifier-border-focus) !important; + top: calc( + calc(var(--checklist-checkboxSize) - var(--checklist-checkboxCheckedSize)) / + 6 + ); + left: calc( + calc(var(--checklist-checkboxSize) - var(--checklist-checkboxCheckedSize)) / + 6 + ); +} + +/* Checklist mobile styling */ + +.is-mobile .checklist-plugin-main .group-header { + display: flex; + margin-bottom: 12px; +} +.is-mobile .checklist-plugin-main .group-header .title { + font-weight: 500; + color: var(--text-muted); + font-size: var(--font-adaptive-small); +} +.is-mobile .checklist-plugin-main .group-header button { + width: fit-content !important; + margin-left: 5px; +} +.is-mobile .checklist-plugin-main .group .classic { + display: flex; + align-items: center; + padding: 5px 0; +} +.is-mobile .checklist-plugin-main .group .classic .content { + padding: 0; + display: inline-block; +} +.is-mobile .checklist-plugin-main .group .classic .toggle { + padding: 0; + margin-right: 1rem; + width: fit-content !important; + display: inline-block; +} + +/* --------------- */ +/* Dataview */ +/* --------------- */ + +.markdown-preview-view .table-view-table { + font-size: calc(var(--font-adaptive-normal) - 1px); +} +.markdown-preview-view .table-view-table > thead > tr > th { + font-weight: 600; + font-size: calc(var(--font-adaptive-normal) - 1px); + color: var(--text-normal); + border-bottom: 1px solid var(--text-faint); + cursor: var(--cursor); + font-family: var(--font-monospace); +} + +/* --------------- */ +/* Day Planner */ +/* --------------- */ + +.plugin-obsidian-day-planner { + display: flex !important; + align-items: center; +} +.day-planner { + position: relative; + display: flex; + align-items: center; +} + +/* --------------- */ +/* Style Settings */ +/* --------------- */ + +.setting-item-heading.style-settings-heading, +.style-settings-container .style-settings-heading { + cursor: var(--cursor); +} +.modal.mod-settings .setting-item .pickr button.pcr-button { + box-shadow: none; + border-radius: 40px; + height: 24px; + width: 24px; +} +.setting-item .pickr .pcr-button:after, +.setting-item .pickr .pcr-button:before { + border-radius: 40px; + box-shadow: none; + border: none; +} + +/* --------------- */ +/* MacOs-like Translucency */ +/* --------------- */ + +.is-translucent:not(.macOS-translucent).theme-light { + --opacity-translucency: 0.6; +} + +.is-translucent:not(.macOS-translucent).theme-dark { + --opacity-translucency: 0.7; +} + +.is-translucent .workspace-leaf-resize-handle { + opacity: var(--opacity-translucency); + background-color: transparent; +} + +.macOS-translucent.is-translucent.is-translucent ::-webkit-scrollbar { + display: none; +} + +.macOS-translucent.is-translucent .titlebar, +.macOS-translucent.is-translucent .status-bar { + background-color: var(--background-translucent) !important; +} + +.macOS-translucent.is-translucent .titlebar-button:hover { + background-color: var(--background-primary); +} + +.macOS-translucent.is-translucent .workspace { + background-color: var(--background-translucent) !important; +} + +.macOS-translucent.is-translucent .workspace-split .workspace-tabs { + background: var(--background-primary) !important; +} + +.macOS-translucent.is-translucent .workspace-tab-container-inner { + background-color: transparent !important; + border: transparent; +} + +.macOS-translucent.is-translucent .workspace-split .workspace-tabs, +.macOS-translucent.is-translucent .graph-controls, +.macOS-translucent.is-translucent .nav-file-title.is-active { + background-color: transparent !important; + box-shadow: inset -10px 0 4px -10px rgba(0, 0, 0, 0.04); +} + +.focus-mode.macOS-translucent.is-translucent .workspace { + background-color: var(--background-primary) !important; +} + +.macOS-translucent.is-translucent .workspace-ribbon.mod-right, +.macOS-translucent.is-translucent .workspace-ribbon.mod-left { + background: transparent; +} + +.macOS-translucent.is-translucent .mod-horizontal .workspace-leaf { + border-bottom: 0px; + background-color: transparent; + box-shadow: none !important; +} + +.macOS-translucent.is-translucent.theme-light .workspace { + --text-muted: hsl( + var(--base-h), + calc(var(--base-s) - 3%), + calc(var(--base-l) - 50%) + ); + --svg-faint: hsl( + var(--base-h), + calc(var(--base-s) - 3%), + calc(var(--base-l) - 38%) + ); +} + +/* -------------------------------------------------------------------------------- +Icon replacement +Thanks to Kepano, Matthew Meyers, and Chetachi Ezikeuzor +-------------------------------------------------------------------------------- */ + +.tree-item-self .collapse-icon { + width: 20px; +} + +body:not(.minimal-icons-off) .view-action svg, +body:not(.minimal-icons-off) .workspace-tab-header-inner-icon svg, +body:not(.minimal-icons-off) .nav-action-button svg, +body:not(.minimal-icons-off) .graph-controls-button svg { + width: 18px; + height: 18px; +} +body:not(.minimal-icons-off) .menu-item-icon svg { + width: 16px; + height: 16px; +} +body:not(.minimal-icons-off) .workspace-ribbon-collapse-btn svg { + width: 18px; + height: 18px; +} + +body:not(.minimal-icons-off) svg.any-key, +body:not(.minimal-icons-off) svg.blocks, +body:not(.minimal-icons-off) svg.bar-graph, +body:not(.minimal-icons-off) svg.breadcrumbs-trail-icon, +body:not(.minimal-icons-off) svg.audio-file, +body:not(.minimal-icons-off) svg.bold-glyph, +body:not(.minimal-icons-off) svg.italic-glyph, +body:not(.minimal-icons-off) svg.bracket-glyph, +body:not(.minimal-icons-off) svg.broken-link, +body:not(.minimal-icons-off) svg.bullet-list-glyph, +body:not(.minimal-icons-off) svg.bullet-list, +body:not(.minimal-icons-off) svg.calendar-day, +body:not(.minimal-icons-off) svg.calendar-with-checkmark, +body:not(.minimal-icons-off) svg.check-in-circle, +body:not(.minimal-icons-off) svg.check-small, +body:not(.minimal-icons-off) svg.checkbox-glyph, +body:not(.minimal-icons-off) svg.checkmark, +body:not(.minimal-icons-off) svg.clock, +body:not(.minimal-icons-off) svg.cloud, +body:not(.minimal-icons-off) svg.code-glyph, +body:not(.minimal-icons-off) svg.create-new, +body:not(.minimal-icons-off) svg.cross-in-box, +body:not(.minimal-icons-off) svg.cross, +body:not(.minimal-icons-off) svg.crossed-star, +body:not(.minimal-icons-off) svg.dice, +body:not(.minimal-icons-off) svg.disk, +body:not(.minimal-icons-off) svg.document, +body:not(.minimal-icons-off) svg.documents, +body:not(.minimal-icons-off) svg.dot-network, +body:not(.minimal-icons-off) svg.double-down-arrow-glyph, +body:not(.minimal-icons-off) svg.double-up-arrow-glyph, +body:not(.minimal-icons-off) svg.down-arrow-with-tail, +body:not(.minimal-icons-off) svg.down-chevron-glyph, +body:not(.minimal-icons-off) svg.enter, +body:not(.minimal-icons-off) svg.exit-fullscreen, +body:not(.minimal-icons-off) svg.expand-vertically, +body:not(.minimal-icons-off) svg.excalidraw-icon, +body:not(.minimal-icons-off) svg.filled-pin, +body:not(.minimal-icons-off) svg.folder, +body:not(.minimal-icons-off) svg.fullscreen, +body:not(.minimal-icons-off) svg.gear, +body:not(.minimal-icons-off) svg.hashtag, +body:not(.minimal-icons-off) svg.heading-glyph, +body:not(.minimal-icons-off) svg.go-to-file, +body:not(.minimal-icons-off) svg.help .widget-icon, +body:not(.minimal-icons-off) svg.help, +body:not(.minimal-icons-off) svg.highlight-glyph, +body:not(.minimal-icons-off) svg.horizontal-split, +body:not(.minimal-icons-off) svg.image-file, +body:not(.minimal-icons-off) svg.image-glyph, +body:not(.minimal-icons-off) svg.indent-glyph, +body:not(.minimal-icons-off) svg.info, +body:not(.minimal-icons-off) svg.install, +body:not(.minimal-icons-off) svg.keyboard-glyph, +body:not(.minimal-icons-off) svg.left-arrow-with-tail, +body:not(.minimal-icons-off) svg.left-arrow, +body:not(.minimal-icons-off) svg.left-chevron-glyph, +body:not(.minimal-icons-off) svg.lines-of-text, +body:not(.minimal-icons-off) svg.link-glyph, +body:not(.minimal-icons-off) svg.link, +body:not(.minimal-icons-off) svg.magnifying-glass, +body:not(.minimal-icons-off) svg.microphone-filled, +body:not(.minimal-icons-off) svg.microphone, +body:not(.minimal-icons-off) svg.minus-with-circle, +body:not(.minimal-icons-off) svg.note-glyph, +body:not(.minimal-icons-off) svg.number-list-glyph, +body:not(.minimal-icons-off) svg.open-vault, +body:not(.minimal-icons-off) svg.pane-layout, +body:not(.minimal-icons-off) svg.paper-plane, +body:not(.minimal-icons-off) svg.paused, +/*body:not(.minimal-icons-off) svg.pdf-file,*/ +body:not(.minimal-icons-off) svg.pencil, +body:not(.minimal-icons-off) svg.pin, +body:not(.minimal-icons-off) svg.plus-with-circle, +body:not(.minimal-icons-off) svg.popup-open, +body:not(.minimal-icons-off) svg.presentation, +body:not(.minimal-icons-off) svg.price-tag-glyph, +body:not(.minimal-icons-off) svg.quote-glyph, +body:not(.minimal-icons-off) svg.redo-glyph, +body:not(.minimal-icons-off) svg.reset, +body:not(.minimal-icons-off) svg.right-arrow-with-tail, +body:not(.minimal-icons-off) svg.right-arrow, +body:not(.minimal-icons-off) svg.right-chevron-glyph, +body:not(.minimal-icons-off) svg.right-triangle, +body:not(.minimal-icons-off) svg.run-command, +body:not(.minimal-icons-off) svg.search, +body:not(.minimal-icons-off) svg.sheets-in-box, +body:not(.minimal-icons-off) svg.spreadsheet, +body:not(.minimal-icons-off) svg.stacked-levels, +body:not(.minimal-icons-off) svg.star-list, +body:not(.minimal-icons-off) svg.star, +body:not(.minimal-icons-off) svg.strikethrough-glyph, +body:not(.minimal-icons-off) svg.switch, +body:not(.minimal-icons-off) svg.sync-small, +body:not(.minimal-icons-off) svg.sync, +body:not(.minimal-icons-off) svg.tag-glyph, +body:not(.minimal-icons-off) svg.three-horizontal-bars, +body:not(.minimal-icons-off) svg.trash, +body:not(.minimal-icons-off) svg.undo-glyph, +body:not(.minimal-icons-off) svg.unindent-glyph, +body:not(.minimal-icons-off) svg.up-and-down-arrows, +body:not(.minimal-icons-off) svg.up-arrow-with-tail, +body:not(.minimal-icons-off) svg.up-chevron-glyph, +body:not(.minimal-icons-off) svg.vault, +body:not(.minimal-icons-off) svg.vertical-split, +body:not(.minimal-icons-off) svg.vertical-three-dots, +body:not(.minimal-icons-off) svg.wrench-screwdriver-glyph, +body:not(.minimal-icons-off) svg.clock-glyph, +body:not(.minimal-icons-off) svg.command-glyph, +body:not(.minimal-icons-off) svg.add-note-glyph, +body:not(.minimal-icons-off) svg.calendar-glyph, +body:not(.minimal-icons-off) svg.duplicate-glyph, +body:not(.minimal-icons-off) svg.file-explorer-glyph, +body:not(.minimal-icons-off) svg.graph-glyph, +body:not(.minimal-icons-off) svg.import-glyph, +body:not(.minimal-icons-off) svg.languages, +body:not(.minimal-icons-off) svg.links-coming-in, +body:not(.minimal-icons-off) svg.links-going-out, +body:not(.minimal-icons-off) svg.merge-files-glyph, +body:not(.minimal-icons-off) svg.merge-files, +body:not(.minimal-icons-off) svg.open-elsewhere-glyph, +body:not(.minimal-icons-off) svg.paper-plane-glyph, +body:not(.minimal-icons-off) svg.paste-text, +body:not(.minimal-icons-off) svg.paste, +body:not(.minimal-icons-off) svg.percent-sign-glyph, +body:not(.minimal-icons-off) svg.play-audio-glyph, +body:not(.minimal-icons-off) svg.plus-minus-glyph, +body:not(.minimal-icons-off) svg.presentation-glyph, +body:not(.minimal-icons-off) svg.question-mark-glyph, +body:not(.minimal-icons-off) svg.restore-file-glyph, +body:not(.minimal-icons-off) svg.scissors-glyph, +body:not(.minimal-icons-off) svg.scissors, +body:not(.minimal-icons-off) svg.search-glyph, +body:not(.minimal-icons-off) svg.select-all-text, +body:not(.minimal-icons-off) svg.split, +body:not(.minimal-icons-off) svg.star-glyph, +body:not(.minimal-icons-off) svg.stop-audio-glyph, +body:not(.minimal-icons-off) svg.sweep, +body:not(.minimal-icons-off) svg.two-blank-pages, +body:not(.minimal-icons-off) svg.tomorrow-glyph, +body:not(.minimal-icons-off) svg.yesterday-glyph, +body:not(.minimal-icons-off) svg.workspace-glyph, +body:not(.minimal-icons-off) svg.box-glyph, +body:not(.minimal-icons-off) svg.wand, +body:not(.minimal-icons-off) svg.longform, +body:not(.minimal-icons-off) svg.changelog, +body:not(.no-sanctum-icons) svg.reading-glasses { + background-color: currentColor; +} + +body:not(.minimal-icons-off) svg.any-key > path, +body:not(.minimal-icons-off) svg.blocks > path, +body:not(.minimal-icons-off) svg.bar-graph > path, +body:not(.minimal-icons-off) svg.breadcrumbs-trail-icon > path, +body:not(.minimal-icons-off) svg.audio-file > path, +body:not(.minimal-icons-off) svg.bold-glyph > path, +body:not(.minimal-icons-off) svg.italic-glyph > path, +body:not(.minimal-icons-off) svg.bracket-glyph > path, +body:not(.minimal-icons-off) svg.broken-link > path, +body:not(.minimal-icons-off) svg.bullet-list-glyph > path, +body:not(.minimal-icons-off) svg.bullet-list > path, +body:not(.minimal-icons-off) svg.calendar-day > path, +body:not(.minimal-icons-off) svg.calendar-with-checkmark > path, +body:not(.minimal-icons-off) svg.check-in-circle > path, +body:not(.minimal-icons-off) svg.check-small > path, +body:not(.minimal-icons-off) svg.checkbox-glyph > path, +body:not(.minimal-icons-off) svg.checkmark > path, +body:not(.minimal-icons-off) svg.clock > path, +body:not(.minimal-icons-off) svg.cloud > path, +body:not(.minimal-icons-off) svg.code-glyph > path, +body:not(.minimal-icons-off) svg.command-glyph > path, +body:not(.minimal-icons-off) svg.create-new > path, +body:not(.minimal-icons-off) svg.cross-in-box > path, +body:not(.minimal-icons-off) svg.cross > path, +body:not(.minimal-icons-off) svg.crossed-star > path, +body:not(.minimal-icons-off) svg.dice > path, +body:not(.minimal-icons-off) svg.disk > path, +body:not(.minimal-icons-off) svg.document > path, +body:not(.minimal-icons-off) svg.documents > path, +body:not(.minimal-icons-off) svg.dot-network > path, +body:not(.minimal-icons-off) svg.double-down-arrow-glyph > path, +body:not(.minimal-icons-off) svg.double-up-arrow-glyph > path, +body:not(.minimal-icons-off) svg.down-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.down-chevron-glyph > path, +body:not(.minimal-icons-off) svg.enter > path, +body:not(.minimal-icons-off) svg.exit-fullscreen > path, +body:not(.minimal-icons-off) svg.expand-vertically > path, +body:not(.minimal-icons-off) svg.excalidraw-icon > path, +body:not(.minimal-icons-off) svg.filled-pin > path, +body:not(.minimal-icons-off) svg.folder > path, +body:not(.minimal-icons-off) svg.fullscreen > path, +body:not(.minimal-icons-off) svg.gear > path, +body:not(.minimal-icons-off) svg.hashtag > path, +body:not(.minimal-icons-off) svg.heading-glyph > path, +body:not(.minimal-icons-off) svg.go-to-file > path, +body:not(.minimal-icons-off) svg.help .widget-icon > path, +body:not(.minimal-icons-off) svg.help > path, +body:not(.minimal-icons-off) svg.highlight-glyph > path, +body:not(.minimal-icons-off) svg.horizontal-split > path, +body:not(.minimal-icons-off) svg.image-file > path, +body:not(.minimal-icons-off) svg.image-glyph > path, +body:not(.minimal-icons-off) svg.indent-glyph > path, +body:not(.minimal-icons-off) svg.info > path, +body:not(.minimal-icons-off) svg.install > path, +body:not(.minimal-icons-off) svg.keyboard-glyph > path, +body:not(.minimal-icons-off) svg.left-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.left-arrow > path, +body:not(.minimal-icons-off) svg.left-chevron-glyph > path, +body:not(.minimal-icons-off) svg.lines-of-text > path, +body:not(.minimal-icons-off) svg.link-glyph > path, +body:not(.minimal-icons-off) svg.link > path, +body:not(.minimal-icons-off) svg.magnifying-glass > path, +body:not(.minimal-icons-off) svg.microphone-filled > path, +body:not(.minimal-icons-off) svg.microphone > path, +body:not(.minimal-icons-off) svg.minus-with-circle > path, +body:not(.minimal-icons-off) svg.note-glyph > path, +body:not(.minimal-icons-off) svg.number-list-glyph > path, +body:not(.minimal-icons-off) svg.open-vault > path, +body:not(.minimal-icons-off) svg.pane-layout > path, +body:not(.minimal-icons-off) svg.paper-plane > path, +body:not(.minimal-icons-off) svg.paused > path, +/*body:not(.minimal-icons-off) svg.pdf-file > path,*/ +body:not(.minimal-icons-off) svg.pencil > path, +body:not(.minimal-icons-off) svg.pin > path, +body:not(.minimal-icons-off) svg.plus-with-circle > path, +body:not(.minimal-icons-off) svg.popup-open > path, +body:not(.minimal-icons-off) svg.presentation > path, +body:not(.minimal-icons-off) svg.price-tag-glyph > path, +body:not(.minimal-icons-off) svg.quote-glyph > path, +body:not(.minimal-icons-off) svg.redo-glyph > path, +body:not(.minimal-icons-off) svg.reset > path, +body:not(.minimal-icons-off) svg.right-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.right-arrow > path, +body:not(.minimal-icons-off) svg.right-chevron-glyph > path, +body:not(.minimal-icons-off) svg.right-triangle > path, +body:not(.minimal-icons-off) svg.run-command > path, +body:not(.minimal-icons-off) svg.search > path, +body:not(.minimal-icons-off) svg.sheets-in-box > path, +body:not(.minimal-icons-off) svg.spreadsheet > path, +body:not(.minimal-icons-off) svg.stacked-levels > path, +body:not(.minimal-icons-off) svg.star-list > path, +body:not(.minimal-icons-off) svg.star > path, +body:not(.minimal-icons-off) svg.strikethrough-glyph > path, +body:not(.minimal-icons-off) svg.switch > path, +body:not(.minimal-icons-off) svg.sync-small > path, +body:not(.minimal-icons-off) svg.sync > path, +body:not(.minimal-icons-off) svg.tag-glyph > path, +body:not(.minimal-icons-off) svg.three-horizontal-bars > path, +body:not(.minimal-icons-off) svg.trash > path, +body:not(.minimal-icons-off) svg.undo-glyph > path, +body:not(.minimal-icons-off) svg.unindent-glyph > path, +body:not(.minimal-icons-off) svg.up-and-down-arrows > path, +body:not(.minimal-icons-off) svg.up-arrow-with-tail > path, +body:not(.minimal-icons-off) svg.up-chevron-glyph > path, +body:not(.minimal-icons-off) svg.vault > path, +body:not(.minimal-icons-off) svg.vertical-split > path, +body:not(.minimal-icons-off) svg.vertical-three-dots > path, +body:not(.minimal-icons-off) svg.wrench-screwdriver-glyph > path, +body:not(.minimal-icons-off) svg.clock-glyph > path, +body:not(.minimal-icons-off) svg.add-note-glyph > path, +body:not(.minimal-icons-off) svg.calendar-glyph > path, +body:not(.minimal-icons-off) svg.duplicate-glyph > path, +body:not(.minimal-icons-off) svg.file-explorer-glyph > path, +body:not(.minimal-icons-off) svg.graph-glyph > path, +body:not(.minimal-icons-off) svg.import-glyph > path, +body:not(.minimal-icons-off) svg.languages > path, +body:not(.minimal-icons-off) svg.links-coming-in > path, +body:not(.minimal-icons-off) svg.links-going-out > path, +body:not(.minimal-icons-off) svg.merge-files > path, +body:not(.minimal-icons-off) svg.open-elsewhere-glyph > path, +body:not(.minimal-icons-off) svg.paper-plane-glyph > path, +body:not(.minimal-icons-off) svg.paste-text > path, +body:not(.minimal-icons-off) svg.paste > path, +body:not(.minimal-icons-off) svg.percent-sign-glyph > path, +body:not(.minimal-icons-off) svg.play-audio-glyph > path, +body:not(.minimal-icons-off) svg.plus-minus-glyph > path, +body:not(.minimal-icons-off) svg.presentation-glyph > path, +body:not(.minimal-icons-off) svg.question-mark-glyph > path, +body:not(.minimal-icons-off) svg.restore-file-glyph > path, +body:not(.minimal-icons-off) svg.scissors-glyph > path, +body:not(.minimal-icons-off) svg.scissors > path, +body:not(.minimal-icons-off) svg.search-glyph > path, +body:not(.minimal-icons-off) svg.select-all-text > path, +body:not(.minimal-icons-off) svg.split > path, +body:not(.minimal-icons-off) svg.star-glyph > path, +body:not(.minimal-icons-off) svg.stop-audio-glyph > path, +body:not(.minimal-icons-off) svg.sweep > path, +body:not(.minimal-icons-off) svg.two-blank-pages > path, +body:not(.minimal-icons-off) svg.tomorrow-glyph > path, +body:not(.minimal-icons-off) svg.yesterday-glyph > path, +body:not(.minimal-icons-off) svg.workspace-glyph > path, +body:not(.minimal-icons-off) svg.box-glyph > path, +body:not(.minimal-icons-off) svg.wand > path, +body:not(.minimal-icons-off) svg.longform > path, +body:not(.minimal-icons-off) svg.changelog > path, +body:not(.no-sanctum-icons) svg.reading-glasses > path { + display: none; +} + +body:not(.minimal-icons-off) svg.any-key { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.audio-file { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.bar-graph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.breadcrumbs-trail-icon { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.blocks { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.bold-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.italic-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.bracket-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.broken-link { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.bullet-list-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.bullet-list { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.calendar-with-checkmark { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.check-in-circle { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.check-small { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.checkbox-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.checkmark { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.clock { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.clock-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.cloud { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.code-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.cross-in-box { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.cross { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); + -webkit-mask-image: url("data:image/svg+xml,"); + width: 18px; + height: 18px; +} +body:not(.minimal-icons-off) svg.crossed-star { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.dice { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.disk { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4' /%3E%3C/svg%3E"); +} +body:not(.no-svg-replace) svg.document { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.no-svg-replace) + .workspace-leaf-content[data-type='starred'] + svg.document { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) + .nav-action-button[aria-label='New note'] + svg.document, +body:not(.minimal-icons-off) svg.create-new { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z' /%3E%3C/svg%3E"); +} +body:not(.minimal-icons-off) svg.documents { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) + .workspace-leaf-content[data-type='video'] + .view-header + .view-header-icon + svg.document { + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' enable-background='new 0 0 32 32' viewBox='0 0 32 32' xml:space='preserve'%3E%3Cpath d='M10 6h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4z'/%3E%3Cpath fill='none' d='M0 0h32v32H0z'/%3E%3C/svg%3E"); +} +body:not(.minimal-icons-off) + .workspace-leaf-content[data-type='markdown'] + .view-header + .view-header-icon + svg.document { + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' enable-background='new 0 0 32 32' viewBox='0 0 32 32' xml:space='preserve'%3E%3Cpath d='M10 6h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4zm-8 8h4v4h-4zm8 0h4v4h-4z'/%3E%3Cpath fill='none' d='M0 0h32v32H0z'/%3E%3C/svg%3E"); +} +body:not(.minimal-icons-off) svg.dot-network { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.double-down-arrow-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.double-up-arrow-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.down-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.down-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.enter { + transform: translate(-2px); + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.excalidraw-icon { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.expand-vertically { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.filled-pin { + transform: rotate(45deg); + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.folder { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) + .workspace-tab-header[aria-label='File explorer'] + svg.folder { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' /%3E%3C/svg%3E"); +} +body:not(.minimal-icons-off) + .nav-action-button[aria-label='New folder'] + svg.folder { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z' /%3E%3C/svg%3E"); +} +body:not(.minimal-icons-off) svg.fullscreen { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.gear { + -webkit-mask-image: url("data:image/svg+xml,"); +} +body:not(.minimal-icons-off) svg.hashtag { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.heading-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.go-to-file { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.help .widget-icon, +body:not(.minimal-icons-off) svg.help { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.highlight-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.horizontal-split { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.image-file { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.image-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.indent-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.info { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.install { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.keyboard-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.left-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.left-arrow { + -webkit-mask-image: url("data:image/svg+xml,"); +} +body:not(.minimal-icons-off) svg.left-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.lines-of-text { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.link-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); + transform: rotate(90deg); +} +body:not(.minimal-icons-off) svg.link { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); + transform: rotate(90deg); +} +body:not(.minimal-icons-off) svg.magnifying-glass { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.microphone-filled { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.microphone { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.minus-with-circle { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.note-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.number-list-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.open-vault { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.pane-layout { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.paper-plane { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.paused { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.pencil { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.pin { + transform: rotate(45deg); + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.plus-with-circle { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.popup-open { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.presentation { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.price-tag-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.quote-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) + .workspace-tab-header[aria-label='Dictionary'] + svg.quote-glyph { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' /%3E%3C/svg%3E"); +} +body:not(.minimal-icons-off) svg.redo-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.reset { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.right-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.right-arrow { + -webkit-mask-image: url("data:image/svg+xml,"); +} +body:not(.minimal-icons-off) svg.right-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.right-triangle { + color: var(--text-faint); + background-color: var(--text-faint); + height: 12px; + width: 12px; + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.command-glyph, +body:not(.minimal-icons-off) svg.run-command { + -webkit-mask-image: url("data:image/svg+xml,"); +} +body:not(.minimal-icons-off) svg.search { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.sheets-in-box { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.spreadsheet { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.stacked-levels { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.star-list { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.star { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.strikethrough-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.switch { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.sync-small { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.sync { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.tag-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.three-horizontal-bars { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.trash { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.undo-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.unindent-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.up-and-down-arrows { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.up-arrow-with-tail { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.up-chevron-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.vault { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.vertical-split { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.vertical-three-dots { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.wrench-screwdriver-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.add-note-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.calendar-day { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.calendar-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.duplicate-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.file-explorer-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.graph-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.import-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.languages { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.links-coming-in { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.links-going-out { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.merge-files { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.open-elsewhere-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.paper-plane-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.paste-text { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.paste { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.percent-sign-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.play-audio-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.plus-minus-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.presentation-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.question-mark-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.restore-file-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.scissors-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.scissors { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.search-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.select-all-text { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.split { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.star-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.stop-audio-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.sweep { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.two-blank-pages { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.tomorrow-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.yesterday-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.workspace-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.box-glyph { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.wand { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.longform { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.changelog { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} +body:not(.minimal-icons-off) svg.reading-glasses { + -webkit-mask-image: url('data:image/svg+xml;utf8,'); +} + +/* ─────────────────────────────────────────────────── */ +/* Plugin Compatibility info for the Obsidian Hub */ +/* ─────────────────────────────────────────────────── */ + +/* @plugins +core: +- backlink +- command-palette +- file-explorer +- global-search +- graph +- outgoing-link +- outline +- page-preview +- starred +- switcher +- tag-pane +- file-recovery +- daily-notes +- random-note +- publish +- sync +- word-count +community: +- sliding-panes-obsidian +- obsidian-codemirror-options +- obsidian-kanban +- dataview +- obsidian-hider +- calendar +- mysnippets-plugin +- cmenu-plugin +- obsidian-outliner +- readwise-official +- tag-wrangler +- todoist-sync-plugin +- templater-obsidian +- obsidian-system-dark-mode +- obsidian-style-settings +*/ + +/* Style Settings */ + +/* @settings +name: Things Theme +id: things-style +settings: + - + id: features + title: Features + type: heading + level: 2 + collapsed: true + - + id: minimal-icons-off + title: Default icons + description: Use default icons instead of minimal set + type: class-toggle + default: false + - + id: full-file-names + title: Show full file names + description: Turn off trimming on files in sidebar + type: class-toggle + - + id: links-int-on + title: Underline internal links + description: Show underlines on internal links + type: class-toggle + default: true + - + id: links-ext-on + title: Underline external links + description: Show underlines on external links + type: class-toggle + default: true + - + id: show-mobile-hamburger + title: Display hamburger menu on mobile + description: Display the top-left hamburger menu on mobile + type: class-toggle + default: false + - + id: fonts + title: Fonts + type: heading + level: 2 + collapsed: true + - + id: text + title: Text font + description: Used in preview mode + type: variable-text + default: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif + - + id: text-editor + title: Editor font + description: Used in edit mode + type: variable-text + default: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif + - + id: font-monospace + title: Monospace font + description: Used for code blocks and front matter + type: variable-text + default: JetBrains Mono,Menlo,SFMono-Regular,Consolas,"Roboto Mono",monospace + - + id: font-ui + title: UI font + description: Used for buttons, menus and sidebar + type: variable-text + default: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif + - + id: custom-fonts + title: Typography + type: heading + level: 2 + collapsed: true + - + id: default-font-color + title: Default font colors + description: Use the default font color styling for bold, italics, and quotes + type: class-toggle + default: false + - + id: accent-h + title: Link hue color + description: Hue of both internal and external links + type: variable-number-slider + default: 215 + min: 0 + max: 360 + step: 1 + - + id: strong-color + title: Bold font color + type: variable-color + format: hex + default: '#FF82B2' + - + id: em-color + title: Italics font color + type: variable-color + format: hex + default: '#FF82B2' + - + id: green + title: Blockquotes font color + type: variable-color + format: hex + default: '#3EB4BF' + - + id: code-color-l + title: Inline code blocks font color (Light mode) + type: variable-color + format: hex + default: '#5C5C5C' + - + id: code-color-d + title: Inline code blocks font color (Dark mode) + type: variable-color + format: hex + default: '#A6A6A6' + - + id: tag-background-color-l + title: Tag background color (Light mode) + type: variable-color + format: hex + default: '#BDE1D3' + - + id: tag-font-color-l + title: Tag font color (Light mode) + type: variable-color + format: hex + default: '#1D694B' + - + id: tag-background-color-d + title: Tag background color (Dark mode) + type: variable-color + format: hex + default: '#1D694B' + - + id: tag-font-color-d + title: Tag font color (Dark mode) + type: variable-color + format: hex + default: '#' + - + id: editor-font-size + title: Editor font size + description: Font size in em for editor and preview overall font size + type: variable-number + default: 1 + format: em + - + id: font-small + title: Sidebar and tag font size + description: Font size in px of sidebar, tags, and small text + type: variable-number + default: 13 + format: px + - + id: font-smaller + title: Smaller font size + description: Font size in px of smaller text + type: variable-number + default: 11 + format: px + - + id: line-height + title: Body line height + description: Line height of the main text + type: variable-number + default: 1.5 + - + id: line-width + title: Normal line width + description: Number of characters per line + type: variable-number + default: 45 + format: rem + - + id: max-width + title: Maximum line width + description: Percentage of space inside a pane that a line can fill. Recommended values between 80 to 100 + type: variable-number + default: 90 + format: '%' + - + id: headings + title: Headings + type: heading + level: 2 + collapsed: true + - + id: level-1-headings + title: Level 1 Headings + type: heading + level: 3 + collapsed: true + - + id: h1 + title: H1 font size + description: Accepts any CSS font-size value + type: variable-text + default: 1.5em + - + id: h1-weight + title: H1 font weight + description: Accepts numbers representing the CSS font-weight + type: variable-number + default: 700 + - + id: h1-color + title: H1 color + type: variable-color + format: hex + default: '#' + - + id: level-2-headings + title: Level 2 Headings + type: heading + level: 3 + collapsed: true + - + id: h2 + title: H2 font size + description: Accepts any CSS font-size value + type: variable-text + default: 1.3em + - + id: h2-weight + title: H2 font weight + description: Accepts numbers representing the CSS font-weight + type: variable-number + default: 700 + - + id: h2-color + title: H2 color + type: variable-color + format: hex + default: '#2E80F2' + - + id: level-3-headings + title: Level 3 Headings + type: heading + level: 3 + collapsed: true + - + id: h3 + title: H3 font size + description: Accepts any CSS font-size value + type: variable-text + default: 1.1em + - + id: h3-weight + title: H3 font weight + description: Accepts numbers representing the CSS font-weight + type: variable-number + default: 600 + - + id: h3-color + title: H3 color + type: variable-color + format: hex + default: '#2E80F2' + - + id: level-4-headings + title: Level 4 Headings + type: heading + level: 3 + collapsed: true + - + id: h4 + title: H4 font size + description: Accepts any CSS font-size value + type: variable-text + default: 0.9em + - + id: h4-weight + title: H4 font weight + description: Accepts numbers representing the CSS font-weight + type: variable-number + default: 500 + - + id: h4-color + title: H4 color + type: variable-color + format: hex + default: '#E5B567' + - + id: h4-transform + title: H4 transform + description: Transform the H4 heading text + type: variable-select + default: uppercase + options: + - + label: Uppercase + value: uppercase + - + label: None + value: none + - + id: level-5-headings + title: Level 5 Headings + type: heading + level: 3 + collapsed: true + - + id: h5 + title: H5 font size + description: Accepts any CSS font-size value + type: variable-text + default: 0.85em + - + id: h5-weight + title: H5 font weight + description: Accepts numbers representing the CSS font-weight + type: variable-number + default: 500 + - + id: h5-color + title: H5 color + type: variable-color + format: hex + default: '#E83E3E' + - + id: level-6-headings + title: Level 6 Headings + type: heading + level: 3 + collapsed: true + - + id: h6 + title: H6 font size + description: Accepts any CSS font-size value + type: variable-text + default: 0.85em + - + id: h6-weight + title: H6 font weight + description: Accepts numbers representing the CSS font-weight + type: variable-number + default: 400 + - + id: h6-color + title: H6 color + type: variable-color + format: hex + default: '#' + - + id: advanced + title: Advanced + type: heading + level: 2 + collapsed: true + - + title: Disable mobile floating-action button + description: Revert placement of edit/preview button to default in header (mobile) + id: floating-button-off + type: class-toggle + default: false + - + title: MacOS-like translucent window + description: Give workspace a MacOS-like translucency + id: macOS-translucent + type: class-toggle + default: false + - + id: cursor + title: Cursor style + description: The cursor style for UI elements + type: variable-select + default: default + options: + - + label: Default + value: default + - + label: Pointer + value: pointer + - + label: Crosshair + value: crosshair + - + id: credits + title: Credits + type: heading + description: Created with ❤︎ by @colineckert. This theme uses code from Minimal by @kepano. Support @kepano at buymeacoffee.com/kepano and @colineckert at buymeacoffee.com/colineckert + level: 2 + collapsed: true + +*/ diff --git a/docs/.obsidian/workspace b/docs/.obsidian/workspace new file mode 100644 index 0000000000..2005115db2 --- /dev/null +++ b/docs/.obsidian/workspace @@ -0,0 +1,141 @@ +{ + "main": { + "id": "2c9313c97191b3d5", + "type": "split", + "children": [ + { + "id": "17a0267a38d6cd5c", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "data-management/MySQL/MySQL-Index.md", + "mode": "preview", + "source": false + } + } + } + ], + "direction": "vertical" + }, + "left": { + "id": "c2ba06bd2f318734", + "type": "split", + "children": [ + { + "id": "8aec8ec279c7aff1", + "type": "tabs", + "children": [ + { + "id": "ae760b575766f3f0", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": {} + } + }, + { + "id": "f88a42db0e3bb960", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "c7bda26138bbb70d", + "type": "leaf", + "state": { + "type": "starred", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "4d54828aaab36d9f", + "type": "split", + "children": [ + { + "id": "4acb1c44f8a68ec8", + "type": "tabs", + "children": [ + { + "id": "f56e43f509009e33", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "data-management/MySQL/MySQL-Index.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "e22cbd6030bde448", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "data-management/MySQL/MySQL-Index.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "37585a229386609d", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "1b738c49f6ecdf2c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "data-management/MySQL/MySQL-Index.md" + } + } + } + ], + "currentTab": 3 + } + ], + "direction": "horizontal", + "width": 300 + }, + "active": "17a0267a38d6cd5c", + "lastOpenFiles": [ + "interview/Kafka-FAQ.md", + "work/DPA.md", + "data-structure-algorithms/soultion/Array-Solution.md", + "data-structure-algorithms/Array.md", + "data-structure-algorithms/Recursion.md", + "data-management/Big-Data/Bloom-Filter.md", + "data-structure-algorithms/Linked-List.md" + ] +} \ No newline at end of file diff --git a/docs/.obsidian/workspace.json b/docs/.obsidian/workspace.json new file mode 100644 index 0000000000..d648b6a945 --- /dev/null +++ b/docs/.obsidian/workspace.json @@ -0,0 +1,302 @@ +{ + "main": { + "id": "2c9313c97191b3d5", + "type": "split", + "children": [ + { + "id": "233cec07312ea132", + "type": "tabs", + "children": [ + { + "id": "17a0267a38d6cd5c", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "data-management/MySQL/MySQL-Index.md", + "mode": "preview", + "source": false + }, + "icon": "lucide-file", + "title": "MySQL-Index" + } + }, + { + "id": "264bec0c6c3126ab", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "data-structure-algorithms/soultion/LinkedList-Soultion.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "LinkedList-Soultion" + } + }, + { + "id": "e24a131a4a8c13f9", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "data-structure-algorithms/algorithm/Dynamic-Programming.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "Dynamic-Programming" + } + }, + { + "id": "56243bd84331da43", + "type": "leaf", + "state": { + "type": "release-notes", + "state": { + "currentVersion": "1.8.9" + }, + "icon": "lucide-book-up", + "title": "Release Notes 1.8.9" + } + }, + { + "id": "80d5c719688f185a", + "type": "leaf", + "state": { + "type": "release-notes", + "state": { + "currentVersion": "1.8.10" + }, + "icon": "lucide-book-up", + "title": "Release Notes 1.8.10" + } + }, + { + "id": "c7c1397df03f59cc", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "新标签页" + } + }, + { + "id": "f02bfed475530fbe", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "data-structure-algorithms/soultion/DP-Solution.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "DP-Solution" + } + }, + { + "id": "0d9db99c2c25b1c2", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "data-structure-algorithms/soultion/DP-Solution.md", + "mode": "source", + "source": false + }, + "icon": "lucide-file", + "title": "DP-Solution" + } + } + ], + "currentTab": 4 + } + ], + "direction": "vertical" + }, + "left": { + "id": "c2ba06bd2f318734", + "type": "split", + "children": [ + { + "id": "8aec8ec279c7aff1", + "type": "tabs", + "children": [ + { + "id": "ae760b575766f3f0", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "文件列表" + } + }, + { + "id": "f88a42db0e3bb960", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "搜索" + } + }, + { + "id": "c7bda26138bbb70d", + "type": "leaf", + "state": { + "type": "starred", + "state": {}, + "icon": "lucide-file", + "title": "插件不再活动" + } + }, + { + "id": "123cfa366479ce4d", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "书签" + } + } + ] + } + ], + "direction": "horizontal", + "width": 200 + }, + "right": { + "id": "4d54828aaab36d9f", + "type": "split", + "children": [ + { + "id": "4acb1c44f8a68ec8", + "type": "tabs", + "children": [ + { + "id": "f56e43f509009e33", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "data-management/Redis/Redis-Datatype.md", + "collapseAll": true, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Redis-Datatype 的反向链接列表" + } + }, + { + "id": "e22cbd6030bde448", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "file": "data-management/Redis/Redis-Datatype.md", + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Redis-Datatype 的出链列表" + } + }, + { + "id": "37585a229386609d", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "标签" + } + }, + { + "id": "1b738c49f6ecdf2c", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "followCursor": false, + "showSearch": false, + "searchQuery": "" + }, + "icon": "lucide-list", + "title": "大纲" + } + } + ], + "currentTab": 3 + } + ], + "direction": "horizontal", + "width": 300 + }, + "left-ribbon": { + "hiddenItems": { + "switcher:打开快速切换": false, + "graph:查看关系图谱": false, + "canvas:新建白板": false, + "daily-notes:打开/创建今天的日记": false, + "templates:插入模板": false, + "command-palette:打开命令面板": false + } + }, + "active": "80d5c719688f185a", + "lastOpenFiles": [ + "framework/SpringWebFlux/Webflux~.md", + "framework/SpringWebFlux/Webflux.md", + "framework/SpringWebFlux/Untitled.md", + "framework/SpringWebFlux/响应式编程~.md", + "framework/SpringWebFlux/SpringWebFlux~.md", + "framework/SpringWebFlux/WebFlux~.md", + "data-management/Redis/Redis-Datatype.md", + "data-management/Redis/Redis-Database.md", + "data-management/Redis/Nosql-Overview.md", + "data-management/Redis/Nosql-Overview~.md", + "data-management/Redis/reproduce/Cache-Design.md", + "data-management/Redis/reproduce/Redis为什么变慢了-常见延迟问题定位与分析.md", + "data-management/Redis/reproduce/Key 寻址算法.md", + "interview/Redis-FAQ.md", + "data-structure-algorithms/soultion/Binary-Tree-Solution.md", + "未命名文件夹", + "java/IO~.md", + "java/IO.md", + "java/未命名.md", + "interview/Ability-FAQ~.md", + "data-structure-algorithms/algorithm/BFS.md", + "data-structure-algorithms/algorithm/Untitled.md", + "interview/Algorithm.md", + "interview/Alto.md", + "interview/Untitled.md", + "data-structure-algorithms/data-structure/Binary-Tree.md", + "data-structure-algorithms/soultion/DP-Solution.md", + "Untitled.canvas", + "data-structure-algorithms/algorithm", + "data-structure-algorithms/未命名文件夹" + ] +} \ No newline at end of file diff --git a/docs/.vuepress/.DS_Store b/docs/.vuepress/.DS_Store index cdeeb958e3..cd94417745 100644 Binary files a/docs/.vuepress/.DS_Store and b/docs/.vuepress/.DS_Store differ diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 22edeb96f4..42bf241df7 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -104,7 +104,7 @@ function genJavaSidebar() { return [ { title: "Java", - collapsable: false, + collapsable: true, children: [ "Java-8", "Java-Throwable", @@ -123,6 +123,7 @@ function genJavaSidebar() { title: "JUC", collapsable: true, children: [ + ["JUC/readJUC","开篇——聊聊并发编程"], "JUC/Java-Memory-Model", "JUC/volatile","JUC/synchronized","JUC/CAS", ['JUC/Concurrent-Container','Collection 大局观'], @@ -149,18 +150,43 @@ function genDSASidebar() { return [ { title: "数据结构", - collapsable: false, - sidebarDepth: 2, // 可选的, 默认值是 1 - children: ["Array","Linked-List","Stack","Queue","Binary-Tree","Skip-List"] + collapsable: true, + //sidebarDepth: 2, // 可选的, 默认值是 1 + children: [ + ['data-structure/Array','数组'], + ['data-structure/Linked-List','链表'], + ['data-structure/Stack','栈'], + ['data-structure/Queue','队列'], + ['data-structure/Binary-Tree','二叉树'], + ['data-structure/Skip-List','跳表'] + ] }, { title: "算法", - collapsable: false, + collapsable: true, children: [ "complexity", - "sort", - ['Recursion', '递归'], - ['Dynamic-Programming', '动态规划'] + ['algorithm/Sort', '排序'], + ['algorithm/Binary-Search', '二分查找'], + ['algorithm/Recursion', '递归'], + ['algorithm/Backtracking', '回溯'], + ['algorithm/DFS-BFS', 'DFS vs BFS'], + ['algorithm/Double-Pointer', '双指针'], + ['algorithm/Dynamic-Programming', '动态规划'] + ] + }, + { + title: "刷题", + collapsable: true, + children: [ + ['soultion/Binary-Tree-Solution', '二叉树'], + ['soultion/Array-Solution', '数组'], + ['soultion/String-Solution', '字符串'], + ['soultion/LinkedList-Soultion', '链表'], + ['soultion/DFS-Solution', 'DFS'], + ['soultion/Math-Solution', '数学'], + ['soultion/stock-problems', '股票问题'], + ['soultion/剑指offer', '剑指offer'] ] } ]; @@ -169,19 +195,41 @@ function genDSASidebar() { function genDesignPatternSidebar() { return [ ['Design-Pattern-Overview', '设计模式前传'], - ['Singleton-Pattern', '单例模式'], - ['Factory-Pattern', '工厂模式'], - ['Prototype-Pattern', '原型模式'], - ['Builder-Pattern', '建造者模式'], - ['Decorator-Pattern', '装饰模式'], - ['Proxy-Pattern', '代理模式'], - ['Adapter-Pattern', '适配器模式'], - ['Chain-of-Responsibility-Pattern', '责任链模式'], - ['Observer-Pattern', '观察者模式'], - ['Facade-Pattern', '外观模式'], - ['Template-Pattern', '模板方法模式'], - ['Strategy-Pattern', '策略模式'], - ['Pipeline-Pattern', '管道模式'] + { + title: "创建型模式", + collapsable: true, + sidebarDepth: 3, // 可选的, 默认值是 1 + children: [ + ['Singleton-Pattern', '单例模式'], + ['Factory-Pattern', '工厂模式'], + ['Prototype-Pattern', '原型模式'], + ['Builder-Pattern', '建造者模式'] + ] + }, + { + title: "结构型模式", + collapsable: true, + sidebarDepth: 3, // 可选的, 默认值是 1 + children: [ + ['Decorator-Pattern', '装饰模式'], + ['Proxy-Pattern', '代理模式'], + ['Adapter-Pattern', '适配器模式'] + ] + }, + { + title: "行为模式", + collapsable: true, + sidebarDepth: 2, // 可选的, 默认值是 1 + children: [ + ['Chain-of-Responsibility-Pattern', '责任链模式'], + ['Observer-Pattern', '观察者模式'], + ['Template-Pattern', '模板方法模式'], + ['Strategy-Pattern', '策略模式'], + ['Facade-Pattern', '外观模式'] + ] + }, + ['Pipeline-Pattern', '管道模式'], + ['Spring-Design.md', 'Spring 中的设计模式'] ]; } @@ -190,25 +238,30 @@ function genDataManagementSidebar(){ { title: "MySQL", collapsable: true, - sidebarDepth: 2, // 可选的, 默认值是 1 + //sidebarDepth: 1, // 可选的, 默认值是 1 children: [ ['MySQL/MySQL-Framework', 'MySQL 架构介绍'], ['MySQL/MySQL-Storage-Engines', 'MySQL 存储引擎'], ['MySQL/MySQL-Index', 'MySQL 索引'], - ['MySQL/MySQL-select', 'MySQL 查询'], - ['MySQL/数据库三范式', '数据库三范式'], + ['MySQL/MySQL-Transaction', 'MySQL 事务'], + ['MySQL/MySQL-Log', 'MySQL 日志'], + ['MySQL/MySQL-Lock', 'MySQL 锁'], + ['MySQL/MySQL-Select', 'MySQL 查询'], + ['MySQL/MySQL-Optimization', 'MySQL 优化'], + ['MySQL/Three-Normal-Forms', '数据库三范式'] ] }, { title: "Redis", collapsable: true, + sidebarDepth: 2, // 可选的, 默认值是 1 children: [ ['Redis/ReadRedis', 'Redis 开篇'], ['Redis/Redis-Datatype', 'Redis 数据类型'], ['Redis/Redis-Persistence', 'Redis 持久化'], ['Redis/Redis-Conf', 'Redis 配置'], ['Redis/Redis-Transaction', 'Redis 事务'], - ['Redis/Reids-Lock', 'Redis 分布式锁'], + ['Redis/Redis-Lock', 'Redis 分布式锁'], ['Redis/Redis-Master-Slave', 'Redis 主从'], ['Redis/Redis-Sentinel', 'Redis 哨兵'], ['Redis/Redis-Cluster', 'Redis 集群'], @@ -222,9 +275,12 @@ function genDataManagementSidebar(){ ['Big-Data/Hello-BigData', '大数据'], ['Big-Data/Hive', 'Hive'], ['Big-Data/Bloom-Filter', '布隆过滤器'], - ['Big-Data/Kylin', 'Kylin'] + ['Big-Data/Kylin', 'Kylin'], + ['Big-Data/HBase', 'HBase'], + ['Big-Data/Phoenix', 'Phoneix'] ] } + ]; } @@ -281,6 +337,7 @@ function genDistributionSidebar(){ sidebarDepth: 2, // 可选的, 默认值是 1 children: [ ['message-queue/Kafka/Hello-Kafka', 'Hello-Kafka'], + ['message-queue/Kafka/Kafka-Version', 'Kafka版本问题'], ['message-queue/Kafka/Kafka-Workflow','Kafka-Workflow'], ['message-queue/Kafka/Kafka-Producer','Kafka-Producer'], ['message-queue/Kafka/Kafka-Consumer','Kafka-Consumer'], @@ -318,7 +375,7 @@ function genNetworkSidebar(){ function genInterviewSidebar(){ return [ ['Java-Basics-FAQ', 'Java基础部分'], - ['Collections-FAQ', 'Java集合部分'], + ['Java-Collections-FAQ', 'Java集合部分'], ['JUC-FAQ', 'Java 多线程部分'], ['JVM-FAQ', 'JVM 部分'], ['MySQL-FAQ', 'MySQL 部分'], @@ -326,11 +383,10 @@ function genInterviewSidebar(){ ['Network-FAQ', '计算机网络部分'], ['Kafka-FAQ', 'Kafka 部分'], ['ZooKeeper-FAQ', 'Zookeeper 部分'], + ['RPC-FAQ', 'RPC 部分'], ['MyBatis-FAQ', 'MyBatis 部分'], ['Spring-FAQ', 'Spring 部分'], - ['SpringBoot-FAQ', 'Spring Boot 部分'], ['Design-Pattern-FAQ', '设计模式部分'], - ['Tomcat-FAQ', 'Tomcat 部分'], ['Elasticsearch-FAQ', 'Elasticsearch 部分'], ]; } \ No newline at end of file diff --git a/docs/.vuepress/dist b/docs/.vuepress/dist index 6e12227e6c..105cc8a4b2 160000 --- a/docs/.vuepress/dist +++ b/docs/.vuepress/dist @@ -1 +1 @@ -Subproject commit 6e12227e6cbc377c379237b62b79700c0d4fdbf4 +Subproject commit 105cc8a4b28dbf98f7e847970e21da82cdaba28c diff --git a/docs/.vuepress/public/qcode.png b/docs/.vuepress/public/qcode.png index 22641ffb9e..0c93c28828 100644 Binary files a/docs/.vuepress/public/qcode.png and b/docs/.vuepress/public/qcode.png differ diff --git a/docs/.vuepress/theme/.DS_Store b/docs/.vuepress/theme/.DS_Store index 5139a2bae2..b6fd03ec58 100644 Binary files a/docs/.vuepress/theme/.DS_Store and b/docs/.vuepress/theme/.DS_Store differ diff --git a/docs/.vuepress/theme/vuepress-theme-reco/.DS_Store b/docs/.vuepress/theme/vuepress-theme-reco/.DS_Store index 26f6db1bae..41406f682d 100644 Binary files a/docs/.vuepress/theme/vuepress-theme-reco/.DS_Store and b/docs/.vuepress/theme/vuepress-theme-reco/.DS_Store differ diff --git a/docs/.vuepress/theme/vuepress-theme-reco/components/.DS_Store b/docs/.vuepress/theme/vuepress-theme-reco/components/.DS_Store index 149cc56f80..f5d0703465 100644 Binary files a/docs/.vuepress/theme/vuepress-theme-reco/components/.DS_Store and b/docs/.vuepress/theme/vuepress-theme-reco/components/.DS_Store differ diff --git a/docs/.vuepress/theme/vuepress-theme-reco/styles/palette.styl b/docs/.vuepress/theme/vuepress-theme-reco/styles/palette.styl index 21b758e69a..ea6620af89 100644 --- a/docs/.vuepress/theme/vuepress-theme-reco/styles/palette.styl +++ b/docs/.vuepress/theme/vuepress-theme-reco/styles/palette.styl @@ -20,6 +20,8 @@ $lightColor3 = rgba(255, 255, 255, .3) $lightColor2 = rgba(255, 255, 255, .2) $lightColor1 = rgba(255, 255, 255, .1) +$accentColor = #2A6FDB + $textShadow = 0 2px 4px $darkColor1; $borderRadius = .25rem $lineNumbersWrapperWidth = 2.5rem diff --git a/docs/Go/.DS_Store b/docs/Go/.DS_Store new file mode 100644 index 0000000000..08970f11ff Binary files /dev/null and b/docs/Go/.DS_Store differ diff --git a/docs/Go/Go.md b/docs/Go/Go.md new file mode 100755 index 0000000000..8a533fc908 --- /dev/null +++ b/docs/Go/Go.md @@ -0,0 +1,133 @@ +> Go 语言入门垫脚石 +> +> https://www.runoob.com/go/go-tutorial.html + + + +Go语言是什么? + +Go出自名门Google公司,是一门支持并发 、垃圾回收的编译型高级编程语言。Go兼具静态编译语言的高性能以及动态语言的高开发效率。 该项目的三位领导者均是著名的语言学家: + +Rob Pike: + +Go 语言项目总负责人,贝尔实验室 Unix 团队成员,参与的项目包括 Plan9,Inferno 操作系统和 Limbo 编程语言 + +Ken Thompson: + +贝尔实验室 Unix 团队成员,C 语言、Unix 和 Plan 9 的创始人之一,与 Rob Pike 共同开发了 UTF-8 字符集规范,图灵奖获得者 + +Robert Griesemer: + +参与开发 Java HotSpot 虚拟机、 V8 Javascript engine + + + +**为什么选** **Go****?** + +❑ 语言简单、开发效率高 + ❑ 高效的垃圾回收机制 + ❑ 支持多返回值 + ❑ 更丰富的内置类型:map、slice、channel、interface ❑ 语言层面支持并发编程 + +❑ 编译型语言,编译即测试 ❑ 跨平台编译 + + + + + + + + + + + + + + + +基础概念 + +数据类型和语句 + +Go 程序的测试 + +标准库的用法 + + + +## Hello world + +Go 语言的基础组成有以下几个部分: + +- 包声明 +- 引入包 +- 函数 +- 变量 +- 语句 & 表达式 +- 注释 + +```go +package main + +import "fmt" + +func main() { + /* 这是我的第一个简单的程序 */ + fmt.Println("Hello, World!") +} +``` + +> 1. 第一行代码 *package main* 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。 +> 2. 下一行 *import "fmt"* 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。 +> 3. 下一行 *func main()* 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。 +> 4. 下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。 +> 5. 下一行 *fmt.Println(...)* 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 +> 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 +> Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。 +> 6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。 + + + + + +与Java 的区别: + +- 行分隔符:在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。 + + + +## 工作区和GOPATH + +我们学习 Go 语言时,要做的第一件事,都是根据自己电脑的计算架构(比如,是 32 位的计算机还是 64 位的计算机)以及操作系统(比如,是 Windows 还是 Linux),从[Go 语言官网](https://golang.google.cn)下载对应的二进制包,也就是可以拿来即用的安装包。 + +随后,我们会解压缩安装包、放置到某个目录、配置环境变量,并通过在命令行中输入`go version`来验证是否安装成功。 + +在这个过程中,我们还需要配置 3 个环境变量,也就是 GOROOT、GOPATH 和 GOBIN。这里我可以简单介绍一下。 + +- GOROOT:Go 语言安装根目录的路径,也就是 GO 语言的安装路径。 +- GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。 +- GOBIN:GO 程序生成的可执行文件(executable file)的路径。 + +其中,GOPATH 背后的概念是最多的,也是最重要的。那么,**今天我们的面试问题是:你知道设置 GOPATH 有什么意义吗?** + +关于这个问题,它的**典型回答**是这样的: + +你可以把 GOPATH 简单理解成 Go 语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个目录都代表 Go 语言的一个工作区(workspace)。 + +我们需要利于这些工作区,去放置 Go 语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file)。 + +事实上,由于 Go 语言项目在其生命周期内的所有操作(编码、依赖管理、构建、测试、安装等)基本上都是围绕着 GOPATH 和工作区进行的。所以,它的背后至少有 3 个知识点,分别是: + +**1. Go 语言源码的组织方式是怎样的;** + +**2. 你是否了解源码安装后的结果(只有在安装后,Go 语言源码才能被我们或其他代码使用);** + +**3. 你是否理解构建和安装 Go 程序的过程(这在开发程序以及查找程序问题的时候都很有用,否则你很可能会走弯路)。** + + + +## 命令源码文件 + +**源码文件又分为三种,即:命令源码文件、库源码文件和测试源码文件,它们都有着不同的用途和编写规则。** + + \ No newline at end of file diff --git "a/docs/TODO/complexity\347\232\204\345\211\257\346\234\254.md" "b/docs/TODO/complexity\347\232\204\345\211\257\346\234\254.md" deleted file mode 100644 index 32979ae6e1..0000000000 --- "a/docs/TODO/complexity\347\232\204\345\211\257\346\234\254.md" +++ /dev/null @@ -1,40 +0,0 @@ -作为一个伪高级开发,想换坑了,之前也没系统刷过算法题,今天打开LeetCode第一题,看解析突然发现大学时候学的时间复杂度、空间复杂度在我脑中只有$O(n)$、$O(0)$ 这几个符号,具体什么意思,学渣体质的我那会应该就没学会,所以,,,输入并输出一下, - - - - - - - - - -算法与数据结构是面试考察的重中之重,也是大家日后学习时需要着重训练的部分。简单的总结一下,大约有这些内容: - - - -## **算法 - Algorithms** - -1. 排序算法:快速排序、归并排序、计数排序 -2. 搜索算法:回溯、递归、剪枝技巧 -3. 图论:最短路、最小生成树、网络流建模 -4. 动态规划:背包问题、最长子序列、计数问题 -5. 基础技巧:分治、倍增、二分、贪心 - -## **数据结构 - Data Structures** - -1. 数组与链表:单 / 双向链表、跳舞链 -2. 栈与队列 -3. 树与图:最近公共祖先、并查集 -4. 哈希表 -5. 堆:大 / 小根堆、可并堆 -6. 字符串:字典树、后缀树 - - - -对于上面总结的这部分内容,力扣已经为大家准备好了相关专题,等待大家来练习啦。 - -算法部分,我们开设了 [初级算法 - 帮助入门](https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/)、[中级算法 - 巩固训练](https://leetcode-cn.com/explore/interview/card/top-interview-questions-medium/)、 [高级算法 - 提升进阶](https://leetcode-cn.com/explore/interview/card/top-interview-questions-hard/) 三个不同的免费探索主题,包含:数组、字符串、搜索、排序、动态规划、数学、图论等许多内容。大家可以针对自己当前的基础与能力,选择相对应的栏目进行练习。为了能够达到较好的效果,建议小伙伴将所有题目都练习 2~3 遍,吃透每一道题目哦。 - -数据结构部分,我们也开设了一个非常系统性的 [数据结构](https://leetcode-cn.com/explore/learn/) 板块,有练习各类数据结构的探索主题,其中包含:队列与栈、数组与字符串、链表、哈希表、二叉树等丰富的内容。每一个章节都包含文字讲解与生动的图片演示,同时配套相关题目。相信只要认真练习,一定能受益匪浅。 - -力扣将热门面试问题里比较新的题目按照类别进行了整理,以供大家按模块练习。 diff --git a/docs/_images/.DS_Store b/docs/_images/.DS_Store index 5b27340e81..90d90ebc34 100644 Binary files a/docs/_images/.DS_Store and b/docs/_images/.DS_Store differ diff --git a/docs/_images/Spring/cycle-demo.png b/docs/_images/Spring/cycle-demo.png new file mode 100644 index 0000000000..e27743eff8 Binary files /dev/null and b/docs/_images/Spring/cycle-demo.png differ diff --git a/docs/_images/Spring/cycle-dependency-code.png b/docs/_images/Spring/cycle-dependency-code.png new file mode 100644 index 0000000000..b032b328a0 Binary files /dev/null and b/docs/_images/Spring/cycle-dependency-code.png differ diff --git a/docs/_images/Spring/cycle-dependency-constructor.png b/docs/_images/Spring/cycle-dependency-constructor.png new file mode 100644 index 0000000000..78ee686144 Binary files /dev/null and b/docs/_images/Spring/cycle-dependency-constructor.png differ diff --git a/docs/_images/Spring/cycle-dependency-index.png b/docs/_images/Spring/cycle-dependency-index.png new file mode 100644 index 0000000000..d70f6c4339 Binary files /dev/null and b/docs/_images/Spring/cycle-dependency-index.png differ diff --git a/docs/_images/Spring/getEarlyBeanReference-code.png b/docs/_images/Spring/getEarlyBeanReference-code.png new file mode 100644 index 0000000000..42ce28648e Binary files /dev/null and b/docs/_images/Spring/getEarlyBeanReference-code.png differ diff --git a/docs/_images/Spring/spring-aop-demo.svg b/docs/_images/Spring/spring-aop-demo.svg new file mode 100644 index 0000000000..150a14d37c --- /dev/null +++ b/docs/_images/Spring/spring-aop-demo.svg @@ -0,0 +1,3 @@ + + +验证参数验证参数前置日志前置日志add()add()后置日志后置日志验证参数验证参数前置日志前置日志sub()sub()后置日志后置日志验证参数验证参数前置日志前置日志mul()mul()后置日志后置日志验证参数验证参数前置日志前置日志div()div()后置日志后置日志后置日志后置日志验证参数验证参数前置日志前置日志add()add()sub()sub()mul()mul()div()div()抽取横切关注点抽取横切关注点业务逻辑业务逻辑业务逻辑业务逻辑切面切面AOPAOP验证验证日志日志Viewer does not support full SVG 1.1 \ No newline at end of file diff --git a/docs/_images/Spring/spring-aop-log.png b/docs/_images/Spring/spring-aop-log.png new file mode 100644 index 0000000000..718171766a --- /dev/null +++ b/docs/_images/Spring/spring-aop-log.png @@ -0,0 +1,3 @@ + + +调用者调用者CalculatorCalculator日志代理日志代理验证代理验证代理记录开始日志记录开始日志参数检验参数检验记录结束日志记录结束日志Viewer does not support full SVG 1.1 \ No newline at end of file diff --git a/docs/_images/Spring/spring-aop-log.svg b/docs/_images/Spring/spring-aop-log.svg new file mode 100644 index 0000000000..718171766a --- /dev/null +++ b/docs/_images/Spring/spring-aop-log.svg @@ -0,0 +1,3 @@ + + +调用者调用者CalculatorCalculator日志代理日志代理验证代理验证代理记录开始日志记录开始日志参数检验参数检验记录结束日志记录结束日志Viewer does not support full SVG 1.1 \ No newline at end of file diff --git a/docs/_images/Spring/spring-createbean.png b/docs/_images/Spring/spring-createbean.png new file mode 100644 index 0000000000..fe377d4d1a Binary files /dev/null and b/docs/_images/Spring/spring-createbean.png differ diff --git a/docs/_images/Spring/spring-getbean.png b/docs/_images/Spring/spring-getbean.png new file mode 100644 index 0000000000..64851f91a7 Binary files /dev/null and b/docs/_images/Spring/spring-getbean.png differ diff --git a/images/.DS_Store b/docs/_images/ad/.DS_Store similarity index 75% rename from images/.DS_Store rename to docs/_images/ad/.DS_Store index d1bd9f9216..21fad860c7 100644 Binary files a/images/.DS_Store and b/docs/_images/ad/.DS_Store differ diff --git a/docs/_images/ad/ad-platforms.png b/docs/_images/ad/ad-platforms.png new file mode 100644 index 0000000000..a240924a57 Binary files /dev/null and b/docs/_images/ad/ad-platforms.png differ diff --git a/docs/_images/ad/mobile_search.png b/docs/_images/ad/mobile_search.png new file mode 100644 index 0000000000..901bfa05d0 Binary files /dev/null and b/docs/_images/ad/mobile_search.png differ diff --git a/docs/_images/ad/real-time-report.png b/docs/_images/ad/real-time-report.png new file mode 100644 index 0000000000..269b8ab4da Binary files /dev/null and b/docs/_images/ad/real-time-report.png differ diff --git a/docs/_images/ad/report-ad.png b/docs/_images/ad/report-ad.png new file mode 100644 index 0000000000..94f63b2e60 Binary files /dev/null and b/docs/_images/ad/report-ad.png differ diff --git a/docs/_images/ad/report-feature.png b/docs/_images/ad/report-feature.png new file mode 100644 index 0000000000..f8030e81cc Binary files /dev/null and b/docs/_images/ad/report-feature.png differ diff --git a/docs/_images/ad/report-framework.png b/docs/_images/ad/report-framework.png new file mode 100644 index 0000000000..586ee859ea --- /dev/null +++ b/docs/_images/ad/report-framework.png @@ -0,0 +1,4 @@ + + + +网页报告网页报告定制报告定制报告API 报告API 报告移动端报告移动端报告数据应用数据应用物理存储物理存储基础宽表基础宽表数据采集数据采集 RSYNC...消费数据消费数据数据源数据源物化视图物化视图HBaseHBase关键词PV关键词PV计费Kafka计费KafkaAdtech KafkaAdtech Kafka点击数据点击数据排名数据排名数据展现数据展现数据逻辑存储逻辑存储数据计算(数据模型)数据计算(数据模型)...账号报告账号报告计划报告计划报告关键词报告关键词报告PhoenixPhoenixHBaseHBaseHBaseHBase数据存储数据存储分时报告分时报告普通创意普通创意排名数据排名数据 HDFS / Hive...计费日志计费日志组报告组报告搜索词报告搜索词报告分地域报告分地域报告分人群报告分人群报告创意组件创意组件应用组件应用组件视频类样式视频类样式视频类样式视频类样式子链类样式子链类样式商品类样式商品类样式子链类样式子链类样式基础报告基础报告维度报告维度报告创意报告创意报告............样式报告样式报告kylinkylinViewer does not support full SVG 1.1 \ No newline at end of file diff --git a/docs/_images/ad/report-framework.svg b/docs/_images/ad/report-framework.svg new file mode 100644 index 0000000000..586ee859ea --- /dev/null +++ b/docs/_images/ad/report-framework.svg @@ -0,0 +1,4 @@ + + + +网页报告网页报告定制报告定制报告API 报告API 报告移动端报告移动端报告数据应用数据应用物理存储物理存储基础宽表基础宽表数据采集数据采集 RSYNC...消费数据消费数据数据源数据源物化视图物化视图HBaseHBase关键词PV关键词PV计费Kafka计费KafkaAdtech KafkaAdtech Kafka点击数据点击数据排名数据排名数据展现数据展现数据逻辑存储逻辑存储数据计算(数据模型)数据计算(数据模型)...账号报告账号报告计划报告计划报告关键词报告关键词报告PhoenixPhoenixHBaseHBaseHBaseHBase数据存储数据存储分时报告分时报告普通创意普通创意排名数据排名数据 HDFS / Hive...计费日志计费日志组报告组报告搜索词报告搜索词报告分地域报告分地域报告分人群报告分人群报告创意组件创意组件应用组件应用组件视频类样式视频类样式视频类样式视频类样式子链类样式子链类样式商品类样式商品类样式子链类样式子链类样式基础报告基础报告维度报告维度报告创意报告创意报告............样式报告样式报告kylinkylinViewer does not support full SVG 1.1 \ No newline at end of file diff --git a/docs/_images/ad/report-platform.png b/docs/_images/ad/report-platform.png new file mode 100644 index 0000000000..a674ac297f Binary files /dev/null and b/docs/_images/ad/report-platform.png differ diff --git a/docs/_images/ad/sogou-data-center-ETL.drawio.svg b/docs/_images/ad/sogou-data-center-ETL.drawio.svg new file mode 100644 index 0000000000..a4b1a3ae56 --- /dev/null +++ b/docs/_images/ad/sogou-data-center-ETL.drawio.svg @@ -0,0 +1,4 @@ + + + +xuripvxuripvxuribasedataxuribasedataprocessPvTaskprocessPvTaskprocessClickTaskprocessClickTaskxuricostxuricostxuripv_prexuripv_prexurilogallxurilogallxuri_pv_hourxuri_pv_hourimportPcXuriPvHour2HiveTaskimportMobileXuriPvHour2HiveTaskimportPcXuriPvHour2Hive...pv_logpv_log1变多,1条日志拆分成多条广告展现1变多,1条日志拆分成多条广告展现给每条广告展现设置唯一 pvid, 并拆分素材三元组,计算关键词排名给每条广告展现设置唯一 pvid, 并拆分素材三元组,计算关键词排名processPvPreTaskprocessPvPreTask一次搜索会打印1条日志,会有多个广告展现,拼接成1条展现日志一次搜索会打印1条日志,会有多个广告展现,拼接成1条展现日志xuri_pv_hourxuri_pv_hourxuri_cpcreportxuri_cpcreportxuri_ideareportxuri_ideareportideareport_viewideareport_viewcpcreport_viewcpcreport_viewregionreport_viewregionreport_viewhourreport_viewhourreport_viewipreport_viewipreport_viewstylereport_styletype_viewstylereport_styletype...stylereport_materialtype_viewstylereport_materialtype...stylereport_material_viewstylereport_material...stylereport_material_clickposition_viewstylereport_material_...账号、计划、组、关键词报告账号、计划、组、关键词报告地域报告地域报告分时报告分时报告IP报告IP报告创意报告创意报告样式维度报告样式维度报告素材类型维度报告素材类型维度报告素材id维度素材id维度素材点击位置维度素材点击位置维度cpcreport_viewcpcreport_view搜索词报告搜索词报告大宽表大宽表计费组生成,单条计费日志,包含点四元祖和消耗计费组生成,单条计费日志,包含点四元祖和消耗Viewer does not support full SVG 1.1 \ No newline at end of file diff --git a/docs/_images/ad/xuri.p4p.sogou.png b/docs/_images/ad/xuri.p4p.sogou.png new file mode 100644 index 0000000000..a674ac297f Binary files /dev/null and b/docs/_images/ad/xuri.p4p.sogou.png differ diff --git a/docs/work/.DS_Store b/docs/_images/algorithms/.DS_Store similarity index 92% rename from docs/work/.DS_Store rename to docs/_images/algorithms/.DS_Store index e920c599d2..4f15a9793e 100644 Binary files a/docs/work/.DS_Store and b/docs/_images/algorithms/.DS_Store differ diff --git a/docs/_images/algorithms/stock-problems.png b/docs/_images/algorithms/stock-problems.png new file mode 100644 index 0000000000..e51cca9f95 Binary files /dev/null and b/docs/_images/algorithms/stock-problems.png differ diff --git a/docs/_images/data-structure/.DS_Store b/docs/_images/data-structure/.DS_Store index c19ed93f2b..e67b729ad8 100644 Binary files a/docs/_images/data-structure/.DS_Store and b/docs/_images/data-structure/.DS_Store differ diff --git a/docs/_images/data-structure/linked-list/double-linkedlist-add.png b/docs/_images/data-structure/linked-list/double-linkedlist-add.png new file mode 100644 index 0000000000..c04cea1ed1 Binary files /dev/null and b/docs/_images/data-structure/linked-list/double-linkedlist-add.png differ diff --git a/docs/_images/data-structure/linked-list/double-linkedlist-del.png b/docs/_images/data-structure/linked-list/double-linkedlist-del.png new file mode 100644 index 0000000000..7b8d0677d2 Binary files /dev/null and b/docs/_images/data-structure/linked-list/double-linkedlist-del.png differ diff --git a/docs/_images/data-structure/linked-list/doule-linkedlist-node.png b/docs/_images/data-structure/linked-list/doule-linkedlist-node.png new file mode 100644 index 0000000000..f0d654b956 Binary files /dev/null and b/docs/_images/data-structure/linked-list/doule-linkedlist-node.png differ diff --git a/docs/_images/data-structure/linked-list/doule-linkedlist.png b/docs/_images/data-structure/linked-list/doule-linkedlist.png new file mode 100644 index 0000000000..ff1865f23c Binary files /dev/null and b/docs/_images/data-structure/linked-list/doule-linkedlist.png differ diff --git a/docs/_images/data-structure/linked-list/single-linkedlist-add.png b/docs/_images/data-structure/linked-list/single-linkedlist-add.png new file mode 100644 index 0000000000..b14464df82 Binary files /dev/null and b/docs/_images/data-structure/linked-list/single-linkedlist-add.png differ diff --git a/docs/_images/data-structure/linked-list/single-linkedlist-del.png b/docs/_images/data-structure/linked-list/single-linkedlist-del.png new file mode 100644 index 0000000000..0ac9ae96b3 Binary files /dev/null and b/docs/_images/data-structure/linked-list/single-linkedlist-del.png differ diff --git a/docs/_images/data-structure/linked-list/single-linkedlist-head.png b/docs/_images/data-structure/linked-list/single-linkedlist-head.png new file mode 100644 index 0000000000..b6b44d72c7 Binary files /dev/null and b/docs/_images/data-structure/linked-list/single-linkedlist-head.png differ diff --git a/docs/_images/data-structure/linked-list/single-linkedlist-node.png b/docs/_images/data-structure/linked-list/single-linkedlist-node.png new file mode 100644 index 0000000000..a3435041f7 Binary files /dev/null and b/docs/_images/data-structure/linked-list/single-linkedlist-node.png differ diff --git a/docs/_images/data-structure/linked-list/single-linkedlist.png b/docs/_images/data-structure/linked-list/single-linkedlist.png new file mode 100644 index 0000000000..fed5021108 Binary files /dev/null and b/docs/_images/data-structure/linked-list/single-linkedlist.png differ diff --git a/docs/_images/data-structure/skip-list/skiplist-banner.png b/docs/_images/data-structure/skip-list/skiplist-banner.png new file mode 100644 index 0000000000..74ced7d76b Binary files /dev/null and b/docs/_images/data-structure/skip-list/skiplist-banner.png differ diff --git a/docs/_images/data-structure/tree/68747470733a2f2f747661312e73696e61696d672e636e2f6c617267652f30303753385a496c6c7931676475367871726e6e666a3331393230686f7463762e6a7067.jpeg b/docs/_images/data-structure/tree/68747470733a2f2f747661312e73696e61696d672e636e2f6c617267652f30303753385a496c6c7931676475367871726e6e666a3331393230686f7463762e6a7067.jpeg new file mode 100644 index 0000000000..fdfb661a3d Binary files /dev/null and b/docs/_images/data-structure/tree/68747470733a2f2f747661312e73696e61696d672e636e2f6c617267652f30303753385a496c6c7931676475367871726e6e666a3331393230686f7463762e6a7067.jpeg differ diff --git a/docs/_images/data-structure/tree/array-2-binary-tree.png b/docs/_images/data-structure/tree/array-2-binary-tree.png new file mode 100644 index 0000000000..d3b5e6024f Binary files /dev/null and b/docs/_images/data-structure/tree/array-2-binary-tree.png differ diff --git a/docs/_images/data-structure/tree/avl-tree.png b/docs/_images/data-structure/tree/avl-tree.png new file mode 100644 index 0000000000..d79558af14 Binary files /dev/null and b/docs/_images/data-structure/tree/avl-tree.png differ diff --git a/docs/_images/data-structure/tree/balance-binary-tree.jpeg b/docs/_images/data-structure/tree/balance-binary-tree.jpeg new file mode 100644 index 0000000000..fedb1b3059 Binary files /dev/null and b/docs/_images/data-structure/tree/balance-binary-tree.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-search-tree-insert.gif b/docs/_images/data-structure/tree/binary-search-tree-insert.gif new file mode 100644 index 0000000000..ccf1038bd9 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-search-tree-insert.gif differ diff --git a/docs/_images/data-structure/tree/binary-search-tree-penjee.gif b/docs/_images/data-structure/tree/binary-search-tree-penjee.gif new file mode 100644 index 0000000000..73b8b69b87 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-search-tree-penjee.gif differ diff --git a/docs/_images/data-structure/tree/binary-search-tree.jpeg b/docs/_images/data-structure/tree/binary-search-tree.jpeg new file mode 100644 index 0000000000..599e3ac217 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-search-tree.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-serach-tree-del.png b/docs/_images/data-structure/tree/binary-serach-tree-del.png new file mode 100644 index 0000000000..6c668e6440 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-serach-tree-del.png differ diff --git a/docs/_images/data-structure/tree/binary-tree-2-array.png b/docs/_images/data-structure/tree/binary-tree-2-array.png new file mode 100644 index 0000000000..8df2706690 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-2-array.png differ diff --git a/docs/_images/data-structure/tree/binary-tree-dfs.png b/docs/_images/data-structure/tree/binary-tree-dfs.png new file mode 100644 index 0000000000..a50aba25ba Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-dfs.png differ diff --git a/docs/_images/data-structure/tree/binary-tree-inorder.png b/docs/_images/data-structure/tree/binary-tree-inorder.png new file mode 100644 index 0000000000..f5ab39b8e6 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-inorder.png differ diff --git a/docs/_images/data-structure/tree/binary-tree-leveltraverse.png b/docs/_images/data-structure/tree/binary-tree-leveltraverse.png new file mode 100644 index 0000000000..9d80955094 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-leveltraverse.png differ diff --git a/docs/_images/data-structure/tree/binary-tree-node-store.jpeg b/docs/_images/data-structure/tree/binary-tree-node-store.jpeg new file mode 100644 index 0000000000..3ec509d48c Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-node-store.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-tree-postorder.jpeg b/docs/_images/data-structure/tree/binary-tree-postorder.jpeg new file mode 100644 index 0000000000..3798cda5e3 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-postorder.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-tree-preorder.png b/docs/_images/data-structure/tree/binary-tree-preorder.png new file mode 100644 index 0000000000..778afd1c5d Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-preorder.png differ diff --git a/docs/_images/data-structure/tree/binary-tree-special-case.jpeg b/docs/_images/data-structure/tree/binary-tree-special-case.jpeg new file mode 100644 index 0000000000..3bcb093e7e Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-special-case.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-tree-store1.jpeg b/docs/_images/data-structure/tree/binary-tree-store1.jpeg new file mode 100644 index 0000000000..14dfc1db33 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-store1.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-tree-store2.jpeg b/docs/_images/data-structure/tree/binary-tree-store2.jpeg new file mode 100644 index 0000000000..44b74f4583 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-store2.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-tree-structure.jpeg b/docs/_images/data-structure/tree/binary-tree-structure.jpeg new file mode 100644 index 0000000000..38df7334c9 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-structure.jpeg differ diff --git a/docs/_images/data-structure/tree/binary-tree-three-store.jpeg b/docs/_images/data-structure/tree/binary-tree-three-store.jpeg new file mode 100644 index 0000000000..41667730a8 Binary files /dev/null and b/docs/_images/data-structure/tree/binary-tree-three-store.jpeg differ diff --git a/docs/_images/data-structure/tree/skewed-binary-tree.jpeg b/docs/_images/data-structure/tree/skewed-binary-tree.jpeg new file mode 100644 index 0000000000..658ffeb655 Binary files /dev/null and b/docs/_images/data-structure/tree/skewed-binary-tree.jpeg differ diff --git "a/docs/_images/data-structure/\344\270\255\345\272\217\351\201\215\345\216\206.png" "b/docs/_images/data-structure/\344\270\255\345\272\217\351\201\215\345\216\206.png" deleted file mode 100644 index 23f4b860c9..0000000000 Binary files "a/docs/_images/data-structure/\344\270\255\345\272\217\351\201\215\345\216\206.png" and /dev/null differ diff --git "a/docs/_images/data-structure/\345\211\215\345\272\217\351\201\215\345\216\206.png" "b/docs/_images/data-structure/\345\211\215\345\272\217\351\201\215\345\216\206.png" deleted file mode 100644 index cfffe0a381..0000000000 Binary files "a/docs/_images/data-structure/\345\211\215\345\272\217\351\201\215\345\216\206.png" and /dev/null differ diff --git "a/docs/_images/data-structure/\347\272\265\345\220\221\351\201\215\345\216\206.png" "b/docs/_images/data-structure/\347\272\265\345\220\221\351\201\215\345\216\206.png" deleted file mode 100644 index a71df4b4f5..0000000000 Binary files "a/docs/_images/data-structure/\347\272\265\345\220\221\351\201\215\345\216\206.png" and /dev/null differ diff --git a/docs/_images/design-pattern/UML-type.png b/docs/_images/design-pattern/UML-type.png deleted file mode 100644 index 35d9bd2829..0000000000 Binary files a/docs/_images/design-pattern/UML-type.png and /dev/null differ diff --git a/docs/_images/design-pattern/abstract-factory-demo.png b/docs/_images/design-pattern/abstract-factory-demo.png deleted file mode 100644 index 7162ebfb46..0000000000 Binary files a/docs/_images/design-pattern/abstract-factory-demo.png and /dev/null differ diff --git a/docs/_images/design-pattern/abstract-factory-uml.png b/docs/_images/design-pattern/abstract-factory-uml.png deleted file mode 100644 index 88ca80d309..0000000000 Binary files a/docs/_images/design-pattern/abstract-factory-uml.png and /dev/null differ diff --git a/docs/_images/design-pattern/ali-strategy.png b/docs/_images/design-pattern/ali-strategy.png deleted file mode 100644 index 952bd661ae..0000000000 Binary files a/docs/_images/design-pattern/ali-strategy.png and /dev/null differ diff --git a/docs/_images/design-pattern/builder-UML.png b/docs/_images/design-pattern/builder-UML.png deleted file mode 100644 index de7d87a074..0000000000 Binary files a/docs/_images/design-pattern/builder-UML.png and /dev/null differ diff --git a/docs/_images/design-pattern/builder-car.png b/docs/_images/design-pattern/builder-car.png deleted file mode 100644 index c6f41f4554..0000000000 Binary files a/docs/_images/design-pattern/builder-car.png and /dev/null differ diff --git a/docs/_images/design-pattern/decorator-uml.png b/docs/_images/design-pattern/decorator-uml.png deleted file mode 100644 index b534ffad1f..0000000000 Binary files a/docs/_images/design-pattern/decorator-uml.png and /dev/null differ diff --git a/docs/_images/design-pattern/factory-method-uml.png b/docs/_images/design-pattern/factory-method-uml.png deleted file mode 100644 index 7f6a0eb799..0000000000 Binary files a/docs/_images/design-pattern/factory-method-uml.png and /dev/null differ diff --git a/docs/_images/design-pattern/hello-file.png b/docs/_images/design-pattern/hello-file.png deleted file mode 100644 index 64042c701c..0000000000 Binary files a/docs/_images/design-pattern/hello-file.png and /dev/null differ diff --git a/docs/_images/design-pattern/observer-uml.png b/docs/_images/design-pattern/observer-uml.png deleted file mode 100644 index fa14885acb..0000000000 Binary files a/docs/_images/design-pattern/observer-uml.png and /dev/null differ diff --git a/docs/_images/design-pattern/pipeline-result.png b/docs/_images/design-pattern/pipeline-result.png deleted file mode 100644 index 1e64c5061b..0000000000 Binary files a/docs/_images/design-pattern/pipeline-result.png and /dev/null differ diff --git a/docs/_images/design-pattern/responsibility-pattern-uml.png b/docs/_images/design-pattern/responsibility-pattern-uml.png deleted file mode 100644 index abc7ce238e..0000000000 Binary files a/docs/_images/design-pattern/responsibility-pattern-uml.png and /dev/null differ diff --git a/docs/_images/design-pattern/simple-factory-uml.png b/docs/_images/design-pattern/simple-factory-uml.png deleted file mode 100644 index f82cdab658..0000000000 Binary files a/docs/_images/design-pattern/simple-factory-uml.png and /dev/null differ diff --git a/docs/_images/distribution/cap.jpg b/docs/_images/distribution/cap.jpg new file mode 100644 index 0000000000..81afb6de8c Binary files /dev/null and b/docs/_images/distribution/cap.jpg differ diff --git a/docs/_images/message-queue/mesage-what.png b/docs/_images/distribution/message-queue/mesage-what.png similarity index 100% rename from docs/_images/message-queue/mesage-what.png rename to docs/_images/distribution/message-queue/mesage-what.png diff --git a/docs/_images/message-queue/message-acaticemq.png b/docs/_images/distribution/message-queue/message-acaticemq.png similarity index 100% rename from docs/_images/message-queue/message-acaticemq.png rename to docs/_images/distribution/message-queue/message-acaticemq.png diff --git a/docs/_images/message-queue/message-kafka.png b/docs/_images/distribution/message-queue/message-kafka.png similarity index 100% rename from docs/_images/message-queue/message-kafka.png rename to docs/_images/distribution/message-queue/message-kafka.png diff --git a/docs/_images/message-queue/message-overview.png b/docs/_images/distribution/message-queue/message-overview.png similarity index 100% rename from docs/_images/message-queue/message-overview.png rename to docs/_images/distribution/message-queue/message-overview.png diff --git a/docs/_images/message-queue/message-pubsub-mode.png b/docs/_images/distribution/message-queue/message-pubsub-mode.png similarity index 100% rename from docs/_images/message-queue/message-pubsub-mode.png rename to docs/_images/distribution/message-queue/message-pubsub-mode.png diff --git a/docs/_images/message-queue/message-push-pull.png b/docs/_images/distribution/message-queue/message-push-pull.png similarity index 100% rename from docs/_images/message-queue/message-push-pull.png rename to docs/_images/distribution/message-queue/message-push-pull.png diff --git a/docs/_images/message-queue/message-queue-mode.png b/docs/_images/distribution/message-queue/message-queue-mode.png similarity index 100% rename from docs/_images/message-queue/message-queue-mode.png rename to docs/_images/distribution/message-queue/message-queue-mode.png diff --git a/docs/_images/message-queue/message-queue.png b/docs/_images/distribution/message-queue/message-queue.png similarity index 100% rename from docs/_images/message-queue/message-queue.png rename to docs/_images/distribution/message-queue/message-queue.png diff --git a/docs/_images/message-queue/message-rabbitmq.png b/docs/_images/distribution/message-queue/message-rabbitmq.png similarity index 100% rename from docs/_images/message-queue/message-rabbitmq.png rename to docs/_images/distribution/message-queue/message-rabbitmq.png diff --git a/docs/_images/message-queue/message-rocketmq.png b/docs/_images/distribution/message-queue/message-rocketmq.png similarity index 100% rename from docs/_images/message-queue/message-rocketmq.png rename to docs/_images/distribution/message-queue/message-rocketmq.png diff --git a/docs/_images/message-queue/message-server-mode.png b/docs/_images/distribution/message-queue/message-server-mode.png similarity index 100% rename from docs/_images/message-queue/message-server-mode.png rename to docs/_images/distribution/message-queue/message-server-mode.png diff --git a/docs/_images/message-queue/message-user-1.png b/docs/_images/distribution/message-queue/message-user-1.png similarity index 100% rename from docs/_images/message-queue/message-user-1.png rename to docs/_images/distribution/message-queue/message-user-1.png diff --git a/docs/_images/message-queue/message-user-2.png b/docs/_images/distribution/message-queue/message-user-2.png similarity index 100% rename from docs/_images/message-queue/message-user-2.png rename to docs/_images/distribution/message-queue/message-user-2.png diff --git a/docs/_images/message-queue/message-user-3.png b/docs/_images/distribution/message-queue/message-user-3.png similarity index 100% rename from docs/_images/message-queue/message-user-3.png rename to docs/_images/distribution/message-queue/message-user-3.png diff --git a/docs/_images/message-queue/message-user-4.png b/docs/_images/distribution/message-queue/message-user-4.png similarity index 100% rename from docs/_images/message-queue/message-user-4.png rename to docs/_images/distribution/message-queue/message-user-4.png diff --git a/docs/_images/distribution/message-queue/mq-one2many.jpg b/docs/_images/distribution/message-queue/mq-one2many.jpg new file mode 100644 index 0000000000..eea45d340f Binary files /dev/null and b/docs/_images/distribution/message-queue/mq-one2many.jpg differ diff --git a/docs/_images/distribution/message-queue/mq-point2point.jpg b/docs/_images/distribution/message-queue/mq-point2point.jpg new file mode 100644 index 0000000000..b33db11faa Binary files /dev/null and b/docs/_images/distribution/message-queue/mq-point2point.jpg differ diff --git a/docs/_images/message-queue/mq_index.png b/docs/_images/distribution/message-queue/mq_index.png similarity index 100% rename from docs/_images/message-queue/mq_index.png rename to docs/_images/distribution/message-queue/mq_index.png diff --git a/docs/_images/message-queue/mq_overview.png b/docs/_images/distribution/message-queue/mq_overview.png similarity index 100% rename from docs/_images/message-queue/mq_overview.png rename to docs/_images/distribution/message-queue/mq_overview.png diff --git "a/docs/_images/message-queue/\345\260\217\347\213\227\351\222\261\351\222\261.png" "b/docs/_images/distribution/message-queue/\345\260\217\347\213\227\351\222\261\351\222\261.png" similarity index 100% rename from "docs/_images/message-queue/\345\260\217\347\213\227\351\222\261\351\222\261.png" rename to "docs/_images/distribution/message-queue/\345\260\217\347\213\227\351\222\261\351\222\261.png" diff --git a/docs/_images/distribution/zab-commit.png b/docs/_images/distribution/zab-commit.png new file mode 100644 index 0000000000..89650a4e01 Binary files /dev/null and b/docs/_images/distribution/zab-commit.png differ diff --git a/docs/_images/zookeeper/2PC.png b/docs/_images/distribution/zookeeper/2PC.png similarity index 100% rename from docs/_images/zookeeper/2PC.png rename to docs/_images/distribution/zookeeper/2PC.png diff --git a/docs/_images/distribution/zookeeper/3PC.png b/docs/_images/distribution/zookeeper/3PC.png new file mode 100644 index 0000000000..95c7a9c735 Binary files /dev/null and b/docs/_images/distribution/zookeeper/3PC.png differ diff --git a/docs/_images/distribution/zookeeper/paxos.png b/docs/_images/distribution/zookeeper/paxos.png new file mode 100644 index 0000000000..07fe08e5a8 Binary files /dev/null and b/docs/_images/distribution/zookeeper/paxos.png differ diff --git a/docs/_images/distribution/zookeeper/zab-commit.png b/docs/_images/distribution/zookeeper/zab-commit.png new file mode 100644 index 0000000000..b08dc54ec2 Binary files /dev/null and b/docs/_images/distribution/zookeeper/zab-commit.png differ diff --git a/docs/_images/distribution/zookeeper/zab.png b/docs/_images/distribution/zookeeper/zab.png new file mode 100644 index 0000000000..5bf2483a8b Binary files /dev/null and b/docs/_images/distribution/zookeeper/zab.png differ diff --git a/docs/_images/zookeeper/zk-conf.png b/docs/_images/distribution/zookeeper/zk-conf.png similarity index 100% rename from docs/_images/zookeeper/zk-conf.png rename to docs/_images/distribution/zookeeper/zk-conf.png diff --git a/docs/_images/zookeeper/zk-elect.jpg b/docs/_images/distribution/zookeeper/zk-elect.jpg similarity index 100% rename from docs/_images/zookeeper/zk-elect.jpg rename to docs/_images/distribution/zookeeper/zk-elect.jpg diff --git a/docs/_images/zookeeper/zk-listener.png b/docs/_images/distribution/zookeeper/zk-listener.png similarity index 100% rename from docs/_images/zookeeper/zk-listener.png rename to docs/_images/distribution/zookeeper/zk-listener.png diff --git a/docs/_images/zookeeper/zk-loadbalancing.png b/docs/_images/distribution/zookeeper/zk-loadbalancing.png similarity index 100% rename from docs/_images/zookeeper/zk-loadbalancing.png rename to docs/_images/distribution/zookeeper/zk-loadbalancing.png diff --git a/docs/_images/distribution/zookeeper/zk-manage-node.jpg b/docs/_images/distribution/zookeeper/zk-manage-node.jpg new file mode 100644 index 0000000000..b222ef0e21 Binary files /dev/null and b/docs/_images/distribution/zookeeper/zk-manage-node.jpg differ diff --git a/docs/_images/zookeeper/zk-unify-conf.png b/docs/_images/distribution/zookeeper/zk-unify-conf.png similarity index 100% rename from docs/_images/zookeeper/zk-unify-conf.png rename to docs/_images/distribution/zookeeper/zk-unify-conf.png diff --git a/docs/_images/distribution/zookeeper/zk-work.png b/docs/_images/distribution/zookeeper/zk-work.png new file mode 100644 index 0000000000..ab33287294 Binary files /dev/null and b/docs/_images/distribution/zookeeper/zk-work.png differ diff --git a/docs/_images/zookeeper/zk-write-data.png b/docs/_images/distribution/zookeeper/zk-write-data.png similarity index 100% rename from docs/_images/zookeeper/zk-write-data.png rename to docs/_images/distribution/zookeeper/zk-write-data.png diff --git a/docs/_images/zookeeper/zk-znode.png b/docs/_images/distribution/zookeeper/zk-znode.png similarity index 100% rename from docs/_images/zookeeper/zk-znode.png rename to docs/_images/distribution/zookeeper/zk-znode.png diff --git a/docs/_images/end.jpg b/docs/_images/end.jpg new file mode 100644 index 0000000000..3a6ae42eda Binary files /dev/null and b/docs/_images/end.jpg differ diff --git a/docs/_images/redis/.DS_Store b/docs/_images/java/.DS_Store similarity index 58% rename from docs/_images/redis/.DS_Store rename to docs/_images/java/.DS_Store index bf1618e94f..c56bcdd79b 100644 Binary files a/docs/_images/redis/.DS_Store and b/docs/_images/java/.DS_Store differ diff --git a/docs/_images/java/JVM/java-object.jpg b/docs/_images/java/JVM/java-object.jpg deleted file mode 100644 index bc17bccfaf..0000000000 Binary files a/docs/_images/java/JVM/java-object.jpg and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-bytecode.png b/docs/_images/java/JVM/jvm-bytecode.png deleted file mode 100644 index 19c7898e17..0000000000 Binary files a/docs/_images/java/JVM/jvm-bytecode.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-class-load.png b/docs/_images/java/JVM/jvm-class-load.png deleted file mode 100644 index e4f7fd62b6..0000000000 Binary files a/docs/_images/java/JVM/jvm-class-load.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-compile.png b/docs/_images/java/JVM/jvm-compile.png deleted file mode 100644 index 3429c6dcc9..0000000000 Binary files a/docs/_images/java/JVM/jvm-compile.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-dynamic-linking.png b/docs/_images/java/JVM/jvm-dynamic-linking.png deleted file mode 100644 index d706307643..0000000000 Binary files a/docs/_images/java/JVM/jvm-dynamic-linking.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-framework.png b/docs/_images/java/JVM/jvm-framework.png deleted file mode 100644 index e152cf5509..0000000000 Binary files a/docs/_images/java/JVM/jvm-framework.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-javap.png b/docs/_images/java/JVM/jvm-javap.png deleted file mode 100644 index 240b310de9..0000000000 Binary files a/docs/_images/java/JVM/jvm-javap.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-jdk-jre.png b/docs/_images/java/JVM/jvm-jdk-jre.png deleted file mode 100644 index a7e43e5232..0000000000 Binary files a/docs/_images/java/JVM/jvm-jdk-jre.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-pc-counter.png b/docs/_images/java/JVM/jvm-pc-counter.png deleted file mode 100644 index 2a03b82c4d..0000000000 Binary files a/docs/_images/java/JVM/jvm-pc-counter.png and /dev/null differ diff --git a/docs/_images/java/JVM/jvm-stack-frame.png b/docs/_images/java/JVM/jvm-stack-frame.png deleted file mode 100644 index 53b6fd1e63..0000000000 Binary files a/docs/_images/java/JVM/jvm-stack-frame.png and /dev/null differ diff --git "a/docs/_images/java/JVM/\345\240\206\345\206\205\345\255\230\347\251\272\351\227\264.png" "b/docs/_images/java/JVM/\345\240\206\345\206\205\345\255\230\347\251\272\351\227\264.png" deleted file mode 100644 index 0ac7cd1298..0000000000 Binary files "a/docs/_images/java/JVM/\345\240\206\345\206\205\345\255\230\347\251\272\351\227\264.png" and /dev/null differ diff --git "a/docs/_images/java/JVM/\345\244\215\345\210\266\347\256\227\346\263\225.png" "b/docs/_images/java/JVM/\345\244\215\345\210\266\347\256\227\346\263\225.png" deleted file mode 100644 index 85d6e5cffd..0000000000 Binary files "a/docs/_images/java/JVM/\345\244\215\345\210\266\347\256\227\346\263\225.png" and /dev/null differ diff --git a/docs/_images/java/juc/thread-pool-reject.png b/docs/_images/java/juc/thread-pool-reject.png new file mode 100644 index 0000000000..5e8629ad13 Binary files /dev/null and b/docs/_images/java/juc/thread-pool-reject.png differ diff --git a/docs/_images/leetcode-hot100-array.png b/docs/_images/leetcode-hot100-array.png new file mode 100644 index 0000000000..31c5607bb7 Binary files /dev/null and b/docs/_images/leetcode-hot100-array.png differ diff --git a/docs/_images/message-queue/Kafka/DMA.png b/docs/_images/message-queue/Kafka/DMA.png deleted file mode 100644 index a8cf48a85c..0000000000 Binary files a/docs/_images/message-queue/Kafka/DMA.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/Sendfile.png b/docs/_images/message-queue/Kafka/Sendfile.png deleted file mode 100644 index ce51efc629..0000000000 Binary files a/docs/_images/message-queue/Kafka/Sendfile.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/controller-leader.png b/docs/_images/message-queue/Kafka/controller-leader.png deleted file mode 100644 index cbab054742..0000000000 Binary files a/docs/_images/message-queue/Kafka/controller-leader.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/interceptor-demo.png b/docs/_images/message-queue/Kafka/interceptor-demo.png deleted file mode 100644 index 804d1ce455..0000000000 Binary files a/docs/_images/message-queue/Kafka/interceptor-demo.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-ack-slg.png b/docs/_images/message-queue/Kafka/kafka-ack-slg.png deleted file mode 100644 index 494769ff03..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-ack-slg.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-ack=-1.png b/docs/_images/message-queue/Kafka/kafka-ack=-1.png deleted file mode 100644 index 0647f7439d..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-ack=-1.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-ack=1.png b/docs/_images/message-queue/Kafka/kafka-ack=1.png deleted file mode 100644 index 95be4d96a5..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-ack=1.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-apis.png b/docs/_images/message-queue/Kafka/kafka-apis.png deleted file mode 100644 index db6053ccc2..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-apis.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-consume-group.png b/docs/_images/message-queue/Kafka/kafka-consume-group.png deleted file mode 100644 index c3931c7a75..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-consume-group.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-leo.png b/docs/_images/message-queue/Kafka/kafka-leo.png deleted file mode 100644 index d5f2b6e9c4..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-leo.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-partition.jpg b/docs/_images/message-queue/Kafka/kafka-partition.jpg deleted file mode 100644 index e8cd1de32b..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-partition.jpg and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-producer-thread.png b/docs/_images/message-queue/Kafka/kafka-producer-thread.png deleted file mode 100644 index 1ecda6e19c..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-producer-thread.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-segement.jpg b/docs/_images/message-queue/Kafka/kafka-segement.jpg deleted file mode 100644 index 8bc98a8dd6..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-segement.jpg and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-start.png b/docs/_images/message-queue/Kafka/kafka-start.png deleted file mode 100644 index 1080ae6a7b..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-start.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-streams-data-clean.png b/docs/_images/message-queue/Kafka/kafka-streams-data-clean.png deleted file mode 100644 index 3c6109c9a0..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-streams-data-clean.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-workflow.jpg b/docs/_images/message-queue/Kafka/kafka-workflow.jpg deleted file mode 100644 index 49ed343ac4..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-workflow.jpg and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kafka-write-flow.png b/docs/_images/message-queue/Kafka/kafka-write-flow.png deleted file mode 100644 index 487d735acb..0000000000 Binary files a/docs/_images/message-queue/Kafka/kafka-write-flow.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kakfa-java-demo.png b/docs/_images/message-queue/Kafka/kakfa-java-demo.png deleted file mode 100644 index 491cb41ad0..0000000000 Binary files a/docs/_images/message-queue/Kafka/kakfa-java-demo.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kakfa-principle.png b/docs/_images/message-queue/Kafka/kakfa-principle.png deleted file mode 100644 index c1ce1730bc..0000000000 Binary files a/docs/_images/message-queue/Kafka/kakfa-principle.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/kakfa-streams-flow.png b/docs/_images/message-queue/Kafka/kakfa-streams-flow.png deleted file mode 100644 index 180c313609..0000000000 Binary files a/docs/_images/message-queue/Kafka/kakfa-streams-flow.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/log_anatomy.png b/docs/_images/message-queue/Kafka/log_anatomy.png deleted file mode 100644 index a649499926..0000000000 Binary files a/docs/_images/message-queue/Kafka/log_anatomy.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/log_consumer.png b/docs/_images/message-queue/Kafka/log_consumer.png deleted file mode 100644 index fbc45f2060..0000000000 Binary files a/docs/_images/message-queue/Kafka/log_consumer.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/mmap.png b/docs/_images/message-queue/Kafka/mmap.png deleted file mode 100644 index 9b5668086f..0000000000 Binary files a/docs/_images/message-queue/Kafka/mmap.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/mq.png b/docs/_images/message-queue/Kafka/mq.png deleted file mode 100644 index 0eb2e236d7..0000000000 Binary files a/docs/_images/message-queue/Kafka/mq.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/sumer-groups.png b/docs/_images/message-queue/Kafka/sumer-groups.png deleted file mode 100644 index 16fe2936cb..0000000000 Binary files a/docs/_images/message-queue/Kafka/sumer-groups.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/zero-copy.png b/docs/_images/message-queue/Kafka/zero-copy.png deleted file mode 100644 index f9ac3ba2b3..0000000000 Binary files a/docs/_images/message-queue/Kafka/zero-copy.png and /dev/null differ diff --git a/docs/_images/message-queue/Kafka/zookeeper-store.png b/docs/_images/message-queue/Kafka/zookeeper-store.png deleted file mode 100644 index 143f682a4a..0000000000 Binary files a/docs/_images/message-queue/Kafka/zookeeper-store.png and /dev/null differ diff --git "a/docs/_images/message-queue/Kafka/\344\274\240\347\273\237\346\266\210\346\201\257\351\230\237\345\210\227.png" "b/docs/_images/message-queue/Kafka/\344\274\240\347\273\237\346\266\210\346\201\257\351\230\237\345\210\227.png" deleted file mode 100644 index 8122b29cf3..0000000000 Binary files "a/docs/_images/message-queue/Kafka/\344\274\240\347\273\237\346\266\210\346\201\257\351\230\237\345\210\227.png" and /dev/null differ diff --git a/docs/_images/mysql/.DS_Store b/docs/_images/mysql/.DS_Store new file mode 100644 index 0000000000..0e7bf57afa Binary files /dev/null and b/docs/_images/mysql/.DS_Store differ diff --git a/docs/_images/mysql/COMPACT-And-REDUNDANT-Row-Format.jpg b/docs/_images/mysql/COMPACT-And-REDUNDANT-Row-Format.jpg deleted file mode 100644 index ac01b41a4c..0000000000 Binary files a/docs/_images/mysql/COMPACT-And-REDUNDANT-Row-Format.jpg and /dev/null differ diff --git a/docs/_images/mysql/Index-advantage.png b/docs/_images/mysql/Index-advantage.png new file mode 100644 index 0000000000..1e6e27fe58 Binary files /dev/null and b/docs/_images/mysql/Index-advantage.png differ diff --git a/docs/_images/mysql/Infimum-Rows-Supremum.jpg b/docs/_images/mysql/Infimum-Rows-Supremum.jpg deleted file mode 100644 index 2894d4b105..0000000000 Binary files a/docs/_images/mysql/Infimum-Rows-Supremum.jpg and /dev/null differ diff --git a/docs/_images/mysql/InnoDB-B-Tree-Node.jpg b/docs/_images/mysql/InnoDB-B-Tree-Node.jpg deleted file mode 100644 index 22409dfe63..0000000000 Binary files a/docs/_images/mysql/InnoDB-B-Tree-Node.jpg and /dev/null differ diff --git a/docs/_images/mysql/MySQL-B+Tree-store.png b/docs/_images/mysql/MySQL-B+Tree-store.png new file mode 100644 index 0000000000..313b63dac7 Binary files /dev/null and b/docs/_images/mysql/MySQL-B+Tree-store.png differ diff --git a/docs/_images/mysql/MySQL-B-Tree.png b/docs/_images/mysql/MySQL-B-Tree.png new file mode 100644 index 0000000000..6020f054ad Binary files /dev/null and b/docs/_images/mysql/MySQL-B-Tree.png differ diff --git a/docs/_images/mysql/MySQL-InnoDB-Index-primary.png b/docs/_images/mysql/MySQL-InnoDB-Index-primary.png new file mode 100644 index 0000000000..d27ab2d139 Binary files /dev/null and b/docs/_images/mysql/MySQL-InnoDB-Index-primary.png differ diff --git a/docs/_images/mysql/MySQL-InnoDB-Index.png b/docs/_images/mysql/MySQL-InnoDB-Index.png new file mode 100644 index 0000000000..fe85c75eac Binary files /dev/null and b/docs/_images/mysql/MySQL-InnoDB-Index.png differ diff --git a/docs/_images/mysql/MySQL-MyISAM-Index.png b/docs/_images/mysql/MySQL-MyISAM-Index.png new file mode 100644 index 0000000000..51a83c831f Binary files /dev/null and b/docs/_images/mysql/MySQL-MyISAM-Index.png differ diff --git a/docs/_images/mysql/MySQL-search-tree.png b/docs/_images/mysql/MySQL-search-tree.png new file mode 100644 index 0000000000..98d2779252 Binary files /dev/null and b/docs/_images/mysql/MySQL-search-tree.png differ diff --git a/docs/_images/mysql/bTree.png b/docs/_images/mysql/bTree.png deleted file mode 100644 index c25ab404d3..0000000000 Binary files a/docs/_images/mysql/bTree.png and /dev/null differ diff --git a/docs/_images/mysql/count+1-solve.png b/docs/_images/mysql/count+1-solve.png new file mode 100644 index 0000000000..ca8f879332 Binary files /dev/null and b/docs/_images/mysql/count+1-solve.png differ diff --git a/docs/_images/mysql/count+1.png b/docs/_images/mysql/count+1.png new file mode 100644 index 0000000000..92f37c68b7 Binary files /dev/null and b/docs/_images/mysql/count+1.png differ diff --git a/docs/_images/mysql/count+1_1.png b/docs/_images/mysql/count+1_1.png new file mode 100644 index 0000000000..22d4654f31 Binary files /dev/null and b/docs/_images/mysql/count+1_1.png differ diff --git a/docs/_images/mysql/count-problem.png b/docs/_images/mysql/count-problem.png new file mode 100644 index 0000000000..6d553c1c62 Binary files /dev/null and b/docs/_images/mysql/count-problem.png differ diff --git a/docs/_images/mysql/expalin.jpg b/docs/_images/mysql/expalin.jpg deleted file mode 100644 index da052f7fe6..0000000000 Binary files a/docs/_images/mysql/expalin.jpg and /dev/null differ diff --git a/docs/_images/mysql/explain-1.png b/docs/_images/mysql/explain-1.png new file mode 100644 index 0000000000..6357a622a0 Binary files /dev/null and b/docs/_images/mysql/explain-1.png differ diff --git a/docs/_images/mysql/explain-2.png b/docs/_images/mysql/explain-2.png new file mode 100644 index 0000000000..de193e71d8 Binary files /dev/null and b/docs/_images/mysql/explain-2.png differ diff --git a/docs/_images/mysql/explain-key.png b/docs/_images/mysql/explain-key.png deleted file mode 100644 index 5d4f617e2f..0000000000 Binary files a/docs/_images/mysql/explain-key.png and /dev/null differ diff --git a/docs/_images/mysql/index-ICP.png b/docs/_images/mysql/index-ICP.png new file mode 100644 index 0000000000..ca84b2cd14 Binary files /dev/null and b/docs/_images/mysql/index-ICP.png differ diff --git a/docs/_images/mysql/mysql-framework1.png b/docs/_images/mysql/mysql-framework1.png deleted file mode 100644 index b5b52403c4..0000000000 Binary files a/docs/_images/mysql/mysql-framework1.png and /dev/null differ diff --git a/docs/_images/mysql/mysql-log-banner.png b/docs/_images/mysql/mysql-log-banner.png new file mode 100644 index 0000000000..0d368dc892 Binary files /dev/null and b/docs/_images/mysql/mysql-log-banner.png differ diff --git a/docs/_images/mysql/mysql.png b/docs/_images/mysql/mysql.png deleted file mode 100644 index 535d627b70..0000000000 Binary files a/docs/_images/mysql/mysql.png and /dev/null differ diff --git a/docs/_images/mysql/optimization-orderby.png b/docs/_images/mysql/optimization-orderby.png deleted file mode 100644 index d13f3656b6..0000000000 Binary files a/docs/_images/mysql/optimization-orderby.png and /dev/null differ diff --git a/docs/_images/mysql/pre-index.png b/docs/_images/mysql/pre-index.png new file mode 100644 index 0000000000..ffc8e22317 Binary files /dev/null and b/docs/_images/mysql/pre-index.png differ diff --git a/docs/_images/mysql/search-index-demo.png b/docs/_images/mysql/search-index-demo.png new file mode 100644 index 0000000000..d99baa5d84 Binary files /dev/null and b/docs/_images/mysql/search-index-demo.png differ diff --git a/docs/_images/mysql/tablespace-segment-extent-page-row.jpg b/docs/_images/mysql/tablespace-segment-extent-page-row.jpg deleted file mode 100644 index 66db246165..0000000000 Binary files a/docs/_images/mysql/tablespace-segment-extent-page-row.jpg and /dev/null differ diff --git a/docs/_images/redis/CLIENT_DIRTY_CAS.png b/docs/_images/redis/CLIENT_DIRTY_CAS.png deleted file mode 100644 index a6c6cbc1ea..0000000000 Binary files a/docs/_images/redis/CLIENT_DIRTY_CAS.png and /dev/null differ diff --git a/docs/_images/redis/RLock-UML.png b/docs/_images/redis/RLock-UML.png deleted file mode 100644 index b8c878acf7..0000000000 Binary files a/docs/_images/redis/RLock-UML.png and /dev/null differ diff --git a/docs/_images/redis/RLock.png b/docs/_images/redis/RLock.png deleted file mode 100644 index 95e0e9a3ba..0000000000 Binary files a/docs/_images/redis/RLock.png and /dev/null differ diff --git a/docs/_images/redis/bgrewriteaof.png b/docs/_images/redis/bgrewriteaof.png deleted file mode 100644 index c180db8a1c..0000000000 Binary files a/docs/_images/redis/bgrewriteaof.png and /dev/null differ diff --git a/docs/_images/redis/bitmap1.gif b/docs/_images/redis/bitmap1.gif deleted file mode 100644 index f1e5916fa6..0000000000 Binary files a/docs/_images/redis/bitmap1.gif and /dev/null differ diff --git a/docs/_images/redis/bitmap2.gif b/docs/_images/redis/bitmap2.gif deleted file mode 100644 index 7ee3a1509b..0000000000 Binary files a/docs/_images/redis/bitmap2.gif and /dev/null differ diff --git a/docs/_images/redis/c-string.jpg b/docs/_images/redis/c-string.jpg deleted file mode 100644 index f53ed206f9..0000000000 Binary files a/docs/_images/redis/c-string.jpg and /dev/null differ diff --git a/docs/_images/redis/cluster-info.png b/docs/_images/redis/cluster-info.png deleted file mode 100644 index 1c270fed92..0000000000 Binary files a/docs/_images/redis/cluster-info.png and /dev/null differ diff --git a/docs/_images/redis/data-type-structure.jpg b/docs/_images/redis/data-type-structure.jpg deleted file mode 100644 index 0d9540e811..0000000000 Binary files a/docs/_images/redis/data-type-structure.jpg and /dev/null differ diff --git a/docs/_images/redis/nosql-index.jpg b/docs/_images/redis/nosql-index.jpg deleted file mode 100644 index 9eb9900db9..0000000000 Binary files a/docs/_images/redis/nosql-index.jpg and /dev/null differ diff --git a/docs/_images/redis/nosqlwhy.png b/docs/_images/redis/nosqlwhy.png deleted file mode 100644 index 0b299c4c19..0000000000 Binary files a/docs/_images/redis/nosqlwhy.png and /dev/null differ diff --git a/docs/_images/redis/redis-aof-conf.jpg b/docs/_images/redis/redis-aof-conf.jpg deleted file mode 100644 index a12c68cdaa..0000000000 Binary files a/docs/_images/redis/redis-aof-conf.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-aof-conf.png b/docs/_images/redis/redis-aof-conf.png deleted file mode 100644 index 55d2f09535..0000000000 Binary files a/docs/_images/redis/redis-aof-conf.png and /dev/null differ diff --git a/docs/_images/redis/redis-aof-file.png b/docs/_images/redis/redis-aof-file.png deleted file mode 100644 index 6f6c2eed04..0000000000 Binary files a/docs/_images/redis/redis-aof-file.png and /dev/null differ diff --git a/docs/_images/redis/redis-aof-rewrite-work.png b/docs/_images/redis/redis-aof-rewrite-work.png deleted file mode 100644 index 10f919c7b1..0000000000 Binary files a/docs/_images/redis/redis-aof-rewrite-work.png and /dev/null differ diff --git a/docs/_images/redis/redis-aof-summary.png b/docs/_images/redis/redis-aof-summary.png deleted file mode 100644 index 80d9d01f38..0000000000 Binary files a/docs/_images/redis/redis-aof-summary.png and /dev/null differ diff --git a/docs/_images/redis/redis-aof-write-log.png b/docs/_images/redis/redis-aof-write-log.png deleted file mode 100644 index c10f2933b9..0000000000 Binary files a/docs/_images/redis/redis-aof-write-log.png and /dev/null differ diff --git a/docs/_images/redis/redis-backlog_buffer.png b/docs/_images/redis/redis-backlog_buffer.png deleted file mode 100644 index 3770fefc90..0000000000 Binary files a/docs/_images/redis/redis-backlog_buffer.png and /dev/null differ diff --git a/docs/_images/redis/redis-bgsave.png b/docs/_images/redis/redis-bgsave.png deleted file mode 100644 index 85fed50b84..0000000000 Binary files a/docs/_images/redis/redis-bgsave.png and /dev/null differ diff --git a/docs/_images/redis/redis-cap.png b/docs/_images/redis/redis-cap.png deleted file mode 100644 index 9a16045c7f..0000000000 Binary files a/docs/_images/redis/redis-cap.png and /dev/null differ diff --git a/docs/_images/redis/redis-cg-commands.png b/docs/_images/redis/redis-cg-commands.png deleted file mode 100644 index 45919a7063..0000000000 Binary files a/docs/_images/redis/redis-cg-commands.png and /dev/null differ diff --git a/docs/_images/redis/redis-cluster-framework.png b/docs/_images/redis/redis-cluster-framework.png deleted file mode 100644 index 6802ac9e60..0000000000 Binary files a/docs/_images/redis/redis-cluster-framework.png and /dev/null differ diff --git a/docs/_images/redis/redis-cluster-new.jpg b/docs/_images/redis/redis-cluster-new.jpg deleted file mode 100644 index fb9032fcfe..0000000000 Binary files a/docs/_images/redis/redis-cluster-new.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-cluster-ping.png b/docs/_images/redis/redis-cluster-ping.png deleted file mode 100644 index 9e85719ac8..0000000000 Binary files a/docs/_images/redis/redis-cluster-ping.png and /dev/null differ diff --git a/docs/_images/redis/redis-cluster-slot.png b/docs/_images/redis/redis-cluster-slot.png deleted file mode 100644 index 230d2ccac0..0000000000 Binary files a/docs/_images/redis/redis-cluster-slot.png and /dev/null differ diff --git a/docs/_images/redis/redis-cluster-vote.png b/docs/_images/redis/redis-cluster-vote.png deleted file mode 100644 index e9a2f97c6e..0000000000 Binary files a/docs/_images/redis/redis-cluster-vote.png and /dev/null differ diff --git a/docs/_images/redis/redis-consistency.png b/docs/_images/redis/redis-consistency.png deleted file mode 100644 index c840f20b96..0000000000 Binary files a/docs/_images/redis/redis-consistency.png and /dev/null differ diff --git a/docs/_images/redis/redis-group-strucure.png b/docs/_images/redis/redis-group-strucure.png deleted file mode 100644 index 1f94555cb2..0000000000 Binary files a/docs/_images/redis/redis-group-strucure.png and /dev/null differ diff --git a/docs/_images/redis/redis-hash.jpg b/docs/_images/redis/redis-hash.jpg deleted file mode 100644 index 2f95be5e61..0000000000 Binary files a/docs/_images/redis/redis-hash.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-increment-copy.png b/docs/_images/redis/redis-increment-copy.png deleted file mode 100644 index d0c39734a3..0000000000 Binary files a/docs/_images/redis/redis-increment-copy.png and /dev/null differ diff --git a/docs/_images/redis/redis-kv-conflict.jpg b/docs/_images/redis/redis-kv-conflict.jpg deleted file mode 100644 index 32025db80d..0000000000 Binary files a/docs/_images/redis/redis-kv-conflict.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-kv.jpg b/docs/_images/redis/redis-kv.jpg deleted file mode 100644 index 1f032fa1de..0000000000 Binary files a/docs/_images/redis/redis-kv.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-master-slave-index.png b/docs/_images/redis/redis-master-slave-index.png deleted file mode 100644 index d9d7a42c92..0000000000 Binary files a/docs/_images/redis/redis-master-slave-index.png and /dev/null differ diff --git a/docs/_images/redis/redis-message-type.png b/docs/_images/redis/redis-message-type.png deleted file mode 100644 index 0304c198f4..0000000000 Binary files a/docs/_images/redis/redis-message-type.png and /dev/null differ diff --git a/docs/_images/redis/redis-mix-persistence-file.png b/docs/_images/redis/redis-mix-persistence-file.png deleted file mode 100644 index db19558671..0000000000 Binary files a/docs/_images/redis/redis-mix-persistence-file.png and /dev/null differ diff --git a/docs/_images/redis/redis-mix-persistence.png b/docs/_images/redis/redis-mix-persistence.png deleted file mode 100644 index b15d20828a..0000000000 Binary files a/docs/_images/redis/redis-mix-persistence.png and /dev/null differ diff --git a/docs/_images/redis/redis-psubscribe.png b/docs/_images/redis/redis-psubscribe.png deleted file mode 100644 index 58ab1599de..0000000000 Binary files a/docs/_images/redis/redis-psubscribe.png and /dev/null differ diff --git a/docs/_images/redis/redis-psubscribe1.png b/docs/_images/redis/redis-psubscribe1.png deleted file mode 100644 index d3979a6d67..0000000000 Binary files a/docs/_images/redis/redis-psubscribe1.png and /dev/null differ diff --git a/docs/_images/redis/redis-pub-sub.png b/docs/_images/redis/redis-pub-sub.png deleted file mode 100644 index b2ae50d962..0000000000 Binary files a/docs/_images/redis/redis-pub-sub.png and /dev/null differ diff --git a/docs/_images/redis/redis-publish.png b/docs/_images/redis/redis-publish.png deleted file mode 100644 index fe9d88da26..0000000000 Binary files a/docs/_images/redis/redis-publish.png and /dev/null differ diff --git a/docs/_images/redis/redis-rdb-bak.png b/docs/_images/redis/redis-rdb-bak.png deleted file mode 100644 index 31c538690f..0000000000 Binary files a/docs/_images/redis/redis-rdb-bak.png and /dev/null differ diff --git a/docs/_images/redis/redis-rdb-file.png b/docs/_images/redis/redis-rdb-file.png deleted file mode 100644 index 81e2e0ed6a..0000000000 Binary files a/docs/_images/redis/redis-rdb-file.png and /dev/null differ diff --git a/docs/_images/redis/redis-rdb-summary.png b/docs/_images/redis/redis-rdb-summary.png deleted file mode 100644 index c65b393bd3..0000000000 Binary files a/docs/_images/redis/redis-rdb-summary.png and /dev/null differ diff --git a/docs/_images/redis/redis-rdb.png b/docs/_images/redis/redis-rdb.png deleted file mode 100644 index 0279a22be3..0000000000 Binary files a/docs/_images/redis/redis-rdb.png and /dev/null differ diff --git a/docs/_images/redis/redis-rehash.jpg b/docs/_images/redis/redis-rehash.jpg deleted file mode 100644 index 1cbe4c1ca6..0000000000 Binary files a/docs/_images/redis/redis-rehash.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-rehash.png b/docs/_images/redis/redis-rehash.png deleted file mode 100644 index 374ef066e2..0000000000 Binary files a/docs/_images/redis/redis-rehash.png and /dev/null differ diff --git a/docs/_images/redis/redis-replicaof.png b/docs/_images/redis/redis-replicaof.png deleted file mode 100644 index 920ed8ca86..0000000000 Binary files a/docs/_images/redis/redis-replicaof.png and /dev/null differ diff --git a/docs/_images/redis/redis-rpop.png b/docs/_images/redis/redis-rpop.png deleted file mode 100644 index 0e15bfb30b..0000000000 Binary files a/docs/_images/redis/redis-rpop.png and /dev/null differ diff --git a/docs/_images/redis/redis-rpoplpush.png b/docs/_images/redis/redis-rpoplpush.png deleted file mode 100644 index f91da98939..0000000000 Binary files a/docs/_images/redis/redis-rpoplpush.png and /dev/null differ diff --git a/docs/_images/redis/redis-sds.jpg b/docs/_images/redis/redis-sds.jpg deleted file mode 100644 index 4d74127985..0000000000 Binary files a/docs/_images/redis/redis-sds.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-select-master.png b/docs/_images/redis/redis-select-master.png deleted file mode 100644 index e5f4d2bbdd..0000000000 Binary files a/docs/_images/redis/redis-select-master.png and /dev/null differ diff --git a/docs/_images/redis/redis-sentinel-cluster.png b/docs/_images/redis/redis-sentinel-cluster.png deleted file mode 100644 index 6029c71e13..0000000000 Binary files a/docs/_images/redis/redis-sentinel-cluster.png and /dev/null differ diff --git a/docs/_images/redis/redis-sentinel-slave.png b/docs/_images/redis/redis-sentinel-slave.png deleted file mode 100644 index 1ef37adbf4..0000000000 Binary files a/docs/_images/redis/redis-sentinel-slave.png and /dev/null differ diff --git a/docs/_images/redis/redis-sentinel.png b/docs/_images/redis/redis-sentinel.png deleted file mode 100644 index d256f130dd..0000000000 Binary files a/docs/_images/redis/redis-sentinel.png and /dev/null differ diff --git a/docs/_images/redis/redis-set.gif b/docs/_images/redis/redis-set.gif deleted file mode 100644 index 3e511647fe..0000000000 Binary files a/docs/_images/redis/redis-set.gif and /dev/null differ diff --git a/docs/_images/redis/redis-setkv-aof.png b/docs/_images/redis/redis-setkv-aof.png deleted file mode 100644 index 4d31075e18..0000000000 Binary files a/docs/_images/redis/redis-setkv-aof.png and /dev/null differ diff --git a/docs/_images/redis/redis-snapshotting.png b/docs/_images/redis/redis-snapshotting.png deleted file mode 100644 index 4a39a8868f..0000000000 Binary files a/docs/_images/redis/redis-snapshotting.png and /dev/null differ diff --git a/docs/_images/redis/redis-stream-cg.png b/docs/_images/redis/redis-stream-cg.png deleted file mode 100644 index 4dba663ec1..0000000000 Binary files a/docs/_images/redis/redis-stream-cg.png and /dev/null differ diff --git a/docs/_images/redis/redis-stream.png b/docs/_images/redis/redis-stream.png deleted file mode 100644 index a46e76e04f..0000000000 Binary files a/docs/_images/redis/redis-stream.png and /dev/null differ diff --git a/docs/_images/redis/redis-string-length.jpg b/docs/_images/redis/redis-string-length.jpg deleted file mode 100644 index 5f907a1214..0000000000 Binary files a/docs/_images/redis/redis-string-length.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-string.jpg b/docs/_images/redis/redis-string.jpg deleted file mode 100644 index 3c4625ae76..0000000000 Binary files a/docs/_images/redis/redis-string.jpg and /dev/null differ diff --git a/docs/_images/redis/redis-subscribe.png b/docs/_images/redis/redis-subscribe.png deleted file mode 100644 index 510f5146e8..0000000000 Binary files a/docs/_images/redis/redis-subscribe.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-case1.png b/docs/_images/redis/redis-transaction-case1.png deleted file mode 100644 index 33c0c28b86..0000000000 Binary files a/docs/_images/redis/redis-transaction-case1.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-case2.png b/docs/_images/redis/redis-transaction-case2.png deleted file mode 100644 index bb6478bf6b..0000000000 Binary files a/docs/_images/redis/redis-transaction-case2.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-case3.png b/docs/_images/redis/redis-transaction-case3.png deleted file mode 100644 index dc8a3d47f1..0000000000 Binary files a/docs/_images/redis/redis-transaction-case3.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-case4.png b/docs/_images/redis/redis-transaction-case4.png deleted file mode 100644 index 75e37e2cdc..0000000000 Binary files a/docs/_images/redis/redis-transaction-case4.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-watch1.png b/docs/_images/redis/redis-transaction-watch1.png deleted file mode 100644 index 168d241076..0000000000 Binary files a/docs/_images/redis/redis-transaction-watch1.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-watch2.png b/docs/_images/redis/redis-transaction-watch2.png deleted file mode 100644 index 8a0e0fd888..0000000000 Binary files a/docs/_images/redis/redis-transaction-watch2.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-watch3.png b/docs/_images/redis/redis-transaction-watch3.png deleted file mode 100644 index 9470a87ac0..0000000000 Binary files a/docs/_images/redis/redis-transaction-watch3.png and /dev/null differ diff --git a/docs/_images/redis/redis-transaction-watch4.png b/docs/_images/redis/redis-transaction-watch4.png deleted file mode 100644 index b903cb109b..0000000000 Binary files a/docs/_images/redis/redis-transaction-watch4.png and /dev/null differ diff --git a/docs/_images/redis/redis-watch.png b/docs/_images/redis/redis-watch.png deleted file mode 100644 index f27d10c410..0000000000 Binary files a/docs/_images/redis/redis-watch.png and /dev/null differ diff --git a/docs/_images/redis/redis-zset.gif b/docs/_images/redis/redis-zset.gif deleted file mode 100644 index 826a5ceef4..0000000000 Binary files a/docs/_images/redis/redis-zset.gif and /dev/null differ diff --git a/docs/_images/redis/rehash.gif b/docs/_images/redis/rehash.gif deleted file mode 100644 index cc9edcc425..0000000000 Binary files a/docs/_images/redis/rehash.gif and /dev/null differ diff --git a/docs/_images/redis/watched_keys.png b/docs/_images/redis/watched_keys.png deleted file mode 100644 index 0c4822bd24..0000000000 Binary files a/docs/_images/redis/watched_keys.png and /dev/null differ diff --git a/docs/_images/redis/zset.svg b/docs/_images/redis/zset.svg deleted file mode 100644 index 67bde867f4..0000000000 --- a/docs/_images/redis/zset.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_images/zookeeper/3PC.png b/docs/_images/zookeeper/3PC.png deleted file mode 100644 index 9bb07d2f45..0000000000 Binary files a/docs/_images/zookeeper/3PC.png and /dev/null differ diff --git a/docs/architecture/Refactoring.md b/docs/architecture/Refactoring.md new file mode 100644 index 0000000000..e5c9c48acb --- /dev/null +++ b/docs/architecture/Refactoring.md @@ -0,0 +1,35 @@ +# 重构 + +> 重构是在不改变软件可观察行为的前提下改善其内部结构 + + + +重构(名词):对软件内部结构的一种调整,目的是在不改变「软件之可察行为」前提下,提高其可理解性,降低其修改成本。 + + + +重构(动词):使用一系列重构准则(手法〕,在不改变「软件之可察行为」前提 下,调整其结构。 + + + + + + + + + +## 测试 + +测试驱动开发(Test-Driven Development,TDD) + + + + + +做法 + +创造一个新函数,根据这个函数的意图来对它命名(以它”做什么“来命名,而不是以它”怎么做“命名) + + + +《重构 改善既有代码的设计》 \ No newline at end of file diff --git "a/docs/architecture/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/docs/architecture/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" new file mode 100755 index 0000000000..bd47b14253 --- /dev/null +++ "b/docs/architecture/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" @@ -0,0 +1,26 @@ +**秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系统**。 + +## 架构原则:“4 要 1 不要” + +1. 数据要尽量少 + + 所谓“数据要尽量少”,首先是指用户请求的数据能少就少。请求的数据包括上传给系统的数据和系统返回给用户 的数据(通常就是网页)。 + +2. 请求数要尽量少 +3. 路径要尽量短 +4. 依赖要尽量少 +5. 不要有单点 + + + +# 动静分离 + +所谓“动静分离”,其实就是把用户请求的数据(如 HTML 页面)划分为“动态数据”和“静态数据”。 + +**第一,你应该把静态数据缓存到离用户最近的地方**。静态数据就是那些相对不会变化的数据,因此我们可以把它们缓存起来。缓存到哪里呢?常见的就三种,用户浏览器里、CDN 上或者在服务端的 Cache 中。你应该根据情况,把它们尽量缓存到离用户最近的地方。 + +**第二,静态化改造就是要直接缓存 HTTP 连接**。相较于普通的数据缓存而言,你肯定还听过系统的静态化改造。静态化改造是直接缓存 HTTP 连接而不是仅仅缓存数据,如下图所示,Web 代理服务器根据请求 URL,直接取出对应的 HTTP 响应头和响应体然后直接返回,这个响应过程简单得连 HTTP 协议都不用重新组装,甚至连 HTTP 请求头也不需要解析。 + + + +第三,让谁来缓存静态数据也很重要。不同语言写的 Cache 软件处理缓存数据的效率也各不相同。以 Java 为例,因为 Java 系统本身也有其弱点(比如不擅长处理大量连接请求,每个连接消耗的内存较多,Servlet 容器解析 HTTP 协议较慢),所以你可以不在 Java 层做缓存,而是直接在 Web 服务器层上做,这样你就可以屏蔽 Java 语言层面的一些弱点;而相比起来,Web 服务器(如 Nginx、Apache、Varnish)也更擅长处理大并发的静态文件请求。 \ No newline at end of file diff --git a/docs/collection/.DS_Store b/docs/collection/.DS_Store new file mode 100644 index 0000000000..5fe2d83286 Binary files /dev/null and b/docs/collection/.DS_Store differ diff --git "a/docs/others/23\344\270\255\350\256\276\350\256\241\346\250\241\345\274\217\351\200\232\344\277\227\350\247\243\351\207\212.md" "b/docs/collection/23\344\270\255\350\256\276\350\256\241\346\250\241\345\274\217\351\200\232\344\277\227\350\247\243\351\207\212.md" similarity index 100% rename from "docs/others/23\344\270\255\350\256\276\350\256\241\346\250\241\345\274\217\351\200\232\344\277\227\350\247\243\351\207\212.md" rename to "docs/collection/23\344\270\255\350\256\276\350\256\241\346\250\241\345\274\217\351\200\232\344\277\227\350\247\243\351\207\212.md" diff --git "a/docs/others/API \351\235\242\350\257\225\345\233\233\350\277\236\346\235\200\357\274\232\346\216\245\345\217\243\345\246\202\344\275\225\350\256\276\350\256\241\357\274\237\345\256\211\345\205\250\345\246\202\344\275\225\344\277\235\350\257\201\357\274\237\347\255\276\345\220\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237\351\230\262\351\207\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237.md" "b/docs/collection/API \351\235\242\350\257\225\345\233\233\350\277\236\346\235\200\357\274\232\346\216\245\345\217\243\345\246\202\344\275\225\350\256\276\350\256\241\357\274\237\345\256\211\345\205\250\345\246\202\344\275\225\344\277\235\350\257\201\357\274\237\347\255\276\345\220\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237\351\230\262\351\207\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237.md" similarity index 100% rename from "docs/others/API \351\235\242\350\257\225\345\233\233\350\277\236\346\235\200\357\274\232\346\216\245\345\217\243\345\246\202\344\275\225\350\256\276\350\256\241\357\274\237\345\256\211\345\205\250\345\246\202\344\275\225\344\277\235\350\257\201\357\274\237\347\255\276\345\220\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237\351\230\262\351\207\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237.md" rename to "docs/collection/API \351\235\242\350\257\225\345\233\233\350\277\236\346\235\200\357\274\232\346\216\245\345\217\243\345\246\202\344\275\225\350\256\276\350\256\241\357\274\237\345\256\211\345\205\250\345\246\202\344\275\225\344\277\235\350\257\201\357\274\237\347\255\276\345\220\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237\351\230\262\351\207\215\345\246\202\344\275\225\345\256\236\347\216\260\357\274\237.md" diff --git a/docs/others/DataGrip.md b/docs/collection/DataGrip.md similarity index 100% rename from docs/others/DataGrip.md rename to docs/collection/DataGrip.md diff --git "a/docs/others/GitHub \351\252\232\346\223\215\344\275\234\357\274\214\344\270\252\344\272\272\351\241\265\350\277\230\350\203\275\350\277\231\344\271\210\347\216\251\357\274\237.md" "b/docs/collection/GitHub \351\252\232\346\223\215\344\275\234\357\274\214\344\270\252\344\272\272\351\241\265\350\277\230\350\203\275\350\277\231\344\271\210\347\216\251\357\274\237.md" similarity index 100% rename from "docs/others/GitHub \351\252\232\346\223\215\344\275\234\357\274\214\344\270\252\344\272\272\351\241\265\350\277\230\350\203\275\350\277\231\344\271\210\347\216\251\357\274\237.md" rename to "docs/collection/GitHub \351\252\232\346\223\215\344\275\234\357\274\214\344\270\252\344\272\272\351\241\265\350\277\230\350\203\275\350\277\231\344\271\210\347\216\251\357\274\237.md" diff --git "a/docs/others/IDEA \351\230\262\346\255\242\345\206\231\344\273\243\347\240\201\346\262\211\350\277\267\346\217\222\344\273\266.md" "b/docs/collection/IDEA \351\230\262\346\255\242\345\206\231\344\273\243\347\240\201\346\262\211\350\277\267\346\217\222\344\273\266.md" similarity index 100% rename from "docs/others/IDEA \351\230\262\346\255\242\345\206\231\344\273\243\347\240\201\346\262\211\350\277\267\346\217\222\344\273\266.md" rename to "docs/collection/IDEA \351\230\262\346\255\242\345\206\231\344\273\243\347\240\201\346\262\211\350\277\267\346\217\222\344\273\266.md" diff --git a/docs/others/Idea2020.1.md b/docs/collection/Idea2020.1.md similarity index 100% rename from docs/others/Idea2020.1.md rename to docs/collection/Idea2020.1.md diff --git a/docs/others/JVM-Issue.md b/docs/collection/JVM-Issue.md similarity index 100% rename from docs/others/JVM-Issue.md rename to docs/collection/JVM-Issue.md diff --git "a/docs/others/Java \345\255\246\344\271\240\350\267\257\347\272\277.md" "b/docs/collection/Java \345\255\246\344\271\240\350\267\257\347\272\277.md" similarity index 100% rename from "docs/others/Java \345\255\246\344\271\240\350\267\257\347\272\277.md" rename to "docs/collection/Java \345\255\246\344\271\240\350\267\257\347\272\277.md" diff --git "a/docs/others/Java\344\270\2559\344\270\252\345\244\204\347\220\206Exception\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/collection/Java\344\270\2559\344\270\252\345\244\204\347\220\206Exception\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" similarity index 100% rename from "docs/others/Java\344\270\2559\344\270\252\345\244\204\347\220\206Exception\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" rename to "docs/collection/Java\344\270\2559\344\270\252\345\244\204\347\220\206Exception\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" diff --git "a/docs/others/MySQL \344\274\230\345\214\226\345\267\245\345\205\267.md" "b/docs/collection/MySQL \344\274\230\345\214\226\345\267\245\345\205\267.md" similarity index 100% rename from "docs/others/MySQL \344\274\230\345\214\226\345\267\245\345\205\267.md" rename to "docs/collection/MySQL \344\274\230\345\214\226\345\267\245\345\205\267.md" diff --git "a/docs/others/Redis \344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206.md" "b/docs/collection/Redis \344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206.md" similarity index 100% rename from "docs/others/Redis \344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206.md" rename to "docs/collection/Redis \344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206.md" diff --git "a/docs/others/Spring Boot \351\233\206\346\210\220 JUnit5.md" "b/docs/collection/Spring Boot \351\233\206\346\210\220 JUnit5.md" similarity index 100% rename from "docs/others/Spring Boot \351\233\206\346\210\220 JUnit5.md" rename to "docs/collection/Spring Boot \351\233\206\346\210\220 JUnit5.md" diff --git "a/docs/others/fastjson\346\274\217\346\264\236.md" "b/docs/collection/fastjson\346\274\217\346\264\236.md" similarity index 100% rename from "docs/others/fastjson\346\274\217\346\264\236.md" rename to "docs/collection/fastjson\346\274\217\346\264\236.md" diff --git "a/docs/collection/feed/\346\234\252\345\221\275\345\220\215.md" "b/docs/collection/feed/\346\234\252\345\221\275\345\220\215.md" new file mode 100755 index 0000000000..e69de29bb2 diff --git "a/docs/others/google-api\350\256\276\350\256\241\346\214\207\345\215\227.md" "b/docs/collection/google-api\350\256\276\350\256\241\346\214\207\345\215\227.md" similarity index 100% rename from "docs/others/google-api\350\256\276\350\256\241\346\214\207\345\215\227.md" rename to "docs/collection/google-api\350\256\276\350\256\241\346\214\207\345\215\227.md" diff --git "a/docs/collection/\344\272\254\344\270\234\350\220\245\351\224\200\346\212\225\346\224\276\345\271\263\345\217\260\344\275\216\344\273\243\347\240\201\357\274\210Low-Code\357\274\211\345\256\236\350\267\265.md" "b/docs/collection/\344\272\254\344\270\234\350\220\245\351\224\200\346\212\225\346\224\276\345\271\263\345\217\260\344\275\216\344\273\243\347\240\201\357\274\210Low-Code\357\274\211\345\256\236\350\267\265.md" new file mode 100755 index 0000000000..c9f391a2d3 --- /dev/null +++ "b/docs/collection/\344\272\254\344\270\234\350\220\245\351\224\200\346\212\225\346\224\276\345\271\263\345\217\260\344\275\216\344\273\243\347\240\201\357\274\210Low-Code\357\274\211\345\256\236\350\267\265.md" @@ -0,0 +1,103 @@ +## 前言 + +过去的2020年,一场忽如其来的“新冠”病毒,迅速的重塑了当今整个世界的方方面面,作为现代社会中很重要的领域之一——软件科技行业,当然也深受其影响。 + +各个软件科技公司都开始着重考虑节省人力成本和提高人力效率。在不可逆的社会浪潮之中,大家都开始想方设法地翻找各自的“工具箱”,看看有没有什么工具可以帮助各自在巨浪中生存下去。在大家的努力下,一条2014年由Forrester提出的“低代码(Low-Code)”技术构想被大家所重新拾起,成为了互联网软件开发界新进“红人”。 + +本文将介绍低代码的相关概念,以及结合京东营销投放平台自身情况,把从0到1落地低代码的实践进行介绍,从这个项目的角度进行经验分享。 + +## 什么是低代码 + +“Low-Code” 是什么? + +如果你第一次听说,会不会猜测这是指代码写的很差劲的贬义词?或者说是不是指很底层编译语言? + +但是其都不是,那到底是什么意思? + +作为一名搜商比情商还高的程序员,本能的第一时间进行了网上搜索,首先找到的是全球网络上最大且最受大众欢迎的参考工具书wikipedia上的一个词条:Low-code development platform。 + +wikipedia词条上给的定义是: + +> A low-code development platform (LCDP) is software that provides an development environment programmers use to create application software through graphical user interfaces and configuration instead of traditional hand-coded computer programming. + +从中我们可以提炼两个关键信息,1. 是一个应用软件的开发环境。2. 提供的是易用友好的可视化方式进行编程。 + +## 解决了什么 + +从低代码的定义上,我们可以看出低代码平台可以解决三大软件开发方面的问题:减少重复工作、聚合平台能力、形成一体化生态。 + +**减少重复工作**,设计合理的低代码平台会尽可能保证服务能力的原子性,可以消灭绝大部分繁琐和重复的代码,最大化软件复用。再往深一些看,低代码不只是少些代码而已,代码写的少,bug也就越少,要测的代码也少了,后续的应用构建、部署、管理等多个环节都减少了,对于软件开发的整个生命周期的人力都节省了。 + +**聚合平台能力**,低代码平台强调服务能力的复用。我们一般认为使用过的成熟服务相对新服务是更可信的,更多的服务复用,意味着可以提高服务的性能、成本、稳定性、安全性、可持续发展能力等。这同时帮助平台在能力服务方面越来越丰富、健壮,更好的服务可以引入更多业务使用,继而继续扩展服务,形成良性循环。 + +**形成一体化生态**,当聚合了越来越多的平台能力后,平台可以提供多层次多粒度复用手段,比如页面组件库、逻辑函数库、应用模板库等,引入更多的用户,甚至可以引入第三方研发一起实现平台共建,从而让平台服务的聚合逐步升级成为设计、研发、使用三位一体的平台生态。 + +### 可能存在的问题 + +软件开发界有一句名言: + +> 软件开发没有“银弹”。 + +意思是说对于软件开发来说,没有一种方法是可以适用所有场景的。当然,低代码也有一些自身的局限。 + +首先,低代码或许可以降级开发门槛,但**复杂度并不会降低**。可视化开发的自由度越高,组件粒度就越细,配置的复杂度就越高。同时,平台上的各种可视化组件、逻辑动作和部署环境都是黑盒,如果内部出问题无法排查和解决。 + +其次,由于低代码更多考虑的是服务复用性、通用性,导致它更加适用于一些通用业务,对于一些定制化要求很高的需求并不友好。所以,对于低代码平台的设计,我们在实践方面也一直在寻求“易用”和“复杂度”之间的平衡。 + +### 为什么我们要做低代码改造 + +在解释这个问题之前,先简单介绍一下京东营销投放平台的素材管理功能是什么。 + +一句话概括,就是用户可以通过系统,维护自己的素材池(素材可以是商品、广告、优惠券等),针对素材进行增删改查,素材池以一个整体在前台进行投放,投放时可以使用各种策略:个性化千人千面、人工干预、热度等。 + +对于素材管理的多种服务,我们可以分成两大类:**通用标准能力和自定义业务能力**。 + +通用能力包括素材组、场次、素材方面的增删改查管理,个性化千人千面、人工干预、热度等的投放策略。 + +定制化的能力一般都是和具体素材类型相关的,比如数据校验、数据封装、数据渲染等。 + + + +通过这样的分析,我们可以发现对于通用的标准能力是非常适合使用低代码设计的,标准化沉淀下来的通用能力,可以在不同的素材上进行复用。新素材的开发只需要关注业务向的定制能力,这样大大减少了新素材接入的开发成本。 + +### 架构设计 + +对于低代码的架构设计,核心点应该为开发者尽可能屏蔽底层技术细节、减少不必要的技术复杂度,并支撑其更好地应对业务复杂度(满足灵活通用的业务场景需求)。 + +设计的难点关键处在于如何解耦业务和技术复杂度,做到让基础设施下沉,形成能力的标准化可复用。 + + + +在我们分析的基础上,对于京东营销投放平台的素材管理能力进行了低代码平台设计。下面是我们的架构图: + + + +架构上,在后端服务方面,我们标准化沉淀下来素材管理的通用能力,形成标准化流程;在定制能力上,我们支持在素材类型垂直的进行自定义能力开发接入。 + +中间层我们设计一套适配层,针对服务后端和前端的标准化对接。 + +在前端设计上,我们主要引入可视化表单和列表的配置能力,把前端的素材内容形成各种各样可复用的组件,通过灵活搭建的方式进行前端页面渲染配置(注:图中drip是我们部门自研的一套工具集,包含脚手架,组件等前端常用功能)。 + +### 京东营销投放平台低代码实践成果 + +低代码平台上线以后,对于产品、研发、测试的工作方式都进行了转变,从原来传统的关注全流程业务定制开发,转变为了主要平台研发关注基础能力开发和轻量业务研发关注业务逻辑开发。 + +最终我们上线的低代码平台,核心可视化配置分成两部分,**表单配置和列表页配置**。 + + + +表单配置核心包括基础组件选择、表单页配置和组件数据配置。基础组件列出了平台所支持的所有可视化组件;表单页配置支持动态配置表单项;组件配置针对表单页上配置的表单项,进行进一步的数据绑定配置,支持和后端协议的打通。 + + + +列表页配置核心包括基础组件选择和表格布局配置。基础组件列出列表渲染所支持的各种可视化组件,表格布局支持动态配置列表展示项。 + + + +投放低代码平台上线以来,**大大提高到了研发人效、增强了系统稳定性**。以今年2021年上半年最忙的五月份来说(支持年中618大促),一个月时间投放平台支持接入了14种新素材,整体单素材接入需要的平均研发人力从原来的8人日降低到2人日。 + +## 结语 + +数据显示,中国企业的数字化转型市场需求大概需要5亿个新的应用或者APP,这个庞大的需求,如果按照传统的软件开发模式,不仅成本高昂,产品的输出和供给也受到限制。低代码平台的出现,在某一些场景下,**发挥低代码配置灵活和复用性高的特点**,可以更快更好的满足市场需求。 + +本文也是趁着低代码潮流,在2021年初起对京东营销投放平台的素材管理能力进行从0到1 低代码改造,感谢整个团队的付出和部门内部的支持,在日常工作较饱和的情况下,愿意支持内部系统创新。在一路的摸索过程中,也遇到了很多问题,收获了很多的宝贵经验,最终使得低代码平台1.0版本很快的在4月份上线,2.0版本在7月份上线,现在的低代码平台已经完全覆盖素材接入管理流程,相应的人效红利也在逐步的展现,让团队有更多的时间精力去做其他的探索。 \ No newline at end of file diff --git "a/docs/others/\345\233\276\350\247\243git \345\270\270\347\224\250\345\221\275\344\273\244.md" "b/docs/collection/\345\233\276\350\247\243git \345\270\270\347\224\250\345\221\275\344\273\244.md" similarity index 100% rename from "docs/others/\345\233\276\350\247\243git \345\270\270\347\224\250\345\221\275\344\273\244.md" rename to "docs/collection/\345\233\276\350\247\243git \345\270\270\347\224\250\345\221\275\344\273\244.md" diff --git "a/docs/others/\345\256\271\347\201\276 vs \345\244\207\344\273\275.md" "b/docs/collection/\345\256\271\347\201\276 vs \345\244\207\344\273\275.md" similarity index 100% rename from "docs/others/\345\256\271\347\201\276 vs \345\244\207\344\273\275.md" rename to "docs/collection/\345\256\271\347\201\276 vs \345\244\207\344\273\275.md" diff --git "a/docs/others/\345\271\266\344\270\215\346\230\257\346\211\200\346\234\211\351\241\271\347\233\256\351\203\275\351\200\202\345\220\210\345\276\256\346\234\215\345\212\241.md" "b/docs/collection/\345\271\266\344\270\215\346\230\257\346\211\200\346\234\211\351\241\271\347\233\256\351\203\275\351\200\202\345\220\210\345\276\256\346\234\215\345\212\241.md" similarity index 100% rename from "docs/others/\345\271\266\344\270\215\346\230\257\346\211\200\346\234\211\351\241\271\347\233\256\351\203\275\351\200\202\345\220\210\345\276\256\346\234\215\345\212\241.md" rename to "docs/collection/\345\271\266\344\270\215\346\230\257\346\211\200\346\234\211\351\241\271\347\233\256\351\203\275\351\200\202\345\220\210\345\276\256\346\234\215\345\212\241.md" diff --git "a/docs/others/\346\234\200\350\257\246\347\273\206\347\232\204 IDEA \344\270\255\344\275\277\347\224\250 Debug \346\225\231\347\250\213.md" "b/docs/collection/\346\234\200\350\257\246\347\273\206\347\232\204 IDEA \344\270\255\344\275\277\347\224\250 Debug \346\225\231\347\250\213.md" similarity index 100% rename from "docs/others/\346\234\200\350\257\246\347\273\206\347\232\204 IDEA \344\270\255\344\275\277\347\224\250 Debug \346\225\231\347\250\213.md" rename to "docs/collection/\346\234\200\350\257\246\347\273\206\347\232\204 IDEA \344\270\255\344\275\277\347\224\250 Debug \346\225\231\347\250\213.md" diff --git "a/docs/others/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/docs/collection/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" similarity index 100% rename from "docs/others/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" rename to "docs/collection/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" diff --git "a/docs/others/\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206.md" "b/docs/collection/\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206.md" similarity index 100% rename from "docs/others/\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206.md" rename to "docs/collection/\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206.md" diff --git "a/docs/others/\350\201\212\350\201\212\347\256\200\345\216\206.md" "b/docs/collection/\350\201\212\350\201\212\347\256\200\345\216\206.md" similarity index 100% rename from "docs/others/\350\201\212\350\201\212\347\256\200\345\216\206.md" rename to "docs/collection/\350\201\212\350\201\212\347\256\200\345\216\206.md" diff --git "a/docs/others/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\270\272\344\273\200\344\271\210SpringBoot\347\232\204 jar \345\217\257\344\273\245\347\233\264\346\216\245\350\277\220\350\241\214\357\274\237.md" "b/docs/collection/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\270\272\344\273\200\344\271\210SpringBoot\347\232\204 jar \345\217\257\344\273\245\347\233\264\346\216\245\350\277\220\350\241\214\357\274\237.md" similarity index 100% rename from "docs/others/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\270\272\344\273\200\344\271\210SpringBoot\347\232\204 jar \345\217\257\344\273\245\347\233\264\346\216\245\350\277\220\350\241\214\357\274\237.md" rename to "docs/collection/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\270\272\344\273\200\344\271\210SpringBoot\347\232\204 jar \345\217\257\344\273\245\347\233\264\346\216\245\350\277\220\350\241\214\357\274\237.md" diff --git "a/docs/others/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\351\253\230\345\271\266\345\217\221\344\270\213\357\274\214\344\275\240\351\203\275\346\200\216\344\271\210\351\200\211\346\213\251\346\234\200\344\274\230\347\232\204\347\272\277\347\250\213\346\225\260\357\274\237.md" "b/docs/collection/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\351\253\230\345\271\266\345\217\221\344\270\213\357\274\214\344\275\240\351\203\275\346\200\216\344\271\210\351\200\211\346\213\251\346\234\200\344\274\230\347\232\204\347\272\277\347\250\213\346\225\260\357\274\237.md" similarity index 100% rename from "docs/others/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\351\253\230\345\271\266\345\217\221\344\270\213\357\274\214\344\275\240\351\203\275\346\200\216\344\271\210\351\200\211\346\213\251\346\234\200\344\274\230\347\232\204\347\272\277\347\250\213\346\225\260\357\274\237.md" rename to "docs/collection/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\351\253\230\345\271\266\345\217\221\344\270\213\357\274\214\344\275\240\351\203\275\346\200\216\344\271\210\351\200\211\346\213\251\346\234\200\344\274\230\347\232\204\347\272\277\347\250\213\346\225\260\357\274\237.md" diff --git a/docs/data-management/.DS_Store b/docs/data-management/.DS_Store index 0c6ece912c..c40a6e6781 100644 Binary files a/docs/data-management/.DS_Store and b/docs/data-management/.DS_Store differ diff --git a/docs/data-management/Big-Data/.DS_Store b/docs/data-management/Big-Data/.DS_Store index 61e8d19dd4..b24b0adada 100644 Binary files a/docs/data-management/Big-Data/.DS_Store and b/docs/data-management/Big-Data/.DS_Store differ diff --git a/docs/data-management/Big-Data/Bloom-Filter.md b/docs/data-management/Big-Data/Bloom-Filter.md index a575e78e75..9bffa3d59d 100644 --- a/docs/data-management/Big-Data/Bloom-Filter.md +++ b/docs/data-management/Big-Data/Bloom-Filter.md @@ -387,11 +387,11 @@ public class RedissonBloomFilterDemo { ## 参考与感谢 -https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf +- https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf -http://www.justdojava.com/2019/10/22/bloomfilter/ +- http://www.justdojava.com/2019/10/22/bloomfilter/ -https://www.cnblogs.com/cpselvis/p/6265825.html +- https://www.cnblogs.com/cpselvis/p/6265825.html -https://juejin.im/post/5cc5aa7ce51d456e431adac5 +- https://juejin.im/post/5cc5aa7ce51d456e431adac5 diff --git a/docs/data-management/Big-Data/Doris.md b/docs/data-management/Big-Data/Doris.md new file mode 100644 index 0000000000..2a649d38a9 --- /dev/null +++ b/docs/data-management/Big-Data/Doris.md @@ -0,0 +1,29 @@ +## 背景 + +Doris 由百度大数据部研发 ( 之前叫百度 Palo,2018年贡献到 Apache 社区后,更名为 Apache Doris ), Doris 从最初的只为解决百度凤巢报表的专用系统,到现在在百度内部,已有有超过200个产品线在使用,部署机器超过1000台,单一业务最大可达到上百 TB。 + +Apache Doris 作为一款开源的 MPP 分析型数据库产品,主要用于解决近实时的报表和多维分析。不仅能够在亚秒级响应时间即可获得查询结果,有效的支持实时数据分析。相较于其他业界比较火的 OLAP 数据库系统,Doris 的分布式架构非常简洁,支持弹性伸缩,易于运维,节省大量人力和时间成本。目前国内社区比较活跃,也有腾讯、京东、美团、小米等大厂在使用。 + + + +## 功能特性 + + + + + +更多应用场景可以参考以下链接: + +- [知乎用户画像与实时数据的架构与实践](https://mp.weixin.qq.com/s/i5qbiKN6ruOk2Snpyy6DBw) +- [Apache Doris 在京东广告平台的应用](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247483954&idx=1&sn=2bf970d108d8cbda3b4984b48dd5d88e&chksm=cfe0122bf8979b3dbe0e5540e87173bd0cc0fd816c3f5d5366c8dd4c2e1cb61def8475b177c7&scene=178&cur_album_id=1536842014548393984#rd) +- [Apache Doris 在京东搜索实时 OLAP 中的应用实践](https://xie.infoq.cn/article/7d0671528ba454e67476bd76f) +- [基于Apache Doris 的小米增长分析平台实践](https://blog.csdn.net/DorisDB/article/details/108402104) +- [基于 Doris 的有道精品课数据中台建设实践](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247484929&idx=1&sn=3ce7fc004042f07c17cdd50a37ae2f1b&chksm=cfe01618f8979f0ed8c10bc319cfae5c8e58cd49abb416e4f9cdd9a90546905cbe0e0501a8b6&scene=178&cur_album_id=1536842014548393984#rd) +- [Doris on ES 在快手商业化的最佳实践](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247484996&idx=1&sn=6b1332612548927beb6f05ae739c11cc&chksm=cfe0165df8979f4bfd0cebfd4ec9bf4f76ece4320ad78415ad7b169cf31609ba7793cf733cef&scene=178&cur_album_id=1536842014548393984#rd) +- [Apache Doris 在韵达物流领域的应用实践](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247492503&idx=1&sn=af7a930ccab3db3b3e3902ee1340f669&chksm=cfe3f38ef8947a98b0670993ed496ac9fba708eaefa264afcd7a8b093ce7d4dda332bb9aa3ca&scene=178&cur_album_id=1536842014548393984#rd) +- [百度基于 Iceberg 拓展 Doris 数据湖能力的实践](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247494320&idx=1&sn=bb4b9ba450d750d30bc4f3fd51634e7e&chksm=cfe3faa9f89473bf66b2c530ae57a5b1d2d2111fad4eda9254f03e4471c69de43b8d2951fdb7&scene=178&cur_album_id=1536842014548393984#rd) +- [新东方在线教育实时数仓的落地实践](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247500698&idx=1&sn=c8df28edc926ed266f8b25f330f501b5&chksm=cfe3d383f8945a95d1fc7ae4b497df9a3fc8dd5a90cc65f957f21e9061e8065ba52959cf274b&scene=178&cur_album_id=1536842014548393984#rd) +- [Doris在作业帮实时数仓中的应用&实践](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247484565&idx=1&sn=bc4c7e6f1408b659cbdc235d609aa26a&chksm=cfe0148cf8979d9a6ec7af22073e1ef2aba9420d6f5af7ddc39f23b3aab226e07c02cd46883d&scene=178&cur_album_id=1536842014548393984#rd) +- [Doris 在百度用户画像人群业务的应用](https://mp.weixin.qq.com/s?__biz=Mzg5MDEyODc1OA==&mid=2247484637&idx=1&sn=962f71e4dd089af01437de74517bb403&chksm=cfe014c4f8979dd25823ee5b40373b09247a296910c60d282de7a2331c9a452045a615b144e0&scene=178&cur_album_id=1536842014548393984#rd) +- [Apache Doris在美团外卖数仓中的应用实践](https://tech.meituan.com/2020/04/09/doris-in-meituan-waimai.html) +- …… \ No newline at end of file diff --git a/docs/data-management/Big-Data/HBase.md b/docs/data-management/Big-Data/HBase.md new file mode 100755 index 0000000000..d34e43f028 --- /dev/null +++ b/docs/data-management/Big-Data/HBase.md @@ -0,0 +1,427 @@ +--- +title: HBase +date: 2023-03-09 +tags: + - HBase +categories: Big Data +--- + + + +# 一、HBase 简介 + +### 1.1 HBase 定义 + +HBase 是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库。 + +### 1.2 HBase 的起源 + +HBase 是一个基于 HDFS 的分布式、面向列的开源数据库,是一个结构化数据的分布式存储系统,利用 HBase 技术可在廉价 PC Server上搭建起大规模结构化存储集群。 + +HBase 的原型是 Google 的 BigTable 论文,受到了该论文思想的启发,目前作为 Hadoop 的子项目来开发维护,用于支持结构化的数据存储。 + +[Apache](http://www.apache.org/) HBase™是 [Hadoop](http://hadoop.apache.org/) 数据库,这是一个分布式,可扩展的大数据存储。 + +当您需要随机,实时读取/写入您的大数据时使用Apache HBase™。该项目的目标是托管非常大的表 - 数十亿行×数百万列 - 在商品硬件集群上。Apache HBase是一个开源的,分布式的,版本化的非关系数据库,其模型是由 Chang 等人在 Google 的 [Bigtable:一种用于结构化数据](http://research.google.com/archive/bigtable.html)的[分布式存储系统](http://research.google.com/archive/bigtable.html)之后建模的。就像Bigtable利用Google文件系统提供的分布式数据存储一样,Apache HBase 在 Hadoop 和HDFS 之上提供了类似 Bigtable 的功能。 + + + +### 1.3 **HBase**在商业项目中的能力 + +每天: + +1. 消息量:发送和接收的消息数超过60亿 +2. 将近1000亿条数据的读写 +3. 高峰期每秒150万左右操作 +4. 整体读取数据占有约55%,写入占有45% +5. 超过2PB的数据,涉及冗余共6PB数据 +6. 数据每月大概增长300千兆字节。 + + + +### 1.4 特性 + +Hbase是一种NoSQL数据库,这意味着它不像传统的RDBMS数据库那样支持SQL作为查询语言。Hbase是一种分布式存储的数据库,技术上来讲,它更像是分布式存储而不是分布式数据库,它缺少很多RDBMS系统的特性,比如列类型,辅助索引,触发器,和高级查询语言等。那Hbase有什么特性呢?如下: + +- 强读写一致,但是不是“最终一致性”的数据存储,这使得它非常适合高速的计算聚合 +- 自动分片,通过Region分散在集群中,当行数增长的时候,Region也会自动的切分和再分配 +- 自动的故障转移 +- Hadoop/HDFS集成,和HDFS开箱即用,不用太麻烦的衔接 +- 丰富的“简洁,高效”API,Thrift/REST API,Java API +- 块缓存,布隆过滤器,可以高效的列查询优化 +- 操作管理,Hbase提供了内置的web界面来操作,还可以监控JMX指标 + + + +### 1.5 什么时候用Hbase? + +Hbase不适合解决所有的问题: + +- 首先数据库量要足够多,如果有十亿及百亿行数据,那么Hbase是一个很好的选项,如果只有几百万行甚至不到的数据量,RDBMS是一个很好的选择。因为数据量小的话,真正能工作的机器量少,剩余的机器都处于空闲的状态 +- 其次,如果你不需要辅助索引,静态类型的列,事务等特性,一个已经用RDBMS的系统想要切换到Hbase,则需要重新设计系统。 +- 最后,保证硬件资源足够,每个HDFS集群在少于5个节点的时候,都不能表现的很好。因为HDFS默认的复制数量是3,再加上一个NameNode。 + +Hbase在单机环境也能运行,但是请在开发环境的时候使用 + + + +### 1.6 HBase 数据模型 + +逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。但从 HBase 的底层物理存储结构(K-V)来看,HBase 更像是一个 **multi-dimensional map**(多维度map)。 + +#### 1.6.1 HBase逻辑结构 + + + +> 通过横向切分 Region 和纵向切分 列族 来存储大数据 + +#### 1.6.2 HBase 物理存储结构 + + + + + +### 1.7 数据模型(相关术语) + +#### Name Space + +命名空间,类似于关系型数据库的 database 概念,每个命名空间下有多个表。HBase 两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。 +一个表可以自由选择是否有命名空间,如果创建表的时候加上了命名空间后,这个表名字以 `:`作为区分! + +#### Table + +类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,数据属性,比如超时时间(TTL),压缩算法(COMPRESSION)等,都在列族的定义中定义,不需要声明具体的列。 +这意味着,往 HBase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景。 + +#### Row + +HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成。一个行包含了多个列,这些列通过列族来分类,行中的数据所属列族只能从该表所定义的列族中选取,不能定义这个表中不存在的列族,否则报错 `NoSuchColumnFamilyException`。 + +#### RowKey + +Rowkey 由用户指定的一串不重复的字符串定义,是一行的唯一标识!数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。 +如果使用了之前已经定义的 RowKey,那么会将之前的数据更新掉! + +#### Column Family + +列族是多个列的集合。一个列族可以动态地灵活定义多个列。表的相关属性大部分都定义在列族上,同一个表里的不同列族可以有完全不同的属性配置,但是同一个列族内的所有列都会有相同的属性。 +列族存在的意义是 HBase 会把相同列族的列尽量放在同一台机器上,所以说,如果想让某几个列被放到一起,你就给他们定义相同的列族。 +官方建议一张表的列族定义的越少越好,列族太多会极大程度地降低数据库性能,且目前版本 Hbase 的架构,容易出 BUG。 + +#### Column Qualifier + +Hbase 中的列是可以随意定义的,一个行中的列不限名字、不限数量,只限定列族。因此列必须依赖于列族存在!列的名称前必须带着其所属的列族!例如 info:name,info:age。 +因为 HBase 中的列全部都是灵活的,可以随便定义的,因此创建表的时候并不需要指定列!列只有在你插入第一条数据的时候才会生成。其他行有没有当前行相同的列是不确定,只有在扫描数据的时候才能得知! + +#### TimeStamp + +用于标识数据的不同版本(version)。时间戳默认由系统指定,也可以由用户显式指定。 +在读取单元格的数据时,版本号可以省略,如果不指定,Hbase默认会获取最后一个版本的数据返回! + +#### Cell + +由 `{rowkey, column Family:column Qualifier, time Stamp}` 唯一确定的单元。 +Cell 中的数据是没有类型的,全部是字节码形式存储。 + +#### Region + +Region 由一个表的若干行组成!在 Region 中行的排序按照行键(rowkey)字典排序。 +Region 不能跨 RegionSever,且当数据量大的时候,HBase 会拆分 Region。 +Region 由 RegionServer 进程管理。HBase 在进行负载均衡的时候,一个 Region 有可能会从当前 RegionServer移动到其他 RegionServer 上。 +Region 是基于 HDFS 的,它的所有数据存取操作都是调用了 HDFS 的客户端接口来实现的。 + + + +### 1.8 HBase基本架构 + + + +- Zookeeper,作为分布式的协调。RegionServer也会把自己的信息写到ZooKeeper中。 +- HDFS是Hbase运行的底层文件系统 +- RegionServer,理解为数据节点,存储数据的。 +- Master RegionServer要实时的向Master报告信息。Master知道全局的RegionServer运行情况,可以控制RegionServer的故障转移和Region的切分。 + + + +详细点的: + + + +架构角色: + +#### Region Server + +RegionServer是一个服务,负责多个Region的管理。其实现类为HRegionServer,主要作用如下: + +- 对于数据的操作:get, put, delete; +- 对于Region的操作:splitRegion、compactRegion。 +- 客户端从ZooKeeper获取RegionServer的地址,从而调用相应的服务,获取数据。 + +#### Master + +Master是所有Region Server的管理者,其实现类为HMaster,主要作用如下: + +- 对于表的操作:create, delete, alter,这些操作可能需要跨多个ReginServer,因此需要Master来进行协调! +- 对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。 +- 即使Master进程宕机,集群依然可以执行数据的读写,只是不能进行表的创建和修改等操作!当然Master也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置,以及更重要的分割和合并都需要它的操作。 + +#### Zookeeper + +RegionServer非常依赖ZooKeeper服务,ZooKeeper管理了HBase所有RegionServer的信息,包括具体的数据段存放在哪个RegionServer上。 +客户端每次与HBase连接,其实都是先与ZooKeeper通信,查询出哪个RegionServer需要连接,然后再连接RegionServer。 +Zookeeper中记录了读取数据所需要的元数据表hbase:meata,因此关闭Zookeeper后,客户端是无法实现读操作的! +HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作 + +#### HDFS + +HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。 + + +#### 架构细化 + + + +- HMaster是Master Server的实现,负责监控集群中的RegionServer实例,同时是所有metadata改变的接口,在集群中,通常运行在NameNode上面,[这里有一篇更细的HMaster介绍](https://links.jianshu.com/go?to=http%3A%2F%2Fblog.zahoor.in%2F2012%2F08%2Fhbase-hmaster-architecture%2F) + - HMasterInterface暴露的接口,Table(createTable, modifyTable, removeTable, enable, disable),ColumnFamily (addColumn, modifyColumn, removeColumn),Region (move, assign, unassign) + - Master运行的后台线程:LoadBalancer线程,控制region来平衡集群的负载。CatalogJanitor线程,周期性的检查hbase:meta表。 +- HRegionServer是RegionServer的实现,服务和管理Regions,集群中RegionServer运行在DataNode + - HRegionRegionInterface暴露接口:Data (get, put, delete, next, etc.),Region (splitRegion, compactRegion, etc.) + - RegionServer后台线程:CompactSplitThread,MajorCompactionChecker,MemStoreFlusher,LogRoller +- Regions,代表table,Region有多个Store(列簇),Store有一个Memstore和多个StoreFiles(HFiles),StoreFiles的底层是Block。 + + + + + +## 二、Hello HBase + +https://hbase.apache.org/book.html#quickstart + + + +## 三、HBase 进阶 + +#### 3.1 Hbase中RegionServer架构 + + + +##### 1)StoreFile + +保存实际数据的物理文件,StoreFile 以 Hfile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的。 + +##### 2)MemStore + +写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。 + +##### 3)WAL + +由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。(l类似于NameNode中fsimage和edits_log的作用) +每间隔hbase.regionserver.optionallogflushinterval(默认1s), HBase会把操作从内存写入WAL。 +一个RegionServer上的所有Region共享一个WAL实例。 +WAL的检查间隔由hbase.regionserver.logroll.period定义,默认值为1小时。检查的内容是把当前WAL中的操作跟实际持久化到HDFS上的操作比较,看哪些操作已经被持久化了,被持久化的操作就会被移动到.oldlogs文件夹内(这个文件夹也是在HDFS上的)。一个WAL实例包含有多个WAL文件。WAL文件的最大数量通过hbase.regionserver.maxlogs(默认是32)参数来定义。 + +##### 4)BlockCache + +读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。 + + + +### 3.2 Hbase读写流程 + +#### 一、写数据流程 + + + +写流程: + +1. Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。 +2. 访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。 +3. 与目标Region Server进行通讯; +4. 将数据顺序写入(追加)到WAL; +5. 将数据写入对应的MemStore,数据会在MemStore进行排序; +6. 向客户端发送ack; +7. 等达到MemStore的刷写时机后,将数据刷写到HFile。 + +读写都是交给regionserver负责处理! + + + +写数据分两步: + +- step1: 找所写数据region所在的regionserver +- step2: regionserver写数据 + + + +① 如何知道应该将请求发送给哪个regionserver? + +> 如何知道region和regionserver的对应关系? hbase:meta +> 如何知道 hbase:meta 所在的regionserver? 查询zk的/hbase/meta-region-server获取 + +当获取meta表后,如何得知当前的数据应该放入哪个region? 继而得知如何放入哪个regionserver? + +> 根据当前数据插入的表获取所有的region,再根据插入数据的rowkey和region的startkey和endkey进行比较,判断当前数据应该放入哪个region! +> 再读取region所在行的info:server列,获取对应的regionserver即可! + +②向regionserver发送写请求 + +a) 由regionserver找到对应region的WAL对象,进行预写日志记录 +b) 数据根据列族,写入到对应的store对象的memstore中 +c) 通知客户端写成功 + +③后续 + +memstore满的时候,或触发了其他的flush条件,memstore中的数据会被刷写到storefile中! + 书写后,过期的WAL文件会移动到oldWALS目录中。 + + + + + +#### 二、读取数据流程 + + + +读流程 + +1. Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。 +2. 访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。 +3. 与目标Region Server进行通讯; +4. 分别在Block Cache(读缓存),MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。 +5. 将查询到的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。 +6. 将合并后的最终结果返回给客户端。 + + + +总体分两步: + +①找查询的region对应的regionserver +②regionserver处理读请求,返回数据 + +step1: + +a) 查询zk的/hbase/meta-region-server获取 hbase:meta表的regionserver +b) 向 hbase:meta表的regionserver 发请求,下载meta表,缓存到客户端本地,方便以后使用 +c) 从hbase:meta表中,根据查询的rowkey和表名,确定要查询的region,继而确定region所在的regionserver + +step2: + +d) 向这些region的regionserver发请求,查询指定列族的数据 +e) 先查memstore,再查blockcache, 再查storefile +f) 如果扫到了storefile,那么数据所在的block(16k)会被缓存如blockcache + + + +#### 三、存储设计 + +在Hbase中,表被分割成多个更小的块然后分散的存储在不同的服务器上,这些小块叫做Regions,存放Regions的地方叫做RegionServer。Master进程负责处理不同的RegionServer之间的Region的分发。在Hbase实现中HRegionServer和HRegion类代表RegionServer和Region。HRegionServer除了包含一些HRegions之外,还处理两种类型的文件用于数据存储 + +- HLog, 预写日志文件,也叫做WAL(write-ahead log) +- HFile 真实的数据存储文件 + +##### HLog + +- MasterProcWAL:HMaster记录管理操作,比如解决冲突的服务器,表创建和其它DDLs等操作到它的WAL文件中,这个WALs存储在MasterProcWALs目录下,它不像RegionServer的WALs,HMaster的WAL也支持弹性操作,就是如果Master服务器挂了,其它的Master接管的时候继续操作这个文件。 + +- WAL记录所有的Hbase数据改变,如果一个RegionServer在MemStore进行FLush的时候挂掉了,WAL可以保证数据的改变被应用到。如果写WAL失败了,那么修改数据的完整操作就是失败的。 + + - 通常情况,每个RegionServer只有一个WAL实例。在2.0之前,WAL的实现叫做HLog + - WAL位于*/hbase/WALs/*目录下 + - MultiWAL: 如果每个RegionServer只有一个WAL,由于HDFS必须是连续的,导致必须写WAL连续的,然后出现性能问题。MultiWAL可以让RegionServer同时写多个WAL并行的,通过HDFS底层的多管道,最终提升总的吞吐量,但是不会提升单个Region的吞吐量。 + +- WAL的配置: + + ```jsx + // 启用multiwal + + hbase.wal.provider + multiwal + + ``` + +[Wiki百科关于WAL](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FWrite-ahead_logging) + +##### HFile + +HFile是Hbase在HDFS中存储数据的格式,它包含多层的索引,这样在Hbase检索数据的时候就不用完全的加载整个文件。索引的大小(keys的大小,数据量的大小)影响block的大小,在大数据集的情况下,block的大小设置为每个RegionServer 1GB也是常见的。 + +> 探讨数据库的数据存储方式,其实就是探讨数据如何在磁盘上进行有效的组织。因为我们通常以如何高效读取和消费数据为目的,而不是数据存储本身。 + +###### Hfile生成方式 + +起初,HFile中并没有任何Block,数据还存在于MemStore中。 + +Flush发生时,创建HFile Writer,第一个空的Data Block出现,初始化后的Data Block中为Header部分预留了空间,Header部分用来存放一个Data Block的元数据信息。 + +而后,位于MemStore中的KeyValues被一个个append到位于内存中的第一个Data Block中: + +**注**:如果配置了Data Block Encoding,则会在Append KeyValue的时候进行同步编码,编码后的数据不再是单纯的KeyValue模式。Data Block Encoding是HBase为了降低KeyValue结构性膨胀而提供的内部编码机制。 + + + + + +###### 读写简流程 + + + + + + + +## 四、HBase API + +> 不过在公司使用的时候,一般不使用原生的Hbase API,使用原生的API会导致访问不可监控,影响系统稳定性,以致于版本升级的不可控。 + + + +## 五、与 Hive的集成 + +https://cwiki.apache.org/confluence/display/Hive/HBaseIntegration + +### HBase 与 Hive 的对比 + +1.Hive + +(1) 数据仓库 + +Hive 的本质其实就相当于将 HDFS 中已经存储的文件在 Mysql 中做了一个双射关系,以方便使用 HQL 去管理查询。 + +(2) 用于数据分析、清洗 + +Hive 适用于离线的数据分析和清洗,延迟较高。 + +(3) 基于 HDFS、MapReduce + +Hive 存储的数据依旧在 DataNode 上,编写的 HQL 语句终将是转换为 MapReduce 代码执行。 + +2.HBase + +(1) 数据库 + +是一种面向列族存储的非关系型数据库。 + +(2) 用于存储结构化和非结构化的数据 + +适用于单表非关系型数据的存储,不适合做关联查询,类似 JOIN 等操作。 + +(3) 基于 HDFS + +数据持久化存储的体现形式是 HFile,存放于 DataNode 中,被 ResionServer 以 region 的形式进行管理。 + +(4) 延迟较低,接入在线业务使用 + +面对大量的企业数据,HBase 可以直线单表大量数据的存储,同时提供了高效的数据访问速度。 + + + +## 来源与感谢: + +- https://hbase.apache.org/ + +- https://hbase.apache.org/book.html#_preface + +- [《入门HBase,看这一篇就够了》](https://www.jianshu.com/p/b23800d9b227) + +- [《hbase 系列文章》](https://blog.csdn.net/weixin_42796403/category_10748539.html) + diff --git a/docs/data-management/Big-Data/Kylin.md b/docs/data-management/Big-Data/Kylin.md index afa8241690..e6dc3c4dad 100644 --- a/docs/data-management/Big-Data/Kylin.md +++ b/docs/data-management/Big-Data/Kylin.md @@ -1,22 +1,30 @@ -# Kylin +--- +title: Kylin +date: 2023-03-09 +tags: + - OLAP +categories: OLAP +--- -> Apache Kylin™是一个开源的、分布式的分析型数据仓库,提供Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的表。 + + +> 之前总觉得 Javaer 对数据层面的掌握,在 MySQL、缓存、MQ 这些就行了,可是现如今这个行业是真“卷”啊,这不,大数据相关知识也得了解了解。这玩意一般是用来做大数据的固化查询的,也早就应用在了各个互联网公司,后续还有篇实际使用。 +> +> Apache Kylin™是一个开源的、分布式的分析型数据仓库,提供 Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的表。 > > 有完善的中文文档:http://kylin.apache.org/cn/docs/ - - ## 前言 随着移动互联网、物联网等技术的发展,近些年人类所积累的数据正在呈爆炸式的增长,大数据时代已经来临。但是海量数据的收集只是大数据技术的第一步,如何让数据产生价值才是大数据领域的终极目标。Hadoop 的出现解决了数据存储问题,但如何对海量数据进行OLAP 查询,却一直令人十分头疼。 -企业中的查询大致可分为即席查询和定制查询两种。之前出现的很多 OLAP 引擎,包括 Hive、Presto、SparkSQL 等,虽然在很大程度上降低了数据分析的难度,但它们都只适用于即席查询的场景。它们的优点是查询灵活,但是随着数据量和计算复杂度的增长,响应时间不能得到保证。而定制查询多数情况下是对用户的操作做出实时反应,Hive等查询引擎动辄数分钟甚至数十分钟的响应时间显然是不能满足需求的。在很长一段时间里,企业只能对数据仓库中的数据进行提前计算,再将算好后的结果存储在MySQL等关系型数据库中,再提供给用户进行查询。但是当业务复杂度和数据量逐渐升高后,使用这套方案的开发成本和维护成本都显著上升。因此,如何对已经固化下来的查询进行亚秒级返回一直是企业应用中的一个痛点。 +企业中的查询大致可分为**即席查询**和**定制查询**两种。之前出现的很多 OLAP 引擎,包括 Hive、Presto、SparkSQL 等,虽然在很大程度上降低了数据分析的难度,但它们都只适用于即席查询的场景。它们的优点是查询灵活,但是随着数据量和计算复杂度的增长,响应时间不能得到保证。而定制查询多数情况下是对用户的操作做出实时反应,Hive 等查询引擎动辄数分钟甚至数十分钟的响应时间显然是不能满足需求的。在很长一段时间里,企业只能对数据仓库中的数据进行提前计算,再将算好后的结果存储在 MySQL 等关系型数据库中,再提供给用户进行查询。但是当业务复杂度和数据量逐渐升高后,使用这套方案的开发成本和维护成本都显著上升。因此,如何对已经固化下来的查询进行亚秒级返回一直是企业应用中的一个痛点。 在这种情况下,Apache Kylin 应运而生。不同于“大规模并行处理”(Massive Parallel Processing,MPP)架构的 Hive、Presto 等,Apache Kylin 采用“**预计算**”的模式,用户只需要提前定义好查询维度,Kylin 将帮助我们进行计算,并将结果存储到 **HBase** 中,为海量数据的查询和分析提供亚秒级返回,是一种典型的“**空间换时间**”的解决方案。Apache Kylin 的出现不仅很好地解决了海量数据快速查询的问题,也避免了手动开发和维护提前计算程序带来的一系列麻烦。 -Apache Kylin 最初由 eBay 公司开发,并贡献给 Apache 基金会,但是目前 Apache Kylin 的核心开发团队已经自立门户,创建了Kyligence 公司。值得一提的是,Apache Kylin 是第一个由中国人主导的Apache顶级项目(2017年4月19日,华为的 CarbonData 成为Apache 顶级项目,因此 Apache Kylin 不再是唯一由国人贡献的 Apache 顶级项目)。由于互联网技术和开源思想进入我国的时间较晚,开源软件的世界一直是由西方国家主导,在数据领域也不例外。从 Hadoop 到 Spark,再到最近大热的机器学习平台 TenserFlow 等,均是如此。但近些年来,我们很欣喜地看到以 Apache Kylin 为首的各种以国人主导的开源项目不断地涌现出来,这些技术不断缩小着我国与西方开源技术强国之间的差距,提升我国技术人员在国际开源社区的影响力。 +Apache Kylin 最初由 eBay 公司开发,并贡献给 Apache 基金会,但是目前 Apache Kylin 的核心开发团队已经自立门户,创建了 Kyligence 公司。值得一提的是,Apache Kylin 是第一个由中国人主导的 Apache 顶级项目(2017 年 4 月 19 日,华为的 CarbonData 成为 Apache 顶级项目,因此 Apache Kylin 不再是唯一由国人贡献的 Apache 顶级项目)。由于互联网技术和开源思想进入我国的时间较晚,开源软件的世界一直是由西方国家主导,在数据领域也不例外。从 Hadoop 到 Spark,再到最近大热的机器学习平台 TenserFlow 等,均是如此。但近些年来,我们很欣喜地看到以 Apache Kylin 为首的各种以国人主导的开源项目不断地涌现出来,这些技术不断缩小着我国与西方开源技术强国之间的差距,提升我国技术人员在国际开源社区的影响力。 ## 一、核心概念 @@ -28,7 +36,7 @@ Data Warehouse,简称 DW,中文名数据仓库,是商业智能(BI)中 简而言之,用途不同。数据库面向事务,而数据仓库面向分析。数据库一般存储在线的业务数据,需要对上层业务的改变做出实时反应,涉及到增删查改等操作,所以需要遵循三大范式,需要 ACID。而数据仓库中存储的则主要是历史数据,主要目的是为企业决策提供支持,所以可能存在大量数据冗余,但利于多个维度查询,为决策者提供更多观察视角。 -在传统BI领域中,数据仓库的数据同样存储在 Oracle、MySQL 等数据库中,而在大数据领域中最常用的数据仓库就是 Apache Hive,Hive 也是 Apache Kylin 默认的数据源。 +在传统 BI 领域中,数据仓库的数据同样存储在 Oracle、MySQL 等数据库中,而在大数据领域中最常用的数据仓库就是 Apache Hive,Hive 也是 Apache Kylin 默认的数据源。 ### OLAP @@ -42,25 +50,25 @@ OLAP(Online Analytical Process),联机分析处理,以多维度的方式 简单地说,维度就是观察数据的角度。比如传感器的采集数据,可以从时间的维度来观察: - + 也可以进一步细化,从时间和设备两个角度观察: - + -维度一般是离散的值,比如时间维度上的每一个独立的日期,或者设备维度上的每一个独立的设备。因此统计时可以把维度相同的记录聚合在一起,然后应用聚合函数做累加、均值、最大值、最小值等聚合计算。 +**维度**一般是离散的值,比如时间维度上的每一个独立的日期,或者设备维度上的每一个独立的设备。因此统计时可以把维度相同的记录聚合在一起,然后应用聚合函数做累加、均值、最大值、最小值等聚合计算。 -度量就是被聚合的统计值,也就是聚合运算的结果,它一般是连续的值,如以上两个图中的温度值,或是其他测量点,比如湿度等等。通过对度量的比较和分析,我们就可以对数据做出评估,比如这个月设备运行是否稳定,某个设备的平均温度是否明显高于其他同类设备等等。 +**度量**就是被聚合的统计值,也就是聚合运算的结果,它一般是连续的值,如以上两个图中的温度值,或是其他测量点,比如湿度等等。通过对度量的比较和分析,我们就可以对数据做出评估,比如这个月设备运行是否稳定,某个设备的平均温度是否明显高于其他同类设备等等。 -### Cube和Cuboid +### Cube 和 Cuboid 了解了维度和度量之后,我们可以将数据模型上的所有字段进行分类:它们要么是维度,要么是度量。根据定义好的维度和度量,我们就可以构建 Cube 了。 -对于一个给定的数据模型,我们可以对其上的所有维度进行组合。对于N个维度来说,组合所有可能性共有2的N次方种。对于每一种维度的组合,将度量做聚合计算,然后将运算的结果保存为一个物化视图,称为Cuboid。所有维度组合的Cuboid作为一个整体,被称为Cube。 +对于一个给定的数据模型,我们可以对其上的所有维度进行组合。对于 N 个维度来说,组合所有可能性共有 2 的 N 次方种。对于每一种维度的组合,将度量做聚合计算,然后将运算的结果保存为一个物化视图,称为 Cuboid。所有维度组合的 Cuboid 作为一个整体,被称为 Cube。 -举个例子。假设有一个电商的销售数据集,其中维度包括时间(Time)、商品(Item)、地点(Location)和供应商(Supplier),度量为销售额(GMV)。那么所有维度的组合就有2的4次方,即16种,比如一维度(1D)的组合有[Time]、[Item]、[Location]、[Supplier]4种;二维度(2D)的组合有[Time Item]、[Time Location]、[Time Supplier]、[Item Location]、[Item Supplier]、[Location Supplier]6种;三维度(3D)的组合也有4种;最后零维度(0D)和四维度(4D)的组合各有1种,总共16种。 +举个例子。假设有一个电商的销售数据集,其中维度包括时间(Time)、商品(Item)、地点(Location)和供应商(Supplier),度量为销售额(GMV)。那么所有维度的组合就有 2 的 4 次方,即 16 种,比如一维度(1D)的组合有[Time]、[Item]、[Location]、[Supplier] 4 种;二维度(2D)的组合有[Time Item]、[Time Location]、[Time Supplier]、[Item Location]、[Item Supplier]、[Location Supplier] 6种;三维度(3D)的组合也有 4 种;最后零维度(0D)和四维度(4D)的组合各有 1 种,总共 16 种。 -计算Cubiod,即按维度来聚合销售额。如果用SQL语句来表达计算Cuboid [Time, Location],那么SQL语句如下: +计算 Cubiod,即按维度来聚合销售额。如果用 SQL 语句来表达计算 Cuboid [Time, Location],那么 SQL 语句如下: ```sql select Time, Location, Sum(GMV) as GMV from Sales group by Time, Location @@ -68,6 +76,8 @@ select Time, Location, Sum(GMV) as GMV from Sales group by Time, Location 将计算的结果保存为物化视图,所有 Cuboid 物化视图的总称就是 Cube。 +> Cube 中只包含聚合数据,所以用户的所有查询都应该是聚合查询 (包含 “group by”),不能出现 select * 这种 + ### 事实表和维度表 事实表(Fact Table)是指存储有事实记录的表,如系统日志、销售记录、传感器数值等;事实表的记录是动态增长的,所以它的体积通常远大于维度表。 @@ -86,37 +96,19 @@ select Time, Location, Sum(GMV) as GMV from Sales group by Time, Location 还有一种更为复杂的模型,具有多个事实表,维表可以在不同事实表之间公用,这种模型被称为**星座模型**。 -不过,Kylin目前只支持星形模型和雪花模型。 - - - - +不过,Kylin 目前只支持星形模型和雪花模型。 -### 执行 “select *” 报错 -Cube 中只包含聚合数据,所以用户的所有查询都应该是聚合查询 (包含 “group by”)。 +## 二、技术架构 - -## 概览 - -Apache Kylin™是一个开源的、分布式的分析型数据仓库,提供Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的表。 - -Apache Kylin™ 令使用者仅需三步,即可实现超大数据集上的亚秒级查询。 - -1. 定义数据集上的一个星形或雪花形模型 -2. 在定义的数据表上构建cube -3. 使用标准 SQL 通过 ODBC、JDBC 或 RESTFUL API 进行查询,仅需亚秒级响应时间即可获得查询结果 - -Kylin 提供与多种数据可视化工具的整合能力,如 Tableau,PowerBI 等,令用户可以使用 BI 工具对 Hadoop 数据进行分析。 +Apache Kylin 系统主要可以分为**离线构建**和**在线查询**两部分。  +上图左侧为数据源,目前 Kylin 默认的数据源是 Apache Hive,保存着待分析的用户数据。 - -Apache Kylin 系统主要可以分为**离线构建**和**在线查询**两部分。 - -上图左侧为数据源,目前 Kylin 默认的数据源是 Apache Hive,保存着待分析的用户数据。根据元数据的定义,构建引擎从数据源抽取数据,并构建 Cube。数据以关系表的形式输入,并且必须符合星形模型。构建技术主要为 MapReduce(Spark目前在beta版本)。构建后的 Cube 保存在右侧存储引擎中,目前 Kylin 默认的存储为 Apache HBase。 +根据元数据的定义,构建引擎从数据源抽取数据,并构建 Cube。数据以关系表的形式输入,并且必须符合星形模型。构建技术主要为 MapReduce(Spark目前在beta版本)。构建后的 Cube 保存在右侧存储引擎中,目前 Kylin 默认的存储为 Apache HBase。 完成离线构建后,用户可以从上方的查询系统发送 SQL 进行查询分析。Kylin 提供了 RESTful API、JDBC/ODBC 接口供用户调用。无论从哪个接口进入,SQL 最终都会来到 REST 服务层,再转交给查询引擎进行处理。查询引擎解析 SQL,生成基于关系表的逻辑执行计划,然后将其转译为基于 Cube 的物理执行计划,最后查询预计算生成的 Cube 并产生结果。整个过程不会访问原始数据源。如果用户提交的查询语句未在 Kylin 中预先定义,Kylin 会返回一个错误。 @@ -150,34 +142,12 @@ Apache Kylin 的这种架构使得它拥有许多非常棒的特性: -## Kylin 生态圈 - -###### Kylin 核心: - -Kylin 基础框架,包括元数据(Metadata)引擎,查询引擎,Job 引擎及存储引擎等,同时包括 REST 服务器以响应客户端请求 - -###### 扩展: - -支持额外功能和特性的插件 - -###### 整合: - -与调度系统,ETL,监控等生命周期管理系统的整合 - -###### 用户界面: - -在 Kylin 核心之上扩展的第三方用户界面 - -###### 驱动: - -ODBC 和 JDBC 驱动以支持不同的工具和产品,比如 Tableau - - - - +### 参考与感谢: -### 参考与来源: +- 原文:[《一文读懂Apache Kylin》](https://www.jianshu.com/p/abd5e90ab051) +- [《Apache Kylin 在百度地图的实践》](https://www.infoq.cn/article/practis-of-apache-kylin-in-baidu-map/) +- 美团技术团队:[Apache Kylin的实践与优化](https://tech.meituan.com/2020/11/19/apache-kylin-practice-in-meituan.html) +- [【硬刚Kylin】Kylin入门/原理/调优/OLAP解决方案和行业典型应用](https://www.modb.pro/db/79232) -[《一文读懂Apache Kylin》](https://www.jianshu.com/p/abd5e90ab051): 特别好的入门文章 \ No newline at end of file diff --git a/docs/data-management/Big-Data/OLAP.md b/docs/data-management/Big-Data/OLAP.md new file mode 100755 index 0000000000..bca7f45014 --- /dev/null +++ b/docs/data-management/Big-Data/OLAP.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data-management/Big-Data/Phoenix.md b/docs/data-management/Big-Data/Phoenix.md new file mode 100755 index 0000000000..74efa69593 --- /dev/null +++ b/docs/data-management/Big-Data/Phoenix.md @@ -0,0 +1,235 @@ +## 一、Phoenix 简介 + +Phoenix 最早是 saleforce 的一个开源项目,后来成为 Apache 的顶级项目。 + +Phoenix 构建在 HBase 之上的开源 SQL 层。能够让我们使用标准的 JDBC API 去建表, 插入数据和查询 HBase 中的数据, 从而可以避免使用 HBase 的客户端 API。在我们的应用和 HBase 之间添加了 Phoenix, 并不会降低性能, 而且我们也少写了很多代码。 + +phoneix的本质就是定义了大量的协处理器,使用协处理器帮助我们完成HBase的操作! + + + +## 二、Phoenix 特点 + +将 SQL 查询编译为 HBase 扫描 +确定扫描 Rowkey 的最佳开始和结束位置 +扫描并行执行 +将 where 子句推送到服务器端的过滤器 +通过协处理器进行聚合操作 +完美支持 HBase 二级索引创建 +DML命令以及通过DDL命令创建和操作表和版本化增量更改。 +容易集成:如Spark,Hive,Pig,Flume和Map Reduce。 + + + +## 三、Phoenix 架构 + + + + + +## 四、和Hbase中数据的关系映射 + +| 模型 | HBase | Phoneix(SQL) | +| ---- | ------------------------------------ | ------------------------ | +| 库 | namespace | database | +| 表 | table | table | +| 列族 | column Family cf:cq | 列 | +| 列名 | column Quailfier | | +| 值 | value | | +| 行键 | rowkey(唯一) dt\|area\|city\|adsid | 主键(dt,area,city,adsid) | + + Phoenix 将 HBase 的数据模型映射到关系型模型中! + + + +## 五、Phoenix使用场景 + +### 5.1 场景一:新建表 + +通过phoneix在hbase中创建表,通过phoneix向hbase的表执行增删改查! + +```sql +--使用默认的0号列族 +CREATE TABLE IF NOT EXISTS "us_population" ( + state CHAR(2) NOT NULL, + city VARCHAR NOT NULL, + population BIGINT + CONSTRAINT my_pk PRIMARY KEY (state, city)); + +--如果希望列族有意义 +CREATE TABLE IF NOT EXISTS us_population ( + state CHAR(2) NOT NULL, + city VARCHAR NOT NULL, + info.population BIGINT + CONSTRAINT my_pk PRIMARY KEY (state, city)); + +``` + +默认所有的小写,都会转大写!在查询时的小写也会转大写! + +如果必须用小写,需要加"", 在以后操作时,都需要加"",尽量不要使用小写! + +### 5.2 场景二:映射Hbase中已有表 + +hbase中已经存在了一个表,在phoneix中建表,映射上,进行操作! + +在phoneix中,只读操作! 创建一个View! + +```sql +CREATE VIEW IF NOT EXISTS "t2" ( + id VARCHAR PRIMARY KEY , + "cf1"."name" VARCHAR , + "cf2"."age" VARCHAR , + "cf2"."gender" VARCHAR + ); + +``` + +在phoneix中,可读可写操作! 创建一个Table! + +```sql +CREATE TABLE IF NOT EXISTS "t4" ( + id VARCHAR PRIMARY KEY , + "cf1"."name" VARCHAR , + "cf2"."age" VARCHAR , + "cf2"."gender" VARCHAR + ) column_encoded_bytes=0; +``` + + + +## 六、Phoenix使用语法 + +进入Phoenix客户端界面 + +``` +[hadoop@hadoop101 phoenix]$ /opt/module/phoenix/bin/sqlline.py hadoop102,hadoop103,hadoop104:2181 +``` + + + +### 1)显示所有表 + +``` +!table 或 !tables +``` + + + +### 2)创建表 + +``` +CREATE TABLE IF NOT EXISTS us_population ( + state CHAR(2) NOT NULL, + city VARCHAR NOT NULL, + population BIGINT + CONSTRAINT my_pk PRIMARY KEY (state, city)); +``` + +说明: + +- char类型必须添加长度限制 +- varchar 可以不用长度限制 +- 主键映射到 HBase 中会成为 Rowkey. 如果有多个主键(联合主键), 会把多个主键的值拼成 rowkey +- 在 Phoenix 中, 默认会把表名,字段名等自动转换成大写. 如果要使用消息, 需要把他们用双引号括起来. + + + +### 3)插入数据 + +``` +upsert into us_population values('NY','NewYork',8143197); +upsert into us_population values('CA','Los Angeles',3844829); +upsert into us_population values('IL','Chicago',2842518); +说明: upset可以看成是update和insert的结合体. +``` + +### 4)查询记录 + +``` +select * from US_POPULATION; +select * from us_population where state='NY'; +``` + + +5)删除记录 + +``` +delete from us_population where state='NY'; +``` + + +6)删除表 + +``` +drop table us_population; +``` + + +7)退出命令行 + +``` +! quit +``` + + + +## 七、使用JDBC连接 + +添加如下依赖: + + +```xml + + + org.apache.phoenix + phoenix-core + 5.0.0-HBase-2.0 + + + + + org.apache.hadoop + hadoop-common + 3.1.1 + +``` + +测试连接: + +```java +import java.sql.*; + +public class MyPhoenix { +public static void main(String[] args) throws SQLException, ClassNotFoundException { + + //Class.forName("org.apache.phoenix.jdbc.PhoenixDriver"); + + Connection connection = DriverManager.getConnection("jdbc:phoenix:hadoop102:2181"); + + String sql = "select * from US_POPULATION"; + + PreparedStatement ps = connection.prepareStatement(sql); + + ResultSet resultSet = ps.executeQuery(); + + while (resultSet.next()){ + System.out.println(resultSet.getString("STATE")+" " + + resultSet.getString("CITY") + " " + + resultSet.getLong("POPULATION")); + + } + + resultSet.close(); + + ps.close(); + + connection.close(); +} +} +``` + + + + + \ No newline at end of file diff --git a/docs/data-management/MySQL/.DS_Store b/docs/data-management/MySQL/.DS_Store index 2977494c45..62c3b6dcb5 100644 Binary files a/docs/data-management/MySQL/.DS_Store and b/docs/data-management/MySQL/.DS_Store differ diff --git a/docs/data-management/MySQL/1.MySQL.md b/docs/data-management/MySQL/1.MySQL.md deleted file mode 100644 index 7c46523333..0000000000 --- a/docs/data-management/MySQL/1.MySQL.md +++ /dev/null @@ -1,33 +0,0 @@ -## 数据类型 - -### 整型 - -| 整型 | 存储范围 | 字节 | -| ------------ | ------------------------------------------------------------ | ---- | -| tinyint | 有符号值:-128到127(-2^7到2^7-1) 无符号值:0到255(0到2^8-1) | 1 | -| smallint | 有符号值:-32768到32767(-2^15到2^15-1) 无符号值:0到65535(0到2^16-1) | 2 | -| mediumint | 有符号值:-2^23到2^23-1 无符号值:0到2^24-1 | 3 | -| int(integer) | 有符号值:-2^31到2^31-1 无符号值:0到2^32-1 | 4 | -| bigint | 有符号值:-2^63到2^63-1 无符号值:0到2^64-1 | 8 | - -``` -可使用unsigned控制是否有正负 -可以使用zerofill来进行前导零填充 -``` - - - -https://blog.csdn.net/csdn_c_/article/details/78305742 - - - -### 浮点型 - -| 整型 | 存储范围 | 字节 | -| ------------ | ------------------------------------------------------------ | ---- | -| float | 有符号值:-128到127(-2^7到2^7-1) 无符号值:0到255(0到2^8-1) | 1 | -| smallint | 有符号值:-32768到32767(-2^15到2^15-1) 无符号值:0到65535(0到2^16-1) | 2 | -| mediumint | 有符号值:-2^23到2^23-1 无符号值:0到2^24-1 | 3 | -| int(integer) | 有符号值:-2^31到2^31-1 无符号值:0到2^32-1 | 4 | -| bigint | 有符号值:-2^63到2^63-1 无符号值:0到2^64-1 | 8 | - diff --git a/docs/data-management/MySQL/MySQL-FAQ.md b/docs/data-management/MySQL/MySQL-FAQ.md deleted file mode 100644 index cc56e8ffe4..0000000000 --- a/docs/data-management/MySQL/MySQL-FAQ.md +++ /dev/null @@ -1,1705 +0,0 @@ - - -> 写在之前:不建议那种上来就是各种面试题罗列,然后背书式的去记忆,对技术的提升帮助很小,对正经面试也没什么帮助,有点东西的面试官深挖下就懵逼了。 -> -> 个人建议把面试题看作是费曼学习法中的回顾、简化的环节,准备面试的时候,跟着题目先自己讲给自己听,看看自己会满意吗,不满意就继续学习这个点,如此反复,好的offer离你不远的,奥利给 - -## 一、MySQL架构 - -和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,**插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离**。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。 - - - - - -- **连接层**:最上层是一些客户端和连接服务。**主要完成一些类似于连接处理、授权认证、及相关的安全方案**。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 - -- **服务层**:第二层服务层,主要完成大部分的核心服务功能, 包括查询解析、分析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等 - -- **引擎层**:第三层存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取 - -- **存储层**:第四层为数据存储层,主要是将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互 - - - -> 画出 MySQL 架构图,这种变态问题都能问的出来 -> -> MySQL 的查询流程具体是?or 一条SQL语句在MySQL中如何执行的? -> - -客户端请求 ---> 连接器(验证用户身份,给予权限) ---> 查询缓存(存在缓存则直接返回,不存在则执行后续操作) ---> 分析器(对SQL进行词法分析和语法分析操作) ---> 优化器(主要对执行的sql优化选择最优的执行方案方法) ---> 执行器(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) ---> 去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果) - - - ------- - - - -> 说说MySQL有哪些存储引擎?都有哪些区别? - -## 二、存储引擎 - -存储引擎是MySQL的组件,用于处理不同表类型的SQL操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。 - -使用哪一种引擎可以灵活选择,**一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求**,使用合适的存储引擎,将会提高整个数据库的性能 。 - - MySQL服务器使用**可插拔**的存储引擎体系结构,可以从运行中的 MySQL 服务器加载或卸载存储引擎 。 - -### 查看存储引擎 - -```mysql --- 查看支持的存储引擎 -SHOW ENGINES - --- 查看默认存储引擎 -SHOW VARIABLES LIKE 'storage_engine' - ---查看具体某一个表所使用的存储引擎,这个默认存储引擎被修改了! -show create table tablename - ---准确查看某个数据库中的某一表所使用的存储引擎 -show table status like 'tablename' -show table status from database where name="tablename" -``` - -### 设置存储引擎 - -```mysql --- 建表时指定存储引擎。默认的就是INNODB,不需要设置 -CREATE TABLE t1 (i INT) ENGINE = INNODB; -CREATE TABLE t2 (i INT) ENGINE = CSV; -CREATE TABLE t3 (i INT) ENGINE = MEMORY; - --- 修改存储引擎 -ALTER TABLE t ENGINE = InnoDB; - --- 修改默认存储引擎,也可以在配置文件my.cnf中修改默认引擎 -SET default_storage_engine=NDBCLUSTER; -``` - -默认情况下,每当 `CREATE TABLE` 或 `ALTER TABLE` 不能使用默认存储引擎时,都会生成一个警告。为了防止在所需的引擎不可用时出现令人困惑的意外行为,可以启用 `NO_ENGINE_SUBSTITUTION SQL` 模式。如果所需的引擎不可用,则此设置将产生错误而不是警告,并且不会创建或更改表 - - - -### 存储引擎对比 - -常见的存储引擎就 InnoDB、MyISAM、Memory、NDB。 - -InnoDB 现在是 MySQL 默认的存储引擎,支持**事务、行级锁定和外键** - -#### 文件存储结构对比 - -在 MySQL中建立任何一张数据表,在其数据目录对应的数据库目录下都有对应表的 `.frm` 文件,`.frm` 文件是用来保存每个数据表的元数据(meta)信息,包括表结构的定义等,与数据库存储引擎无关,也就是任何存储引擎的数据表都必须有`.frm`文件,命名方式为 数据表名.frm,如user.frm。 - -查看MySQL 数据保存在哪里:`show variables like 'data%'` - -MyISAM 物理文件结构为: - -- `.frm`文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等 -- `.MYD` (`MYData`) 文件:MyISAM 存储引擎专用,用于存储MyISAM 表的数据 -- `.MYI` (`MYIndex`)文件:MyISAM 存储引擎专用,用于存储MyISAM 表的索引相关信息 - -InnoDB 物理文件结构为: - -- `.frm` 文件:与表相关的元数据信息都存放在frm文件,包括表结构的定义信息等 - -- `.ibd` 文件或 `.ibdata` 文件: 这两种文件都是存放 InnoDB 数据的文件,之所以有两种文件形式存放 InnoDB 的数据,是因为 InnoDB 的数据存储方式能够通过配置来决定是使用**共享表空间**存放存储数据,还是用**独享表空间**存放存储数据。 - - 独享表空间存储方式使用`.ibd`文件,并且每个表一个`.ibd`文件 - 共享表空间存储方式使用`.ibdata`文件,所有表共同使用一个`.ibdata`文件(或多个,可自己配置) - -> ps:正经公司,这些都有专业运维去做,数据备份、恢复啥的,让我一个 Javaer 搞这的话,加钱不? - -#### 面试这么回答 - -1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一; -2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败; -3. InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。 -4. InnoDB 不保存表的具体行数,执行` select count(*) from table` 时需要全表扫描。而 MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快; -5. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一; - -| 对比项 | MyISAM | InnoDB | -| -------- | -------------------------------------------------------- | ------------------------------------------------------------ | -| 主外键 | 不支持 | 支持 | -| 事务 | 不支持 | 支持 | -| 行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作 | -| 缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 | -| 表空间 | 小 | 大 | -| 关注点 | 性能 | 事务 | -| 默认安装 | 是 | 是 | - - - -> 一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把Mysql重启,再insert一条记录,这条记录的ID是18还是15 ? - -如果表的类型是MyISAM,那么是18。因为MyISAM表会把自增主键的最大ID 记录到数据文件中,重启MySQL自增主键的最大ID也不会丢失; - -如果表的类型是InnoDB,那么是15。因为InnoDB 表只是把自增主键的最大ID记录到内存中,所以重启数据库或对表进行OPTION操作,都会导致最大ID丢失。 - -> 哪个存储引擎执行 select count(*) 更快,为什么? - -MyISAM更快,因为MyISAM内部维护了一个计数器,可以直接调取。 - -- 在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,当执行 select count(\*) from t 时,直接返回总数据。 - -- 在 InnoDB 存储引擎中,跟 MyISAM 不一样,没有将总行数存储在磁盘上,当执行 select count(\*) from t 时,会先把数据读出来,一行一行的累加,最后返回总数量。 - -InnoDB 中 count(\*) 语句是在执行的时候,全表扫描统计总数量,所以当数据越来越大时,语句就越来越耗时了,为什么 InnoDB 引擎不像 MyISAM 引擎一样,将总行数存储到磁盘上?这跟 InnoDB 的事务特性有关,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。 - - - -## 三、数据类型 - -主要包括以下五大类: - -- 整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT -- 浮点数类型:FLOAT、DOUBLE、DECIMAL -- 字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB -- 日期类型:Date、DateTime、TimeStamp、Time、Year -- 其他数据类型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等 - - - - - - - - - -> CHAR 和 VARCHAR 的区别? - -char是固定长度,varchar长度可变: - -char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。 - -存储时,前者不管实际存储数据的长度,直接按 char 规定的长度分配存储空间;而后者会根据实际存储的数据分配最终的存储空间 - -相同点: - -1. char(n),varchar(n)中的n都代表字符的个数 -2. 超过char,varchar最大长度n的限制后,字符串会被截断。 - -不同点: - -1. char不论实际存储的字符数都会占用n个字符的空间,而varchar只会占用实际字符应该占用的字节空间加1(实际长度length,0<=length<255)或加2(length>255)。因为varchar保存数据时除了要保存字符串之外还会加一个字节来记录长度(如果列声明长度大于255则使用两个字节来保存长度)。 -2. 能存储的最大空间限制不一样:char的存储上限为255字节。 -3. char在存储时会截断尾部的空格,而varchar不会。 - -char是适合存储很短的、一般固定长度的字符串。例如,char非常适合存储密码的MD5值,因为这是一个定长的值。对于非常短的列,char比varchar在存储空间上也更有效率。 - - - -> 列的字符串类型可以是什么? - -字符串类型是:SET、BLOB、ENUM、CHAR、CHAR、TEXT、VARCHAR - - - -> BLOB和TEXT有什么区别? - -BLOB是一个二进制对象,可以容纳可变数量的数据。有四种类型的BLOB:TINYBLOB、BLOB、MEDIUMBLO和 LONGBLOB - -TEXT是一个不区分大小写的BLOB。四种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。 - -BLOB 保存二进制数据,TEXT 保存字符数据。 - ------- - - - -## 四、索引 - ->说说你对 MySQL 索引的理解? -> ->数据库索引的原理,为什么要用 B+树,为什么不用二叉树? -> ->聚集索引与非聚集索引的区别? -> ->InnoDB引擎中的索引策略,了解过吗? -> ->创建索引的方式有哪些? -> ->聚簇索引/非聚簇索引,mysql索引底层实现,为什么不用B-tree,为什么不用hash,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方? - -- MYSQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构,所以说**索引的本质是:数据结构** - -- 索引的目的在于提高查询效率,可以类比字典、 火车站的车次表、图书的目录等 。 - -- 可以简单的理解为“排好序的快速查找数据结构”,数据本身之外,**数据库还维护者一个满足特定查找算法的数据结构**,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图是一种可能的索引方式示例。 - -  - - 左边的数据表,一共有两列七条记录,最左边的是数据记录的物理地址 - - 为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值,和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到对应的数据,从而快速检索出符合条件的记录。 - -- 索引本身也很大,不可能全部存储在内存中,**一般以索引文件的形式存储在磁盘上** - -- 平常说的索引,没有特别指明的话,就是B+树(多路搜索树,不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。此外还有哈希索引等。 - - - - -### 基本语法: - -- 创建: - - - 创建索引:`CREATE [UNIQUE] INDEX indexName ON mytable(username(length));` - - 如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。 - - - 修改表结构(添加索引):`ALTER table tableName ADD [UNIQUE] INDEX indexName(columnName)` - -- 删除:`DROP INDEX [indexName] ON mytable;` - -- 查看:`SHOW INDEX FROM table_name\G` --可以通过添加 \G 来格式化输出信息。 - -- 使用ALERT命令 - - - `ALTER TABLE tbl_name ADD PRIMARY KEY (column_list):` 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。 - - `ALTER TABLE tbl_name ADD UNIQUE index_name (column_list` 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。 - - `ALTER TABLE tbl_name ADD INDEX index_name (column_list)` 添加普通索引,索引值可出现多次。 - - `ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list)`该语句指定了索引为 FULLTEXT ,用于全文索引。 - - - -### 优势 - -- **提高数据检索效率,降低数据库IO成本** - -- **降低数据排序的成本,降低CPU的消耗** - - - -### 劣势 - -- 索引也是一张表,保存了主键和索引字段,并指向实体表的记录,所以也需要占用内存 -- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。 - 因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段, - 都会调整因为更新所带来的键值变化后的索引信息 - - - -### MySQL索引分类 - -#### 数据结构角度 - -- B+树索引 -- Hash索引 -- Full-Text全文索引 -- R-Tree索引 - -#### 从物理存储角度 - -- 聚集索引(clustered index) - -- 非聚集索引(non-clustered index),也叫辅助索引(secondary index) - - 聚集索引和非聚集索引都是B+树结构 - -#### 从逻辑角度 - -- 主键索引:主键索引是一种特殊的唯一索引,不允许有空值 -- 普通索引或者单列索引:每个索引只包含单个列,一个表可以有多个单列索引 -- 多列索引(复合索引、联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合 -- 唯一索引或者非唯一索引 -- 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。 - MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建 - - - -> 为什么MySQL 索引中用B+tree,不用B-tree 或者其他树,为什么不用 Hash 索引 -> -> 聚簇索引/非聚簇索引,MySQL 索引底层实现,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方? -> -> 使用索引查询一定能提高查询的性能吗?为什么? - -### MySQL索引结构 - -**首先要明白索引(index)是在存储引擎(storage engine)层面实现的,而不是server层面**。不是所有的存储引擎都支持所有的索引类型。即使多个存储引擎支持某一索引类型,它们的实现和行为也可能有所差别。 - -#### B+Tree索引 - -MyISAM 和 InnoDB 存储引擎,都使用 B+Tree的数据结构,它相对与 B-Tree结构,所有的数据都存放在叶子节点上,且把叶子节点通过指针连接到一起,形成了一条数据链表,以加快相邻数据的检索效率。 - -**先了解下 B-Tree 和 B+Tree 的区别** - -##### B-Tree - -B-Tree是为磁盘等外存储设备设计的一种平衡查找树。 - -系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。 - -InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB,可通过参数 `innodb_page_size` 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:`show variables like 'innodb_page_size';` - -而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。 - -B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。 - -一棵m阶的B-Tree有如下特性: -1. 每个节点最多有m个孩子 -2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。 -3. 若根节点不是叶子节点,则至少有2个孩子 -4. 所有叶子节点都在同一层,且不包含其它关键字信息 -5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn) -6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1 -7. ki(i=1,…n)为关键字,且关键字升序排序 -8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1) - -B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree: - - - -每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。 - -模拟查找关键字29的过程: - -1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】 -2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。 -3. 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】 -4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。 -5. 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】 -6. 在磁盘块8中的关键字列表中找到关键字29。 - -分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。 - -##### B+Tree - -B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是用 B+Tree 实现其索引结构。 - -从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,**所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上**,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。 - -B+Tree相对于B-Tree有几点不同: - -1. 非叶子节点只存储键值信息; -2. 所有叶子节点之间都有一个链指针; -3. 数据记录都存放在叶子节点中 - -将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示: - - -通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。 - -可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算: - -InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为10^3)。也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录。 - -实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2-4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。 - -B+Tree性质 - -1. 通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。 -2. 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即**索引的最左匹配特性**。 - - - -##### MyISAM主键索引与辅助索引的结构 - -MyISAM引擎的索引文件和数据文件是分离的。**MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址**。索引文件与数据文件分离,这样的索引称为"**非聚簇索引**"。MyISAM的主索引与辅助索引区别并不大,只是主键索引不能有重复的关键字。 - - - -在MyISAM中,索引(含叶子节点)存放在单独的.myi文件中,叶子节点存放的是数据的物理地址偏移量(通过偏移量访问就是随机访问,速度很快)。 - -主索引是指主键索引,键值不可能重复;辅助索引则是普通索引,键值可能重复。 - -通过索引查找数据的流程:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。辅助索引类似。 - - - -##### InnoDB主键索引与辅助索引的结构 - -**InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录**(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,**InnoDB的数据文件本身就是主键索引文件**,这样的索引被称为"“聚簇索引”,一个表只能有一个聚簇索引。 - -###### 主键索引: - -我们知道InnoDB索引是聚集索引,它的索引和数据是存入同一个.idb文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据,如下图中最底层的叶子节点有三行数据,对应于数据表中的id、stu_id、name数据项。 - - - -在Innodb中,索引分叶子节点和非叶子节点,非叶子节点就像新华字典的目录,单独存放在索引段中,叶子节点则是顺序排列的,在数据段中。Innodb的数据文件可以按照表来切分(只需要开启`innodb_file_per_table)`,切分后存放在`xxx.ibd`中,默认不切分,存放在`xxx.ibdata`中。 - -###### 辅助(非主键)索引: - -这次我们以示例中学生表中的name列建立辅助索引,它的索引结构跟主键索引的结构有很大差别,在最底层的叶子结点有两行数据,第一行的字符串是辅助索引,按照ASCII码进行排序,第二行的整数是主键的值。 - -这就意味着,对name列进行条件搜索,需要两个步骤: - -① 在辅助索引上检索name,到达其叶子节点获取对应的主键; - -② 使用主键在主索引上再进行对应的检索操作 - -这也就是所谓的“**回表查询**” - - - - - -**InnoDB 索引结构需要注意的点** - -1. 数据文件本身就是索引文件 - -2. 表数据文件本身就是按 B+Tree 组织的一个索引结构文件 -3. 聚集索引中叶节点包含了完整的数据记录 -4. InnoDB 表必须要有主键,并且推荐使用整型自增主键 - -正如我们上面介绍 InnoDB 存储结构,索引与数据是共同存储的,不管是主键索引还是辅助索引,在查找时都是通过先查找到索引节点才能拿到相对应的数据,如果我们在设计表结构时没有显式指定索引列的话,MySQL 会从表中选择数据不重复的列建立索引,如果没有符合的列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,并且这个字段长度为6个字节,类型为整型。 - -> 那为什么推荐使用整型自增主键而不是选择UUID? - -- UUID是字符串,比整型消耗更多的存储空间; - -- 在B+树中进行查找时需要跟经过的节点值比较大小,整型数据的比较运算比字符串更快速; - -- 自增的整型索引在磁盘中会连续存储,在读取一页数据时也是连续;UUID是随机产生的,读取的上下两行数据存储是分散的,不适合执行where id > 5 && id < 20的条件查询语句。 - -- 在插入或删除数据时,整型自增主键会在叶子结点的末尾建立新的叶子节点,不会破坏左侧子树的结构;UUID主键很容易出现这样的情况,B+树为了维持自身的特性,有可能会进行结构的重构,消耗更多的时间。 - - -> 为什么非主键索引结构叶子节点存储的是主键值? - -保证数据一致性和节省存储空间,可以这么理解:商城系统订单表会存储一个用户ID作为关联外键,而不推荐存储完整的用户信息,因为当我们用户表中的信息(真实名称、手机号、收货地址···)修改后,不需要再次维护订单表的用户数据,同时也节省了存储空间。 - - - -#### Hash索引 - -- 主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。 - - 检索算法:在检索查询时,就再次对待查关键字再次执行相同的Hash算法,得到Hash值,到对应Hash表对应位置取出数据即可,如果发生Hash碰撞,则需要在取值时进行筛选。目前使用Hash索引的数据库并不多,主要有Memory等。 - - MySQL目前有Memory引擎和NDB引擎支持Hash索引。 - - -#### full-text全文索引 - -- 全文索引也是MyISAM的一种特殊索引类型,主要用于全文索引,InnoDB从MYSQL5.6版本提供对全文索引的支持。 - -- 它用于替代效率较低的LIKE模糊匹配操作,而且可以通过多字段组合的全文索引一次性全模糊匹配多个字段。 -- 同样使用B-Tree存放索引数据,但使用的是特定的算法,将字段数据分割后再进行索引(一般每4个字节一次分割),索引文件存储的是分割前的索引字符串集合,与分割后的索引信息,对应Btree结构的节点存储的是分割后的词信息以及它在分割前的索引字符串集合中的位置。 - -#### R-Tree空间索引 - -空间索引是MyISAM的一种特殊索引类型,主要用于地理空间数据类型 - - - -> 为什么Mysql索引要用B+树不是B树? - -用B+树不用B树考虑的是IO对性能的影响,B树的每个节点都存储数据,而B+树只有叶子节点才存储数据,所以查找相同数据量的情况下,B树的高度更高,IO更频繁。数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘页(对应索引树的节点)。其中在MySQL底层对B+树进行进一步优化:在叶子节点中是双向链表,且在链表的头结点和尾节点也是循环指向的。 - - - -> 面试官:为何不采用Hash方式? - -因为Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ Tree是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描。 - -哈希索引不支持多列联合索引的最左匹配规则,如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题。 - - - -### 哪些情况需要创建索引 - -1. 主键自动建立唯一索引 - -2. 频繁作为查询条件的字段 - -3. 查询中与其他表关联的字段,外键关系建立索引 - -4. 单键/组合索引的选择问题,高并发下倾向创建组合索引 - -5. 查询中排序的字段,排序字段通过索引访问大幅提高排序速度 - -6. 查询中统计或分组字段 - - - -### 哪些情况不要创建索引 - -1. 表记录太少 -2. 经常增删改的表 -3. 数据重复且分布均匀的表字段,只应该为最经常查询和最经常排序的数据列建立索引(如果某个数据类包含太多的重复数据,建立索引没有太大意义) -4. 频繁更新的字段不适合创建索引(会加重IO负担) -5. where条件里用不到的字段不创建索引 - - - -### MySQL高效索引 - -**覆盖索引**(Covering Index),或者叫索引覆盖, 也就是平时所说的不需要回表操作 - -- 就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说**查询列要被所建的索引覆盖**。 - -- 索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据,当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。 - -- **判断标准** - - 使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为**using index**,MySQL查询优化器在执行查询前会决定是否有索引覆盖查询 - - - -## 五、MySQL查询 - -> count(*) 和 count(1)和count(列名)区别 ps:这道题说法有点多 - -执行效果上: - -- count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL -- count(1)包括了所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL -- count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。 - -执行效率上: - -- 列名为主键,count(列名)会比count(1)快 -- 列名不为主键,count(1)会比count(列名)快 -- 如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(*) -- 如果有主键,则 select count(主键)的执行效率是最优的 -- 如果表只有一个字段,则 select count(*) 最优。 - - - -> MySQL中 in和 exists 的区别? - -- exists:exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当exists里的条件语句能够返回记录行时(无论记录行是的多少,只要能返回),条件就为真,返回当前loop到的这条记录;反之,如果exists里的条件语句不能返回记录行,则当前loop到的这条记录被丢弃,exists的条件就像一个bool条件,当能返回结果集则为true,不能返回结果集则为false -- in:in查询相当于多个or条件的叠加 - -```mysql -SELECT * FROM A WHERE A.id IN (SELECT id FROM B); -SELECT * FROM A WHERE EXISTS (SELECT * from B WHERE B.id = A.id); -``` - -**如果查询的两个表大小相当,那么用in和exists差别不大**。 - -如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in: - - - -> UNION和UNION ALL的区别? - -UNION和UNION ALL都是将两个结果集合并为一个,**两个要联合的SQL语句 字段个数必须一样,而且字段类型要“相容”(一致);** - -- UNION在进行表连接后会筛选掉重复的数据记录(效率较低),而UNION ALL则不会去掉重复的数据记录; - -- UNION会按照字段的顺序进行排序,而UNION ALL只是简单的将两个结果合并就返回; - - - -### SQL执行顺序 - -- 手写 - - ```mysql - SELECT DISTINCT - FROM - JOIN ON - WHERE - GROUP BY - HAVING - ORDER BY - LIMIT - ``` - -- 机读 - - ```mysql - FROM - ON - JOIN - WHERE - GROUP BY - HAVING - SELECT - DISTINCT - ORDER BY - LIMIT - ``` - -- 总结 - -  - - - -> mysql 的内连接、左连接、右连接有什么区别? -> -> 什么是内连接、外连接、交叉连接、笛卡尔积呢? - -### Join图 - - - ------- - - - -## 六、MySQL 事务 - -> 事务的隔离级别有哪些?MySQL的默认隔离级别是什么? -> -> 什么是幻读,脏读,不可重复读呢? -> -> MySQL事务的四大特性以及实现原理 -> -> MVCC熟悉吗,它的底层原理? - -MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务! - - - -### ACID — 事务基本要素 - - - -事务是由一组SQL语句组成的逻辑处理单元,具有4个属性,通常简称为事务的ACID属性。 - -- **A (Atomicity) 原子性**:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样 -- **C (Consistency) 一致性**:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏 -- **I (Isolation)隔离性**:一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰 -- **D (Durability) 持久性**:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚 - - - -**并发事务处理带来的问题** - -- 更新丢失(Lost Update): 事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题 -- 脏读(Dirty Reads):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据 -- 不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。 -- 幻读(Phantom Reads):幻读与不可重复读类似。它发生在一个事务A读取了几行数据,接着另一个并发事务B插入了一些数据时。在随后的查询中,事务A就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。 - - - -**幻读和不可重复读的区别:** - -- **不可重复读的重点是修改**:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改) -- **幻读的重点在于新增或者删除**:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除) - - - -**并发事务处理带来的问题的解决办法:** - -- “更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。 - -- “脏读” 、 “不可重复读”和“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决: - - - 一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。 - - 另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 **MVCC** 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。 - - - -### 事务隔离级别 - -数据库事务的隔离级别有4种,由低到高分别为 - -- **READ-UNCOMMITTED(读未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。 -- **READ-COMMITTED(读已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。 -- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。 -- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。 - -查看当前数据库的事务隔离级别: - -```mysql -show variables like 'tx_isolation' -``` - -下面通过事例一一阐述在事务的并发操作中可能会出现脏读,不可重复读,幻读和事务隔离级别的联系。 - -数据库的事务隔离越严格,并发副作用越小,但付出的代价就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。 - -#### Read uncommitted - -读未提交,就是一个事务可以读取另一个未提交事务的数据。 - -事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。 - -分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。 - -那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。 - -#### Read committed - -读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 - -事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的… - -分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是**不可重复读**。 - -那怎么解决可能的不可重复读问题?Repeatable read ! - -#### Repeatable read - -重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。 **MySQL的默认事务隔离级别** - -事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。 - -分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,**不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作**。 - -**什么时候会出现幻读?** - -事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。 - -那怎么解决幻读问题?Serializable! - -#### Serializable 序列化 - -Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。简单来说,Serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用问题。这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。 - - - -#### 比较 - -| 事务隔离级别 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 | -| ---------------------------- | ---------------------------------------- | ---- | ---------- | ---- | -| 读未提交(read-uncommitted) | 最低级被,只能保证不读取物理上损坏的数据 | 是 | 是 | 是 | -| 读已提交(read-committed) | 语句级 | 否 | 是 | 是 | -| 可重复读(repeatable-read) | 事务级 | 否 | 否 | 是 | -| 串行化(serializable) | 最高级别,事务级 | 否 | 否 | 否 | - -需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。 - - - -MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` - -这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)**事务隔离级别下使用的是Next-Key Lock 算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)已经可以完全保证事务的隔离性要求,即达到了 SQL标准的 **SERIALIZABLE(可串行化)**隔离级别,而且保留了比较好的并发性能。 - -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读已提交):**,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。 - - - -### MVCC 多版本并发控制 - -MySQL的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC),包括Oracle、PostgreSQL。只是实现机制各不相同。 - -可以认为 MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只是锁定必要的行。 - -MVCC 的实现是通过保存数据在某个时间点的快照来实现的。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。 - -典型的MVCC实现方式,分为**乐观(optimistic)并发控制和悲观(pressimistic)并发控制**。下边通过 InnoDB的简化版行为来说明 MVCC 是如何工作的。 - -InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 - -**REPEATABLE READ(可重读)隔离级别下MVCC如何工作:** - -- SELECT - - InnoDB会根据以下两个条件检查每行记录: - - - InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在开始事务之前已经存在要么是事务自身插入或者修改过的 - - - 行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除 - - 只有符合上述两个条件的才会被查询出来 - -- INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号 - -- DELETE:InnoDB为删除的每一行保存当前系统版本号作为行删除标识 - -- UPDATE:InnoDB为插入的一行新纪录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识 - - -保存这两个额外系统版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且也能保证只会读取到符合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。 - -MVCC 只在 COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。 - - - -### 事务日志 - -InnoDB 使用日志来减少提交事务时的开销。因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新(flush)到磁盘中。 - -事务修改的数据和索引通常会映射到表空间的随机位置,所以刷新这些变更到磁盘需要很多随机 IO。 - -InnoDB 假设使用常规磁盘,随机IO比顺序IO昂贵得多,因为一个IO请求需要时间把磁头移到正确的位置,然后等待磁盘上读出需要的部分,再转到开始位置。 - -InnoDB 用日志把随机IO变成顺序IO。一旦日志安全写到磁盘,事务就持久化了,即使断电了,InnoDB可以重放日志并且恢复已经提交的事务。 - -InnoDB 使用一个后台线程智能地刷新这些变更到数据文件。这个线程可以批量组合写入,使得数据写入更顺序,以提高效率。 - -事务日志可以帮助提高事务效率: - -- 使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。 -- 事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。 -- 事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。 -- 如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。 - -目前来说,大多数存储引擎都是这样实现的,我们通常称之为**预写式日志**(Write-Ahead Logging),修改数据需要写两次磁盘。 - - - -### 事务的实现 - -事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持程度不一样。MySQL 中支持事务的存储引擎有 InnoDB 和 NDB。 - -事务的实现就是如何实现ACID特性。 - -事务的隔离性是通过锁实现,而事务的原子性、一致性和持久性则是通过事务日志实现 。 - - - -> 事务是如何通过日志来实现的,说得越深入越好。 - -事务日志包括:**重做日志redo**和**回滚日志undo** - -- **redo log(重做日志**) 实现持久化和原子性 - - 在innoDB的存储引擎中,事务日志通过重做(redo)日志和innoDB存储引擎的日志缓冲(InnoDB Log Buffer)实现。事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是DBA们口中常说的“日志先行”(Write-Ahead Logging)。当事务提交之后,在Buffer Pool中映射的数据文件才会慢慢刷新到磁盘。此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据redo log中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。 - - 在系统启动的时候,就已经为redo log分配了一块连续的存储空间,以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。所有的事务共享redo log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起。 - -- **undo log(回滚日志)** 实现一致性 - - undo log 主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。 - - Undo记录的是已部分完成并且写入硬盘的未完成的事务,默认情况下回滚日志是记录下表空间中的(共享表空间或者独享表空间) - -二种日志均可以视为一种恢复操作,redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本。二者记录的内容也不同,redo_log是物理日志,记录页的物理修改操作,而undo_log是逻辑日志,根据每行记录进行记录。 - - - -> 又引出个问题:你知道MySQL 有多少种日志吗? - -- **错误日志**:记录出错信息,也记录一些警告信息或者正确的信息。 - -- **查询日志**:记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行。 - -- **慢查询日志**:设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中。 - -- **二进制日志**:记录对数据库执行更改的所有操作。 - -- **中继日志**:中继日志也是二进制日志,用来给slave 库恢复 - -- **事务日志**:重做日志redo和回滚日志undo - - - -> 分布式事务相关问题,可能还会问到 2PC、3PC,,, - -### MySQL对分布式事务的支持 - -分布式事务的实现方式有很多,既可以采用 InnoDB 提供的原生的事务支持,也可以采用消息队列来实现分布式事务的最终一致性。这里我们主要聊一下 InnoDB 对分布式事务的支持。 - -MySQL 从 5.0.3 InnoDB 存储引擎开始支持XA协议的分布式事务。一个分布式事务会涉及多个行动,这些行动本身是事务性的。所有行动都必须一起成功完成,或者一起被回滚。 - -在MySQL中,使用分布式事务涉及一个或多个资源管理器和一个事务管理器。 - - - -如图,MySQL 的分布式事务模型。模型中分三块:应用程序(AP)、资源管理器(RM)、事务管理器(TM): - -- 应用程序:定义了事务的边界,指定需要做哪些事务; -- 资源管理器:提供了访问事务的方法,通常一个数据库就是一个资源管理器; -- 事务管理器:协调参与了全局事务中的各个事务。 - -分布式事务采用两段式提交(two-phase commit)的方式: - -- 第一阶段所有的事务节点开始准备,告诉事务管理器ready。 -- 第二阶段事务管理器告诉每个节点是commit还是rollback。如果有一个节点失败,就需要全局的节点全部rollback,以此保障事务的原子性。 - ------- - - - -## 七、MySQL锁机制 - -> 数据库的乐观锁和悲观锁? -> -> MySQL 中有哪几种锁,列举一下? -> -> MySQL中InnoDB引擎的行锁是怎么实现的? -> -> MySQL 间隙锁有没有了解,死锁有没有了解,写一段会造成死锁的 sql 语句,死锁发生了如何解决,MySQL 有没有提供什么机制去解决死锁 - -锁是计算机协调多个进程或线程并发访问某一资源的机制。 - -在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。 - -打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?这里肯定要用到事物,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。 - - - -### 锁的分类 - -**从对数据操作的类型分类**: - -- **读锁**(共享锁):针对同一份数据,多个读操作可以同时进行,不会互相影响 - -- **写锁**(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁 - -**从对数据操作的粒度分类**: - - -为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据库系统需要在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度(Lock granularity)”的概念。 - -- **表级锁**:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低(MyISAM 和 MEMORY 存储引擎采用的是表级锁); - -- **行级锁**:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高(InnoDB 存储引擎既支持行级锁也支持表级锁,但默认情况下是采用行级锁); - -- **页面锁**:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 - -适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。 - -| | 行锁 | 表锁 | 页锁 | -| ------ | ---- | ---- | ---- | -| MyISAM | | √ | | -| BDB | | √ | √ | -| InnoDB | √ | √ | | -| Memory | | √ | | - - - -### MyISAM 表锁 - -MyISAM 的表锁有两种模式: - -- 表共享读锁 (Table Read Lock):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求; -- 表独占写锁 (Table Write Lock):会阻塞其他用户对同一表的读和写操作; - -MyISAM 表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后, 只有持有锁的线程可以对表进行更新操作。 其他线程的读、 写操作都会等待,直到锁被释放为止。 - -默认情况下,写锁比读锁具有更高的优先级:当一个锁释放时,这个锁会优先给写锁队列中等候的获取锁请求,然后再给读锁队列中等候的获取锁请求。 - - - -### InnoDB 行锁 - -InnoDB 实现了以下两种类型的**行锁**: - -- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。 -- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。 - -为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是**表锁**: - -- 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。 -- 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。 - -**索引失效会导致行锁变表锁**。比如 vchar 查询不写单引号的情况。 - -#### 加锁机制 - -**乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题** - -乐观锁会“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务。用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式 - -悲观锁会“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。另外与乐观锁相对应的,**悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。** - - - -#### 锁模式(InnoDB有三种行锁的算法) - -- **记录锁(Record Locks)**: 单个行记录上的锁。对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项; - - ```mysql - SELECT * FROM table WHERE id = 1 FOR UPDATE; - ``` - - 它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行 - - 在通过 主键索引 与 唯一索引 对数据行进行 UPDATE 操作时,也会对该行数据加记录锁: - - ```mysql - -- id 列为主键列或唯一索引列 - UPDATE SET age = 50 WHERE id = 1; - ``` - -- **间隙锁(Gap Locks)**: 当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但并不存在的记录,叫做“间隙”。 - - InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。 - - 对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。 - - 间隙锁基于非唯一索引,它锁定一段范围内的索引记录。间隙锁基于下面将会提到的`Next-Key Locking` 算法,请务必牢记:**使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据**。 - - ```mysql - SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE; - ``` - - 即所有在`(1,10)`区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。 - - GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况 - -- **临键锁(Next-key Locks)**: **临键锁**,是**记录锁与间隙锁的组合**,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免**幻读**(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。) - - Next-Key 可以理解为一种特殊的**间隙锁**,也可以理解为一种特殊的**算法**。通过**临建锁**可以解决幻读的问题。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,`InnoDB` 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。 - - 对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 - -> select for update有什么含义,会锁表还是锁行还是其他 - -for update 仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。 - -InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁! -假设有个表单 products ,里面有id跟name二个栏位,id是主键。 - -- 明确指定主键,并且有此笔资料,row lock - -```mysql -SELECT * FROM products WHERE id='3' FOR UPDATE; -SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE; -``` - -- 明确指定主键,若查无此笔资料,无lock - -```mysql -SELECT * FROM products WHERE id='-1' FOR UPDATE; -``` - -- 无主键,table lock - -```mysql -SELECT * FROM products WHERE name='Mouse' FOR UPDATE; -``` - -- 主键不明确,table lock - -```mysql -SELECT * FROM products WHERE id<>'3' FOR UPDATE; -``` - -- 主键不明确,table lock - -```mysql -SELECT * FROM products WHERE id LIKE '3' FOR UPDATE; -``` - -**注1**: FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。 -**注2**: 要测试锁定的状况,可以利用MySQL的Command Mode ,开二个视窗来做测试。 - - - -> MySQL 遇到过死锁问题吗,你是如何解决的? - -### 死锁 - -**死锁产生**: - -- 死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环 -- 当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁 -- 锁的行为和顺序和存储引擎相关。以同样的顺序执行语句,有些存储引擎会产生死锁有些不会——死锁有双重原因:真正的数据冲突;存储引擎的实现方式。 - -**检测死锁**:数据库系统实现了各种死锁检测和死锁超时的机制。InnoDB存储引擎能检测到死锁的循环依赖并立即返回一个错误。 - -**死锁恢复**:死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁,InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。所以事务型应用程序在设计时必须考虑如何处理死锁,多数情况下只需要重新执行因死锁回滚的事务即可。 - -**外部锁的死锁检测**:发生死锁后,InnoDB 一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB 并不能完全自动检测到死锁, 这需要通过设置锁等待超时参数 innodb_lock_wait_timeout 来解决 - -**死锁影响性能**:死锁会影响性能而不是会产生严重错误,因为InnoDB会自动检测死锁状况并回滚其中一个受影响的事务。在高并发系统上,当许多线程等待同一个锁时,死锁检测可能导致速度变慢。 有时当发生死锁时,禁用死锁检测(使用innodb_deadlock_detect配置选项)可能会更有效,这时可以依赖`innodb_lock_wait_timeout`设置进行事务回滚。 - - - -**MyISAM避免死锁**: - -- 在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,所以 MyISAM 表不会出现死锁。 - -**InnoDB避免死锁**: - -- 为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个元祖(行)使用`SELECT ... FOR UPDATE`语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。 -- 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁 -- 如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会 -- 通过`SELECT ... LOCK IN SHARE MODE`获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。 -- 改变事务隔离级别 - -如果出现死锁,可以用 `show engine innodb status; `命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。 - ------- - - - -## 八、MySQL调优 - -> 日常工作中你是怎么优化SQL的? -> -> SQL优化的一般步骤是什么,怎么看执行计划(explain),如何理解其中各个字段的含义? -> -> 如何写sql能够有效的使用到复合索引? -> -> 一条sql执行过长的时间,你如何优化,从哪些方面入手? -> -> 什么是最左前缀原则?什么是最左匹配原则? - -### 影响mysql的性能因素 - -- 业务需求对MySQL的影响(合适合度) - -- 存储定位对MySQL的影响 - - 不适合放进MySQL的数据 - - 二进制多媒体数据 - - 流水队列数据 - - 超大文本数据 - - 需要放进缓存的数据 - - 系统各种配置及规则数据 - - 活跃用户的基本信息数据 - - 活跃用户的个性化定制信息数据 - - 准实时的统计信息数据 - - 其他一些访问频繁但变更较少的数据 - -- Schema设计对系统的性能影响 - - 尽量减少对数据库访问的请求 - - 尽量减少无用数据的查询请求 - -- 硬件环境对系统性能的影响 - - - -### 性能分析 - -#### MySQL Query Optimizer - -1. MySQL 中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是 DBA 认为是最优的,这部分最耗费时间) - -2. 当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimize r时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。 - -#### MySQL常见瓶颈 - -- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候 - -- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候 - -- 服务器硬件的性能瓶颈:top,free,iostat 和 vmstat来查看系统的性能状态 - -#### 性能下降SQL慢 执行时间长 等待时间长 原因分析 - -- 查询语句写的烂 -- 索引失效(单值、复合) -- 关联查询太多join(设计缺陷或不得已的需求) -- 服务器调优及各个参数设置(缓冲、线程数等) - - - -#### MySQL常见性能分析手段 - -在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有**慢查询日志**,**EXPLAIN 分析查询**,**profiling分析**以及**show命令查询系统状态及系统变量**,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。 - -##### 性能瓶颈定位 - -我们可以通过 show 命令查看 MySQL 状态及变量,找到系统的瓶颈: - -```mysql -Mysql> show status ——显示状态信息(扩展show status like ‘XXX’) - -Mysql> show variables ——显示系统变量(扩展show variables like ‘XXX’) - -Mysql> show innodb status ——显示InnoDB存储引擎的状态 - -Mysql> show processlist ——查看当前SQL执行,包括执行状态、是否锁表等 - -Shell> mysqladmin variables -u username -p password——显示系统变量 - -Shell> mysqladmin extended-status -u username -p password——显示状态信息 -``` - - - -##### Explain(执行计划) - -是什么:使用 **Explain** 关键字可以模拟优化器执行SQL查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈 - -能干吗: -- 表的读取顺序 -- 数据读取操作的操作类型 -- 哪些索引可以使用 -- 哪些索引被实际使用 -- 表之间的引用 -- 每张表有多少行被优化器查询 - -怎么玩: - -- Explain + SQL语句 -- 执行计划包含的信息(如果有分区表的话还会有**partitions**) - - - -各字段解释 - -- **id**(select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序) - - - id相同,执行顺序从上往下 - - id全不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行 - - id部分相同,执行顺序是先按照数字大的先执行,然后数字相同的按照从上往下的顺序执行 - -- **select_type**(查询类型,用于区别普通查询、联合查询、子查询等复杂查询) - - - **SIMPLE** :简单的select查询,查询中不包含子查询或UNION - - **PRIMARY**:查询中若包含任何复杂的子部分,最外层查询被标记为PRIMARY - - **SUBQUERY**:在select或where列表中包含了子查询 - - **DERIVED**:在from列表中包含的子查询被标记为DERIVED,MySQL会递归执行这些子查询,把结果放在临时表里 - - **UNION**:若第二个select出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层select将被标记为DERIVED - - **UNION RESULT**:从UNION表获取结果的select - -- **table**(显示这一行的数据是关于哪张表的) - -- **type**(显示查询使用了那种类型,从最好到最差依次排列 **system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL** ) - - - system:表只有一行记录(等于系统表),是 const 类型的特例,平时不会出现 - - const:表示通过索引一次就找到了,const 用于比较 primary key 或 unique 索引,因为只要匹配一行数据,所以很快,如将主键置于 where 列表中,mysql 就能将该查询转换为一个常量 - - eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描 - - ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体 - - range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引 - - index:Full Index Scan,index于ALL区别为index类型只遍历索引树。通常比ALL快,因为索引文件通常比数据文件小。(**也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的**) - - ALL:Full Table Scan,将遍历全表找到匹配的行 - - tip: 一般来说,得保证查询至少达到range级别,最好到达ref - -- **possible_keys**(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用) - -- **key** - - - 实际使用的索引,如果为NULL,则没有使用索引 - - - **查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠,仅出现在key列表中** - - - -- **key_len** - - - 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 - - key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的 - -- **ref** (显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值) - -- **rows** (根据表统计信息及索引选用情况,大致估算找到所需的记录所需要读取的行数) - -- **Extra**(包含不适合在其他列中显示但十分重要的额外信息) - - 1. using filesort: 说明mysql会对数据使用一个外部的索引排序,不是按照表内的索引顺序进行读取。mysql中无法利用索引完成的排序操作称为“文件排序”。常见于order by和group by语句中 - - 2. Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。 - - 3. using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错,如果同时出现using where,表明索引被用来执行索引键值的查找;否则索引被用来读取数据而非执行查找操作 - - 4. using where:使用了where过滤 - - 5. using join buffer:使用了连接缓存 - - 6. impossible where:where子句的值总是false,不能用来获取任何元祖 - - 7. select tables optimized away:在没有group by子句的情况下,基于索引优化操作或对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化 - - 8. distinct:优化distinct操作,在找到第一匹配的元祖后即停止找同样值的动作 - - - -**case**: - - - -1. 第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name......】 - -2. 第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,name from t1 where other_column=''】 -3. 第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】 -4. 第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】 -5. 第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的表示用第一个和第四个select的结果进行union操作。【两个结果union操作】 - - - -##### 慢查询日志 - -MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阈值的语句,具体指运行时间超过 `long_query_time` 值的 SQL,则会被记录到慢查询日志中。 - -- `long_query_time` 的默认值为10,意思是运行10秒以上的语句 -- 默认情况下,MySQL数据库没有开启慢查询日志,需要手动设置参数开启 - -**查看开启状态** - -```mysql -SHOW VARIABLES LIKE '%slow_query_log%' -``` - -**开启慢查询日志** - -- 临时配置: - -```mysql -mysql> set global slow_query_log='ON'; -mysql> set global slow_query_log_file='/var/lib/mysql/hostname-slow.log'; -mysql> set global long_query_time=2; -``` - - 也可set文件位置,系统会默认给一个缺省文件host_name-slow.log - - 使用set操作开启慢查询日志只对当前数据库生效,如果MySQL重启则会失效。 - -- 永久配置 - - 修改配置文件my.cnf或my.ini,在[mysqld]一行下面加入两个配置参数 - -```mysql -[mysqld] -slow_query_log = ON -slow_query_log_file = /var/lib/mysql/hostname-slow.log -long_query_time = 3 -``` - -注:log-slow-queries 参数为慢查询日志存放的位置,一般这个目录要有 MySQL 的运行帐号的可写权限,一般都将这个目录设置为 MySQL 的数据存放目录;long_query_time=2 中的 2 表示查询超过两秒才记录;在my.cnf或者 my.ini 中添加 log-queries-not-using-indexes 参数,表示记录下没有使用索引的查询。 - -可以用 `select sleep(4)` 验证是否成功开启。 - -在生产环境中,如果手工分析日志,查找、分析SQL,还是比较费劲的,所以MySQL提供了日志分析工具**mysqldumpslow**。 - -通过 mysqldumpslow --help 查看操作帮助信息 - -- 得到返回记录集最多的10个SQL - - `mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log` - -- 得到访问次数最多的10个SQL - - `mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log` - -- 得到按照时间排序的前10条里面含有左连接的查询语句 - - `mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log` - -- 也可以和管道配合使用 - - `mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more` - -**也可使用 pt-query-digest 分析 RDS MySQL 慢查询日志** - - - -##### Show Profile 分析查询 - -通过慢日志查询可以知道哪些 SQL 语句执行效率低下,通过 explain 我们可以得知 SQL 语句的具体执行情况,索引使用等,还可以结合`Show Profile`命令查看执行状态。 - -- Show Profile 是 MySQL 提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量 - -- 默认情况下,参数处于关闭状态,并保存最近15次的运行结果 - -- 分析步骤 - - 1. 是否支持,看看当前的mysql版本是否支持 - - ```mysql - mysql>Show variables like 'profiling'; --默认是关闭,使用前需要开启 - ``` - - 2. 开启功能,默认是关闭,使用前需要开启 - - ```mysql - mysql>set profiling=1; - ``` - - 3. 运行SQL - - 4. 查看结果 - - ```mysql - mysql> show profiles; - +----------+------------+---------------------------------+ - | Query_ID | Duration | Query | - +----------+------------+---------------------------------+ - | 1 | 0.00385450 | show variables like "profiling" | - | 2 | 0.00170050 | show variables like "profiling" | - | 3 | 0.00038025 | select * from t_base_user | - +----------+------------+---------------------------------+ - ``` - ``` - - 5. 诊断SQL,show profile cpu,block io for query id(上一步前面的问题SQL数字号码) - - 6. 日常开发需要注意的结论 - - - converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。 - - - create tmp table 创建临时表,这个要注意 - - - Copying to tmp table on disk 把内存临时表复制到磁盘 - - - locked - ``` - - - -> 查询中哪些情况不会使用索引? - -### 性能优化 - -#### 索引优化 - -1. 全值匹配我最爱 -2. 最佳左前缀法则,比如建立了一个联合索引(a,b,c),那么其实我们可利用的索引就有(a), (a,b), (a,b,c) -3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描 -4. 存储引擎不能使用索引中范围条件右边的列 -5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select -7. is null ,is not null 也无法使用索引 -8. like "xxxx%" 是可以用到索引的,like "%xxxx" 则不行(like "%xxx%" 同理)。like以通配符开头('%abc...')索引失效会变成全表扫描的操作, -9. 字符串不加单引号索引失效 -10. 少用or,用它来连接时会索引失效 -10. <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 则不行,会导致全表扫描 - - - -**一般性建议** - -- 对于单键索引,尽量选择针对当前query过滤性更好的索引 - -- 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。 - -- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引 - -- 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的 - -- 少用Hint强制索引 - - - -#### 查询优化 - -**永远小标驱动大表(小的数据集驱动大的数据集)** - -```mysql -slect * from A where id in (select id from B)`等价于 -#等价于 -select id from B -select * from A where A.id=B.id -``` - -当 B 表的数据集必须小于 A 表的数据集时,用 in 优于 exists - -```mysql -select * from A where exists (select 1 from B where B.id=A.id) -#等价于 -select * from A -select * from B where B.id = A.id` -``` - -当 A 表的数据集小于B表的数据集时,用 exists优于用 in - -注意:A表与B表的ID字段应建立索引。 - - - -**order by关键字优化** - -- order by子句,尽量使用 Index 方式排序,避免使用 FileSort 方式排序 - -- MySQL 支持两种方式的排序,FileSort 和 Index,Index效率高,它指 MySQL 扫描索引本身完成排序,FileSort 效率较低; -- ORDER BY 满足两种情况,会使用Index方式排序;①ORDER BY语句使用索引最左前列 ②使用where子句与ORDER BY子句条件列组合满足索引最左前列 - -- 尽可能在索引列上完成排序操作,遵照索引建的最佳最前缀 -- 如果不在索引列上,filesort 有两种算法,mysql就要启动双路排序和单路排序 - - 双路排序:MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据 - - 单路排序:从磁盘读取查询需要的所有列,按照order by 列在 buffer对它们进行排序,然后扫描排序后的列表进行输出,效率高于双路排序 - -- 优化策略 - - - 增大sort_buffer_size参数的设置 - - 增大max_lencth_for_sort_data参数的设置 - - - - -**GROUP BY关键字优化** - -- group by实质是先排序后进行分组,遵照索引建的最佳左前缀 -- 当无法使用索引列,增大 `max_length_for_sort_data` 参数的设置,增大`sort_buffer_size`参数的设置 -- where高于having,能写在where限定的条件就不要去having限定了 - - - -#### 数据类型优化 - -MySQL 支持的数据类型非常多,选择正确的数据类型对于获取高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。 - -- 更小的通常更好:一般情况下,应该尽量使用可以正确存储数据的最小数据类型。 - - 简单就好:简单的数据类型通常需要更少的CPU周期。例如,整数比字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型比较复杂。 - -- 尽量避免NULL:通常情况下最好指定列为NOT NULL - ------- - - - -## 九、分区、分表、分库 - -### MySQL分区 - -一般情况下我们创建的表对应一组存储文件,使用`MyISAM`存储引擎时是一个`.MYI`和`.MYD`文件,使用`Innodb`存储引擎时是一个`.ibd`和`.frm`(表结构)文件。 - -当数据量较大时(一般千万条记录级别以上),MySQL的性能就会开始下降,这时我们就需要将数据分散到多组存储文件,保证其单个文件的执行效率 - -**能干嘛** - -- 逻辑数据分割 -- 提高单一的写和读应用速度 -- 提高分区范围读查询的速度 -- 分割数据能够有多个不同的物理文件路径 -- 高效的保存历史数据 - -**怎么玩** - -首先查看当前数据库是否支持分区 - -- MySQL5.6以及之前版本: - - ```mysql - SHOW VARIABLES LIKE '%partition%'; - ``` - -- MySQL5.6: - - ```mysql - show plugins; - ``` - -**分区类型及操作** - -- **RANGE分区**:基于属于一个给定连续区间的列值,把多行分配给分区。mysql将会根据指定的拆分策略,,把数据放在不同的表文件上。相当于在文件上,被拆成了小块.但是,对外给客户的感觉还是一张表,透明的。 - - 按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,比如交易表啊,销售表啊等,可以根据年月来存放数据。可能会产生热点问题,大量的流量都打在最新的数据上了。 - - range 来分,好处在于说,扩容的时候很简单。 - -- **LIST分区**:类似于按RANGE分区,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。 - -- **HASH分区**:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。 - - hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表 - -- **KEY分区**:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。 - -**看上去分区表很帅气,为什么大部分互联网还是更多的选择自己分库分表来水平扩展咧?** - -- 分区表,分区键设计不太灵活,如果不走分区键,很容易出现全表锁 -- 一旦数据并发量上来,如果在分区表实施关联,就是一个灾难 -- 自己分库分表,自己掌控业务场景与访问模式,可控。分区表,研发写了一个sql,都不确定mysql是怎么玩的,不太可控 - - - -> 随着业务的发展,业务越来越复杂,应用的模块越来越多,总的数据量很大,高并发读写操作均超过单个数据库服务器的处理能力怎么办? - -这个时候就出现了**数据分片**,数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中。数据分片的有效手段就是对关系型数据库进行分库和分表。 - -区别于分区的是,分区一般都是放在单机里的,用的比较多的是时间范围分区,方便归档。只不过分库分表需要代码实现,分区则是mysql内部实现。分库分表和分区并不冲突,可以结合使用。 - - - ->说说分库与分表的设计 - -### MySQL分表 - -分表有两种分割方式,一种垂直拆分,另一种水平拆分。 - -- **垂直拆分** - - 垂直分表,通常是按照业务功能的使用频次,把主要的、热门的字段放在一起做为主要表。然后把不常用的,按照各自的业务属性进行聚集,拆分到不同的次要表中;主要表和次要表的关系一般都是一对一的。 - -- **水平拆分(数据分片)** - - 单表的容量不超过500W,否则建议水平拆分。是把一个表复制成同样表结构的不同表,然后把数据按照一定的规则划分,分别存储到这些表中,从而保证单表的容量不会太大,提升性能;当然这些结构一样的表,可以放在一个或多个数据库中。 - - 水平分割的几种方法: - - - 使用MD5哈希,做法是对UID进行md5加密,然后取前几位(我们这里取前两位),然后就可以将不同的UID哈希到不同的用户表(user_xx)中了。 - - 还可根据时间放入不同的表,比如:article_201601,article_201602。 - - 按热度拆分,高点击率的词条生成各自的一张表,低热度的词条都放在一张大表里,待低热度的词条达到一定的贴数后,再把低热度的表单独拆分成一张表。 - - 根据ID的值放入对应的表,第一个表user_0000,第二个100万的用户数据放在第二 个表user_0001中,随用户增加,直接添加用户表就行了。 - - - - - -### MySQL分库 - -> 为什么要分库? - -数据库集群环境后都是多台 slave,基本满足了读取操作; 但是写入或者说大数据、频繁的写入操作对master性能影响就比较大,这个时候,单库并不能解决大规模并发写入的问题,所以就会考虑分库。 - -> 分库是什么? - -一个库里表太多了,导致了海量数据,系统性能下降,把原本存储于一个库的表拆分存储到多个库上, 通常是将表按照功能模块、关系密切程度划分出来,部署到不同库上。 - -优点: - -- 减少增量数据写入时的锁对查询的影响 - -- 由于单表数量下降,常见的查询操作由于减少了需要扫描的记录,使得单表单次查询所需的检索行数变少,减少了磁盘IO,时延变短 - -但是它无法解决单表数据量太大的问题 - - - -**分库分表后的难题** - -分布式事务的问题,数据的完整性和一致性问题。 - -数据操作维度问题:用户、交易、订单各个不同的维度,用户查询维度、产品数据分析维度的不同对比分析角度。 跨库联合查询的问题,可能需要两次查询 跨节点的count、order by、group by以及聚合函数问题,可能需要分别在各个节点上得到结果后在应用程序端进行合并 额外的数据管理负担,如:访问数据表的导航定位 额外的数据运算压力,如:需要在多个节点执行,然后再合并计算程序编码开发难度提升,没有太好的框架解决,更多依赖业务看如何分,如何合,是个难题。 - - - -> 配主从,正经公司的话,也不会让 Javaer 去搞的,但还是要知道 - -## 十、主从复制 - -### 复制的基本原理 - -- slave 会从 master 读取 binlog 来进行数据同步 - -- 三个步骤 - - 1. master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events; - 2. salve 将 master 的 binary log events 拷贝到它的中继日志(relay log); - 3. slave 重做中继日志中的事件,将改变应用到自己的数据库中。MySQL 复制是异步且是串行化的。 - -  - -### 复制的基本原则 - -- 每个 slave只有一个 master -- 每个 salve只能有一个唯一的服务器 ID -- 每个master可以有多个salve - -### 复制的最大问题 - -- 延时 - ------- - - - -## 十一、其他问题 - -### 说一说三个范式 - -- 第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。 -- 第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。 -- 第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系: 关键字段 → 非关键字段 x → 非关键字段y - - - -### 百万级别或以上的数据如何删除 - -关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。 - -1. 所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟) -2. 然后删除其中无用数据(此过程需要不到两分钟) -3. 删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。 -4. 与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。 - - - - - - -## 参考与感谢: - -https://zhuanlan.zhihu.com/p/29150809 - -https://juejin.im/post/5e3eb616f265da570d734dcb#heading-105 - -https://blog.csdn.net/yin767833376/article/details/81511377 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/data-management/MySQL/MySQL-Framework.md b/docs/data-management/MySQL/MySQL-Framework.md index 9f1b8d5c49..f616a373f6 100644 --- a/docs/data-management/MySQL/MySQL-Framework.md +++ b/docs/data-management/MySQL/MySQL-Framework.md @@ -1,38 +1,179 @@ -# MySQL架构介绍 +--- +title: MySQL架构介绍 +date: 2022-08-25 +tags: + - MySQL +categories: MySQL +--- -和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,**插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离**。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。 + - +> Hello,我是海星。 +> +> 学习 MySQL 第一步,不是去学 select 、update,而是先要对他的整体架构设计有个大概的了解,先高屋建瓴,然后逐一攻破。 +和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,**插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离**。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。 +下边是 MySQL 官网中 8.0 版本的一个图,我们展开看一下,对 MySQL 整体架构和可插拔的存储引擎先有个总体回顾。 + + ## 1. 连接层 -最上层是一些客户端和连接服务,包含本地socket通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。**主要完成一些类似于连接处理、授权认证、及相关的安全方案**。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 +要使用 MySQL,第一步肯定要与他进行连接。 + +最上层就是一些客户端和连接服务,包含本地 socket 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的通信。**主要完成一些类似于建立连接、授权认证、及相关的安全方案**。 + +```mysql +# -h 指定 MySQL 服务得 IP 地址,如果是连接本地的 MySQL服务,可以不用这个参数; +# -u 指定用户名,管理员角色名为 root; +# -p 指定密码,如果命令行中不填写密码(为了密码安全,建议不要在命令行写密码),就需要在交互对话里面输入密码 +mysql -h$ip -u$user -p +``` + +输入密码后,就成功建立了连接,我们可以用 `show processlist` 查看当前所有数据库连接的 `session` 状态 + +> 连接状态,一般是`休眠`(sleep),`查询`(query),`连接`(connect),如果一条 SQL 语句是`query`状态,而且`time`时间很长,说明存在`问题` + + + +其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接 + +客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 `wait_timeout` 控制的,默认值是 8 小时。 + +```mysql +mysql> show variables like 'wait_timeout'; ++---------------+-------+ +| Variable_name | Value | ++---------------+-------+ +| wait_timeout | 28800 | ++---------------+-------+ +1 row in set (0.00 sec) +``` + +当然,MySQL 对连接数量也是有限制的,最大连接数由 `max_connections` 参数控制 + +```mysql +mysql> show variables like 'max_connections'; ++-----------------+-------+ +| Variable_name | Value | ++-----------------+-------+ +| max_connections | 151 | ++-----------------+-------+ +1 row in set (0.00 sec) +``` + + ## 2. 服务层 -第二层架构主要完成大部分的核心服务功能, 包括查询解析、分析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等 +第二层架构完成了大部分的核心功能, 包括查询解析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等 + +### 查询缓存 + +第一步的连接建立后,我们就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。 + +MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。 + +> 对一个表的更新,就会把该表上的所有查询缓存清空,所以更新比较频繁的表,查询缓存的命中率就极低,所以不建议使用,官方已经在 8.0 版本移除该功能了。 +> +> 之前版本的 MySQL 也提供“按需使用”的方式。我们可以将参数 query_cache_type 设置成 DEMAND,这样对默认的 SQL 语句就都不使用查询缓存。 +> +> **Note** +> +> The query cache is deprecated as of MySQL 5.7.20, and is removed in MySQL 8.0. + +### 分析器 + +如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。 + +解析器会做如下两件事情。 + +- 第一件事情,**词法分析**。MySQL 会根据你输入的字符串识别出关键字出来,构建出 SQL 语法树,这样方便后面模块获取 SQL 类型、表名、字段名、 where 条件等等。 + +- 第二件事情,**语法分析**。根据词法分析的结果,语法解析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。 + +如果我们输入的 SQL 语句语法不对,就会在解析器这个阶段报错。比如,我下面这条查询语句,把 from 写成了 form,这时 MySQL 解析器就会给报错。 + +```mysql +mysql> select * form user; +ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'form user' at line 1 +``` + +### 优化器 + +经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。比如重写查询、决定表的读取顺序,选择合适的索引等 + +> 比如你执行下面这样的语句,这个语句是执行两个表的join: +> +> ```mysql +> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;` +> ``` +> +> - 既可以先从表t1里面取出c=10的记录的ID值,再根据ID值关联到表t2,再判断t2里面d的值是否等于20。 +> - 也可以先从表t2里面取出d=20的记录的ID值,再根据ID值关联到t1,再判断t1里面c的值是否等于10。 +> +> 这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。 + +### 执行器 + +* MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。 +* 开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误 +* 如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。 + +>```mysql +> select * from T where ID=10; +>``` +> +>比如我们这个例子中的表T中,ID字段没有索引,那么执行器的执行流程是这样的: +> +>1. 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中; +>2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。 +>3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。 + +* 至此,这个整个语句就执行完成了。一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。 + +> 对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。 +> +> 你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。 +> +> 在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此**引擎扫描行数跟 rows_examined 并不是完全相同的。** + +### SQL 接口 + +用于接收客户端发送的各种 SQL 命令,返回用户需要查询的结果,比如 DML、DDL、存储过程、视图、触发器这些 + + + +## 3. 引擎层 + +存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。 + +不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。 -## 3.引擎层 -存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。 -## 4.存储层 +## 4. 存储层 数据存储层,主要是将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互。 -更符合程序员审美的MySQL服务器逻辑架构图 +### MySQL 的查询流程大致是? - +> 一条 SQL 查询语句是如何执行的? +1. **客户端请求**:MySQL 客户端通过协议与 MySQL 服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析(MySQL 8.0 已取消了缓存) +2. **查询接收**:连接器接收请求,管理连接 +3. **解析器**:对 SQL 进行词法分析和语法分析,转换为解析树 +4. **优化器**:优化器生成执行计划,选择最优索引和连接顺序 +5. **查询执行器**:执行器执行查询,通过存储引擎接口获取数据 +6. **存储引擎**:存储引擎检索数据,返回给执行器 +7. **返回结果**:结果通过连接器返回给客户端 -## 查询说明 + -mysql的查询流程大致是: -1. mysql客户端通过协议与mysql服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析 -2. 有一系列预处理,比如检查语句是否写正确了,然后是查询优化(比如是否使用索引扫描,如果是一个不可能的条件,则提前终止),生成查询计划,然后查询引擎启动,开始执行查询,从底层存储引擎调用API获取数据,最后返回给客户端。怎么存数据、怎么取数据,都与存储引擎有关。 -3. 然后,mysql默认使用的BTREE索引,并且一个大方向是,无论怎么折腾sql,至少在目前来说,mysql最多只用到表中的一个索引。 +## Reference +- 《高性能 MySQL》 +- 《MySQL 实战 45 讲》 diff --git a/docs/data-management/MySQL/MySQL-Index.md b/docs/data-management/MySQL/MySQL-Index.md index 9ed3f21f6a..fe6c8f80c3 100644 --- a/docs/data-management/MySQL/MySQL-Index.md +++ b/docs/data-management/MySQL/MySQL-Index.md @@ -1,37 +1,52 @@ -# MySQL索引篇——妈妈再也不用担心我不会索引了 +--- +title: MySQL索引——妈妈再也不用担心我不会索引了 +date: 2022-02-12 +tags: + - MySQL +categories: MySQL +--- ->索引问题,在面试中是肯定会出现的,记一道知乎服务端面试题 + + +>Hello,我是海星。 +> +>先来一道经典的服务端面试题,看下你会吗 +> +>“如果有这样一个查询 `select * from table where a=1 group by b order by c;` 如果每个字段都有一个单列索引,索引会生效吗?如果是复合索引,能说下几种情况吗?“ > ->“如果有这样一个查询 `select * from table where a=1 group by b order by c;` 如果每个字段都有一个单列索引,索引会生效吗?如果是符合索引,能说下几种情况吗? +>这篇文章算是一个 MySQL 索引的知识梳理,包括索引的一些概念、B 树的结构、和索引的原理以及一些索引策略的知识,祝好 + + + +## 一、索引基础回顾 -## 一、回顾索引基础 +### 索引是什么 - MYSQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构,所以说**索引的本质是:数据结构** - 索引的目的在于提高查询效率,可以类比字典、 火车站的车次表、图书的目录等 。 -- 可以简单的理解为“排好序的快速查找数据结构”,数据本身之外,**数据库还维护者一个满足特定查找算法的数据结构**,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图是一种可能的索引方式示例。 +- 可以简单的理解为“排好序的快速查找数据结构”,数据本身之外,**数据库还维护着一个满足特定查找算法的数据结构**,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。 -  -======= -  ->>>>>>> d603e5d78c0acfdf025b57e1fd861df9ad4d2ff7 + > 常见的索引模型其实有很多,哈希表、有序数组,各种搜索树都可以实现索引结构 - 左边的数据表,一共有两列七条记录,最左边的是数据记录的物理地址 + 下图是一种可能的索引方式示例(二叉搜索树) - 为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值,和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到对应的数据,从而快速检索出符合条件的记录。 +  + + 上图左边是一张简单的`学生成绩表`,只有学号 id 和成绩 score 两列(最左边的是数据的物理地址) + + 比如我们想要快速查指定成绩的学生,通过构建一个右边的二叉搜索树当索引,索引节点就是成绩数据,节点指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到对应的数据,从而快速检索出符合条件的学生信息。 - 索引本身也很大,不可能全部存储在内存中,**一般以索引文件的形式存储在磁盘上** -- 平常说的索引,没有特别指明的话,就是 B+ 树(多路搜索树,不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,符合索引,前缀索引,唯一索引默认都是使用 B+ 树索引,统称索引。此外还有哈希索引等。 - ### 优势 - 索引大大减少了服务器需要扫描的数据量(提高数据检索效率) -- 索引可以帮助服务器避免排序和临时表(降低数据排序的成本,降低CPU的消耗) -- 索引可以将随机 I/O 变为顺序 I/O(降低数据库IO成本) +- 索引可以帮助服务器避免排序和临时表(降低数据排序的成本,降低 CPU 的消耗) +- 索引可以将随机 I/O 变为顺序 I/O(降低数据库 IO 成本) @@ -39,109 +54,70 @@ - 索引也是一张表,保存了主键和索引字段,并指向实体表的记录,所以也需要占用内存 - 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。 -因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段, - 都会调整因为更新所带来的键值变化后的索引信息 + 因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息 -### MySQL 索引分类 +### 索引分类 -##### 数据结构角度 +我们从 3 个角度看下索引的分类 -- B+树索引 -- Hash索引 -- Full-Text全文索引 -- R-Tree索引 - -##### 从物理存储角度 - -- 聚集索引(clustered index) - -- 非聚集索引(non-clustered index),也叫辅助索引(secondary index) - - 聚集索引和非聚集索引都是 B+ 树结构 - -##### 从逻辑角度 +**从逻辑角度** - 主键索引:主键索引是一种特殊的唯一索引,不允许有空值 - 普通索引或者单列索引:每个索引只包含单个列,一个表可以有多个单列索引 -- 多列索引(复合索引、联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合 +- 多列索引(复合索引、联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。 - 唯一索引或者非唯一索引 -- 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL 中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。 MYSQL 使用 SPATIAL 关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为 NOT NULL,空间索引只能在存储引擎为 MYISAM 的表中创建 - - - -### 基本语法 - -**创建**: +- Full-Text 全文索引:它查找的是文本中的关键词,而不是直接比较索引中的值 +- 空间索引:空间索引是对空间数据类型的字段建立的索引 -- 创建索引: +**数据结构角度** - ```mysql - CREATE [UNIQUE] INDEX indexName ON mytable(username(length)); - ``` +- Hash 索引:主要就是通过 Hash 算法,将数据库字段数据转换成定长的 Hash 值,与这条数据的行指针一并存入 Hash 表的对应位置;如果发生 Hash 碰撞,则在对应 Hash 键下以链表形式存储。查询时,就再次对待查关键字再次执行相同的 Hash 算法,得到 Hash 值,到对应 Hash 表对应位置取出数据即可,Memory 引擎是支持非唯一哈希索引的,如果发生 Hash 碰撞,会以链表的方式存放多个记录在同一哈希条目中。使用 Hash 索引的数据库并不多, 目前有 Memory 引擎和 NDB 引擎支持 Hash 索引。 - 如果是 CHAR,VARCHAR 类型,length 可以小于字段实际长度;如果是 BLOB 和 TEXT 类型,必须指定 length。 + 缺点是,只支持等值比较查询,像 = 、 in() 这种,不支持范围查找,比如 where id > 10 这种,也不能排序。 -- 修改表结构(添加索引): +- B+ 树索引(下文会详细讲) - ```mysql - ALTER table tableName ADD [UNIQUE] INDEX indexName(columnName); - ``` +**从物理存储角度** -**删除**: - -```mysql -DROP INDEX [indexName] ON mytable; -``` - -**查看**: - -```mysql -SHOW INDEX FROM table_name\G --可以通过添加 \G 来格式化输出信息 -``` - -**修改**: - -- ```mysql - ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): - ``` - - 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。 +- 聚集索引(clustered index) -- ```mysql - ALTER TABLE tbl_name ADD UNIQUE index_name (column_list) - ``` +- 非聚集索引(non-clustered index),也叫辅助索引(secondary index) - 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。 + 聚集索引和非聚集索引都是 B+ 树结构 -- ```mysql - ALTER TABLE tbl_name ADD INDEX index_name (column_list) - ``` + - 添加普通索引,索引值可出现多次。 +## 二、MySQL 索引结构 -- ```mysql - ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list) - ``` +索引可以有很多种结构类型,这样可以为不同的场景提供更好的性能。 - 该语句指定了索引为 FULLTEXT ,用于全文索引。 +> **首先要明白索引(index)是在存储引擎(storage engine)层面实现的,而不是 server 层面**。不是所有的存储引擎都支持所有的索引类型。即使多个存储引擎支持某一索引类型,它们的实现和行为也可能有所差别。 +> +> 像有的 二* 面试官上来就会问:`MySQL 为什么不用 Hash 结构做索引? ` +> +> 我会直接来一句,不好意思,MySQL 也会用 Hash 做索引,Memory 存储引擎就支持 Hash 索引。只是场景用的少,Hash 结构更适用于只有等值查询的场景 +> +> 为什么不用二叉搜索树呢? 这就很简单了,二叉树的叉叉上只有两个数,数据量太多的话,那得多少层呀。 +### 磁盘 IO +介绍索引结构之前,我们先了解下[磁盘IO与预读](https://tech.meituan.com/2014/06/30/mysql-index.html "MySQL索引原理及慢查询优化") -> 为什么 MySQL 索引中用 B+tree,不用 B-tree 或者其他树,为什么不用 Hash 索引 +> 磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分 > -> 聚簇索引/非聚簇索引,MySQL 索引底层实现,叶子结点存放的是数据还是指向数据的内存地址,使用索引需要注意的几个地方? +> - 寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在 5ms 以下; +> - 旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘 7200 转,表示每分钟能转 7200 次,也就是说 1 秒钟能转 120 次,旋转延迟就是 `1/120/2 = 4.17ms`; +> - 传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。 > -> 使用索引查询一定能提高查询的性能吗?为什么? - -## 二、MySQL索引结构 - -介绍索引之前,先了解下[磁盘IO与预读](https://tech.meituan.com/2014/06/30/mysql-index.html "MySQL索引原理及慢查询优化") - -> 磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在 5ms 以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘 7200 转,表示每分钟能转 7200 次,也就是说 1 秒钟能转 120 次,旋转延迟就是 `1/120/2 = 4.17ms`;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘 IO 的时间约等于 `5+4.17 = 9ms` 左右,听起来还挺不错的,但要知道一台 500 -MIPS的机器每秒可以执行 5 亿条指令,因为指令依靠的是电的性质,换句话说执行一次 IO 的时间可以执行 40 万条指令,数据库动辄十万百万乃至千万级数据,每次 9 毫秒的时间,显然是个灾难。下图是计算机硬件延迟的对比图,供大家参考: +>  +> +> 那么访问一次磁盘的时间,即一次磁盘 IO 的时间约等于 `5+4.17 = 9ms` 左右,听起来还挺不错的,但要知道一台 500 -MIPS 的机器每秒可以执行 5 亿条指令,因为指令依靠的是电的性质,换句话说执行一次 IO 的时间可以执行 40 万条指令,数据库动辄十万百万乃至千万级数据,每次 9 毫秒的时间,显然是个灾难。下图是计算机硬件延迟的对比图,供大家参考: +> +>  > > 考虑到磁盘 IO 是非常高昂的操作,计算机操作系统做了一些优化,当一次 IO 时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次 IO 读取的数据我们称之为**一页(page)**。具体一页有多大数据跟操作系统有关,一般为 4k 或 8k,也就是我们读取一页内的数据时候,实际上才发生了一次 IO,这个理论对于索引的数据结构设计非常有帮助。 > @@ -150,160 +126,133 @@ SHOW INDEX FROM table_name\G --可以通过添加 \G 来格式化输出 那是不应该有一种数据结构,可以在每次查找数据时把磁盘 IO 次数控制在一个很小的数量级, B+ 树就这样应用而生。 -索引有很多种类型,可以为不同的场景提供更好的性能。 - -?> **首先要明白索引(index)是在存储引擎(storage engine)层面实现的,而不是server层面**。不是所有的存储引擎都支持所有的索引类型。即使多个存储引擎支持某一索引类型,它们的实现和行为也可能有所差别。 - - - -上文中我们知道 MySQL 从数据结构角度分可以分为:B+树索引、Hash索引、Full-Text全文索引、R-Tree索引等,下边就一一扯扯这样索引 +### 心里有点 B 树 -### **B+Tree索引** +有一点面试经验的同学,可能都碰到过这么一道面试题:MySQL InnoDB 索引为什么用 B+ 树,不用 B 树 -MyISAM 和 InnoDB 存储引擎,都使用 B+Tree 的数据结构,它相对与 B-Tree结构,所有的数据都存放在叶子节点上,且把叶子节点通过指针连接到一起,形成了一条数据链表,以加快相邻数据的检索效率。 +> B-Tree == B Tree,他两是一个东西,没有 B 减树 这玩意 -**先了解下 B-Tree 和 B+Tree 的区别** +先大概(仔细)看下维基百科的概述: -#### [B- Tree](https://blog.csdn.net/u013235478/article/details/50625677?utm_source=app "MySQL索引原理") - -B-Tree 是为磁盘等外存储设备设计的一种平衡查找树。 - -系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。 - -InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB,可通过参数 `innodb_page_size` 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:`show variables like 'innodb_page_size';` - -而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘 I/O 次数,提高查询效率。 - -B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 B-Tree,首先定义一条记录为一个二元组[key, data] ,key 为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。 - -一棵 m 阶的 B-Tree 有如下特性: - -1. 每个节点最多有 m 个孩子 -2. 除了根节点和叶子节点外,其它每个节点至少有 Ceil(m/2) 个孩子。 -3. 若根节点不是叶子节点,则至少有 2 个孩子 -4. **所有叶子节点都在同一层,且不包含其它关键字信息** -5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn) -6. 关键字的个数 n 满足:ceil(m/2)-1 <= n <= m-1 -7. ki(i=1,…n) 为关键字,且关键字升序排序 -8. Pi(i=1,…n) 为指向子树根节点的指针。P(i-1) 指向的子树的所有节点关键字均小于 ki,但都大于 k(i-1) - -B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree: - - - -每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为 17 和 35,P1 指针指向的子树的数据范围为小于 17,P2 指针指向的子树的数据范围为17~35,P3 指针指向的子树的数据范围为大于 35。 +> 在 B 树中,内部(非叶子)节点可以拥有可变数量的子节点(数量范围预先定义好)。当数据被插入或从一个节点中移除,它的子节点数量发生变化。为了维持在预先设定的数量范围内,内部节点可能会被合并或者分离。因为子节点数量有一定的允许范围,所以B 树不需要像其他自平衡查找树那样频繁地重新保持平衡,但是由于节点没有被完全填充,可能浪费了一些空间。子节点数量的上界和下界依特定的实现而设置。例如,在一个 2-3 B树(通常简称[2-3树](https://zh.wikipedia.org/wiki/2-3树)),每一个内部节点只能有 2 或 3 个子节点。 +> +> **B 树中每一个内部节点会包含一定数量的键,键将节点的子树分开**。例如,如果一个内部节点有 3 个子节点(子树),那么它就必须有两个键: *a*1 和 *a*2 。左边子树的所有值都必须小于 *a*1 ,中间子树的所有值都必须在 *a*1 和 *a*2 之间,右边子树的所有值都必须大于 *a*2 。 +> +> 在存取节点数据所耗时间远超过处理节点数据所耗时间的情况下,B树在可选的实现中拥有很多优势,因为存取节点的开销被分摊到里层节点的多次操作上。这通常出现在当节点存储在二级存储器如硬盘存储器上。通过最大化内部里层节点的子节点的数量,树的高度减小,存取节点的开销被缩减。另外,重新平衡树的动作也更少出现。子节点的最大数量取决于,每个子节点必需存储的信息量,和完整磁盘块的大小或者二次存储器中类似的容量。虽然 2-3 树更易于解释,实际运用中,B树使用[二级存储器](https://zh.wikipedia.org/w/index.php?title=二级存储器&action=edit&redlink=1),需要大量数目的子节点来提升效率。 +> +> 而 B+ 树 又是 B 树的变种,B+ 树结构,所有的数据都存放在叶子节点上,且把叶子节点通过指针连接到一起,形成了一条数据链表,以加快相邻数据的检索效率。 -模拟查找关键字 29 的过程: +推荐一个数据结构可视化网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html,可以用来生成各种数据结构 -1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】 -2. 比较关键字 29 在区间(17,35),找到磁盘块1的指针P2。 -3. 根据 P2 指针找到磁盘块 3,读入内存。【磁盘I/O操作第2次】 -4. 比较关键字 29 在区间(26,30),找到磁盘块3的指针P2。 -5. 根据 P2 指针找到磁盘块 8,读入内存。【磁盘I/O操作第3次】 -6. 在磁盘块 8 中的关键字列表中找到关键字 29。 +将 `[11,13,15,16,20,23,25,30,23,27]` 用 B 树 和 B+ 树存储,看下结构 -分析上面过程,发现需要 3 次磁盘 I/O 操作,和 3 次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而 3 次磁盘 I/O 操作是影响整个 B-Tree 查找效率的决定因素。 + -#### B+Tree +#### **B 树和 B+ 树区别** -B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是用 B+Tree 实现其索引结构。 +B-Tree 和 B+Tree 都是为磁盘等外存储设备设计的一种平衡查找树。 -从上一节中的 B-Tree 结构图中可以看到每个节点中不仅包含数据的 key 值,还有 data 值。而每一个页的存储空间是有限的,如果 data 数据较大时将会导致每个节点(即一个页)能存储的 key 的数量很小,当存储的数据量很大时同样会导致 B-Tree 的深度较大,增大查询时的磁盘 I/O 次数,进而影响查询效率。在 B+Tree 中,**所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上**,而非叶子节点上只存储 key 值信息,这样可以大大加大每个节点存储的 key 值数量,降低 B+Tree 的高度。 +| 关键词 | B-树 | B+树 | 备注 | +| -------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------------------- | +| 最大分支,最小分支 | 每个结点最多有m个分支(子树),最少⌈m/2⌉(中间结点)个分支或者2个分支(是根节点非叶子结点)。 | 同左 | m阶对应的就是就是最大分支 | +| n个关键字与分支的关系 | 分支等于n+1 | 分支等于n | 无 | +| 关键字个数(B+树关键字个数要多) | 大于等于⌈m/2⌉-1小于等于m-1 | 大于等于⌈m/2⌉小于等于m | B+树关键字个数要多,+体现在的地方。 | +| 叶子结点相同点 | 每个节点中的元素互不相等且按照从小到大排列;所有的叶子结点都位于同一层。 | 同左 | 无 | +| 叶子结点不相同 | 不包含信息 | 叶子结点包含信息,指针指向记录。 | 无 | +| 叶子结点之间的关系 | 无 | B+树上有一个指针指向关键字最小的叶子结点,所有叶子节点之间链接成一个线性链表 | 无 | +| 非叶子结点 | 一个关键字对应一个记录的存储地址 | 只起到索引的作用 | 无 | +| 存储结构 | 相同 | 同左 | 无 | -B+Tree 相对于 B-Tree 有几点不同: -1. 非叶子节点只存储键值信息; -2. 所有叶子节点之间都有一个链指针; -3. 数据记录都存放在叶子节点中 -将上一节中的 B-Tree 优化,由于 B+Tree 的非叶子节点只存储键值信息,假设每个磁盘块能存储 4 个键值及指针信息,则变成 B+Tree 后其结构如下图所示: - +### 为什么要用 B+ 树 -通常在 B+Tree 上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对 B+Tree 进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。 +心里有了磁盘 IO 和 B 树的概念,接下来就顺理成章了。磁盘 IO 次数越少,那查询效率肯定就越高。而 IO 次数又取决于 B+ 树的高度 -可能上面例子中只有 22 条数据记录,看不出 B+Tree 的优点,下面做一个推算: +我们以 InnoDB 存储引擎来说明。 -InnoDB 存储引擎中页的大小为 16KB,一般表的主键类型为 INT(占用4个字节)或 BIGINT(占用8个字节),指针类型也一般为 4 或 8 个字节,也就是说一个页(B+Tree中的一个节点)中大概存储 `16KB/(8B+8B)=1K` 个键值(因为是估值,为方便计算,这里的 K 取值为 $10^3$)。也就是说一个深度为 3 的 B+Tree 索引可以维护 `10^3 * 10^3 * 10^3 = 10亿` 条记录。 +系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。 -实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree 的高度一般都在 2-4 层。MySQL 的 InnoDB 存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要 1~3 次磁盘 I/O 操作。 +InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB,可通过参数 `innodb_page_size` 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:`show variables like 'innodb_page_size';` -**B+Tree性质** +而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘 I/O 次数,提高查询效率。 -1. 通过上面的分析,我们知道 IO 次数取决于 B+ 数的高度 h,假设当前数据表的数据为 N,每个磁盘块的数据项的数量是 m,则有 `h=㏒(m+1)N`,当数据量 N 一定的情况下,m 越大,h 越小;而 `m = 磁盘块的大小 / 数据项的大小`,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如 int 占4字节,要比 bigint 8 字节少一半。这也是为什么 B+ 树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于 1 时将会退化成线性表。 -2. 当 B+ 树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+ 数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,B+ 树会优先比较 name 来确定下一步的所搜方向,如果name 相同再依次比较 age 和 sex,最后得到检索的数据;但当 (20,F) 这样的没有 name 的数据来的时候,B+ 树就不知道下一步该查哪个节点,因为建立搜索树的时候 name 就是第一个比较因子,必须要先根据name 来搜索才能知道下一步去哪里查询。比如当 (张三,F) 这样的数据来检索时,B+ 树可以用 name 来指定搜索方向,但下一个字段 age 的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即**索引的最左匹配特性**。 +#### 举个例子 -### **Hash索引** +索引是为了更快的查询到数据,MySQL 数据行可能会很多内容 -- 主要就是通过 Hash 算法(常见的 Hash 算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的 Hash 值,与这条数据的行指针一并存入 Hash 表的对应位置;如果发生 Hash 碰撞,则在对应 Hash 键下以链表形式存储。 +以范围查找为例简单看下,B Tree 结构查询 [10-25] 的数据(从根节点开始,随机查找一样的道理,只是我画的图只有 2 层,说服力强的不是那么明显罢了) - 检索算法:在检索查询时,就再次对待查关键字再次执行相同的 Hash 算法,得到 Hash 值,到对应 Hash 表对应位置取出数据即可,如果发生 Hash 碰撞,则需要在取值时进行筛选。使用 Hash 索引的数据库并不多, 目前有 Memory 引擎和 NDB 引擎支持 Hash 索引。 +1. 加载根节点,第一个节点元素15,大于10【磁盘 I/O 操作第 1 次】 +2. 通过根节点的左子节点地址加载,找到 11,13【磁盘 I/O 操作第 2 次】 +3. 重新加载根节点,找到中间节点数据 16,20【磁盘 I/O 操作第 3 次】 +4. 再次加载根节点,23 小于 25,再加载右子节点,找到 25,结束【磁盘 I/O 操作第 4 次】 -- Hash 索引的弊端 + - 一般来说,索引的检索效率非常高,可以一次定位,不像 B-Tree 索引需要进行从根节点到叶节点的多次 IO 操作。有利必有弊,Hash 算法在索引的应用也有很多弊端。 +而 B+ 树对范围查找就简单了,数据都在最下边的叶子节点下,而且链起来了,我只需找到第一个然后遍历就行(暂且不考虑页分裂等其他问题)。 - 1. Hash 索引只支持等值比较查询,包括 =、IN() 等,不支持任何范围查询,如 where price > 100。因为数据在经过 Hash 算法后,其大小关系就可能发生变化。 - 2. Hash 索引不能被排序。同样是因为数据经过 Hash 算法后,大小关系就可能发生变化,排序是没有意义的。 - 3. Hash 索引不能避免表数据的扫描。因为发生 Hash 碰撞时,仅仅比较 Hash 值是不够的,需要比较实际的值以判定是否符合要求。 - 4. Hash 索引在发生大量 Hash 值相同的情况时性能不一定比 B-Tree 索引高。因为碰撞情况会导致多次的表数据的扫描,造成整体性能的低下,可以通过采用合适的 Hash 算法一定程度解决这个问题。 - 5. Hash 索引不能使用部分索引键查询。因为当使用组合索引情况时,是把多个数据库列数据合并后再计算Hash 值,所以对单独列数据计算 Hash 值是没有意义的。 -### **full-text全文索引** -- 全文索引也是 MyISAM 的一种特殊索引类型,主要用于全文索引,InnoDB 从 MYSQL5.6 版本提供对全文索引的支持。 +#### 解答 -- 它用于替代效率较低的 LIKE 模糊匹配操作,而且可以通过多字段组合的全文索引一次性全模糊匹配多个字段。 -- 同样使用 B-Tree 存放索引数据,但使用的是特定的算法,将字段数据分割后再进行索引(一般每4个字节一次分割),索引文件存储的是分割前的索引字符串集合,与分割后的索引信息,对应 BTree 结构的节点存储的是分割后的词信息以及它在分割前的索引字符串集合中的位置。 +> 为什么 MySQL 索引要用 B+ 树不是 B 树? -### **R-Tree空间索引** +B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构。 -空间索引是 MyISAM 的一种特殊索引类型,主要用于地理空间数据类型 。 +用 B+ 树不用 B 树考虑的是 IO 对性能的影响,B 树的每个节点都存储数据,而 B+ 树只有叶子节点才存储数据,所以查找相同数据量的情况下,B 树的高度更高,IO 更频繁。数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘页(对应索引树的节点)。其中在 MySQL 底层对 B+ 树进行进一步优化:**在叶子节点中是双向链表,且在链表的头结点和尾节点也是循环指向的**。 +B-Tree 结构图每个节点中不仅要包含数据的 key 值,还有 data 值。而每一个页的存储空间是有限的,如果 data 数据较大时将会导致每个节点(即一个页)能存储的 key 的数量很小,当存储的数据量很大时同样会导致 B-Tree 的深度较大,增大查询时的磁盘 I/O 次数,进而影响查询效率。在 B+Tree 中,**所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上**,而非叶子节点上只存储 key 值信息,这样可以大大加大每个节点存储的 key 值数量,降低 B+Tree 的高度。 +> IO 次数取决于 B+ 数的高度 h,假设当前数据表的数据为 N,每个磁盘块的数据项的数量是 m,则有 `h=㏒(m+1)N`,当数据量 N 一定的情况下,m 越大,h 越小;而 `m = 磁盘块的大小 / 数据项的大小`,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如 int 占 4 字节,要比 bigint 8 字节少一半。这也是为什么 B+ 树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于 1 时将会退化成线性表。 -> 为什么 MySQL 索引要用 B+ 树不是 B 树? -用B+树不用B树考虑的是IO对性能的影响,B树的每个节点都存储数据,而B+树只有叶子节点才存储数据,所以查找相同数据量的情况下,B树的高度更高,IO更频繁。数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘页(对应索引树的节点)。其中在MySQL底层对B+树进行进一步优化:在叶子节点中是双向链表,且在链表的头结点和尾节点也是循环指向的。 +## 三、MyISAM 和 InnoDB 索引原理 +### MyISAM 主键索引与辅助索引的结构 -> 面试官:为何不采用Hash方式? +MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址**。索引文件与数据文件分离,这样的索引称为"**非聚簇索引**"。MyISAM 的主索引与辅助索引区别并不大,主键索引就是一个名为 PRIMARY 的唯一非空索引。 -因为Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ Tree是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描。 +> 术语 “聚簇” 表示数据行和相邻的键值紧凑的存储在一起 -哈希索引不支持多列联合索引的最左匹配规则,如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题。 + +在 MyISAM 中,索引(含叶子节点)存放在单独的 `.myi` 文件中,叶子节点存放的是数据的物理地址偏移量(通过偏移量访问就是随机访问,速度很快)。 +主索引是指主键索引,键值不可能重复;辅助索引则是普通索引,键值可能重复。 -## 三、MyISAM 和 InnoDB 索引原理 +通过索引查找数据的流程:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。 -### MyISAM 主键索引与辅助索引的结构 +辅助索引类似。 -MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址**。索引文件与数据文件分离,这样的索引称为"**非聚簇索引**"。MyISAM 的主索引与辅助索引区别并不大,只是主键索引不能有重复的关键字。 - -在 MyISAM 中,索引(含叶子节点)存放在单独的 `.myi` 文件中,叶子节点存放的是数据的物理地址偏移量(通过偏移量访问就是随机访问,速度很快)。 +### InnoDB 主键索引与辅助索引的结构 -主索引是指主键索引,键值不可能重复;辅助索引则是普通索引,键值可能重复。 +**InnoDB 引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录**(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,**InnoDB 的数据文件本身就是主键索引文件**,这样的索引被称为"“**聚簇索引**”,一个表只能有一个聚簇索引。 -通过索引查找数据的流程:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。辅助索引类似。 +#### 主键索引: -### InnoDB主键索引与辅助索引的结构 +我们知道 InnoDB 索引是聚集索引,它的索引和数据是存入同一个 `.idb` 文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据,如下图中最底层的叶子节点有三行数据,对应于数据表中的 id、name、score 数据项。 -**InnoDB 引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录**(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,**InnoDB的数据文件本身就是主键索引文件**,这样的索引被称为"“**聚簇索引**”,一个表只能有一个聚簇索引。 + -#### 主键索引: +在 Innodb 中,索引分叶子节点和非叶子节点,非叶子节点就像新华字典的目录,单独存放在索引段中,叶子节点则是顺序排列的,在数据段中。 -我们知道 InnoDB 索引是聚集索引,它的索引和数据是存入同一个 `.idb` 文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据,如下图中最底层的叶子节点有三行数据,对应于数据表中的 id、stu_id、name数据项。 +InnoDB 的数据文件可以按照表来切分(只需要开启`innodb_file_per_table)`,切分后存放在`xxx.ibd`中,不切分存放在 `xxx.ibdata`中。 - +从 MySQL 5.6.6 版本开始,它的默认值就是 ON 了。 -在 Innodb 中,索引分叶子节点和非叶子节点,非叶子节点就像新华字典的目录,单独存放在索引段中,叶子节点则是顺序排列的,在数据段中。InnoDB 的数据文件可以按照表来切分(只需要开启`innodb_file_per_table)`,切分后存放在`xxx.ibd`中,默认不切分,存放在 `xxx.ibdata`中。 +> 扩展点:建议将这个值设置为 ON。因为,一个表单独存储为一个文件更容易管理,而且在你不需要这个表的时候,通过 drop table 命令,系统就会直接删除这个文件。而如果是放在共享表空间中,即使表删掉了,空间也是不会回收的。 +> +> 所以会碰到这种情况,数据库占用空间太大后,把一个最大的表删掉了一半的数据,表文件的大小还是没变~ +> +> 在 MySQL 8.0 版本以前,表结构是存在以.frm 为后缀的文件里。而 MySQL 8.0 版本,则已经允许把表结构定义放在系统数据表中了。 #### 辅助(非主键)索引: -这次我们以示例中学生表中的 name 列建立辅助索引,它的索引结构跟主键索引的结构有很大差别,在最底层的叶子结点有两行数据,第一行的字符串是辅助索引,按照ASCII码进行排序,第二行的整数是主键的值。 +这次我们以示例中学生表中的 name 列建立辅助索引,它的索引结构跟主键索引的结构有很大差别,在最底层的叶子结点有两行数据,第一行的字符串是辅助索引,按照 ASCII 码进行排序,第二行的整数是主键的值。 这就意味着,对 name 列进行条件搜索,需要两个步骤: @@ -312,7 +261,7 @@ MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引 这也就是所谓的“**回表查询**” - + **InnoDB 索引结构需要注意的点** @@ -323,9 +272,34 @@ MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引 正如我们上面介绍 InnoDB 存储结构,索引与数据是共同存储的,不管是主键索引还是辅助索引,在查找时都是通过先查找到索引节点才能拿到相对应的数据,如果我们在设计表结构时没有显式指定索引列的话,MySQL 会从表中选择数据不重复的列建立索引,如果没有符合的列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,并且这个字段长度为 6 个字节,类型为整型。 +> 你可能在一些建表规范里面见到过类似的描述,要求建表语句里一定要有自增主键。当然事无绝对,我们来分析一下哪些场景下应该使用自增主键,而哪些场景下不应该。 +> +> 自增主键的插入数据模式,正符合了递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。 +> +> 而有业务逻辑的字段做主键,则往往不容易保证有序插入,这样写数据成本相对较高。 +> +> 除了考虑性能外,我们还可以从存储空间的角度来看。假设你的表中确实有一个唯一字段,比如字符串类型的身份证号,那应该用身份证号做主键,还是用自增字段做主键呢? +> +> 由于每个非主键索引的叶子节点上都是主键的值。如果用身份证号做主键,那么每个二级索引的叶子节点占用约 20 个字节,而如果用整型做主键,则只要 4 个字节,如果是长整型(bigint)则是 8 个字节。 +> +> **显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。** +> +> 所以,从性能和存储空间方面考量,自增主键往往是更合理的选择。 +> +> 有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的: +> +> 1. 只有一个索引; +> 2. 该索引必须是唯一索引。 +> +> 你一定看出来了,这就是典型的 KV 场景。 +> +> 由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。 +> +> 这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 + -## 三、索引策略 +## 四、索引策略 ### 哪些情况需要创建索引 @@ -335,7 +309,7 @@ MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引 3. 查询中与其他表关联的字段,外键关系建立索引 -4. 单键/组合索引的选择问题,who?高并发下倾向创建组合索引 +4. 单键/组合索引的选择问题,who? 高并发下倾向创建组合索引 5. 查询中排序的字段,排序字段通过索引访问大幅提高排序速度 @@ -355,6 +329,8 @@ MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引 ### [高效索引](高性能的索引策略 "《高性能MySQL》") +> 整理自《高性能 MySQL》 + #### 独立的列 **如果查询中的列不是独立的,MySQL 就不会使用索引**。“独立的列”是指索引不能是表达式的一部分,也不能是函数的参数。 @@ -362,54 +338,103 @@ MyISAM 引擎的索引文件和数据文件是分离的。**MyISAM 引擎索引 比如: ```mysql -EXPLAIN SELECT * FROM user_info where id = 2; +EXPLAIN SELECT * FROM mydb.sys_user where user_id = 2; ``` -在 user_info 表中,id 是主键,有主键索引,索引 exmplain 出来结果就是: +在 sys_user 表中,user_id 是主键,有主键索引,索引 explain 出来结果就是: - + 可见这次查询使用了PRIMARY KEY来优化查询,如果变成这样: ```mysql -EXPLAIN SELECT * FROM user_info where id + 1 = 2; +EXPLAIN SELECT * FROM mydb.sys_user where user_id + 1 = 2; ``` 结果就是: - + #### 前缀索引 -前缀索引说白了就是对文本的前几个字符(具体是几个字符在建立索引时指定)建立索引,这样建立起来的索引更小,所以查询更快。 +前缀索引其实就是对文本的前几个字符(具体是几个字符在建立索引时指定)建立索引,这样建立起来的索引占用空间更小,所以查询更快。 ```mysql ALTER TABLE table_name ADD KEY(column_name(prefix_length)); +ALTER TABLE table_name ADD index index_name(column_name(prefix_length)); ``` 对于内容很长的列,比如 blob, text 或者很长的 varchar 列,必须使用前缀索引,MySQL 不允许索引这些列的完整长度。 所以问题就在于要选择合适长度的前缀,即 prefix_length。前缀太短,选择性太低,前缀太长,索引占用空间太大。 -为了决定前缀的合适长度,需要找到最常见的值得列表,然后和最常见的前缀列进行比较。 + -前缀索引是一种能使索引更小、更快的有效办法,但另一方面也有缺点:MySQL 无法使用前缀索引做 ORDER BY 和 GROUP BY,也无法使用前缀索引做覆盖扫描。 +比如上图中,两个不同的索引同样执行下面的语句 + +```mysql +select id,name,email from user where emai='zhangsan@qq.com' +``` + +执行效果会有很大的差别,普通索引 `idx_email ` 找到满足条件的记录后,再返回主键索引取出数据即可,而前缀索引会多次查到 `zhangs`,然后返回主键索引取出数据进行对比,会扫描多次数据行。 + +如果前缀索引取前 7 个字节构建的话 `idx_pre_email(7)`,就只需要扫描一行。 + +所以使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。 + +为了决定前缀的合适长度,需要找到最常见的值的列表,然后和最常见的前缀列进行比较。 + +前缀索引是一种能使索引更小、更快的有效办法,但另一方面也有缺点:**MySQL 无法使用前缀索引做 ORDER BY 和 GROUP BY,也无法使用前缀索引做『覆盖索引』**。 > 一个常见的场景是针对很长的十六进制唯一 ID 使用前缀索引。 +> +> 身份证号这样的数据如何索引? +> +> - **使用倒序存储**:如果你存储身份证号的时候把它倒过来存,每次查询的时候,你可以这么写 +> +> ```mysql +> select field_list from t where id_card = reverse('input_id_card_string'); +> ``` +> +> - **使用 hash 字段。**你可以在表上再创建一个整数字段,来保存身份证的校验码,同时在这个字段上创建索引。 +> +> ```mysql +> alter table t add id_card_crc int unsigned, add index(id_card_crc); +> --查询 +> select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string' +> ``` + + + +#### 覆盖索引 + +**覆盖索引**(Covering Index),或者叫索引覆盖, 也就是平时所说的**不需要回表操作** + +- 就是 select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件,换句话说**查询列要被所建的索引覆盖**。 + +- 索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据,当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。 + +- **判断标准** + + 使用 explain,可以通过输出的 extra 列来判断,对于一个索引覆盖查询,显示为 **using index**,MySQL 查询优化器在执行查询前会决定是否有索引覆盖查询 #### 多列索引(复合索引、联合索引) +组合索引(concatenated index):由多个列构成的索引,如 `create index idx_emp on emp(col1, col2, col3, ……)`,则我们称 idx_emp 索引为组合索引。 + + + **在多个列上建立独立的单列索引大部分情况下并不能提高 MySQL 的查询性能**。对于下面的查询 where 条件,这两个单列索引都是不好的选择: ```mysql -select film_id, actor_id from table1 where actor_id=1 or film_id=1; +SELECT user_id,user_name FROM mydb.sys_user where user_id = 1 or user_name = 'zhang3'; ``` -MySQL 5.0 版本之前,MySQL会对这个查询使用全表扫描,除非改写成两个查询 UNION 的方式。 +MySQL 5.0 版本之前,MySQL 会对这个查询使用全表扫描,除非改写成两个查询 UNION 的方式。 MySQL 5.0 和后续版本引入了一种叫做“**索引合并**”的策略,查询能够同时使用这两个单列索引进行扫描,并将结果合并。这种算法有三个变种:OR 条件的联合(union),AND 条件的相交(intersection),组合前两种情况的联合及相交。索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建得很糟糕: @@ -421,31 +446,65 @@ MySQL 5.0 和后续版本引入了一种叫做“**索引合并**”的策略, -组合索引(concatenated index):由多个列构成的索引,如 `create index idx_emp on emp(col1, col2, col3, ……)`,则我们称 idx_emp 索引为组合索引。 +##### 最左前缀原则 -在组合索引中有一个重要的概念:引导列(leading column),在上面的例子中,col1 列为**引导列**。当我们进行查询时可以使用 ”where col1 = ? ”,也可以使用 ”where col1 = ? and col2 = ?”,这样的限制条件都会使用索引,但是”where col2 = ? ”查询就不会使用该索引。**所以限制条件中包含先导列时,该限制条件才会使用该组合索引。** +在组合索引中有一个重要的概念:引导列(leading column),在上面的例子中,col1 列为引导列。当我们进行查询时可以使用 ”where col1 = ? ”,也可以使用 ”where col1 = ? and col2 = ?”,这样的限制条件都会使用索引,但是”where col2 = ? ”查询就不会使用该索引。**所以限制条件中包含先导列时,该限制条件才会使用该组合索引。** +> 举个栗子: +> +> 当 B+ 树的数据项是复合的数据结构,比如(name,age,sex)的时候,B+ 树是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,B+ 树会优先比较 name 来确定下一步的所搜方向,如果 name 相同再依次比较 age 和 sex,最后得到检索的数据;但当 (20,F) 这样的没有 name 的数据来的时候,B+ 树就不知道下一步该查哪个节点,因为建立搜索树的时候 name 就是第一个比较因子,必须要先根据 name 来搜索才能知道下一步去哪里查询。比如当 (张三,F) 这样的数据来检索时,B+ 树可以用 name 来指定搜索方向,但下一个字段 age 的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是 F 的数据了, 这个是非常重要的性质,即**索引的最左匹配特性**。 + -#### 覆盖索引 +可以看到,索引项是按照索引定义里面出现的字段顺序排序的。 -**覆盖索引**(Covering Index),或者叫索引覆盖, 也就是平时所说的**不需要回表操作** +当你的逻辑需求是查到所有名字是“Bob”的人时,可以快速定位到 ID = 2,然后向后遍历得到所有需要的结果。 -- 就是 select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件,换句话说**查询列要被所建的索引覆盖**。 +如果你要查的是所有名字第一个字母是“B”的人,你的 SQL 语句的条件是"where name like ‘B %’"。这时,你也能够用上这个索引,查找到第一个符合条件的记录是 ID=2,然后向后遍历,直到不满足条件为止。 -- 索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据,当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。 +可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。 -- **判断标准** +那么就会出现一个问题:**在建立联合索引的时候,如何安排索引内的字段顺序。** - 使用 explain,可以通过输出的 extra 列来判断,对于一个索引覆盖查询,显示为 **using index**,MySQL 查询优化器在执行查询前会决定是否有索引覆盖查询 +这里我们的评估标准是,索引的复用能力。因为可以支持最左前缀,所以当已经有了 (a,b) 这个联合索引后,一般就不需要单独在 a 上建立索引了。因此,**第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。** + + + +##### 索引下推 + +上一段我们说到满足最左前缀原则的时候,最左前缀可以用于在索引中定位记录。这时,你可能要问,那些不符合最左前缀的部分,会怎么样呢? + +我们还是以联合索引(name,age,sex)为例。如果现在有一个需求:检索出表中“名字第一个字是 B,而且年龄是 19 岁的所有男孩”。那么,SQL 语句是这么写的: + +```mysql +mysql> select * from tuser where name like 'B %' and age=19 and sex=F; +``` + +你已经知道了前缀索引规则,所以这个语句在搜索索引树的时候,只能用 “B”,找到第一个满足条件的记录 ID = 2。当然,这还不错,总比全表扫描要好。(组合索引满足最左匹配,但是遇到非等值判断时匹配停止) + +然后呢? + +当然是判断其他条件是否满足。 + +在 MySQL 5.6 之前,只能从 ID = 2 开始一个个回表。到主键索引上找出数据行,再对比字段值。 + +而 MySQL 5.6 引入的**索引下推优化**(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。 + + + +> 索引下推在**非主键索引**上的优化,可以有效减少回表的次数,大大提升了查询的效率 #### 使用索引扫描来做排序 -MySQL 有两种方式可以生成有序的结果,通过排序操作或者按照索引顺序扫描,如果 explain 的 type 列的值为index,则说明 MySQL 使用了索引扫描来做排序(不要和 extra 列的 Using index 搞混了,那个是使用了覆盖索引查询)。扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录,但如果索引不能覆盖查询所需的全部列,那就不得不扫描一条索引记录就回表查询一次对应的整行,这基本上都是随机 IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO 密集型的工作负载时。 +MySQL 有两种方式可以生成有序的结果,通过排序操作或者按照索引顺序扫描,如果 explain 的 type 列的值为 index,则说明 MySQL 使用了索引扫描来做排序(不要和 extra 列的 Using index 搞混了,那个是使用了覆盖索引查询)。 + +扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录,但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就回表查询一次对应的整行,这基本上都是随机 I/O,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在 I/O 密集型的工作负载时。 + +**MySQL 可以使用同一个索引既满足排序,又用于查找行,因此,如果可能,设计索引时应该尽可能地同时满足这两种任务,这样是最好的**。 -**MySQL 可以使用同一个索引既满足排序,又用于查找行,因此,如果可能,设计索引时应该尽可能地同时满足这两种任务,这样是最好的**。只有当索引的列顺序和 order by 子句的顺序完全一致,并且所有列的排序方向(倒序或升序,创建索引时可以指定ASC或DESC)都一样时,MySQL 才能使用索引来对结果做排序,如果查询需要关联多张表,则只有当 order by 子句引用的字段全部为第一个表时,才能使用索引做排序,order by 子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则 MySQL 都需要执行排序操作,而无法使用索引排序。 +**只有当索引的列顺序和 order by 子句的顺序完全一致,并且所有列的排序方向(倒序或升序,创建索引时可以指定 ASC 或 DESC)都一样时,MySQL 才能使用索引来对结果做排序**,如果查询需要关联多张表,则只有当 order by 子句引用的字段全部为第一个表时,才能使用索引做排序,order by 子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则 MySQL 都需要执行排序操作,而无法使用索引排序。 @@ -459,7 +518,8 @@ MyISAM 压缩每个索引块的方法是,先完全保存索引块中的第一 例如,索引块中的第一个值是“perform“,第二个值是”performance“,那么第二个值的前缀压缩后存储的是类似”7,ance“这样的形式。MyISAM 对**行指针**也采用类似的前缀压缩方式。 -压缩块使用更少的空间,代价是某些操作可能更慢。因为每个值的压缩前缀都依赖前面的值,所以 MyISAM 查找时无法在索引块使用二分查找而只能从头开始扫描。正序的扫描速度还不错,但是如果是倒序扫描——例如ORDER BY DESC——就不是很好了。所有在块中查找某一行的操作平均都需要扫描半个索引块。 +压缩块使用更少的空间,代价是某些操作可能更慢。因为每个值的压缩前缀都依赖前面的值,所以 MyISAM 查找时无法在索引块使用二分查找而只能从头开始扫描。正序的扫描速度还不错,但是如果是倒序扫描——例如 ORDER BY DESC——就不是很好了。所有在块中查找某一行的操作平均都需要扫描半个索引块。 + 测试表明,对于 CPU 密集型应用,因为扫描需要随机查找,压缩索引使得 MyISAM 在索引查找上要慢好几倍。压缩索引的倒序扫描就更慢了。压缩索引需要在 CPU 内存资源与磁盘之间做权衡。压缩索引可能只需要十分之一大小的磁盘空间,如果是 I/O 密集型应用,对某些查询带来的好处会比成本多很多。 可以在 CREATE TABLE 语句中指定 PACK_KEYS 参数来控制索引压缩的方式。 @@ -468,10 +528,11 @@ MyISAM 压缩每个索引块的方法是,先完全保存索引块中的第一 #### 重复索引和冗余索引 -MySQL 允许在相同列上创建多个索引,无论是有意的还是无意的。MySQL 需要单独维护重复的索引,并且优化器在优化查询的时候也需要逐个地进行考虑,这会影响性能。 -重复索引是指在相同的列上按照相同的顺序创建的相同类型的索引。应该避免这样创建重复索引,发现以后也应该立即移除。 +MySQL 允许在相同列上创建多个索引,无论是有意的还是无意的。有意的用途没想明白~ + +**重复索引是指在相同的列上按照相同的顺序创建的相同类型的索引**。应该避免这样创建重复索引,发现以后也应该立即移除。 -冗余索引和重复索引有一些不同。如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。因此索引(A,B)也可以当做索引(A)来使用(这种冗余只是对B-Tree索引来说的)。但是如果再创建索引(B,A),则不是冗余索引,索引(B)也不是,因为B不是索引(A,B)的最左前缀。另外,其他不同类型的索引(例如哈希索引或者全文索引)也不会是B-Tree索引的冗余索引,而无论覆盖的索引列是什么。 +冗余索引和重复索引有一些不同。如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。因此索引(A,B)也可以当做索引(A)来使用(这种冗余只是对 B-Tree 索引来说的)。但是如果再创建索引(B,A),则不是冗余索引,索引(B)也不是,因为B不是索引(A,B)的最左前缀。另外,其他不同类型的索引(例如哈希索引或者全文索引)也不会是 B-Tree 索引的冗余索引,而无论覆盖的索引列是什么。 @@ -481,17 +542,17 @@ MySQL 允许在相同列上创建多个索引,无论是有意的还是无意 1. 在 percona server 或者 mariadb 中先打开 userstat=ON 服务器变量,默认是关闭的,然后让服务器运行一段时间,再通过查询` information_schema.index_statistics` 就能查到每个索引的使用频率。 -2. 使用 percona toolkit 中的 pt-index-usage 工具,该工具可以读取查询日志,并对日志中的每个查询进行explain 操作,然后打印出关于索引和查询的报告,这个工具不仅可以找出哪些索引是未使用的,还可以了解查询的执行计划,如:在某些情况下有些类似的查询的执行方式不一样,这可以帮助定位到那些偶尔服务器质量差的查询,该工具也可以将结果写入到 MySQL 的表中,方便查询结果。 +2. 使用 percona toolkit 中的 pt-index-usage 工具,该工具可以读取查询日志,并对日志中的每个查询进行explain 操作,然后打印出关于索引和查询的报告,这个工具不仅可以找出哪些索引是未使用的,还可以了解查询的执行计划。 -## 四、索引优化 +## 五、索引优化 -### 导致SQL执行慢的原因 +### 导致 SQL 执行慢的原因 -1. 硬件问题。如网络速度慢,内存不足,I/O吞吐量小,磁盘空间满了等。 +1. 硬件问题。如网络速度慢,内存不足,I/O 吞吐量小,磁盘空间满了等 -2. 没有索引或者索引失效。(一般在互联网公司,DBA会在半夜把表锁了,重新建立一遍索引,因为当你删除某个数据的时候,索引的树结构就不完整了。所以互联网公司的数据做的是假删除.一是为了做数据分析,二是为了不破坏索引 ) +2. 没有索引或者索引失效 3. 数据过多(分库分表) @@ -499,63 +560,75 @@ MySQL 允许在相同列上创建多个索引,无论是有意的还是无意 -### [建索引的几大原则](https://tech.meituan.com/2014/06/30/mysql-index.html "MySQL索引原理及慢查询优化") +### 索引优化 + +```mysql +CREATE TABLE hero( + id INT NOT NULL auto_increment, + name VARCHAR(100) NOT NULL, + phone CHAR(11) NOT NULL, + country varchar(100) NOT NULL, + PRIMARY KEY (id), + KEY idx_name_phone (name, phone) +); +``` -1. 最左前缀匹配原则,非常重要的原则,MySQL 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。 +1. 全值匹配我最爱(就是搜索条件中的列和索引列一致) -2. =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,MySQL 的查询优化器会帮你优化成索引可以识别的形式。 + > `select name, phone from hero where name = 'star' and phone = '13266666666'` + > + > 因为有「查询优化器」的存在,所有搜索条件调换顺序,改成 `phone = '13266666666' and name = 'star'` 无影响 +2. 最佳左前缀法则,比如建立了一个联合索引(a,b,c),那么其实我们可利用的索引就有(a) (a,b)(a,c)(a,b,c) +3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描 +4. 存储引擎不能使用索引中范围条件右边的列 -3. 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。 + ```mysql + -- 只能用到 name 列 + SELECT * FROM hero WHERE name > 'Join' AND name < 'Lily' AND phone > '13222223333'; + + -- 但是如果左边的列是精确查找,则右边的列可以进行范围查找, 也可以用到 phone 列 + SELECT * FROM hero WHERE name = 'Join' AND phone > '13222223333'; + ``` +5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少 select * +6. `is null` , `is not null` 也无法使用索引 +7. `like "xxxx%"` 是可以用到索引的,`like "%xxxx"` 则不行(like "%xxx%" 同理)。like 以通配符开头('%abc...')索引失效会变成全表扫描的操作, +8. 字符串不加单引号索引失效(隐式类型转换) +9. 少用 or,用它来连接时会索引失效(这个其实不是绝对的,or 走索引与否,还和优化器的**预估**有关,5.0 之后出现的 index merge 技术就是优化这个的) +10. <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 则不行,会导致全表扫描 +11. 使用联合索引时,ASC、DESC 混用会导致索引失效 -4. 索引列不能参与计算,保持列“干净”,比如 `from_unixtime(create_time) = ’2014-05-29’` 就不能使用到索引,原因很简单,b+ 树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成 `create_time = unix_timestamp(’2014-05-29’)`。 -5. 尽量的扩展索引,不要新建索引。比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 +### [建索引的几大原则](https://tech.meituan.com/2014/06/30/mysql-index.html "MySQL索引原理及慢查询优化") + +1. 最左前缀匹配原则,非常重要的原则,MySQL 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如 `a = 1 and b = 2 and c > 3 and d = 4` 如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。 +2. = 和 in 可以乱序,比如 `a = 1 and b = 2 and c = 3` 建立(a,b,c)索引可以任意顺序,MySQL 的查询优化器会帮你优化成索引可以识别的形式。 -### [优化要注意的一些事](https://www.cnblogs.com/frankdeng/p/8990181.html "优化要注意的一些事") +3. 尽量选择区分度高的列作为索引,区分度的公式是 `count(distinct col)/count(*)`,表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是 1,而一些状态、性别字段可能在大数据面前区分度就是 0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要 join 的字段我们都要求是 0.1 以上,即平均 1 条扫描 10 条记录。 -1. 索引其实就是一种归类方式,当某一个字段属性都不能归类,建立索引后是没什么效果的,或归类就二种(0和1),且各自都数据对半分,建立索引后的效果也不怎么强。 +4. 索引列不能参与计算,保持列“干净”,比如 `from_unixtime(create_time) = ’2014-05-29’` 就不能使用到索引,原因很简单,b+ 树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成 `create_time = unix_timestamp(’2014-05-29’)`。 -2. 主键的索引是不一样的,要区别理解。 +5. 尽量的扩展索引,不要新建索引。比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 -3. 当时间存储为时间戳保存的可以建立前缀索引。 +6. 索引列的类型尽量小 + - 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘`I/O`带来的性能损耗 -4. 在什么字段上建立索引,需要根据查询条件而定,不要一上来就建立索引,浪费内存还有可能用不到。 -5. 大字段(blob)不要建立索引,查询也不会走索引。 -6. 常用建立索引的地方: - - 主键的聚集索引 - - 外键索引 - - 类别只有0和1就不要建索引了,没有意义,对性能没有提升,还影响写入性能 - - 用模糊其实是可以走前缀索引 -7. 唯一索引一定要小心使用,它带有唯一约束,由于前期需求不明等情况下,可能造成我们对于唯一列的误判。 +> 我有一个公众号「 **JavaKeeper** 」 +> +> 我还有一个 **GitBook** [github.com/JavaKeeper](https://github.com/Jstarfish/JavaKeeper) + -8. 由于我们建立索引并想让索引能达到最高性能,这个时候我们应当充分考虑该列是否适合建立索引,可以根据列的区分度来判断,区分度太低的情况下可以不考虑建立索引,区分度越高效率越高。 -9. 写入比较频繁的时候,不能开启MySQL的查询缓存,因为在每一次写入的时候不光要写入磁盘还的更新缓存中的数据。 -10. 二次SQL查询区别不大的时候,不能按照二次执行的时间来判断优化结果,没准第一次查询后又保存缓存数据,导致第二次查询速度比第二次快,很多时候我们看到的都是假象。 -11. Explain 执行计划只能解释SELECT操作。 -12. 使用UNION ALL 替换OR多条件查询并集。 -13. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 -14. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:`select id from t where num is null` 可以在 num上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:`select id from t where num=0` -15. 应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。 -16. 应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:`select id from t where num=10 or num=20` 可以这样查询:`select id from t where num=10 union all select id from t where num=20` -17. in 和 not in 也要慎用,否则会导致全表扫描,如:`select id from t where num in(1,2,3)` 对于连续的数值,能用 between 就不要用 in 了:`select id from t where num between 1 and 3` -18. 下面的查询也将导致全表扫描:`select id from t where name like '李%'` 若要提高效率,可以考虑全文检索。 -19. 如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:`select id from t where num=@num` 可以改为强制查询使用索引:`select id from t with(index(索引名)) where num=@num` -20. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:`select id from t where num/2=100` 应改为: `select id from t where num=100*2` -21. 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:`select id from t where substring(name,1,3)='abc' `,name 以 abc 开头的 id 应改为: `select id from t where name like 'abc%'` -22. 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。 -23. 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 -24. 不要写一些没有意义的查询,如需要生成一个空表结构:`select col1,col2 into #t from t where 1=0` 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...) -25. 很多时候用 exists 代替 in 是一个好的选择:`select num from a where num in(select num from b)` 用下面的语句替换:` select num from a where exists(select 1 from b where num=a.num)` -26. 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。 -27. 索引并不是越多越好,索引固然可 以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。 -28. 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。 -29. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。 -30. 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。 -31. 任何地方都不要使用 `select * from t` ,用具体的字段列表代替“*”,不要返回用不到的任何字段。 +## Reference +- https://dev.mysql.com/doc/refman/8.0/en/create-index.html +- https://zh.wikipedia.org/wiki/B%E6%A0%91 +- https://medium.com/@mena.meseha/what-is-the-difference-between-mysql-innodb-b-tree-index-and-hash-index-ed8f2ce66d69 +- https://www.javatpoint.com/b-tree +- https://blog.csdn.net/Abysscarry/article/details/80792876 +- 《MySQL 实战 45 讲》 +- 《高性能 MySQL》 diff --git a/docs/data-management/MySQL/MySQL-Lock.md b/docs/data-management/MySQL/MySQL-Lock.md index 54843b580e..9f7fa29e92 100644 --- a/docs/data-management/MySQL/MySQL-Lock.md +++ b/docs/data-management/MySQL/MySQL-Lock.md @@ -1,106 +1,202 @@ -# MySQL锁 +--- +title: MySQL 锁 +date: 2022-02-15 +tags: + - MySQL 锁 +categories: MySQL +--- -锁是计算机协调多个进程或线程并发访问某一资源的机制。 + -在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则 +> Hello, 我是海星。 +> +> 锁是计算机协调多个进程或线程并发访问某一资源的机制。 +> +> 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。主要用来处理并发问题。 -打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题? + 为什么需要锁,只有并发操作时候才有锁的必要,并发事务访问相同记录的情况大致可以划分为 3 种: +- `读-读`情况:即并发事务相继读取相同的记录 +- `写-写`情况:即并发事务相继对相同的记录做出改动 +- `读-写`或`写-读`情况:也就是一个事务进行读取操作,另一个进行改动操作。 -这里肯定要用到事物,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。 - -## 锁的分类 +## 一、锁的分类有哪些 -#### 从对数据操作的类型分类: -- **读锁**(共享锁):针对同一份数据,多个读操作可以同时进行,不会互相影响 +#### 按操作粒度分类: -- **写锁**(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁。 +> 为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据库系统需要在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度(Lock granularity)”的概念。 -#### 从对数据操作的粒度分类: +- **全局锁**:对整个数据库实例加锁,可以用 `Flush tables with read lock (FTWRL)`设置为只读,就相当于加全局锁了。**全局锁的典型使用场景是,做全库逻辑备份。**也就是把整库每个表都 select 出来存成文本。 +- **页级锁**:对数据页(通常是连续的几个行)加锁,控制并发事务对该页的访问。( BDB 存储引擎使用页级锁) +- **表级锁**:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率较高,并发度最低; + - 表锁的语法是`lock tables … read/write` + - 另一类表级的锁是 MDL(metadata lock)。MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。 +- **行级锁**:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高; -为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据库系统需要在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度(Lock granularity)”的概念。 +适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如 Web 应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。 -一种提高共享资源并发性的方式是让锁定对象更有选择性。尽量只锁定需要修改的部分数据,而不是所有的资源。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。 + MySQL 不同的存储引擎支持不同的锁机制,所有的存储引擎都以自己的方式实现了锁机制 -- **表级锁**:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低; +| | 行锁 | 表锁 | 页锁 | +| ------ | ---- | ---- | ---- | +| MyISAM | | √ | | +| BDB | | √ | √ | +| InnoDB | √ | √ | | +| Memory | | √ | | -- **行级锁**:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高; +#### 按加锁机制分类 -- **页面锁**:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 +**乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题** -适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。 +- 乐观锁会“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务; +- 悲观锁会“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁; +#### 按锁模式(算法)分类 -#### 加锁机制 +- 记录锁(Record Lock):行级锁的特定类型,锁定单个行,确保其他事务无法同时修改或读取该行 -**乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题** +- 间隙锁(Gap Lock):对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行 + +- MDL(Metadata Lock):锁定数据库对象的元数据,如表结构,用于保证数据定义的一致性 +- 临建锁(next-key Lock): 锁定索引项本身和索引范围。即 Record Lock 和 Gap Lock 的结合。可解决幻读问题。 + +#### 按属性分类: + +- **读锁**(共享锁): + + - `共享锁`,英文名:`Shared Locks`,简称`S锁`。在事务要读取一条记录时,需要先获取该记录的`S锁`。 + + - 针对同一份数据,多个读操作可以同时进行,不会互相影响 + + - ```mysql + SELECT ... LOCK IN SHARE MODE; //对读取的记录加S锁 + ``` + +- **写锁**(独占锁、排他锁): + + - 当前写操作没有完成前,它会阻断其他写锁和读锁 + + - `独占锁`,也常称`排他锁`,英文名:`Exclusive Locks`,简称`X锁`。在事务要改动一条记录时,需要先获取该记录的`X锁` + + - ```mysql + SELECT ... FOR UPDATE; //对读取的记录加X锁 + ``` + +#### 按状态分类 -乐观锁会“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务; +- 意向共享锁(Intention Shared Lock):表级锁的辅助锁,表示事务要在某个表或页级锁上获取共享锁。 +- 意向排它锁(Intention Exclusive Lock):表级锁的辅助锁,表示事务要在某个表或页级锁上获取排它锁。 -悲观锁会“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁; +## 二、全局锁 -#### 锁模式 +要使用全局锁,则要执行这条命令: -- 记录锁: 对索引项加锁,锁定符合条件的行。其他事务不能修改 和删除加锁项; -- gap锁: 对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。 -- next-key锁: 锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题。 +```sql +flush tables with read lock +``` + +执行后,**整个数据库就处于只读状态了**,这时其他线程执行以下操作,都会被阻塞 + +如果要释放全局锁,则要执行这条命令: + +```sql +unlock tables +``` + +全局锁主要应用于做**全库逻辑备份**,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。 + + + +## 三、表锁 + +MySQL 里面表级别的锁有这几种: + +- 表锁 +- 元数据锁(MDL) - 意向锁 -- 插入意向锁 +- AUTO-INC 锁 +#### 表锁 +MySQL 支持多种存储引擎,不同存储引擎对锁的支持也是不一样的。 - MySQL 不同的存储引擎支持不同的锁机制,所有的存储引擎都以自己的方式实现了锁机制 +对于`MyISAM`、`MEMORY`、`MERGE`这些存储引擎来说,它们只支持表级锁,而且这些引擎并不支持事务,所以使用这些存储引擎的锁一般都是针对当前会话来说的。 -| | 行锁 | 表锁 | 页锁 | -| ------ | ---- | ---- | ---- | -| MyISAM | | √ | | -| BDB | | √ | √ | -| InnoDB | √ | √ | | -| Memory | | √ | | +#### 元数据锁 + +**另一类表级的锁是 MDL(metadata lock)。**MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。 + +因此,在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。 + +- 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。 + +- 读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。 + +虽然 MDL 锁是系统默认会加的,但却是你不能忽略的一个机制。比如下面这个例子,我经常看到有人掉到这个坑里:给一个小表加个字段,导致整个库挂了。 +1. 首先,线程 A 先启用了事务(但是一直不提交),然后执行一条 select 语句,此时就先对该表加上 MDL 读锁; +2. 然后,线程 B 也执行了同样的 select 语句,此时并不会阻塞,因为「读读」并不冲突; +3. 接着,线程 C 修改了表字段,此时由于线程 A 的事务并没有提交,也就是 MDL 读锁还在占用着,这时线程 C 就无法申请到 MDL 写锁,就会被阻塞, +那么在线程 C 阻塞后,后续有对该表的 select 语句,就都会被阻塞,如果此时有大量该表的 select 语句的请求到来,就会有大量的线程被阻塞住,这时数据库的线程很快就会爆满了。 -### 表锁(偏读) +> 为什么线程 C 因为申请不到 MDL 写锁,而导致后续的申请读锁的查询操作也会被阻塞? -#### 特点: +这是因为申请 MDL 锁的操作会形成一个队列,队列中**写锁获取优先级高于读锁**,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。 -**偏向MyISAM存储引擎,开销小,加锁快,无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低** +所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。 -MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。 -MySQL的表级锁有两种模式: +#### 表级别的意向锁 -- 表共享读锁(Table Read Lock) -- 表独占写锁(Table Write Lock) +- 意向共享锁,英文名:`Intention Shared Lock`,简称`IS锁`。在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」; +- 意向独占锁,英文名:`Intention Exclusive Lock`,简称`IX锁`。在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」; -| 锁类型 | 可否兼容 | 读锁 | 写锁 | -| ------ | -------- | ---- | ---- | -| 读锁 | 是 | 是 | 否 | -| 写锁 | 是 | 否 | 否 | +`IS锁`和`IX锁`的使命只是为了后续在加表级别的`S锁`和`X锁`时判断表中是否有已经被加锁的记录,以避免用遍历的方式来查看表中有没有上锁的记录。 +#### AUTO-INC 锁 - 结合上表,所以对MyISAM表进行操作,会有以下情况: +表里的主键通常都会设置成自增的,这是通过对主键字段声明 `AUTO_INCREMENT` 属性实现的。 -1. 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。 -2. 对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。 +之后在插入数据时,可以不指定主键的值,数据库会自动给主键赋值递增的值,这主要是通过 **AUTO-INC 锁**实现的。 + +AUTO-INC 锁是特殊的表锁机制,锁**不是在一个事务提交后才释放,而是在执行完插入语句后就会立即释放**。 + +**在插入数据时,会加一个表级别的 AUTO-INC 锁**,然后为被 `AUTO_INCREMENT` 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。 + +那么,一个事务在持有 AUTO-INC 锁的过程中,其他事务如果要向该表插入语句都会被阻塞,从而保证插入数据时,被 `AUTO_INCREMENT` 修饰的字段的值是连续递增的。 + +但是, AUTO-INC 锁再对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞。 + +因此, 在 MySQL 5.1.22 版本开始,InnoDB 存储引擎提供了一种**轻量级的锁**来实现自增。 + +一样也是在插入数据的时候,会为被 `AUTO_INCREMENT` 修饰的字段加上轻量级锁,**然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁**。 + +InnoDB 存储引擎提供了个 `innodb_autoinc_lock_mode` 的系统变量,是用来控制选择用 AUTO-INC 锁,还是轻量级的锁。 + +- 当 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 锁,语句执行结束后才释放锁; +- 当 innodb_autoinc_lock_mode = 2,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。 +- 当 innodb_autoinc_lock_mode = 1,相当于两种方式混着来 + - 普通 insert 语句,自增锁在申请之后就马上释放; + - 类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放; + +当 innodb_autoinc_lock_mode = 2 是性能最高的方式,但是当搭配 binlog 的日志格式是 statement 一起使用的时候,可能会造成不同事务中的插入语句为 AUTO_INCREMENT 修饰的列生成的值是交叉的,在「主从复制的场景」中会发生**数据不一致的问题**。 -简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。 -MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。 #### 如何加表锁 -MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。 +MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。 -#### MyISAM表锁优化建议 +#### MyISAM 表锁优化建议 -对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要**尽可能让锁定的时间变短**,然后就是让可能并发进行的操作尽可能的并发。 +对于 MyISAM 存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要**尽可能让锁定的时间变短**,然后就是让可能并发进行的操作尽可能的并发。 看看哪些表被加锁了: @@ -110,15 +206,13 @@ mysql>show open tables; 1. ##### 查询表级锁争用情况 -MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况: +MySQL 内部有两组专门的状态变量记录系统内部锁资源争用情况: ```mysql mysql> show status like 'table%'; ``` - - -这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下: +这里有两个状态变量记录 MySQL 内部表级锁定的情况,两个变量说明如下: - Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1 @@ -126,13 +220,13 @@ mysql> show status like 'table%'; 两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。 -?> 此外,Myisam的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞 +> 此外,Myisam的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞 2. **缩短锁定时间** 如何让锁定时间尽可能的短呢?唯一的办法就是让我们的Query执行时间尽可能的短。 -- **尽两减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;** +- **尽量减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;** - **尽可能的建立足够高效的索引,让数据检索更迅速;** - **尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;** - **利用合适的机会优化MyISAM表数据文件。** @@ -173,147 +267,218 @@ mysql> show status like 'table%'; -### 行锁(偏写) +## 四、行锁 -- 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 +InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。 -- InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁 +> InnoDB 与 MyISAM 的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁 +`行锁`,也称为`记录锁`,顾名思义就是在记录上加的锁。 -Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了。 +行级锁的类型主要有三类: +- Record Lock,记录锁,也就是仅仅把一条记录锁上; +- Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身; +- Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。 -1. InnoDB锁定模式及实现机制 +#### Record Lock - InnoDB的行级锁定同样分为两种类型,**共享锁和排他锁**,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了**意向锁**(表级锁定)的概念,也就有了**意向共享锁**和**意向排他锁**这两种。 +Innodb 存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于 MyISAM 的表级锁定的。当系统并发量较高的时候,Innodb 的整体性能和 MyISAM 相比就会有比较明显的优势了。 - 当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说**InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX)**,我们可以通过以下表格来总结上面这四种所的共存逻辑关系: +Record Lock 称为记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的: +- 当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容); +- 当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)。 +举个例子,当一个事务执行了下面这条语句: -如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。 +```sql +mysql > begin; +mysql > select * from t where id = 4 for update; +``` -意向锁是InnoDB自动加的,不需用户干预。**对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁**(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。 +就是对 t 表中主键 id 为 4 的这条记录加上 X 型的记录锁,这样其他事务就无法对这条记录进行修改了。 -共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE + -排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE +当事务执行 commit 后,事务过程中生成的锁都会被释放。 -用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。 -但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。 -2. InnoDB行锁实现方式 +#### Gap Lock - **InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁** +Gap Lock 称为间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。 - 在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。 +假设,表中有一个范围 id 为(4,8)间隙锁,那么其他事务就无法插入 id = 5、6、7 的记录了,这样就有效的防止幻读现象的发生。 - (1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。 + - (2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。 +间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,**间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的**。 - (3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。 - (4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,**在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引**。 - +#### Next-Key Lock -#### 如何分析行锁定 +Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。 -通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况 +假设,表中有一个范围 id 为(4,8] 的 next-key lock,那么其他事务即不能插入 id = 5,6,7 记录,也不能修改 id = 8 这条记录。 -```mysql -mysql>show status like 'innodb_row_lock%'; -``` + - +所以,next-key lock 即能保护该记录,又能阻止其他事务将新记录插入到被保护记录前面的间隙中。 +**next-key lock 是包含间隙锁+记录锁的,如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的**。 -对各个状态量的说明如下: +比如,一个事务持有了范围为 (4, 8] 的 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,就会被阻塞。 -Innodb_row_lock_current_waits:当前正在等待锁定的数量; -Innodb_row_lock_time:从系统启动到现在锁定总时间长度; -Innodb_row_lock_time_avg:每次等待所花平均时间; -Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间; -Innodb_row_lock_waits:系统启动后到现在总共等待的次数; -对于这5个状态变量,比较重要的主要是 - Innodb_row_lock_time_avg(等待平均时长), - Innodb_row_lock_waits(等待总次数) - Innodb_row_lock_time(等待总时长)这三项。 -尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。 +虽然相同范围的间隙锁是多个事务相互兼容的,但对于记录锁,我们是要考虑 X 型与 S 型关系,X 型的记录锁与 X 型的记录锁是冲突的。 -#### 行锁优化 +#### 插入意向锁 -- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。 +一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。 -- 合理设计索引,尽量缩小锁的范围 +如果有的话,插入操作就会发生**阻塞**,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个**插入意向锁**,表明有事务想在某个区间插入新记录,但是现在处于等待状态。 -- 尽可能较少检索条件,避免间隙锁 +举个例子,假设事务 A 已经对表加了一个范围 id 为(4,8)间隙锁。 -- 尽量控制事务大小,减少锁定资源量和时间长度 +当事务 A 还没提交的时候,事务 B 向该表插入一条 id = 5 的新记录,这时会判断到插入的位置已经被事务 A 加了间隙锁,于是事物 B 会生成一个插入意向锁,然后将锁的状态设置为等待状态(*PS:MySQL 加锁时,是先生成锁结构,然后设置锁的状态,如果锁状态是等待状态,并不是意味着事务成功获取到了锁,只有当锁状态为正常状态时,才代表事务成功获取到了锁*),此时事务 B 就会发生阻塞,直到事务 A 提交了事务。 -- 尽可能低级别事务隔离 +插入意向锁名字虽然有意向锁,但是它并**不是意向锁,它是一种特殊的间隙锁,属于行级别锁**。 +如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。 +插入意向锁与间隙锁的另一个非常重要的差别是:尽管「插入意向锁」也属于间隙锁,但两个事务却不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。 -### 页锁 -开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 +#### InnoDB 行锁实现方式 +**InnoDB 行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁** +在实际应用中,要特别注意 InnoDB 行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。 +1. 在不通过索引条件查询的时候,InnoDB 确实使用的是表锁,而不是行锁。 +2. 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。 +3. 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。 +4. 即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,**在分析锁冲突时,别忘了检查 SQL 的执行计划,以确认是否真正使用了索引**。 -## 死锁 + -死锁是指两个或者多个事务在同一资源上互相占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。 +#### 如何分析行锁定 +通过检查 `InnoDB_row_lock` 状态变量来分析系统上的行锁的争夺情况 +```mysql +mysql>show status like 'innodb_row_lock%'; +``` -乐观锁 + -悲观锁 +- `Innodb_row_lock_current_waits`:当前正在等待锁定的数量; +- `Innodb_row_lock_time`:从系统启动到现在锁定总时间长度; +- `Innodb_row_lock_time_avg`:每次等待所花平均时间; +- `Innodb_row_lock_time_max`:从系统启动到现在等待最常的一次所花的时间; +- `Innodb_row_lock_waits`:系统启动后到现在总共等待的次数; +#### 行锁优化 +- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁 +- 合理设计索引,尽量缩小锁的范围 +- 尽可能较少检索条件,避免间隙锁 +- 尽量控制事务大小,减少锁定资源量和时间长度 +- 尽可能低级别事务隔离 +### 页锁 + +开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 +## 五、InnoDB 锁内存结构 +```mysql +*# 事务T1* +SELECT * FROM hero LOCK IN SHARE MODE*;* +``` +很显然这条语句需要为`hero表`中的所有记录进行加锁,那是不是需要为每条记录都生成一个`锁结构`呢? +在对不同记录加锁时,如果符合下边这些条件: +- 在同一个事务中进行加锁操作 +- 被加锁的记录在同一个页面中 +- 加锁的类型是一样的 +- 等待状态是一样的 +那么这些记录的锁就可以被放到一个`锁结构`中 +`锁`是一个内存结构,InnoDB中用 `lock_t` 这个结构来定义(8.0): +```c +struct lock_t { + /** transaction owning the lock */ + trx_t *trx; + /** list of the locks of the transaction */ + UT_LIST_NODE_T(lock_t) trx_locks; + /** Index for a record lock */ + dict_index_t *index; + /** Hash chain node for a record lock. The link node in a singly + linked list, used by the hash table. */ + lock_t *hash; + union { + /** Table lock */ + lock_table_t tab_lock; + /** Record lock */ + lock_rec_t rec_lock; + }; +``` +## 六、死锁 + + + +死锁是指两个或者多个事务在同一资源上互相占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。 + + + +为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。 +当出现死锁以后,有两种策略: +- 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。 +- 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。 +在 InnoDB 中,`innodb_lock_wait_timeout` 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。 +但是,我们又不可能直接把这个时间设置成一个很小的值,比如 1s。这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。 +所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 `innodb_deadlock_detect` 的默认值本身就是 on。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。 +## 参考 +- 《高性能 MySQL》 +- 《MySQL技术内幕:innodb》 +- 《MySQL实战45讲》 +- 《从根儿上理解MySQL》 diff --git a/docs/data-management/MySQL/MySQL-Log.md b/docs/data-management/MySQL/MySQL-Log.md new file mode 100755 index 0000000000..76ad66835e --- /dev/null +++ b/docs/data-management/MySQL/MySQL-Log.md @@ -0,0 +1,940 @@ +--- +title: MySQL 是怎么想的,搞这么多种日志 +date: 2022-08-01 +tags: + - MySQL + - MySQL 日志 +categories: MySQL +--- + + + +> Hello,我是海星。 +> +> 不管是 DB 还是其他组件,很多看似奇怪的问题,答案往往就藏在日志里。 +> +> 这一篇聊聊 MySQL 的一些日志文件 + +MySQL日志文件:用来记录 MySQL 实例对某种条件做出响应时写入的文件,大概可分为:通用查询日志、慢查询日志、错误日志、二进制日志、中继日志、重做日志和回滚日志。 + +他们都有什么作用和关联,我们一起捋一捋(基于 InnoDB) + + + +我们从一条 SQL 语句说起吧 + +```mysql +UPDATE fish SET type = 2 WHERE name = 'starfish'; +``` + +先说个不怎么被提到的,通用查询日志 + +## 一、通用查询日志(general log) + +通用查询日志记录了所有用户的连接开始时间和截止时间,以及发给 MySQL 数据库服务器的所有 SQL 指令。当我们的数据发生异常时,开启通用查询日志,还原操作时的具体场景,可以帮助我们准确定位问题。 + +```mysql +mysql> SHOW VARIABLES LIKE '%general%'; ++------------------+-------------------------------------------------+ +| Variable_name | Value | ++------------------+-------------------------------------------------+ +| general_log | OFF | +| general_log_file | /usr/local/mysql/data/starfishdeMacBook-Pro.log | ++------------------+-------------------------------------------------+ +2 rows in set (0.00 sec) +``` + +通用日志默认是关闭的,我们开启后看下效果 + +```mysql +SET GLOBAL general_log = 'ON' +## SET @@gobal.generl_log_file = /usr/local/mysql/data/starfishdeMacBook-Pro.log +``` + +客户端执行 update 指令,查看日志目录如下,这玩意会记录从数据库连接到终止之间的所有记录,而且都是文本数据,太占资源了,所以一般没有开启的。 + + + + + +## 二、重做日志(redo log) + +了解这块知识,我们先要知道这么几个前置知识点,先看下官网的 InnoDB 架构图,有个大概印象 + + + +> #### 什么是 随机 IO 和 顺序 IO +> +> 磁盘读写数据的两种方式。随机 IO 需要先找到地址,再读写数据,每次拿到的地址都是随机的。比如 MySQL 执行增删改操作时。就像送外卖,每一单送的地址都不一样,到处跑,效率极低。而顺序 IO,由于地址是连贯的,找到地址后,一次可以读写许多数据,效率比较高,比如在末尾追加日志这种。就像送外卖,所有的单子地址都在一栋楼,一下可以送很多,效率很高。 + +> #### 什么是数据页? +> +> MySQL 的 InnoDB 存储引擎以 Data Page(数据页)作为磁盘和内存之间交互的基本单位,他的大小一般为默认值 16K。 +> +> 从数据页的作用来分,可以分为 Free Page(空闲页)、Clean Page(干净页)、Dirty Page(脏页); +> +> - **当内存数据页 和 磁盘数据页内容不一致的时候,这个内存页 就为 “脏页”**。将内存中的数据同步到磁盘中的这个过程就被称为**“刷脏”** +> - **内存数据页写入磁盘后,内存数据页 和 磁盘数据页内容一致,称之为 “干净页”** +> +> 从类型来分的话,还可以分成存放 UNDO 日志的页、存放 INODE 信息的页、存放表空间头部信息的页等。 + +> #### 什么是缓冲池 | buffer pool? +> +> 关系型数据库的特点就是需要对磁盘中大量的数据进行存取,所以有时候也被叫做基于磁盘的数据库。正是因为数据库需要频繁对磁盘进行 IO 操作,为了改善因为直接读写磁盘导致的 IO 性能问题,所以引入了缓冲池。 +> +> 不过不论是什么类型的页面,每当我们从页面中读取或写入数据时,都必须先将其从硬盘上加载到内存中的`buffer pool`中(也就是说内存中的页面其实就是硬盘中页面的一个副本),然后才能对内存中页面进行读取或写入。如果要修改内存中的页面,为了减少磁盘 I/O,修改后的页面并不立即同步到磁盘,而是作为`脏页`继续呆在内存中,等待后续合适时机将其刷新到硬盘(一般是有后台线程异步刷新),将该页刷到磁盘的操作称为 刷脏页 (本句是重点,后面要吃)。 +> +> #### 内存缓冲区 +>> +> InnoDB 存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。由于 CPU 速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用内存缓冲区技术来提高数据库的整体性能。 +> +> ##### Page Cache +> +> InnoDB 会将读取过的页缓存在内存中,并采取最近最少使用(Least Recently Used,LRU) 算法将缓冲池作为列表进行管理,以增加缓存命中率。 +> +> InnoDB 对 LRU 算法进行了一定的改进,执行**中点插入策略**,默认前 5/8 为`New Sublist`,存储经常被使用的热点页,后 3/8 为`Old Sublist`。新读入的 page 默认被加在`old Sublist`的头部,如果在运行过程中 `old Sublist` 的数据被访问到了,那么这个页就会被移动到 `New Sublist` 的头部。 +> +>  +> +> 每当 InnoDB 缓存新的数据页时,会优先从 `Free List` 中查找空余的缓存区域,如果不存在,那么就需要从 `LRU List` 淘汰一定的尾部节点。不管数据页位于 `New Sublist` 还是 `Old Sublist`,如果没有被访问到,那么最终都会被移动到 `LRU List` 的尾部作为牺牲者。 +> +> ##### Change Buffer +> +> Change Buffer 用于记录数据的修改,因为 InnoDB 的辅助索引不同于聚集索引的顺序插入,如果每次修改二级索引都直接写入磁盘,则会有大量频繁的随机 IO。 +> +> InnoDB 从 1.0.x 版本开始引入了 Change Buffer,主要目的是将对**非唯一辅助索引**页的操作缓存下来,如果辅助索引页已经在缓冲区了,则直接修改;如果不在,则先将修改保存到 Change Buffer。当对应辅助索引页读取到缓冲区时,才将 Change Buffer 的数据合并到真正的辅助索引页中,以此减少辅助索引的随机 IO,并达到操作合并的效果。 +> +>  +> +> 在 MySQL 5.5 之前 Change Buffer 其实叫 Insert Buffer,最初只支持 INSERT 操作的缓存,随着支持操作类型的增加,改名为 Change Buffer,现在 InnoDB 存储引擎可以对 INSERT、DELETE、UPDATE 都进行缓冲,对应着:Insert Buffer、Delete Buffer、Purge buffer。 +> +> ##### Double Write Buffer +> +> 当发生数据库宕机时,可能存储引擎正在写入某个页到表中,而这个页只写了一部分,比如 16KB 的页,只写了前 4KB,之后就发生了宕机。虽然可以通过日志进行数据恢复,但是如果这个页本身已经发生了损坏,再对其进行重新写入是没有意义的。因此 InnoDB 引入 Double Write Buffer 解决数据页的半写问题。 +> +> Double Write Buffer 大小默认为 2M,即 128 个数据页。其中分为两部分,一部分留给`batch write`,提供批量刷新脏页的操作,另一部分是`single page write`,留给用户线程发起的单页刷脏操作。 +> +> 在对缓冲池的脏页进行刷新时,脏页并不是直接写到磁盘,而是会通过`memcpy()`函数将脏页先复制到内存中的 Double Write Buffer 中,如果 Double Write Buffer 写满了,那么就会调用`fsync()`系统调用,一次性将 Double Write Buffer 所有的数据写入到磁盘中,因为这个过程是顺序写入,开销几乎可以忽略。在确保写入成功后,再使用异步 IO 把各个数据页写回自己的表空间中。 + +### 2.1 为什么需要 redo log + +好了,我们步入主题 redo log,这里比较喜欢丁奇老师的比喻 + +> 酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本。 +> +> 如果有人要赊账或者还账的话,掌柜一般有两种做法: +> +> - 一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉; +> - 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。 +> +> 在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。 +> +> 这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受? +> + +- 同样,在 MySQL 里也有这个问题,Innodb 是以`页`为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,整个过程 IO 成本、查找成本都很高。 + +- 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机 IO 写入性能太差! + +这样的性能问题,我们是不能忍的,前面我们讲到数据页在缓冲池中被修改会变成脏页。如果这时宕机,脏页就会失效,这就导致我们修改的数据丢失了,也就无法保证事务的**持久性**。 + +为了解决这些问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路,设计了`redo log`,**具体来说就是只记录事务对数据页做了哪些修改**,这样就能完美地解决性能问题了(相对而言文件更小并且是顺序 IO)。 + +而粉板和账本配合的整个过程,其实就类似 MySQL 里经常说到的 **WAL 技术**(Write-Ahead Loging),它的关键点就是**先写日志,再写磁盘**,也就是先写粉板,等不忙的时候再写账本。这又解决了我们的持久性问题。 + +具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 `redo log`(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。 + +> DBA 口中的**日志先行**说的就是这个 WAL 技术。 +> +> 记录下对磁盘中某某页某某位置数据的修改结果的 redo log,这种日志被称为**物理日志**,可以节省很多磁盘空间。 +> +> 最开始看到的通用查询日志,记录了所有数据库的操作,我们叫**逻辑日志**,还有下边会说的 binlog、undo log 也都属于逻辑日志。 + + + +### 2.2 组织 redo log + +#### 「redo log 结构」 + +MySQL redo日志是一组日志文件,在 MySQL 8.0.30 版本中,MySQL 会生成 32 个 redo log 文件(老版本默认 2 个) + + + +> [`innodb_log_file_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_log_file_size) and [`innodb_log_files_in_group`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_log_files_in_group) are deprecated in MySQL 8.0.30. These variables are superseded by [`innodb_redo_log_capacity`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_redo_log_capacity). For more information, see [Section 15.6.5, “Redo Log”](https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html). + +为了应对 InnoDB 各种各样不同的需求,到 MySQL 8.0 为止,已经有多达 65 种的 REDO 记录,每种都不太一样,我们看下比较通用的结构了解了解就 OK(主要有作用于 Page,作用于 Space 以及提供额外信息的 Logic 类型的三大类) + + + +比如 `MLOG_WRITE_STRING` 类型的 REDO 表示写入一串数据,但是因为不能确定写入的数据占多少字节,所以需要在日志结构中添加一个长度字段来表示写入了多长的数据。 + +除此之外,还有一些复杂的 REDO 类型来记录一些复杂的操作。例如插入一条数据,并不仅仅只是在数据页中插入一条数据,还可能会导致数据页和索引页的分裂,可能要修改数据页中的头信息(Page Header)、目录槽信息(Page Directory)等等。 + +#### 「Mini-Transaction」 + +一个事务中可能有多个增删改的 SQL语句,而一个 SQL 语句在执行过程中可能修改若干个页面,会有多个操作。 + +> 例如一个 INSERT 语句: +> +> - 如果表没有主键,会去更新内存中的 `Max Row ID` 属性,并在其值为 `256` 的倍数时,将其刷新到`系统表空间`的页号为 `7` 的`Max Row ID `属性处。 +> - 接着向聚簇索引插入数据,这个过程要根据索引找到要插入的缓存页位置,向数据页插入记录。这个过程还可能会涉及数据页和索引页的分裂,那就会增加或修改一些缓存页,移动页中的记录。 +> - 如果有二级索引,还会向二级索引中插入记录。 +> +> 最后还可能要改动一些系统页面,比如要修改各种段、区的统计信息,各种链表的统计信息等等。 + +所以 InnoDB 将执行语句的过程中产生的 `redo log` 划分成了若干个不可分割的组,一组 `redo log` 就是对底层页面的一次原子访问,这个原子访问也称为 `Mini-Transaction`,简称 **mtr**。一个 `mtr` 就包含一组 `redo log`,在崩溃恢复时这一组 `redo log` 就是一个不可分割的整体。 + +#### 「redo log block」 + +`redo log` 并不是一条一条写入磁盘的日志文件中的,而且一个原子操作的 `mtr` 包含一组 `redo log`,一条一条的写就无法保证写磁盘的原子性了。 + +磁盘是块设备,InnoDB 中也用 Block 的概念来读写数据,设计了一个 `redo log block` 的数据结构,称为重做日志块(`block`),重做日志块跟缓存页有点类似,只不过日志块记录的是一条条 redo log。 + +`S_FILE_LOG_BLOCK_SIZE` 等于磁盘扇区的大小 512B,每次 IO 读写的最小单位都是一个 Block。 + +一个 `redo log block` 固定 `512字节` 大小,由三个部分组成: + +- 12 字节的 **Block Header**,主要记录一些额外的信息,包括文件信息、log 版本、lsn 等 +- Block 中剩余的中间 498 个字节就是 REDO 真正内容的存放位置 + +- Block 末尾是 4 字节的 **Block Tailer**,记录当前 Block 的 Checksum,通过这个值,读取 Log 时可以明确 Block 数据有没有被完整写盘。 + +#### 「redo log 组成」 + +前置知识点说了,缓冲池有提效的功效,所以 redo log 也不是直接干到日志文件(磁盘中),而是有个类似缓冲池的 `redo log buffer`(内存中),在写 redo log 时会先写 `redo log buffer` + +> 用户态下的缓冲区数据是无法直接写入磁盘的。因为中间必须经过操作系统的内核空间缓冲区(OS Buffer)。 + +写入 `redo log buffer` 后,再写入 `OS Buffer`,然后操作系统调用 `fsync()` 函数将日志刷到磁盘。 + + + +> 扩展点: +> +> - redo log buffer 里面的内容,既然是在操作系统调用 fsync() 函数持久化到磁盘的,那如果事务执行期间 MySQL 发生异常重启,那这部分日志就丢了。由于事务并没有提交,所以这时日志丢了也不会有损失。 +> - fsync() 的时机不是我们控制的,那就有可能在事务还没提交的时候,redo log buffer 中的部分日志被持久化到磁盘中 +> +> 所以,redo log 是存在不同状态的 +> +> 这三种状态分别是: +> +> 1. 存在 redo log buffer 中,物理上是在 MySQL 进程内存中; +> 2. 写到磁盘 (write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里面; +> 3. 持久化到磁盘,对应的是 hard disk。 +> +>  +> +> 日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。 +> +> 为了控制 redo log 的写入策略,InnoDB 提供了 `innodb_flush_log_at_trx_commit` 参数,它有三种可能取值(刷盘策略还会说到): +> +> 1. 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ; +> 2. 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘; +> 3. 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。 +> +> InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。 +> +> 注意,事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,这些 redo log 也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的 redo log,也是可能已经持久化到磁盘的。 + +写完 redo log buffer 后,我们就要顺序追加日志了,可是每次往哪里写,肯定需要个标识的,类似 offset,小结一下接着聊。 + + + +#### 「redo 结构小结」 + +我们把这几个 redo 内容串起来,其实就是 redo log 有 32 个文件,每个文件以 Block 为单位划分,多个文件首尾相连顺序写入 REDO 内容,Redo 又按不同类型有不同内容。 + +一个 `mtr` 中的 redo log 实际上是先写到 redo log buffer,然后再”找机会“ 将一个个 mtr 的日志记录复制到`block`中,最后在一些时机将`block`刷新到磁盘日志文件中。 + +redo 文件结构大致是下图这样: + + + + + +### 2.3 写入 redo log + +刚才说了,会找机会将 block 刷盘,那到底是什么时候呢? + +#### 刷盘时机 + +写入到日志文件(刷新到磁盘)的时机有这么 3 种: + +- MySQL 正常关闭时 +- 每秒刷新一次 +- redo log buffer 剩余空间小于 1/2 时(内存不够用了,要先将脏页写到磁盘) + +每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘,这个策略可由 `innodb_flush_log_at_trx_commit` 参数控制 + +#### 刷盘策略 + +`innodb_flush_log_at_trx_commit` 的值可以是 1、2、0 + +- 当设置该值为 1 时,每次事务提交都要做一次 fsync,这是最安全的配置,即使宕机也不会丢失事务,这是默认值; + +- 当设置为 2 时,则在事务提交时只做 write 操作,只保证写到系统的 page cache,因此实例 crash 不会丢失事务,但宕机则可能丢失事务; + +- 当设置为 0 时,事务提交不会触发 redo 写操作,而是留给后台线程每秒一次的刷盘操作,因此实例 crash 最多丢失 1 秒钟内的事务 + +  + + +#### 日志逻辑序列号(log sequence number,LSN) + +刷盘时机、刷盘策略看着好像挺合适,如果刷盘还没结束,服务器 GG(宕机)了呢? 知道你不慌,redo 可以用来保证持久性嘛~ + +重启服务后,我们肯定需要通过 redo 重放来恢复数据,但是从哪开始恢复呢? + +为解决这个问题 InnoDB 为 redo log 记录了序列号,这被称为 LSN(Log Sequence Number),可以理解为偏移量。 + +在 MySQL Innodb 引擎中 LSN 是一个非常重要的概念,表示从日志记录创建开始到特定的日志记录已经写入的字节数,LSN 的计算是包含每个 BLOCK 的头和尾字段的。 + +在 InnoDB 的日志系统中,LSN 无处不在,它既用于表示修改脏页时的日志序号,也用于记录 checkpoint,通过 LSN,可以具体的定位到其在 redo log 文件中的位置。 + +> 那如何由一个给定 LSN 的日志,在日志文件中找到它存储的位置的偏移量并能正确的读出来呢。所有的日志文件要属于日志组,而在 log_group_t 里的 lsn 和 lsn_offset 字段已经记录了某个日志 lsn 和其存放在文件内的偏移量之间的对应关系。我们可以利用存储在 group 内的 lsn 和给定 lsn 之间的相对位置,来计算出给定 lsn 在文件中的存储位置。(具体怎么算我们先不讨论) + +LSN 是单调递增的,用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。越新的日志 LSN 越大。 + +InnoDB 用检查点( checkpoint_lsn )指示未被刷盘的数据从这里开始,用 lsn 指示下一个应该被写入日志的位置。不过由于有 redo log buffer 的缘故,实际被写入磁盘的位置往往比 lsn 要小。 + +redo log 采用逻辑环形结构来复用空间(循环写入),这种环形结构一般需要几个指针去配合使用 + + + + + +如果 lsn 追上了 checkpoint,就意味着 **redo log 文件满了,这时 MySQL 不能再执行新的更新操作,也就是说 MySQL 会被阻塞**(*所以针对并发量大的系统,适当设置 redo log 的文件大小非常重要*),此时**会停下来将 Buffer Pool 中的脏页刷新到磁盘中,然后标记 redo log 哪些记录可以被擦除,接着对旧的 redo log 记录进行擦除,等擦除完旧记录腾出了空间,checkpoint 就会往后移动(图中顺时针)**,然后 MySQL 恢复正常运行,继续执行新的更新操作。 + +> #### 组提交(group commit) +> +> redo log 的刷盘操作将会是最终影响 MySQL TPS 的瓶颈所在。为了缓解这一问题,MySQL 使用了组提交,将多个刷盘操作合并成一个,如果说 10 个事务依次排队刷盘的时间成本是 10,那么将这 10 个事务一次性一起刷盘的时间成本则近似于 1。 +> +> 当开启 binlog 时(下边还会介绍) +> +> 为了保证 redo log 和 binlog 的数据一致性,MySQL 使用了二阶段提交,由 binlog 作为事务的协调者。而引入二阶段提交使得binlog 又成为了性能瓶颈,先前的 Redo log 组提交也成了摆设。为了再次缓解这一问题,MySQL 增加了 binlog 的组提交,目的同样是将 binlog 的多个刷盘操作合并成一个,结合 redo log 本身已经实现的组提交,分为三个阶段(Flush 阶段、Sync 阶段、Commit 阶段)完成 binlog 组提交,最大化每次刷盘的收益,弱化磁盘瓶颈,提高性能。 +> +> 通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。 +> +> 这时候,你可能有一个疑问,这意味着我从 MySQL 看到的 TPS 是每秒两万的话,每秒就会写四万次磁盘。但是,我用工具测试出来,磁盘能力也就两万左右,怎么能实现两万的 TPS? +> +> 解释这个问题,就要用到组提交(group commit)机制了。假设有三个并发事务 (trx1, trx2, trx3) 在 prepare 阶段,都写完 redo log buffer,持久化到磁盘的过程,对应的 LSN 分别是 50、120 和 160。 +> +> 1. trx1 是第一个到达的,会被选为这组的 leader; +> 2. 等 trx1 要开始写盘的时候,这个组里面已经有了三个事务,这时候 LSN 也变成了 160; +> 3. trx1 去写盘的时候,带的就是 LSN=160,因此等 trx1 返回时,所有 LSN 小于等于 160 的 redo log,都已经被持久化到磁盘; +> 4. 这时候 trx2 和 trx3 就可以直接返回了。 +> +> 所以,一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好。 + +#### Checkpoint + +CheckPoint 的意思是检查点,用于推进 Redo Log 的失效。当触发 Checkpoint 后,会去看 Flush List 中最早的那个节点 old_lsn 是多少,也就是说当前 Flush List 还剩的最早被修改的数据页的 redo log lsn 是多少,并且将这个 lsn 记录到 Checkpoint 中,因为在这之前被修改的数据页都已经刷新到磁盘了,对应的 redo log 也就无效了,所以说之后在这个 old_lsn 之后的 redo log 才是有用的。这就解释了之前说的 redo log 文件组如何覆盖无效日志。 + + + +#### 一个重做全过程的示例 + +我们小结下 redo log 的过程 + + + +以更新事务为例 + +1. 将原始数据读入内存,修改数据的内存副本。 + +2. 先将内存中 Buffer pool 的脏页写入到 Redo log buffer 当中**记录数据的变化**。然后再将 redo log buffer 当中记录数据变化的日志通过 **顺序IO** 刷新到磁盘的 redo log file 当中 + + > 在缓冲池中有一条 Flush 链表用来维护被修改的数据页面,也就是脏页所组成的链表。 + +3. 写入操作系统 **Page Cache** + +4. 刷盘,将重做日志缓冲区中的内容刷新到重做日志文件(如果此时脏页没刷完,会通过redo log 重放来恢复数据) + +5. 唤醒用户线程完成 commit + +6. 随后正常将内存中的脏页刷回磁盘。 + +### 2.4 redo 小结 + +有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 **crash-safe**。 + +所以有了 redo log,再通过 WAL 技术,InnoDB 就可以保证即使数据库发生异常重启,之前已提交的记录都不会丢失,这个能力称为 **crash-safe**(崩溃恢复)。可以看出来, **redo log 保证了事务四大特性中的持久性**。 + +redo log 作用: + +- **实现事务的持久性,让 MySQL 有 crash-safe 的能力**,能够保证 MySQL 在任何时间段突然崩溃,重启后之前已提交的记录都不会丢失; +- **将写操作从「随机写」变成了「顺序写」**,提升 MySQL 写入磁盘的性能。 + + + +## 三、回滚日志(undo log) + +回滚日志的作用是进行事务回滚,是 Innodb 存储引擎层生成的日志,实现了事务中的**原子性**,主要**用于事务回滚和 MVCC**。 + +当事务执行的时候,回滚日志中记录了事务中每次数据更新前的状态。当事务需要回滚的时候,可以通过读取回滚日志,恢复到指定的位置。另一方面,回滚日志也可以让其他的事务读取到这个事务对数据更改之前的值,从而确保了其他事务可以不受这个事务修改数据的影响。 + +> undo Log 是 InnoDB 十分重要的组成部分,它的作用横贯 InnoDB 中两个最主要的部分,并发控制(Concurrency Control)和故障恢复(Crash Recovery)。 +> +> - Undo Log 用来记录每次修改之前的历史值,配合 Redo Log 用于故障恢复 + +#### 3.1 为什么需要 undo log + +##### 事务回滚 + +由于如硬件故障,软件 Bug,运维操作等原因的存在,数据库在任何时刻都有突然崩溃的可能。 + +这个时候没有完成提交的事务可能已经有部分数据写入了磁盘,如果不加处理,会违反数据库对**原子性**的保证。 + +针对这个问题,直观的想法是等到事务真正提交时,才能允许这个事务的任何修改落盘,也就是 No-Steal 策略。显而易见,这种做法一方面造成很大的内存空间压力,另一方面提交时的大量随机 IO 会极大的影响性能。因此,数据库实现中通常会在正常事务进行中,就不断的连续写入 Undo Log,来记录本次修改之前的历史值。当 Crash 真正发生时,可以在 Recovery 过程中通过回放 Undo Log 将未提交事务的修改抹掉。InnoDB 采用的就是这种方式。 + +##### MVCC(Multi-Versioin Concurrency Control) + +用于 MVCC(实现非锁定读),读取一行记录时,若已被其他事务占据,则通过 undo 读取之前的版本。 + +为了避免只读事务与写事务之间的冲突,避免写操作等待读操作,几乎所有的主流数据库都采用了多版本并发控制(MVCC)的方式,也就是为每条记录保存多份历史数据供读事务访问,新的写入只需要添加新的版本即可,无需等待。 + +InnoDB 在这里复用了 Undo Log 中已经记录的历史版本数据来满足 MVCC 的需求。 + +InnoDB 中其实是把 Undo 当做一种数据来维护和使用的,也就是说,Undo Log 日志本身也像其他的数据库数据一样,会写自己对应的Redo Log,通过 Redo Log 来保证自己的原子性。因此,更合适的称呼应该是 **Undo Data**。 + + + +> 我们在执行执行一条“增删改”语句的时候,虽然没有输入 begin 开启事务和 commit 提交事务,但是 MySQL 会**隐式开启事务**来执行“增删改”语句的,执行完就自动提交事务的,这样就保证了执行完“增删改”语句后,我们可以及时在数据库表看到“增删改”的结果了。 +> +> 执行一条语句是否自动提交事务,是由 `autocommit` 参数决定的,默认是开启。所以,执行一条 update 语句也是会使用事务的。 +> + + + +#### 3.2 组织 undo log + +> 前边我们简单提过下,Undo Log 属于逻辑日志,为什么不用物理日志呢? +> +> Undo Log 需要的是事务之间的并发,以及方便的多版本数据维护,其重放逻辑不希望因 DB 的物理存储变化而变化。因此,InnoDB 中的 Undo Log 采用了基于事务的 **Logical Logging** 的方式。 +> + + + +> 各个版本的 MySQL,undo tablespaces 存储有一些差距,我们以 8.0 版本说明 + +#### 「undo log 的两种类型」 + +根据行为的不同,undo log 分为两种:insert undo log 和 update undo log + +- **insert undo log,是在 insert 操作中产生的。** + + insert 操作的记录只对事务本身可见,对于其它事务此记录是不可见的,所以 insert undo log 可以在事务提交后直接删除而不需要进行purge操作。 + + Insert Undo Record 仅仅是为了可能的事务回滚准备的,并不在 MVCC 功能中承担作用。 + +  + + 存在一组长度不定的 Key Fields,因为对应表的主键可能由多个 field 组成,这里需要记录 Record 完整的主键信息,回滚的时候可以通过这个信息在索引中定位到对应的 Record。 + +- **update undo log 是 update 或 delete 操作中产生。** + + 由于 MVCC 需要保留 Record 的多个历史版本,当某个 Record 的历史版本还在被使用时,这个 Record 是不能被真正的删除的。 + + 因此,当需要删除时,其实只是修改对应 Record 的Delete Mark标记。对应的,如果这时这个Record又重新插入,其实也只是修改一下Delete Mark标记,也就是将这两种情况的delete和insert转变成了update操作。再加上常规的Record修改,因此这里的Update Undo Record会对应三种Type: + + - TRX_UNDO_UPD_EXIST_REC + - TRX_UNDO_DEL_MARK_REC + - TRX_UNDO_UPD_DEL_REC。 + + 他们的存储内容也类似,我们看下 TRX_UNDO_UPD_EXIST_REC + +  + + 除了跟 Insert Undo Record 相同的头尾信息,以及主键 Key Fileds 之外,Update Undo Record 增加了: + + - Transaction Id记录了产生这个历史版本事务Id,用作后续MVCC中的版本可见性判断 + - Rollptr指向的是该记录的上一个版本的位置,包括space number,page number和page内的offset。沿着Rollptr可以找到一个Record的所有历史版本。 + - Update Fields中记录的就是当前这个Record版本相对于其之后的一次修改的Delta信息,包括所有被修改的Field的编号,长度和历史值。 + + > 因为会对已经存在的记录产生影响,为了提供 MVCC机制,因此 update undo log 不能在事务提交时就进行删除,而是将事务提交时放到入 history list 上,等待 purge 线程进行最后的删除操作 + > + > 为了更好的支持并发,InnoDB的多版本一致性读是采用了基于回滚段的的方式。另外,对于更新和删除操作,InnoDB并不是真正的删除原来的记录,而是设置记录的delete mark为1。因此为了解决数据Page和Undo Log膨胀的问题,需要引入purge机制进行回收 + > + > 为了保证事务并发操作时,在写各自的undo log时不产生冲突,InnoDB采用回滚段的方式来维护undo log的并发写入和持久化。回滚段实际上是一种 Undo 文件组织方式 + + + +每当 InnoDB 中需要修改某个 Record 时,都会将其历史版本写入一个 Undo Log 中,对应的 Undo Record 是 Update 类型。 + +当插入新的 Record 时,还没有一个历史版本,但为了方便事务回滚时做逆向(Delete)操作,这里还是会写入一个 Insert 类型的 Undo Record。 + +#### 「组织方式」 + +每一次的修改都会产生至少一个 Undo Record,那么大量 Undo Record 如何组织起来,来支持高效的访问和管理呢? + +每个事务其实会修改一组的 Record,对应的也就会产生一组 Undo Record,这些 Undo Record 首尾相连就组成了这个事务的**Undo Log**。除了一个个的 Undo Record 之外,还在开头增加了一个Undo Log Header 来记录一些必要的控制信息,因此,一个 Undo Log 的结构如下所示: + + + +- Trx Id:事务Id +- Trx No:事务的提交顺序,也会用这个来判断是否能Purge +- Delete Mark:标明该Undo Log中有没有TRX_UNDO_DEL_MARK_REC类型的Undo Record,避免Purge时不必要的扫描 +- Log Start Offset:记录Undo Log Header的结束位置,方便之后Header中增加内容时的兼容 +- Xid and DDL Flags:Flag信息 +- Table ID if DDL:表ID +- Next Undo Log:标记后边的Undo Log +- Prev Undo Log:标记前边的Undo Log +- History List Node: + +索引中的同一个 Record 被不同事务修改,会产生不同的历史版本,这些历史版本又通过 **Rollptr** 串成一个链表,供 MVCC 使用。如下图所示: + + + +> 示例中有三个事务操作了表 t 上,主键 id 是 1 的记录,首先事务 X 插入了一条记录,事务 Y、Z 去修改了这条记录。X,Y,Z 三个事务分别有自己的逻辑上连续的三条 Undo Log,每条 Undo Log 有自己的 Undo Log Header。从索引中的这条 Record 沿着 Rollptr 可以依次找到这三个事务 Undo Log 中关于这条记录的历史版本。同时可以看出,Insert 类型 Undo Record 中只记录了对应的主键值:id=1,而 Update 类型的 Undo Record 中还记录了对应的历史版本的生成事务 Trx_id,以及被修改的 name 的历史值。 + +undo 是逻辑日志,只是将数据库**逻辑的**恢复到执行语句或事务之前。 + +我们知道 InnoDB 中默认以 块 为单位存储,一个块默认是 16KB。那么如何用固定的块大小承载不定长的 Undo Log,以实现高效的空间分配、复用,避免空间浪费。InnoDB 的**基本思路**是让多个较小的 Undo Log 紧凑存在一个 Undo Page 中,而对较大的 Undo Log 则随着不断的写入,按需分配足够多的 Undo Page 分散承载 + + + +Undo 的物理组织格式是—— Undo Segment,它会持有至少一个 Undo Page。 + +InnoDB 中的 Undo 文件中准备了大量的 Undo Segment 的槽位,按照1024一组划分为**Rollback Segment**。 + +Undo 的文件组织格式是——Undo Tablespace,每个 Undo Tablespace 最多会包含 128 个 Rollback Segment。MySQL 8.0 最多支持 127 个独立的 Undo Tablespace。 + +在内存中也会维护对应的数据结构来管理 Undo Log,我们就不深入了。 + + + +#### 3.3 MVCC 是如何实现的 + +多版本的目的是为了避免写事务和读事务的互相等待,那么每个读事务都需要在不对 Record 加 Lock 的情况下, 找到对应的应该看到的历史版本。所谓历史版本就是假设在该只读事务开始的时候对整个 DB 打一个快照,之后该事务的所有读请求都从这个快照上获取。当然实现上不能真正去为每个事务打一个快照,这个时间空间成本都太高了。 + +> MVCC 的实现还有一个概念:快照读,快照信息就记录在 undo 中 +> +> 所谓快照读,就是读取的是快照数据,即快照生成的那一刻的数据,像我们常用的**普通的SELECT语句在不加锁情况下就是快照读**。如: +> +> ```mysql +> SELECT * FROM t WHERE ... +> ``` +> +> 和快照读相对应的另外一个概念叫做当前读,当前读就是读取最新数据,所以,**加锁的 SELECT,或者对数据进行增删改都会进行当前读**,比如: +> +> ```mysql +> SELECT * FROM t LOCK IN SHARE MODE; +> +> SELECT * FROM t FOR UPDATE; +> +> INSERT INTO t ... +> +> DELETE FROM t ... +> +> UPDATE t ... +> ``` + +InnoDB **通过 ReadView + undo log 实现 MVCC** + +对于「读提交」和「可重复读」隔离级别的事务来说,它们的快照读(普通 select 语句)是通过 Read View + undo log 来实现的,它们的区别在于创建 Read View 的时机不同: + +- 「读提交」隔离级别是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。 +- 「可重复读」隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。 + +这两个隔离级别实现是通过「事务的 Read View 里的字段」和「记录中的两个隐藏列(trx_id 和 roll_pointer)」的比对,如果不满足可见性,就会顺着 undo log 版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。 + +InnoDB 的做法,是在读事务第一次读取的时候获取一份 ReadView,并一直持有,其中记录所有当前活跃的写事务 ID,由于写事务的 ID 是自增分配的,通过这个 ReadView 我们可以知道在这一瞬间,哪些事务已经提交哪些还在运行,根据 Read Committed 的要求,未提交的事务的修改就是不应该被看见的,对应地,已经提交的事务的修改应该被看到。 + +> **Read View 主要来帮我们解决可见性的问题的**, 即他会来告诉我们本次事务应该看到哪个快照,不应该看到哪个快照。 +> +> 在 Read View 中有几个重要的属性: +> +> - trx_ids,系统当前未提交的事务 ID 的列表。 +> - low_limit_id,未提交的事务中最大的事务 ID。 +> - up_limit_id,未提交的事务中最小的事务 ID。 +> - creator_trx_id,创建这个 Read View 的事务 ID。 +> +> 每开启一个事务,我们都会从数据库中获得一个事务 ID,这个事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序。 + +作为存储历史版本的 Undo Record,其中记录的 trx_id 就是做这个可见性判断的,对应的主索引的 Record 上也有这个值。当一个读事务拿着自己的 ReadView 访问某个表索引上的记录时,会通过比较 Record 上的 trx_id 确定是否是可见的版本,如果不可见就沿着 Record 或 Undo Record 中记录的 rollptr 一路找更老的历史版本。 + +具体的事务 id,指向 undo log 的指针 rollptr,这些信息是放在哪里呢,这就是我们常说的 InnoDB 隐藏字段了 + +> #### InnoDB存储引擎的行结构 +> +> InnoDB 表数据的组织方式为主键聚簇索引,二级索引中采用的是(索引键值, 主键键值)的组合来唯一确定一条记录。 +> InnoDB表数据为主键聚簇索引,mysql默认为每个索引行添加了4个隐藏的字段,分别是: +> +> - DB_ROW_ID:InnoDB引擎中一个表只能有一个主键,用于聚簇索引,如果表没有定义主键会选择第一个非Null 的唯一索引作为主键,如果还没有,生成一个隐藏的DB_ROW_ID作为主键构造聚簇索引。 +> - DB_TRX_ID:最近更改该行数据的事务ID。 +> - DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,其实他指向的就是 Undo Log 中的上一个版本的快照的地址 +> - DELETE BIT:索引删除标志,如果DB删除了一条数据,是优先通知索引将该标志位设置为1,然后通过(purge)清除线程去异步删除真实的数据。 +> + +如下图所示,事务 R 需要查询表 t 上的 id 为 1 的记录,R 开始时事务 X 已经提交,事务 Y 还在运行,事务 Z 还没开始,这些信息都被记录在了事务 R 的 ReadView 中。事务 R 从索引中找到对应的这条 Record[1, stafish],对应的 trx_id 是 Z,不可见。沿着 Rollptr 找到Undo 中的前一版本[1, fish],对应的 trx_id 是 Y,不可见。继续沿着 Rollptr 找到[1, star],trx_id是 X 可见,返回结果。 + + + +前面提到过,作为 Logical Log,Undo 中记录的其实是前后两个版本的 diff 信息,而读操作最终是要获得完整的 Record 内容的,也就是说这个沿着 rollptr 指针一路查找的过程中需要用 Undo Record 中的 diff 内容依次构造出对应的历史版本,这个过程在函数 **row_search_mvcc **中,其中 **trx_undo_prev_version_build** 会根据当前的 rollptr 找到对应的 Undo Record 位置,这里如果是 rollptr指向的是 insert 类型,或者找到了已经 Purge 了的位置,说明到头了,会直接返回失败。否则,就会解析对应的 Undo Record,恢复出trx_id、指向下一条 Undo Record 的 rollptr、主键信息,diff 信息 update vector 等信息。之后通过 **row_upd_rec_in_place**,用update vector 修改当前持有的 Record 拷贝中的信息,获得 Record 的这个历史版本。之后调用自己 ReadView 的 **changes_visible** 判断可见性,如果可见则返回用户。完成这个历史版本的读取。 + + + +#### 3.4 Undo Log 清理 + +我们已经知道,InnoDB 在 Undo Log 中保存了多份历史版本来实现 MVCC,当某个历史版本已经确认不会被任何现有的和未来的事务看到的时候,就应该被清理掉。 + +InnoDB中每个写事务结束时都会拿一个递增的编号**trx_no**作为事务的提交序号,而每个读事务会在自己的ReadView中记录自己开始的时候看到的最大的trx_no为**m_low_limit_no**。那么,如果一个事务的trx_no小于当前所有活跃的读事务Readview中的这个**m_low_limit_no**,说明这个事务在所有的读开始之前已经提交了,其修改的新版本是可见的, 因此不再需要通过undo构建之前的版本,这个事务的Undo Log也就可以被清理了。 + + + +> redo log 和 undo log 区别在哪? +> +> - redo log 记录了此次事务「**完成后**」的数据状态,记录的是更新**之后**的值; +> - undo log 记录了此次事务「**开始前**」的数据状态,记录的是更新**之前**的值; + + + +## 四、二进制日志(binlog) + +前面我们讲过,MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面我们聊到的 redo log 和 undo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(二进制日志)。 + +二进制日志,也被叫做「归档日志」,主要**用于数据备份和主从复制** + +- **主从复制**:在 `Master` 端开启 `binlog`,然后将 `binlog` 发送到各个 `Slave` 端,`Slave` 端重放 `binlog` 从而达到主从数据一致 +- **数据恢复**:可以用 `mysqldump` 做数据备份,binlog 格式是二进制日志,可以使用 `mysqlbinlog` 工具解析,实现数据恢复 + +二进制日志主要记录数据库的更新事件,比如创建数据表、更新表中的数据、数据更新所花费的时长等信息。通过这些信息,我们可以再现数据更新操作的全过程。而且,由于日志的延续性和时效性,我们还可以利用日志,完成无损失的数据恢复和主从服务器之间的数据同步。 + + + +### 4.1 binlog VS redolog + +是不会有点疑惑,binlog 和 redo log 是不是有点重复?这个问题跟 MySQL 的时间线有关系。 + +因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。 + +这两种日志有以下四点区别。 + +1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。 +2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。 +3. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。 +4. redo log 用于掉电等故障恢复。binlog 用于备份恢复、主从复制 + + + +### 4.2 查看 binlog + +查看二进制日志主要有 3 种情况,分别是查看当前正在写入的二进制日志、查看所有的二进制日志和查看二进制日志中的所有数据更新事件。 + +```mysql +mysql> show master status; ++---------------+----------+--------------+------------------+-------------------+ +| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | ++---------------+----------+--------------+------------------+-------------------+ +| binlog.000002 | 27736 | | | | ++---------------+----------+--------------+------------------+-------------------+ +1 row in set (0.00 sec) +``` + +```mysql +mysql> show binary logs; ++---------------+-----------+-----------+ +| Log_name | File_size | Encrypted | ++---------------+-----------+-----------+ +| binlog.000001 | 638 | No | +| binlog.000002 | 27736 | No | ++---------------+-----------+-----------+ +2 rows in set (0.01 sec) +``` + +```mysql +mysql> show variables like '%binlog_format%'; ++---------------+-------+ +| Variable_name | Value | ++---------------+-------+ +| binlog_format | ROW | ++---------------+-------+ +1 row in set (0.00 sec) +``` + + + +### 4.3 Binary logging formats + +`binlog`日志有三种格式,分别为`STATMENT`、`ROW`和`MIXED`。 + +> 在 `MySQL 5.7.7`之前,默认的格式是`STATEMENT`,`MySQL 5.7.7`之后,默认值是 `ROW`。日志格式通过 `binlog-format` 指定。 + +- `STATMENT` :基于 SQL 语句的复制(`statement-based replication, SBR`),每一条会修改数据的 sql 语句会记录到 binlog 中**。** + - 优点:不需要记录每一行的变化,减少了 binlog 日志量,节约了 IO, 从而提高了性能; + - 缺点:在某些情况下会导致主从数据不一致,比如执行`sysdate()`、`slepp()`等。 + +- `ROW` :基于行的复制(`row-based replication, RBR`),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了**。 ** + - 优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题**;** + - 缺点:会产生大量的日志,尤其是 `alter table` 的时候会让日志暴涨 + +- `MIXED` :基于 STATMENT 和 ROW 两种模式的混合复制(`mixed-based replication, MBR`),mixed 格式的意思是,MySQL 自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能,就用 row 格式,否则就用 statement 格式 + +> ``` +>SET GLOBAL binlog_format = 'STATEMENT'; +> SET GLOBAL binlog_format = 'ROW'; +> SET GLOBAL binlog_format = 'MIXED'; +> ``` +> + + + +### 4.4 binlog 的写入机制 + +binlog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。 + +一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及到了 binlog cache 的保存问题。 + +系统给 binlog cache 分配了一片内存,每个线程一个,参数 `binlog_cache_size` 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。 + +事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 中,并清空 binlog cache。 + + + + + +可以看到,每个线程有自己 binlog cache,但是共用同一份 binlog 文件。 + +- 图中的 write,指的就是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。 +- 图中的 fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为 fsync 才占磁盘的 IOPS。 + +write 和 fsync 的时机,是由参数 sync_binlog 控制的: + +1. sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync; +2. sync_binlog=1 的时候,表示每次提交事务都会执行 fsync; +3. sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。 + +因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。 + +但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。 + + + +### 4.5 update 语句执行过程 + +比较重要的 undo、redo、binlog 都介绍完了,我们来看执行器和 InnoDB 引擎在执行一个简单的 update 语句时的内部流程。`update t set name='starfish' where id = 1;` + +1. 执行器先找引擎取 id=1 这一行。id 是主键,引擎直接用树搜索找到这一行。如果 id=1 这一行所在的数据页本来就在内存(buffer pool)中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。 +2. 执行器拿到引擎给的行数据,更新行数据,再调用引擎接口写入这行新数据。 +3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。 +4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。 +5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。 + +这里我给出这个 update 语句的执行流程图,图中浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的。 + + + +你可能注意到了,最后三步看上去有点“绕”,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。 + +### 4.6 两阶段提交 + +为什么必须有“两阶段提交”呢?这是为了让两份日志之间的逻辑一致。要说明这个问题,我们得从文章开头的那个问题说起:**怎样让数据库恢复到半个月内任意一秒的状态?** + +前面我们说过了,binlog 会记录所有的逻辑操作,并且是采用“追加写”的形式。如果你的 DBA 承诺说半个月内可以恢复,那么备份系统中一定会保存最近半个月的所有 binlog,同时系统会定期做整库备份。这里的“定期”取决于系统的重要性,可以是一天一备,也可以是一周一备。 + +当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做: + +- 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库; +- 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。 + +这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。 + +好了,说完了数据恢复过程,我们回来说说,为什么日志需要“两阶段提交”。这里不妨用反证法来进行解释。 + +由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。 + +仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢? + +1. **先写 redo log 后写 binlog**。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。 + 但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。 + 然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。 +2. **先写 binlog 后写 redo log**。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。 + +可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。 + +你可能会说,这个概率是不是很低,平时也没有什么动不动就需要恢复临时库的场景呀? + +其实不是的,不只是误操作后需要用这个过程来恢复数据。当你需要扩容的时候,也就是需要再多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用 binlog 来实现的,这个“不一致”就会导致你的线上出现主从数据库不一致的情况。 + +简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。 + + + +### 4.7 主从同步 + +MySQL主从同步的作用主要有以下几点: + +- 故障切换。 +- 提供一定程度上的备份服务。 +- 实现MySQL数据库的读写分离。 + +MySQL 的主从复制依赖于 binlog ,也就是记录 MySQL 上的所有变化并以二进制形式保存在磁盘上。复制的过程就是将 binlog 中的数据从主库传输到从库上。 + +这个过程一般是**异步**的,也就是主库上执行事务操作的线程不会等待复制 binlog 的线程同步完成。 + + + +具体详细过程如下: + +- MySQL 主库在收到客户端提交事务的请求之后,会先写入 binlog,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端“操作成功”的响应。 +- 从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应。 +- 从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。 + +在完成主从复制之后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。 + +> #### 中继日志(relay log) +> +> 中继日志只在主从服务器架构的从服务器上存在。从服务器为了与主服务器保持一致,要从主服务器读取二进制日志的内容,并且把读取到的信息写入本地的日志文件中,这个从服务器本地的日志文件就叫中继日志。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主从服务器的数据同步。 +> +> Relay log(中继日志)是在MySQL主从复制时产生的日志,在MySQL的主从复制主要涉及到三个线程: +> +> 1) Log dump线程:向从库的IO线程传输主库的Binlog日志 +> +> 2) IO线程:向主库请求Binlog日志,并将Binlog日志写入到本地的relay log中。 +> +> 3) SQL线程:读取Relay log日志,将其解析为SQL语句并逐一执行。 +> +> + +> MySQL 主从复制还有哪些模型? + +主要有三种: + +- **同步复制**:MySQL 主库提交事务的线程要等待所有从库的复制成功响应,才返回客户端结果。这种方式在实际项目中,基本上没法用,原因有两个:一是性能很差,因为要复制到所有节点才返回响应;二是可用性也很差,主库和所有从库任何一个数据库出问题,都会影响业务。 +- **异步复制**(默认模型):MySQL 主库提交事务的线程并不会等待 binlog 同步到各从库,就返回客户端结果。这种模式一旦主库宕机,数据就会发生丢失。 +- **半同步复制**:MySQL 5.7 版本之后增加的一种复制方式,介于两者之间,事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行,比如一主二从的集群,只要数据成功复制到任意一个从库上,主库的事务线程就可以返回给客户端。这种**半同步复制的方式,兼顾了异步复制和同步复制的优点,即使出现主库宕机,至少还有一个从库有最新的数据,不存在数据丢失的风险**。 + + + +## 五、错误日志(error log) + +错误日志记录了 MySQL 服务器启动、停止运行的时间,以及系统启动、运行和停止过程中的诊断信息,包括错误、警告和提示等。当我们的数据库服务器发生系统故障时,错误日志是发现问题、解决故障的首选。 + +错误日志默认是开启的 + +```mysql +mysql> show variables like '%log_error%'; ++----------------------------+----------------------------------------+ +| Variable_name | Value | ++----------------------------+----------------------------------------+ +| binlog_error_action | ABORT_SERVER | +| log_error | /usr/local/mysql/data/mysqld.local.err | +| log_error_services | log_filter_internal; log_sink_internal | +| log_error_suppression_list | | +| log_error_verbosity | 2 | ++----------------------------+----------------------------------------+ +5 rows in set (0.01 sec) +``` + +我们可以看到错误日志的地址,当出现数据库不能正常启动、使用的时候,第一个查的就是错误日志,有时候错误日志中也会有些优化信息,比如告诉我们需要增大 InnoDB 引擎的 redo log 这种。 + + + +## 六、慢查询日志(slow query log) + +慢查询日志用来记录执行时间超过指定时长的查询。它的主要作用是,帮助我们发现那些执行时间特别长的 SQL 查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。 + +```mysql +mysql> show variables like '%slow_query%'; ++---------------------+------------------------------------------------------+ +| Variable_name | Value | ++---------------------+------------------------------------------------------+ +| slow_query_log | OFF | +| slow_query_log_file | /usr/local/mysql/data/starfishdeMacBook-Pro-slow.log | ++---------------------+------------------------------------------------------+ +2 rows in set (0.02 sec) +``` + +默认也是关闭的,其实我们说的慢查询日志,有两个值 + +> Mac 没有 my.ini/my.cnf 文件,需要自己搞,我们只对本次生效吧。`set global slow_query_log=1` + +```mysql +mysql> show variables like '%long_query_time%'; ++-----------------+-----------+ +| Variable_name | Value | ++-----------------+-----------+ +| long_query_time | 10.000000 | ++-----------------+-----------+ +1 row in set (0.01 sec) + +mysql> show variables like '%row_limit%'; ++------------------------+-------+ +| Variable_name | Value | ++------------------------+-------+ +| min_examined_row_limit | 0 | ++------------------------+-------+ +1 row in set (0.01 sec) +``` + +`long_query_time`:慢查询的时间阈值 + +`min_examined_row_limit`:查询扫描过的最少记录数,因为这个值默认是 0,所以常被我们忽略 + +查询的执行时间和最少扫描记录,共同组成了判别一个查询是否是慢查询的条件。如果查询扫描过的记录数大于等于这个变量的值,并且查询执行时间超过 long_query_time 的值,那么,这个查询就被记录到慢查询日志中;反之,则不被记录到慢查询日志中。 + + + +## 小结 + +感谢你读到这里,送你两道面试题吧 + +### 说下 一条 MySQL 更新语句的执行流程是怎样的吧? + +```mysql +mysql> update t set name='starfish' where salary > 999999; +``` + +server 层和 InnoDB 层之间是如何沟通: + +1. salary 有二级索引,行器先找引擎取扫描区间的第一行。根据这条二级索引记录中的主键值执行回表操作(即通过聚簇索引的B+树根节点一层一层向下找,直到在叶子节点中找到相应记录),将获取到的聚簇索引记录返回给 server 层。 +2. server 层得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样,如果一样的话就不更新了,如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作 +3. InnoDB 收到更新请求后,先更新记录的聚簇索引记录,再更新记录的二级索引记录。最后将更新结果返回给 server 层 +4. server 层继续向 InnoDB 索要下一条记录,由于已经通过 B+ 树定位到二级索引扫描区间 `[999999, +∞)` 的第一条二级索引记录,而记录又是被串联成单向链表,所以 InnoDB 直接通过记录头信息的 `next_record` 的属性即可获取到下一条二级索引记录。然后通过该二级索引的主键值进行回表操作,获取到完整的聚簇索引记录再返回给 server 层。 +5. 就这样一层一层的处理 + +具体执行流程: + +1. 先在 B+ 树中定位到该记录(这个过程也被称作**加锁读**),如果该记录所在的页面不在 buffer pool 里,先将其加载到 buffer pool 里再读取。 + +2. 首先更新聚簇索引记录。 更新聚簇索引记录时: + + ① 先向 Undo 页面写 undo 日志。不过由于这是在更改页面,所以修改 Undo 页面前需要先记录一下相应的 redo 日志。 + + ② 将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。 + + > 这里可以会有点疑惑。我们可以直接理解成先写 undo 再写 redo,这里修改后的页面并没有加入 buffer pool 的 flush 链表,记录的 redo 日志也没有加入到 redo log buffer。当这个函数执行完后,才会:先将这个过程产生的 redo 日志写入到 redo log buffer,再将这个过程修改的页面加入到 buffer pool 的 flush 链表中。 + +3. 更新其他的二级索引记录。 + + > 更新二级索引记录时不会再记录 undo 日志,但由于是在修改页面内容,会先记录相应的 redo 日志。 + +4. 记录该语句对应的 binlog 日志,此时记录的 binlog 并没有刷新到硬盘上的 binlog 日志文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 日志刷新到硬盘。 + +5. 引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。 + +### 日志执行顺序? + +时序上先 undo log,redo log 先 prepare, 再写 binlog,最后再把 redo log commit + + + +### 为什么需要记录 REDO + +redo log 是 Innodb 存储引擎层生成的日志,实现了事务中的**持久性**,主要**用于掉电等故障恢复**: + +1. 在系统遇到故障的恢复过程中,可以修复未完成的事务修改的数据。 +2. InnoDB 为了提高数据存取的效率,减少磁盘操作的频率,对数据的更新操作不会立即写到磁盘上,而是把数据更新先保存在内存中(**InnoDB Buffer Pool**),积累到一定程度,再集中进行磁盘读写操作。这样就存在一个问题:一旦出现宕机或者停电等异常情况,内存中保存的数据更新操作可能会丢失。为了保证数据库本身的一致性和**持久性**,InnoDB 维护了 REDO LOG。修改 Page 之前需要先将修改的内容记录到 REDO 中,并保证 REDO LOG 早于对应的 Page 落盘,也就是常说的 WAL。当故障发生导致内存数据丢失后,InnoDB 会在重启时,通过重放 REDO,将 Page 恢复到崩溃前的状态。 + +回答面试官问题时候,如果能指明不同版本的差异,会加分的 + + + +## References + +- https://dev.mysql.com/doc/refman/8.0/en/server-logs.html +- https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_INNODB_REDO_LOG_THREADS.html +- https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html +- [《庖丁解InnoDB之UNDO LOG》](http://mysql.taobao.org/monthly/2021/10/01/) +- [庖丁解InnoDB之Undo LOG](http://catkang.github.io/2021/10/30/mysql-undo.html) +- https://www.51cto.com/article/639652.html \ No newline at end of file diff --git a/docs/data-management/MySQL/MySQL-Master-Slave.md b/docs/data-management/MySQL/MySQL-Master-Slave.md index 30404ce4c5..fb7ba2e62f 100644 --- a/docs/data-management/MySQL/MySQL-Master-Slave.md +++ b/docs/data-management/MySQL/MySQL-Master-Slave.md @@ -1 +1,13 @@ -TODO \ No newline at end of file +TODO + + + + + +备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的: + +1. 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。 +2. 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。 +3. 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。 +4. 备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。 +5. sql_thread 读取中转日志,解析出日志里的命令,并执行。 \ No newline at end of file diff --git a/docs/data-management/MySQL/MySQL-Optimization.md b/docs/data-management/MySQL/MySQL-Optimization.md index e9689f2a35..89292e3b0e 100644 --- a/docs/data-management/MySQL/MySQL-Optimization.md +++ b/docs/data-management/MySQL/MySQL-Optimization.md @@ -1,422 +1,638 @@ -《高性能MySQL》给出的性能定义:完成某件任务所需要的的时间度量,性能既响应时间。 +--- +title: MySQL 优化 +date: 2024-05-09 +tags: + - MySQL +categories: MySQL +--- -假设性能优化就是在一定负载下尽可能的降低响应时间。 + -性能监测工具: **New Relic** **OneAPM** +> 《高性能MySQL》给出的性能定义:完成某件任务所需要的的时间度量,性能既响应时间。 +> +> 我们主要探讨 Select 的优化,包括 MySQL Server 做了哪些工作以及我们作为开发,如何定位问题,以及如何优化,怎么写出高性能 SQL -## 1. 影响mysql的性能因素 -##### 1.1 业务需求对mysql的影响(合适合度) -##### 1.2 存储定位对mysql的影响 +## 一、MySQL Server 优化了什么 -- 不适合放进mysql的数据 - - 二进制多媒体数据 - - 流水队列数据 - - 超大文本数据 -- 需要放进缓存的数据 - - 系统各种配置及规则数据 - - 活跃用户的基本信息数据 - - 活跃用户的个性化定制信息数据 - - 准实时的统计信息数据 - - 其他一些访问频繁但变更较少的数据 +### MySQL Query Optimizer -##### 1.3 Schema设计对系统的性能影响 +MySQL 中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间) -- 尽量减少对数据库访问的请求 -- 尽量减少无用数据的查询请求 +当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimizer 时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。 -##### 1.4 硬件环境对系统性能的影响 +MySQL 查询优化器是一个复杂的组件,它的主要任务是确定执行给定查询的最优方式。以下是 MySQL 查询优化器在处理查询时所进行的一些关键活动: -**典型OLTP应用系统** +#### 1.1 解析查询: - 什么是OLTP:OLTP即联机事务处理,就是我们经常说的关系数据库,意即记录即时的增、删、改、查,就是我们经常应用的东西,这是数据库的基础 +优化器首先解析查询语句,理解其语法和语义。 -对于各种数据库系统环境中大家最常见的OLTP系统,其特点是并发量大,整体数据量比较多,但每次访问的数据比较少,且访问的数据比较离散,活跃数据占总体数据的比例不是太大。对于这类系统的数据库实际上是最难维护,最难以优化的,对主机整体性能要求也是最高的。因为不仅访问量很高,数据量也不小。 +#### 1.2 词法和语法分析: -针对上面的这些特点和分析,我们可以对OLTP的得出一个大致的方向。 虽然系统总体数据量较大,但是系统活跃数据在数据总量中所占的比例不大,那么我们可以通过扩大内存容量来尽可能多的将活跃数据cache到内存中; 虽然IO访问非常频繁,但是每次访问的数据量较少且很离散,那么我们对磁盘存储的要求是IOPS表现要很好,吞吐量是次要因素; 并发量很高,CPU每秒所要处理的请求自然也就很多,所以CPU处理能力需要比较强劲; 虽然与客户端的每次交互的数据量并不是特别大,但是网络交互非常频繁,所以主机与客户端交互的网络设备对流量能力也要求不能太弱。 +检查查询语句是否符合SQL语法规则。 -**典型OLAP应用系统** +#### 1.3 语义分析: -用于数据分析的OLAP系统的主要特点就是数据量非常大,并发访问不多,但每次访问所需要检索的数据量都比较多,而且数据访问相对较为集中,没有太明显的活跃数据概念。 +确保查询引用的所有数据库对象(如表、列、别名等)都是存在的,并且用户具有相应的访问权限。 -什么是OLAP:OLAP即联机分析处理,是数据仓库的核心部心,所谓数据仓库是对于大量已经由OLTP形成的数据的一种分析型的数据库,用于处理商业智能、决策支持等重要的决策信息;数据仓库是在数据库应用到一定程序之后而对历史数据的加工与分析 基于OLAP系统的各种特点和相应的分析,针对OLAP系统硬件优化的大致策略如下: 数据量非常大,所以磁盘存储系统的单位容量需要尽量大一些; 单次访问数据量较大,而且访问数据比较集中,那么对IO系统的性能要求是需要有尽可能大的每秒IO吞吐量,所以应该选用每秒吞吐量尽可能大的磁盘; 虽然IO性能要求也比较高,但是并发请求较少,所以CPU处理能力较难成为性能瓶颈,所以CPU处理能力没有太苛刻的要求; +#### 1.4 查询重写: -虽然每次请求的访问量很大,但是执行过程中的数据大都不会返回给客户端,最终返回给客户端的数据量都较小,所以和客户端交互的网络设备要求并不是太高; +可能对查询进行一些变换,以提高其效率。例如,使用等价变换简化查询或应用数据库的视图定义。 -此外,由于OLAP系统由于其每次运算过程较长,可以很好的并行化,所以一般的OLAP系统都是由多台主机构成的一个集群,而集群中主机与主机之间的数据交互量一般来说都是非常大的,所以在集群中主机之间的网络设备要求很高。 +#### 1.5 确定执行计划: +优化器会生成一个或多个可能的执行计划,并估算每个计划的成本(如I/O操作、CPU使用等)。 +#### 1.6 选择最佳执行计划: -## 2. 性能分析 +执行成本包括 I/O 成本和 CPU 成本。MySQL 有一套自己的计算公式,在一条单表查询语句真正执行之前,MySQL 的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案,这个成本最低的方案就是所谓的`执行计划`,之后才会调用存储引擎提供的接口真正的执行查询。 -### 2.1 MySQL常见瓶颈 +#### 1.7 索引选择: -- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候 +确定是否使用索引以及使用哪个索引。考虑因素包括索引的选择性、查询条件、索引的前缀等。 -- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候 +#### 1.8 表访问顺序: -- 服务器硬件的性能瓶颈:top,free, iostat和vmstat来查看系统的性能状态 +对于涉及多个表的查询,优化器决定最佳的表访问顺序,以减少数据的访问量。 +#### 1.9 连接算法选择: +join 我们每天都在用,左、右连接、内连接就不详细介绍了 -**查看Linux系统性能的常用命令** + -MySQL数据库是常见的两个瓶颈是CPU和I/O的瓶颈。CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候,磁盘I/O瓶颈发生在装入数据远大于内存容量的时候,如果应用分布在网络上,那么查询量相当大的时候那么瓶颈就会出现在网络上。Linux中我们常用mpstat、vmstat、iostat、sar和top来查看系统的性能状态。 +对于连接操作,优化器会选择最合适的算法,如嵌套循环、块嵌套循环、哈希连接等。 -`mpstat`: mpstat是Multiprocessor Statistics的缩写,是实时系统监控工具。其报告为CPU的一些统计信息,这些信息存放在/proc/stat文件中。在多CPUs系统里,其不但能查看所有CPU的平均状况信息,而且能够查看特定CPU的信息。mpstat最大的特点是可以查看多核心cpu中每个计算核心的统计数据,而类似工具vmstat只能查看系统整体cpu情况。 +- **嵌套循环连接(Nested-Loop Join)**:驱动表只访问一次,但被驱动表却可能被多次访问,访问次数取决于对驱动表执行单表查询后的结果集中的记录条数的连接执行方式称之为`嵌套循环连接`。 -`vmstat`:vmstat命令是最常见的Linux/Unix监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况。这个命令是我查看Linux/Unix最喜爱的命令,一个是Linux/Unix都支持,二是相比top,我可以看到整个机器的CPU、内存、IO的使用情况,而不是单单看到各个进程的CPU使用率和内存使用率(使用场景不一样)。 + 左(外)连接的驱动表就是左边的那个表,右(外)连接的驱动表就是右边的那个表。内连接驱动表就无所谓了。 -`iostat`: 主要用于监控系统设备的IO负载情况,iostat首次运行时显示自系统启动开始的各项统计信息,之后运行iostat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。 +- **基于块的嵌套循环连接(Block Nested-Loop Join)**: 块嵌套循环连接是嵌套循环连接的优化版本。每次访问被驱动表,被驱动表的记录会被加载到内存中,与驱动表匹配,然后清理内存,然后再取下一条,这样 I/O 成本是超级高的。 -`sar`: sar(System Activity Reporter系统活动情况报告)是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读写情况、系统调用的使用情况、磁盘I/O、CPU效率、内存使用状况、进程活动及IPC有关的活动等。 + 所以为了减少了对被驱动表的访问次数,引入了 `join buffer` 的概念,执行连接查询前申请的一块固定大小的内存,先把若干条驱动表结果集中的记录装在这个`join buffer`中,然后开始扫描被驱动表,每一条被驱动表的记录一次性和`join buffer`中的多条驱动表记录做匹配,因为匹配的过程都是在内存中完成的,所以这样可以显著减少被驱动表的`I/O`代价。 -`top`:top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。比较准确的说,top命令提供了实时的对系统处理器的状态监视。它将显示系统中CPU最“敏感”的任务列表。该命令可以按CPU使用。内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。 +#### 1.10 子查询优化: -除了服务器硬件的性能瓶颈,对于MySQL系统本身,我们可以使用工具来优化数据库的性能,通常有三种:使用索引,使用EXPLAIN分析查询以及调整MySQL的内部配置。 +对于子查询,优化器决定是将其物化、转换为半连接、还是其他形式。 +#### 1.11 谓词下推: +将查询条件(谓词)尽可能地下推到存储引擎层面,以便尽早过滤数据。 -### 2.2 性能下降SQL慢 执行时间长 等待时间长 原因分析 +#### 1.12 分区修剪: -- 查询语句写的烂 -- 索引失效(单值 复合) -- 关联查询太多join(设计缺陷或不得已的需求) -- 服务器调优及各个参数设置(缓冲、线程数等) +如果表被分区,优化器会识别出只需要扫描的分区。 +#### 1.13 排序和分组优化: +优化器会考虑使用索引来执行排序和分组操作。 -### 2.3 MySql Query Optimizer +#### 1.14 临时表和物化: -1. Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间) +优化器可能会决定使用临时表来存储中间结果,以简化查询。 -2. 当客户端向MySQL 请求一条Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给MySQL Query Optimizer时,MySQL Query Optimizer 首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示Hint信息是否可以完全确定该Query 的执行计划。如果没有 Hint 或Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。 +#### 1.15 并行查询执行: +对于某些查询,优化器可以决定使用并行执行来提高性能。 +#### 1.16 执行计划缓存: -### 2.4 MySQL常见性能分析手段 +如果可能,优化器会重用之前缓存的执行计划,以减少解析和优化的开销。 -在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有**慢查询日志**,**EXPLAIN 分析查询**,**profiling分析**以及**show命令查询系统状态及系统变量**,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。 +#### 1.17 生成执行语句: -#### 2.4.1 性能瓶颈定位 +最终,优化器生成用于执行查询的底层指令。 -我们可以通过show命令查看MySQL状态及变量,找到系统的瓶颈: +#### 1.18 监控和调整: -```shell -Mysql> show status ——显示状态信息(扩展show status like ‘XXX’) +优化器的行为可以通过各种参数进行调整,以适应特定的工作负载和系统配置。 -Mysql> show variables ——显示系统变量(扩展show variables like ‘XXX’) +#### 1.19 统计信息更新: -Mysql> show innodb status ——显示InnoDB存储引擎的状态 +优化器依赖于表和索引的统计信息来做出决策,因此需要确保统计信息是最新的。 -Mysql> show processlist ——查看当前SQL执行,包括执行状态、是否锁表等 +InnoDB存储引擎的统计数据收集是数据库性能优化的重要组成部分,因为这些统计数据会被MySQL查询优化器用来生成查询执行计划。以下是InnoDB统计数据收集的一些关键点: -Shell> mysqladmin variables -u username -p password——显示系统变量 +1. **统计数据存储方式**: + - InnoDB提供了两种存储统计数据的方式:永久性统计数据和非永久性统计数据。 + - 永久性统计数据存储在磁盘上,服务器重启后依然存在。 + - 非永久性统计数据存储在内存中,服务器关闭时会被清除。 +2. **系统变量控制**: + - `innodb_stats_persistent`:控制是否使用永久性统计数据,默认在MySQL 5.6.6之后的版本中为ON。 + - `innodb_stats_persistent_sample_pages`:控制永久性统计数据采样的页数,默认值为2012。 + - `innodb_stats_transient_sample_pages`:控制非永久性统计数据采样的页数,默认值为82。 +3. **统计信息的更新**: + - `ANALYZE TABLE`:可以用于手动更新统计信息,它将重新计算表的统计数据3。 + - `innodb_stats_auto_recalc`:控制是否自动重新计算统计数据,默认为ON24。 +4. **统计数据的收集**: + - InnoDB通过采样页面来估计表中的行数和其他统计信息24。 + - `innodb_table_stats`和`innodb_index_stats`:这两个内部表存储了关于表和索引的统计数据24。 +5. **特定表的统计数据属性**: + - 在创建或修改表时,可以通过`STATS_PERSISTENT`、`STATS_AUTO_RECALC`和`STATS_SAMPLE_PAGES`属性来控制表的统计数据行为234。 +6. **NULL值的处理**: + - `innodb_stats_method`变量决定了在统计索引列不重复值的数量时如何对待NULL值310。 +7. **手动更新统计数据**: + - 可以手动更新`innodb_table_stats`和`innodb_index_stats`表中的统计数据,之后需要使用`FLUSH TABLE`命令让优化器重新加载统计信息4。 +8. **非永久性统计数据**: + - 当`innodb_stats_persistent`设置为OFF时,新创建的表将使用非永久性统计数据,这些数据存储在内存中45。 +9. **统计数据的自动更新**: + - 如果表中数据变动超过一定比例(默认10%),并且`innodb_stats_auto_recalc`为ON,InnoDB将自动更新统计数据34。 -Shell> mysqladmin extended-status -u username -p password——显示状态信息 -``` +通过以上信息,我们了解到 InnoDB 的统计数据收集是一个动态的过程,旨在帮助优化器做出更好的查询执行计划决策。数据库管理员可以根据系统的具体需求和性能指标来调整相关的系统变量,以优化统计数据的收集和使用。 +> 问个问题:为什么 InnoDB `rows`这个统计项的值是估计值呢? +> +> `InnoDB`统计一个表中有多少行记录的套路大概是这样的:按照一定算法(并不是纯粹随机的)选取几个叶子节点页面,计算每个页面中主键值记录数量,然后计算平均一个页面中主键值的记录数量乘以全部叶子节点的数量就算是该表的`n_rows`值。 +通过`EXPLAIN`或`EXPLAIN ANALYZE`命令可以查看查询优化器的执行计划,这有助于理解查询的执行方式,并据此进行优化。优化器的目标是找到最快、最高效的执行计划,但有时它也可能做出不理想的决策,特别是在数据量变化或统计信息不准确时。在这种情况下,可以通过调整索引、修改查询或使用SQL提示词来引导优化器做出更好的选择。 -#### 2.4.2 Explain(执行计划) -- 是什么:使用Explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈 -- 能干吗 - - 表的读取顺序 - - 数据读取操作的操作类型 - - 哪些索引可以使用 - - 哪些索引被实际使用 - - 表之间的引用 - - 每张表有多少行被优化器查询 -- 怎么玩 +## 二、业务开发者可以优化什么 - - Explain + SQL语句 - - 执行计划包含的信息 + - -- 各字段解释 - - **id**(select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序) - - id相同,执行顺序从上往下 - - id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行 - - id相同不同,同时存在 - - **select_type**(查询的类型,用于区别普通查询、联合查询、子查询等复杂查询) +假设性能优化就是在一定负载下尽可能的降低响应时间。那我们作为一名业务开发,想优化 MySQL,一般就是优化 CRUD 性能,那优化的前提肯定是比较烂,才优化,要么是服务器或者网络烂,要么是 DB 设计的烂,当然更多的一般是 SQL 写的烂。 - - **SIMPLE** :简单的select查询,查询中不包含子查询或UNION - - **PRIMARY**:查询中若包含任何复杂的子部分,最外层查询被标记为PRIMARY - - **SUBQUERY**:在select或where列表中包含了子查询 - - **DERIVED**:在from列表中包含的子查询被标记为DERIVED,mysql会递归执行这些子查询,把结果放在临时表里 - - **UNION**:若第二个select出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层select将被标记为DERIVED - - **UNION RESULT**:从UNION表获取结果的select + - - **table**(显示这一行的数据是关于哪张表的) +### 2.1 影响 MySQL 的性能因素 | 常见瓶颈 - - **type**(显示查询使用了那种类型,从最好到最差依次排列 **system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL** ) +- ##### 硬件资源 - - system:表只有 一行记录(等于系统表),是const类型的特例,平时不会出现 - - const:表示通过索引一次就找到了,const用于比较primary key或unique索引,因为只要匹配一行数据,所以很快,如将主键置于where列表中,mysql就能将该查询转换为一个常量 - - eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描 - - ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体 - - range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引 - - index:Full Index Scan,index于ALL区别为index类型只遍历索引树。通常比ALL快,因为索引文件通常比数据文件小。(**也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的**) - - all:Full Table Scan,将遍历全表找到匹配的行 + - CPU:CPU 的性能直接影响到 MySQL 的计算速度。多核 CPU 可以提高并发处理能力 - ?> 一般来说,得保证查询至少达到range级别,最好到达ref + - 内存:足够的内存可以有效减少磁盘 I/O,提高缓存效率 - - **possible_keys**(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用) - - - **key** + - 磁盘:磁盘的 I/O 性能对数据库读写速度有显著影响,特别是对于读密集型操作。比如装入数据远大于内存容量的时候,磁盘 I/O 就会达到瓶颈 - - (实际使用的索引,如果为NULL,则没有使用索引) +- ##### 网络 - - **查询中若使用了覆盖索引,则该索引和查询的select字段重叠,仅出现在key列表中** + 网络带宽和延迟也会影响分布式数据库或应用服务器与数据库服务器之间的通信效率 -  +- ##### DB 设计 - - **key_len** - - 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 - - key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的 + - 存储引擎的选择 + - 参数配置,如 `innodb_buffer_pool_size`,对数据库性能有决定性影响 - - **ref** (显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值) - - **rows** (根据表统计信息及索引选用情况,大致估算找到所需的记录所需要读取的行数) - - **Extra**(包含不适合在其他列中显示但十分重要的额外信息) +- ##### 连接数和线程管理: - 1. using filesort: 说明mysql会对数据使用一个外部的索引排序,不是按照表内的索引顺序进行读取。mysql中无法利用索引完成的排序操作称为“文件排序” + - 高并发时,连接数和线程的高效管理对性能至关重要 - 2. Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。 +- ##### 数据库设计 - 3. using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错,如果同时出现using where,表明索引被用来执行索引键值的查找;否则索引被用来读取数据而非执行查找操作 + - 合理的表结构设计、索引优化、数据类型选择等都会影响性能 + - 比如哪种超大文本、二进制媒体数据啥的就别往 MySQL 放了 - 4. using where:使用了where过滤 +- ##### 开发的技术水平 - 5. using join buffer:使用了连接缓存 + - 开发的水平对性能的影响,查询语句写的烂,比如不管啥上来就 `SELECT *`,一个劲的 `left join` + - 索引失效(单值 复合) - 6. impossible where:where子句的值总是false,不能用来获取任何元祖 +当然锁、长事务这类使用中的坑,会对性能造成影响,我也举不全。 - 7. select tables optimized away:在没有group by子句的情况下,基于索引优化操作或对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化 - 8. distinct:优化distinct操作,在找到第一匹配的元祖后即停止找同样值的动作 - +### 2.2 性能分析 -- case: +#### 先查外忧 - +外忧,就是我们业务开发,一般情况下不用解决,或者一般这锅背不到我们头上的问题,比如硬件、网络这种 - - -1. 第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表 示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name......】 +> 查看 Linux 系统性能的常用命令 +> +> MySQL 数据库是常见的两个瓶颈是 CPU 和 I/O 的瓶颈。CPU 在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候,磁盘 I/O 瓶颈发生在装入数据远大于内存容量的时候,如果应用分布在网络上,那么查询量相当大的时候那么瓶颈就会出现在网络上。Linux 中我们常用 mpstat、vmstat、iostat、sar 和 top 来查看系统的性能状态。 +> +> `mpstat`: mpstat 是 Multiprocessor Statistics 的缩写,是实时系统监控工具。其报告为 CPU 的一些统计信息,这些信息存放在 /proc/stat 文件中。在多CPUs系统里,其不但能查看所有CPU的平均状况信息,而且能够查看特定CPU的信息。mpstat最大的特点是可以查看多核心cpu中每个计算核心的统计数据,而类似工具vmstat只能查看系统整体cpu情况。 +> +> `vmstat`:vmstat 命令是最常见的 Linux/Unix 监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的 CPU 使用率,内存使用,虚拟内存交换情况,IO 读写情况。这个命令是我查看 Linux/Unix 最喜爱的命令,一个是 Linux/Unix 都支持,二是相比top,我可以看到整个机器的CPU、内存、IO的使用情况,而不是单单看到各个进程的CPU使用率和内存使用率(使用场景不一样)。 +> +> `iostat`: 主要用于监控系统设备的 IO 负载情况,iostat 首次运行时显示自系统启动开始的各项统计信息,之后运行 iostat 将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。 +> +> `sar`: sar(System Activity Reporter系统活动情况报告)是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读写情况、系统调用的使用情况、磁盘 I/O、CPU效率、内存使用状况、进程活动及 IPC 有关的活动等。 +> +> `top`:top 命令是 Linux 下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于 Windows 的任务管理器。top 显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。比较准确的说,top 命令提供了实时的对系统处理器的状态监视。它将显示系统中 CPU 最“敏感”的任务列表。该命令可以按 CPU 使用。内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。 -2. 第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,name from t1 where other_column=''】 -3. 第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】 -4. 第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】 -5. 第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的表示用第一个和第四个select的结果进行union操作。【两个结果union操作】 +除了服务器硬件的性能瓶颈,对于 MySQL 系统本身,我们可以使用工具来优化数据库的性能,通常有三种:使用索引,使用 EXPLAIN 分析查询以及调整 MySQL 的内部配置。 -#### 2.4.3 慢查询日志 +#### 再定内患 | MySQL 常见性能分析手段 -MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。 +在优化 MySQL 时,通常需要对数据库进行分析,常见的分析手段有**慢查询日志**,**EXPLAIN 分析查询**,**profiling 分析**以及**show命令查询系统状态及系统变量**,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。 -- long_query_time的默认值为10,意思是运行10秒以上的语句。 -- 默认情况下,MySQL数据库没有开启慢查询日志,需要手动设置参数开启。 -- 如果不是调优需要的话,一般不建议启动该参数。 +##### 2.2.1 性能瓶颈定位 -**查看开启状态** - -`SHOW VARIABLES LIKE '%slow_query_log%'` - -**开启慢查询日志** - -- 临时配置: +我们可以通过 show 命令查看 MySQL 状态及变量,找到系统的瓶颈: ```mysql -mysql> set global slow_query_log='ON'; -mysql> set global slow_query_log_file='/var/lib/mysql/hostname-slow.log'; -mysql> set global long_query_time=2; -``` +Mysql> show status ——显示状态信息(扩展show status like ‘XXX’) - 也可set文件位置,系统会默认给一个缺省文件host_name-slow.log +Mysql> show variables ——显示系统变量(扩展show variables like ‘XXX’) - 使用set操作开启慢查询日志只对当前数据库生效,如果MySQL重启则会失效。 +Mysql> show innodb status ——显示InnoDB存储引擎的状态 -- 永久配置 +Mysql> show processlist ——查看当前SQL执行,包括执行状态、是否锁表等 - 修改配置文件my.cnf或my.ini,在[mysqld]一行下面加入两个配置参数 +Shell> mysqladmin variables -u username -p password——显示系统变量 -```cnf -[mysqld] -slow_query_log = ON -slow_query_log_file = /var/lib/mysql/hostname-slow.log -long_query_time = 3 +Shell> mysqladmin extended-status -u username -p password——显示状态信息 ``` - 注:log-slow-queries参数为慢查询日志存放的位置,一般这个目录要有mysql的运行帐号的可写权限,一般都 将这个目录设置为mysql的数据存放目录;long_query_time=2中的2表示查询超过两秒才记录;在my.cnf或者 my.ini中添加log-queries-not-using-indexes参数,表示记录下没有使用索引的查询。 - -可以用 `select sleep(4)` 验证是否成功开启。 -在生产环境中,如果手工分析日志,查找、分析SQL,还是比较费劲的,所以MySQL提供了日志分析工具mysqldumpslow。 -通过 mysqldumpslow --help查看操作帮助信息 +##### 2.2.2 Explain(执行计划) -- 得到返回记录集最多的10个SQL +- 是什么:使用 Explain 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈 +- 能干吗 + - 表的读取顺序 + - 数据读取操作的操作类型 + - 哪些索引可以使用 + - 哪些索引被实际使用 + - 表之间的引用 + - 每张表有多少行被优化器查询 - `mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log` +- 怎么玩 -- 得到访问次数最多的10个SQL + - Explain + SQL语句 + - 执行计划包含的信息 + + 这里我建两个一样的表 t1、t2 用于示例说明 + + ```mysql + CREATE TABLE t1 ( + id INT NOT NULL AUTO_INCREMENT, + col1 VARCHAR(100), + col2 INT, + col3 VARCHAR(100), + part1 VARCHAR(100), + part2 VARCHAR(100), + part3 VARCHAR(100), + common_field VARCHAR(100), + PRIMARY KEY (id), + KEY idx_key1 (col1), + UNIQUE KEY idx_key2 (col2), + KEY idx_key3 (col3), + KEY idx_key_part(part1, part2, part3) + ) Engine=InnoDB CHARSET=utf8; + ``` - `mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log` +- 各字段解释 -- 得到按照时间排序的前10条里面含有左连接的查询语句 + - **id**(select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序) - `mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log` + - id 相同,执行顺序从上往下 + - id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行 + - id 相同不同,同时存在,相同的属于一组,从上往下执行 -- 也可以和管道配合使用 + - **select_type**(查询的类型,用于区别普通查询、联合查询、子查询等复杂查询) - `mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more` + - **SIMPLE** :简单的 select 查询,查询中不包含子查询或 UNION + - **PRIMARY**:查询中若包含任何复杂的子部分,最外层查询被标记为 PRIMARY + - **SUBQUERY**:在 select 或 where 列表中包含了子查询 + - **DERIVED**:在 from 列表中包含的子查询被标记为 DERIVED,mysql 会递归执行这些子查询,把结果放在临时表里 + - **UNION**:若第二个 select 出现在 UNION 之后,则被标记为 UNION,若 UNION 包含在 from 子句的子查询中,外层 select 将被标记为 DERIVED + - **UNION RESULT**:从 UNION 表获取结果的 select -**也可使用 pt-query-digest 分析 RDS MySQL 慢查询日志** + - **table**(显示这一行的数据是关于哪张表的) + - **partitions**(匹配的分区信息,高版本才有的) + - **type**(显示查询使用了那种类型,从最好到最差依次排列 **system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL** ) -#### 2.4.4 Show Profile分析查询 + - system:表只有一行记录(等于系统表),是 const 类型的特例,平时不会出现 + - const:表示通过索引一次就找到了,const 用于比较 primary key 或 unique 索引,因为只要匹配一行数据,所以很快,如将主键置于 where 列表中,mysql 就能将该查询转换为一个常量 + - eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描 + - ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体 + - ref_or_null:当对普通二级索引进行等值匹配查询,该索引列的值也可以是`NULL`值时,那么对该表的访问方法就可能是ref_or_null + - index_merge: 在某些场景下可以使用`Intersection`、`Union`、`Sort-Union`这三种索引合并的方式来执行查询 + - range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般就是在你的 where 语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引 + - index:Full Index Scan,index 于 ALL 区别为 index 类型只遍历索引树。通常比 ALL 快,因为索引文件通常比数据文件小。(**也就是说虽然 all 和 index 都是读全表,但 index 是从索引中读取的,而 all 是从硬盘中读的**) + - all:Full Table Scan,将遍历全表找到匹配的行 -通过慢日志查询可以知道哪些SQL语句执行效率低下,通过explain我们可以得知SQL语句的具体执行情况,索引使用等,还可以结合Show Profile命令查看执行状态。 + > 一般来说,得保证查询至少达到 range 级别,最好到达 ref -- Show Profile是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量 + - **possible_keys**(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用) + + - **key** -- 默认情况下,参数处于关闭状态,并保存最近15次的运行结果 + - 实际使用的索引,如果为NULL,则没有使用索引 -- 分析步骤 + - **查询中若指定了使用了覆盖索引,则该索引和查询的 select 字段重叠,仅出现在 key 列表中** - 1. 是否支持,看看当前的mysql版本是否支持 + - **key_len** + + - 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 + - key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的 + + - **ref** (显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值) - ```mysql - mysql>Show variables like 'profiling'; --默认是关闭,使用前需要开启 - ``` + - **rows** (根据表统计信息及索引选用情况,大致估算找到所需的记录所需要读取的行数) - 2. 开启功能,默认是关闭,使用前需要开启 + - **filtered**(某个表经过搜索条件过滤后剩余记录条数的百分比) - ```mysql - mysql>set profiling=1; - ``` + - **Extra**(包含不适合在其他列中显示但十分重要的额外信息) - 3. 运行SQL + 额外信息有好几十个,我们看几个常见的 - 4. 查看结果,show profiles; + 1. `using filesort`:说明 MySQL 会对数据使用一个外部的索引排序,不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称为“文件排序” - 5. 诊断SQL,show profile cpu,block io for query 上一步前面的问题SQL数字号码; + 2. `Using temporary`:使用了临时表保存中间结果,比如去重、排序之类的,比如我们在执行许多包含`DISTINCT`、`GROUP BY`、`UNION`等子句的查询过程中,如果不能有效利用索引来完成查询,`MySQL`很有可能寻求通过建立内部的临时表来执行查询。 - 6. 日常开发需要注意的结论 + 3. `using index`:表示相应的 select 操作中使用了覆盖索引,避免访问了表的数据行,效率不错,如果同时出现 `using where`,表明索引被用来执行索引键值的查找;否则索引被用来读取数据而非执行查找操作 - - converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。 + 4. `using where`:当某个搜索条件需要在`server层`进行判断时 - - create tmp table 创建临时表,这个要注意 + 5. `using join buffer`:使用了连接缓存 - - Copying to tmp table on disk 把内存临时表复制到磁盘 + 6. `impossible where`:where 子句的值总是 false,不能用来获取任何元祖 - - locked + 7. `Using index condition` : 查询使用了索引,但是查询条件不能完全由索引本身来满足 + `Using index condition `通常出现在以下几种情况: + + - **索引条件下推(Index Condition Pushdown, ICP)**:这是 MySQL 的一个优化策略,它将查询条件的过滤逻辑“下推”到存储引擎层,而不是在服务器层处理。这样可以减少从存储引擎检索的数据量,从而提高查询效率。 + - **部分索引**:当查询条件只涉及索引的一部分列时,MySQL 可以使用索引来快速定位到满足条件的行,但是可能需要回表(即访问表的实际数据行)来检查剩余的条件。 + - **复合索引**:在使用复合索引(即索引包含多个列)的情况下,如果查询条件只匹配索引的前几列,那么剩余的列可能需要通过 `Using index condition` 来进一步过滤。 + + 8. `select tables optimized away`:在没有group by子句的情况下,基于索引优化操作或对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化 + + 9. `distinct`:优化distinct操作,在找到第一匹配的元祖后即停止找同样值的动作 + + > **Json格式的执行计划** + > + > MySQL 5.7及以上版本支持使用`EXPLAIN FORMAT=JSON`命令,该命令返回查询的执行计划,以 JSON 格式展示。 + > + > `EXPLAIN` 的 JSON 输出包含了多个层次和字段,以下是一些主要的字段: + > + > - **`query_block`**: 表示查询块,对应于`EXPLAIN`表格中的一行。对于简单查询,只有一个查询块;对于包含子查询或UNION的复杂查询,会有多个查询块。 + > - **`select_id`**: 查询块的唯一标识符。 + > - **`select_type`**: 查询类型(如`SIMPLE`、`PRIMARY`、`UNION`等)。 + > - **`table`**: 正在访问的表名。 + > - **`partitions`**: 表分区信息。 + > - **`join`**: 如果是连接查询,这里会提供连接的详细信息。 + > - **`condition`**: 应用的条件。 + > - **`used_columns`**: 实际使用到的列。 + > - **`attached_condition`**: 附加条件,如`WHERE`子句或`ON`子句的条件。 + > - **`output`**: 表示查询块的输出,通常是一个子查询或派生表。 + > - **`cost_model`**: 包含成本模型相关的信息,如: + > - **`rows_estimated`**: 估计需要读取的行数。 + > - **`rows_examined`**: 实际检查的行数。 + > - **`cost`**: 查询的成本。 + > - **`execution_info`**: 包含执行信息,如: + > - **`execution_mode`**: 执行模式(如`PACKET_BASED`或`ROW_BASED`)。 + > + > ```mysql + > mysql> EXPLAIN FORMAT=JSON SELECT * FROM t1 INNER JOIN t2 ON t1.col1 = t2.col2 WHERE t1.common_field = 'a'\G + > *************************** 1. row *************************** + > EXPLAIN: { + > "query_block": { + > "select_id": 1, + > "cost_info": { + > "query_cost": "0.70" + > }, + > "nested_loop": [ + > { + > "table": { + > "table_name": "t1", # t1表是驱动表 + > "access_type": "ALL", # 访问方法为ALL,意味着使用全表扫描访问 + > "possible_keys": [ # 可能使用的索引 + > "idx_key1" + > ], + > "rows_examined_per_scan": 1, + > "rows_produced_per_join": 1, + > "filtered": "100.00", + > "cost_info": { + > "read_cost": "0.25", + > "eval_cost": "0.10", + > "prefix_cost": "0.35", + > "data_read_per_join": "1K" + > }, + > "used_columns": [ + > "id", + > "col1", + > "col2", + > "col3", + > "part1", + > "part2", + > "part3", + > "common_field" + > ], + > "attached_condition": "((`fish`.`t1`.`common_field` = 'a') and (`fish`.`t1`.`col1` is not null))" + > } + > }, + > { + > "table": { + > "table_name": "t2", + > "access_type": "eq_ref", + > "possible_keys": [ + > "idx_key2" + > ], + > "key": "idx_key2", + > "used_key_parts": [ + > "col2" + > ], + > "key_length": "5", + > "ref": [ + > "fish.t1.col1" + > ], + > "rows_examined_per_scan": 1, + > "rows_produced_per_join": 1, + > "filtered": "100.00", + > "index_condition": "(cast(`fish`.`t1`.`col1` as double) = cast(`fish`.`t2`.`col2` as double))", + > "cost_info": { + > "read_cost": "0.25", + > "eval_cost": "0.10", + > "prefix_cost": "0.70", + > "data_read_per_join": "1K" + > }, + > "used_columns": [ + > "id", + > "col1", + > "col2", + > "col3", + > "part1", + > "part2", + > "part3", + > "common_field" + > ] + > } + > } + > ] + > } + > } + > ``` + +##### 2.2.3 OPTIMIZER TRACE + +MySQL 的 `OPTIMIZER TRACE` 特性提供了一种深入理解查询优化器如何决定执行计划的方法。通过这个特性,你可以获取关于查询优化过程的详细信息,包括优化器所做的选择、考虑的替代方案、成本估算等 + +MySQL 5.6.3 版本开始,`OPTIMIZER TRACE` 作为一个系统变量引入。要使用它,你需要设置 `optimizer_trace` 变量为 `ON`,并确保你有 `TRACE` 权限。 + +启用 `OPTIMIZER TRACE`: +```mysql +SET optimizer_trace="enabled=on"; +``` -## 3. 性能优化 +执行你的查询后,获取优化器跟踪结果: -### 3.1 索引优化 +```mysql +SELECT * FROM information_schema.OPTIMIZER_TRACE; +``` -1. 全值匹配我最爱 -2. 最佳左前缀法则 -3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描 -4. 存储引擎不能使用索引中范围条件右边的列 -5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select -6. mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描 -7. is null ,is not null 也无法使用索引 -8. like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作 -9. 字符串不加单引号索引失效 -10. 少用or,用它来连接时会索引失效 +`OPTIMIZER_TRACE `表包含了多个列,提供了优化器决策过程的详细信息。以下是一些关键列: +- **`query`**: 执行的SQL查询。 +- **`trace`**: 优化过程的详细跟踪信息,通常以JSON格式展示。 +- **`missed_uses`**: 优化器未能使用的潜在优化。 +- **`step`**: 优化过程中的步骤编号。 +- **`level`**: 跟踪信息的层次级别。 +- **`OK`**: 指示步骤是否成功完成。 +- **`reason`**: 如果步骤未成功,原因说明。 -**一般性建议** -- 对于单键索引,尽量选择针对当前query过滤性更好的索引 +##### 2.2.4 慢查询日志 -- 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。 +MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阈值的语句,具体指运行时间超过`long_query_time` 值的SQL,则会被记录到慢查询日志中。 -- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引 +- `long_query_time` 的默认值为10,意思是运行10 秒以上的语句。 +- 默认情况下,MySQL 数据库没有开启慢查询日志,需要手动设置参数开启。 +- 如果不是调优需要的话,一般不建议启动该参数。 -- 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的 +**开启慢查询日志** -- 少用Hint强制索引 +临时配置: - +```mysql +mysql> set global slow_query_log='ON'; +mysql> set global slow_query_log_file='/var/lib/mysql/hostname-slow.log'; +mysql> set global long_query_time=2; +``` -### 3.2 查询优化 +可以用 `select sleep(4)` 验证是否成功开启。 -- **永远小标驱动大表(小的数据集驱动大的数据集)** +在生产环境中,如果手工分析日志,查找、分析SQL,还是比较费劲的,所以 MySQL 提供了日志分析工具 `mysqldumpslow`。 + +> 通过 mysqldumpslow --help 查看操作帮助信息 +> +> - 得到返回记录集最多的10个SQL +> +> `mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log` +> +> - 得到访问次数最多的10个SQL +> +> `mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log` +> +> - 得到按照时间排序的前10条里面含有左连接的查询语句 +> +> `mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log` +> +> - 也可以和管道配合使用 +> +> `mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more` - `slect * from A where id in (select id from B)` +**也可使用 pt-query-digest 分析 RDS MySQL 慢查询日志** - 等价于 - `select id from B` - `select * from A where A.id=B.id` +##### 2.2.5 Show Profile 分析查询 - 当B表的数据集必须小于A表的数据集时,用in优于exists +通过慢日志查询可以知道哪些 SQL 语句执行效率低下,通过 explain 我们可以得知 SQL 语句的具体执行情况,索引使用等,还可以结合`Show Profile` 命令查看执行状态。 - `select * from A where exists (select 1 from B where B.id=A.id)` +- `Show Profile`是 MySQL 提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于 SQL 的调优的测量 +- 默认情况下,参数处于关闭状态,并保存最近 15 次的运行结果 +- 分析步骤 - 等价于 +1. 启用profiling并执行查询: - `select *from A` + ```mysql + SET profiling = 1; + SELECT * FROM t1 WHERE col1 = 'a'; + ``` - `select * from B where B.id = A.id` +2. 查看所有PROFILES: - 当A表的数据集小于B表的数据集时,用exists优于用in + ```mysql + SHOW PROFILES; + ``` - 注意:A表与B表的ID字段应建立索引。 +3. 查看特定查询的 PROFILE, 进行诊断: - + ```mysql + SHOW PROFILE CPU, BLOCK IO FOR QUERY query_id; + ``` -- order by关键字优化 + -- - order by子句,尽量使用Index方式排序,避免使用FileSort方式排序 -- - - mysql支持两种方式的排序,FileSort和Index,Index效率高,它指MySQL扫描索引本身完成排序,FileSort效率较低; - - ORDER BY 满足两种情况,会使用Index方式排序;①ORDER BY语句使用索引最左前列 ②使用where子句与ORDER BY子句条件列组合满足索引最左前列 - +#### 2.3 性能优化 -- - 尽可能在索引列上完成排序操作,遵照索引建的最佳最前缀 - - 如果不在索引列上,filesort有两种算法,mysql就要启动双路排序和单路排序 +##### 2.3.1 索引优化 -- - - 双路排序 - - 单路排序 - - 由于单路是后出的,总体而言好过双路 +1. **选择合适的索引类型**:根据查询需求,选择适合的索引类型,如普通索引、唯一索引、全文索引等。 +2. **合理设计索引**:创建索引时,应考虑列的选择性,即不同值的比例,选择性高的列更应优先索引。 +3. **使用覆盖索引**:创建覆盖索引,使得查询可以直接从索引中获取所需的数据,而无需回表查询。 +4. **遵循最左前缀匹配原则**:在使用复合索引时,查询条件应该包含索引最左边的列,以确保索引的有效使用。 +5. **避免冗余和重复索引**:定期检查并删除重复或冗余的索引,以减少维护成本和提高性能。 +6. **使用索引条件下推(ICP)**:在MySQL 5.6及以上版本,利用索引条件下推减少服务器层的数据处理。 +7. **索引维护**:定期对索引进行维护,如重建索引,以保持索引的性能和效率。 +8. **使用EXPLAIN分析查询**:通过EXPLAIN命令分析查询的执行计划,确保索引被正确使用。 +9. **避免在索引列上进行运算或使用函数**:这会导致索引失效,从而进行全表扫描。 +10. **负向条件索引**:负向条件查询(如不等于、not in等)不会使用索引,建议用in查询优化。 +11. **对文本建立前缀索引**:对于长文本字段,考虑建立前缀索引以减少索引大小并提高效率。 +12. **建立索引的列不为NULL**:NULL值会影响索引的有效性,尽量确保索引列不包含NULL值。 +13. **明确知道只会返回一条记录时,使用limit 1**:这可以提高查询效率。 +14. **范围查询字段放最后**:在复合索引中,将范围查询的字段放在最后,以提高查询效率。 +15. **尽量全值匹配**:尽量使用索引列的全值匹配,以提高索引的使用效率。 +16. **Like查询时,左侧不要加%**:这会导致索引失效,应尽量避免。 +17. **注意null/not null对索引的影响**:null值可能会影响索引的使用,需要特别注意。 +18. **字符类型务必加上引号**:确保字符类型的值在使用时加上引号,以利用索引。 +19. **OR关键字左右尽量都为索引列**:确保OR条件的两侧字段都是索引列,以提高查询效率。 -- - 优化策略 +这些策略可以帮助提高 MySQL 数据库的性能,但应根据具体的数据分布和查询模式来设计索引。索引不是越多越好,不恰当的索引可能会降低数据库的插入、更新和删除性能 -- - - 增大sort_buffer_size参数的设置 - - 增大max_lencth_for_sort_data参数的设置 -  +##### 2.3.2 查询优化 -- GROUP BY关键字优化 - - group by实质是先排序后进行分组,遵照索引建的最佳左前缀 - - 当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置 - - where高于having,能写在where限定的条件就不要去having限定了。 +1. **减少返回的列**:避免使用`SELECT *`,只选择需要的列。 +2. **减少返回的行**:使用WHERE子句精确过滤数据、使用LIMIT子句限制返回结果的数量。 +3. **优化JOIN操作**: + - 确保JOIN操作中的表已经正确索引。 + - 使用小表驱动大表的原则。 + - 考虑表的连接顺序,减少中间结果集的大小。 +4. **优化子查询**:将子查询转换为连接(JOIN)操作,以减少数据库的嵌套查询开销。 +5. **使用临时表或派生表**:对于复杂的子查询,可以考虑使用临时表或派生表来简化查询。 +6. **使用聚合函数和GROUP BY优化**: + - 在适当的情况下使用聚合函数减少数据量。 + - 确保GROUP BY子句中的列上有索引。 +7. **避免在WHERE子句中使用函数**:这可能导致索引失效,从而进行全表扫描。 +8. **优化ORDER BY子句**:如果可能,使用索引排序来优化排序操作。 +9. **使用UNION ALL代替UNION**:如果不需要去重,使用UNION ALL可以提高性能。 +10. **优化数据表结构**:避免冗余字段,使用合适的数据类型。 +11. **使用分区表**:对于非常大的表,使用分区可以提高查询效率。 +12. **使用缓存**:对于重复查询的数据,使用缓存来减少数据库的访问。 +13. **避免使用SELECT DISTINCT**:DISTINCT操作会降低查询性能,仅在必要时使用。 +14. **优化LIKE语句**:避免在LIKE语句中使用前导通配符(如'%keyword')。 +15. **使用合适的事务隔离级别**:降低事务隔离级别可以减少锁的竞争,但要注意数据一致性。 +16. **监控和优化慢查询**:开启慢查询日志,定期分析慢查询,找出性能瓶颈。 +17. **批量操作**:对于插入或更新操作,尽量使用批量操作来减少事务开销。 +18. **避免在索引列上使用OR条件**:OR条件可能导致查询优化器放弃使用索引。 +19. **使用物化视图**:对于复杂的查询,可以考虑使用物化视图来预先计算并存储结果。 -### 3.3 数据类型优化 +##### 2.3.3 数据类型优化 -MySQL支持的数据类型非常多,选择正确的数据类型对于获取高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。 +MySQL 支持的数据类型非常多,选择正确的数据类型对于获取高性能至关重要。不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择。 - 更小的通常更好:一般情况下,应该尽量使用可以正确存储数据的最小数据类型。 @@ -424,7 +640,50 @@ MySQL支持的数据类型非常多,选择正确的数据类型对于获取高 - 尽量避免NULL:通常情况下最好指定列为NOT NULL + + +> 规范: +> +> - 必须把字段定义为NOT NULL并且提供默认值 +> - null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化 +> - null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多 +> - null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识 +> - 对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录 +> +> - **禁止使用TEXT、BLOB类型**:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能 +> +> - **禁止使用小数存储货币** +> - 必须使用varchar(20)存储手机号 +> +> - **禁止使用ENUM,可使用TINYINT代替** +> +> - 禁止在更新十分频繁、区分度不高的属性上建立索引 +> +> - 禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性 +> +> - 禁止使用属性隐式转换:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描 +> +> - 禁止在WHERE条件的属性上使用函数或者表达式 +> +> - `SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' `会导致全表扫描 +> +> - 正确的写法是:`SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')` +> +> - 建立组合索引,必须把区分度高的字段放在前面 +> +> - 禁止负向查询,以及%开头的模糊查询 +> +> - 负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描 +> - %开头的模糊查询,会导致全表扫描 +> +> - 禁止大表使用JOIN查询,禁止大表使用子查询:会产生临时表,消耗较多内存与CPU,极大影响数据库性能 +> + +### References + +- [58到家数据库30条军规解读](https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651959906&idx=1&sn=2cbdc66cfb5b53cf4327a1e0d18d9b4a&chksm=bd2d07be8a5a8ea86dc3c04eced3f411ee5ec207f73d317245e1fefea1628feb037ad71531bc&scene=21#wechat_redirect) + + -> https://www.jianshu.com/p/3c79039e82aa diff --git a/docs/data-management/MySQL/MySQL-Schema.md b/docs/data-management/MySQL/MySQL-Schema.md deleted file mode 100644 index 411e779fcb..0000000000 --- a/docs/data-management/MySQL/MySQL-Schema.md +++ /dev/null @@ -1,237 +0,0 @@ -- 尽量避免过度设计,例如会导致极其复杂查询的schema设计,或者有很多列的表设计 -- 使用小而简单的合适数据类型,除非真实数据模型中有确切的需要,否则应该尽可能的避免使用NULL值。 -- 尽量使用相同的数据类型存储相似或相关的值,尤其是要在关联条件中使用的列。 -- 注意可变长字符串,其在临时表和排序时可能导致悲观的按最大长度分配内存。 -- 尽量使用整型定义标识列。 -- 避免使用MySQL已经遗弃的特性,例如指定浮点数的精度,或者整数的显示宽度。 -- 小心使用ENUM和SET。 -- 最好避免使用BIT。 - - - - - -# 58到家数据库30条军规解读 - -**军规适用场景**:并发量大、数据量大的互联网业务 - -**军规**:介绍内容 - -**解读**:讲解原因,解读比军规更重要 - - - -## 一、基础规范 - -**(1)必须使用InnoDB存储引擎** - -解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高 - - - -**(2)必须使用UTF8字符集** - -解读:万国码,无需转码,无乱码风险,节省空间 - - - -**(3)数据表、数据字段必须加入中文注释** - -解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的 - - - -**(4)禁止使用存储过程、视图、触发器、Event** - -解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,CPU计算还是上移吧 - - - -**(5)禁止存储大文件或者大照片** - -解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存URI多好 - - - -## 二、命名规范 - -**(6)只允许使用内网域名,而不是ip连接数据库** - - - -**(7)线上环境、开发环境、测试环境数据库内网域名遵循命名规范** - -业务名称:xxx - -线上环境:dj.xxx.db - -开发环境:dj.xxx.rdb - -测试环境:dj.xxx.tdb - -**从库**在名称后加-s标识,**备库**在名称后加-ss标识 - -线上从库:dj.xxx-s.db - -线上备库:dj.xxx-sss.db - - -**(8)库名、表名、字段名:小写,下划线风格,不超过32个字符,必须见名知意,禁止拼音英文混用 - -(9)表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx** - -**(10)单实例表数目必须小于500** - - -**(11)单表列数目必须小于30** - -解读: - -a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用 - -b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率 - -c) 无主键的表删除,在row模式的主从架构,会导致备库夯住 - - - -**(13)禁止使用外键,如果有外键完整性约束,需要应用程序控制** - -解读:外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先 - - - -## 四、字段设计规范 - -**(14)必须把字段定义为NOT NULL并且提供默认值** - -解读: - -a)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化 - -b)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多 - -c)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识 - -d)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录 - - - -**(15)禁止使用TEXT、BLOB类型** - -解读:会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能 - - - -**(16)禁止使用小数存储货币** - -解读:使用整数吧,小数容易导致钱对不上 - - - -**(17)必须使用varchar(20)存储手机号** - -解读: - -a)涉及到区号或者国家代号,可能出现+-() - -b)手机号会去做数学运算么? - -c)varchar可以支持模糊查询,例如:like“138%” - - - -**(18)禁止使用ENUM,可使用TINYINT代替** - -解读: - -a)增加新的ENUM值要做DDL操作 - -b)ENUM的内部实际存储就是整数,你以为自己定义的是字符串? - - - -## 五、索引设计规范 - -**(19)单表索引建议控制在5个以内** - - - -**(20)单索引字段数不允许超过5个** - -解读:字段超过5个时,实际已经起不到有效过滤数据的作用了 - - - -**(21)禁止在更新十分频繁、区分度不高的属性上建立索引** - -解读: - -a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能 - -b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似 - - - -**(22)建立组合索引,必须把区分度高的字段放在前面** - -解读:能够更加有效的过滤数据 - - - -## 六、SQL使用规范 - -**(23)禁止使用SELECT \*,只获取必要的字段,需要显示说明列属性** - -解读: - -a)读取不需要的列会增加CPU、IO、NET消耗 - -b)不能有效的利用覆盖索引 - -c)使用SELECT *容易在增加或者删除字段后出现程序BUG - - - -**(24)禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性** - -解读:容易在增加或者删除字段后出现程序BUG - - - -**(25)禁止使用属性隐式转换** - -解读:SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,猜猜为什么?(这个线上问题不止出现过一次) - - - -**(26)禁止在WHERE条件的属性上使用函数或者表达式** - -解读:SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描 - -正确的写法是:SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00') - - - -**(27)禁止负向查询,以及%开头的模糊查询** - -解读: - -a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描 - -b)%开头的模糊查询,会导致全表扫描 - - - -**(28)禁止大表使用JOIN查询,禁止大表使用子查询** - -解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能 - - - -**(29)禁止使用OR条件,必须改为IN查询** - -解读:旧版本Mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢? - - -**(30)应用程序必须捕获SQL异常,并有相应处理** \ No newline at end of file diff --git a/docs/data-management/MySQL/MySQL-Segmentation.md b/docs/data-management/MySQL/MySQL-Segmentation.md index 190a52d5f8..c406866d38 100644 --- a/docs/data-management/MySQL/MySQL-Segmentation.md +++ b/docs/data-management/MySQL/MySQL-Segmentation.md @@ -36,48 +36,77 @@ subtopic -### 分区类型及操作 - -#### RANGE分区 - -mysql将会根据指定的拆分策略,,把数据放在不同的表文件上。相当于在文件上,被拆成了小块.但是,对外给客户的感觉还是一张表,透明的。 - - 按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。 - - range 来分,好处在于说,扩容的时候很简单 - -#### List分区 - -MySQL中的LIST分区在很多方面类似于RANGE分区。和按照RANGE分区一样,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。 - -#### 其它 - -- Hash分区: hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表 - -- Key分区 - -- 子分区 - -#### 对NULL值的处理 - - -MySQL中的分区在禁止空值NULL上没有进行处理,无论它是一个列值还是一个用户定义表达式的值,一般而言,在这种情况下MySQL把NULL当做零。如果你不希望出现类似情况,建议在设计表时声明该列“NOT NULL” - +- **RANGE分区**:基于属于一个给定连续区间的列值,把多行分配给分区。mysql将会根据指定的拆分策略,把数据放在不同的表文件上。相当于在文件上,被拆成了小块.但是,对外给客户的感觉还是一张表,透明的。 + + 按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,比如交易表啊,销售表啊等,可以根据年月来存放数据。可能会产生热点问题,大量的流量都打在最新的数据上了。 + + range 来分,好处在于说,扩容的时候很简单。 + + ```mysql + CREATE TABLE sales ( + id INT, + amount DECIMAL(10,2), + sale_date DATE + ) + PARTITION BY RANGE (YEAR(sale_date)) ( + PARTITION p0 VALUES LESS THAN (2000), + PARTITION p1 VALUES LESS THAN (2005), + PARTITION p2 VALUES LESS THAN (2010), + PARTITION p3 VALUES LESS THAN MAXVALUE + ); + ``` + +- **LIST分区**:按列表划分,类似于RANGE分区,但使用的是明确的值列表。 + + 它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。 + + ```mysql + CREATE TABLE customers ( + id INT, + name VARCHAR(50), + country VARCHAR(50) + ) + PARTITION BY LIST (country) ( + PARTITION p0 VALUES IN ('USA', 'Canada'), + PARTITION p1 VALUES IN ('UK', 'France'), + PARTITION p2 VALUES IN ('Germany', 'Italy') + ); + ``` + +- **HASH分区**:按哈希算法划分,将数据根据某个列的哈希值均匀分布到不同的分区中。 + + hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表 + + ```mysql + CREATE TABLE orders ( + id INT, + order_date DATE, + customer_id INT + ) + PARTITION BY HASH(id) PARTITIONS 4; + ``` + +- **KEY分区**:类似于HASH分区,但使用MySQL内部的哈希函数。 + + ```mysql + CREATE TABLE logs ( + id INT, + log_date DATE + ) + PARTITION BY KEY(id) PARTITIONS 4; + ``` + **看上去分区表很帅气,为什么大部分互联网还是更多的选择自己分库分表来水平扩展咧?** -回答: +- 分区表,分区键设计不太灵活,如果不走分区键,很容易出现全表锁 -1)分区表,分区键设计不太灵活,如果不走分区键,很容易出现全表锁 - -2)一旦数据量并发量上来,如果在分区表实施关联,就是一个灾难 - -3)自己分库分表,自己掌控业务场景与访问模式,可控。分区表,研发写了一个sql,都不确定mysql是怎么玩的,不太可控 - -4)运维的坑,嘿嘿 +- 一旦数据并发量上来,如果在分区表实施关联,就是一个灾难 +- 自己分库分表,自己掌控业务场景与访问模式,可控。分区表,研发写了一个sql,都不确定mysql是怎么玩的,不太可控 + ## Mysql分库 diff --git a/docs/data-management/MySQL/MySQL-Storage-Engines.md b/docs/data-management/MySQL/MySQL-Storage-Engines.md index 4f5c5094e4..19f5543935 100644 --- a/docs/data-management/MySQL/MySQL-Storage-Engines.md +++ b/docs/data-management/MySQL/MySQL-Storage-Engines.md @@ -1,14 +1,39 @@ -# Mysql Storage Engines +--- +title: MySQL Storage Engine +date: 2023-05-31 +tags: + - MySQL +categories: MySQL +--- + + + +> 存储引擎是 MySQL 的组件,用于处理不同表类型的 SQL 操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。 +> +> 使用哪一种引擎可以灵活选择,**一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求**,使用合适的存储引擎,将会提高整个数据库的性能 。 +> +> MySQL 服务器使用可插拔的存储引擎体系结构,可以从运行中的MySQL服务器加载或卸载存储引擎 。 -存储引擎是MySQL的组件,用于处理不同表类型的SQL操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。 +> [MySQL 5.7 可供选择的存储引擎](https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html) -使用哪一种引擎可以灵活选择,**一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求**,使用合适的存储引擎,将会提高整个数据库的性能 。 +## 一、存储引擎的作用与架构 - MySQL服务器使用可插拔的存储引擎体系结构,可以从运行中的MySQL服务器加载或卸载存储引擎 。 +MySQL 存储引擎是数据库的底层核心组件,负责数据的**存储、检索、事务控制**以及**并发管理**。其架构采用**插件式设计**,允许用户根据业务需求灵活选择引擎类型,例如 InnoDB、MyISAM、Memory 等。这种设计将**查询处理**与**数据存储**解耦,提升了系统的可扩展性和灵活性 。 -> [MySQL 5.7 可供选择的存储引擎](https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html) +MySQL 的体系架构分为四层: + +- **连接层**:管理客户端连接、认证与线程分配,支持 SSL 安全协议。 +- **核心服务层**:处理 SQL 解析、优化、缓存及内置函数执行。 +- **存储引擎层**:实际负责数据的存储和提取,支持多引擎扩展。 +- **数据存储层**:通过文件系统与存储引擎交互,管理物理文件。 + + + +## 二、核心存储引擎详解 + +### 2.1 常用存储引擎 -### 查看存储引擎 +**查看存储引擎** ```mysql -- 查看支持的存储引擎 @@ -25,166 +50,144 @@ show table status like 'tablename' show table status from database where name="tablename" ``` +以下是 MySQL 主要存储引擎的对比表格,整合了各引擎的核心特性及适用场景,结合最新版本(MySQL 8.0+)特性更新: +| **存储引擎** | **核心特性** | **事务支持** | **锁级别** | **索引类型** | **文件结构** | **适用场景** | +| ---------------------- | ------------------------------------------------------------ | ------------ | -------------- | ----------------------- | -------------------------------------- | -------------------------------- | +| **InnoDB** | 支持ACID事务、行级锁、MVCC、外键约束,具备崩溃恢复能力,默认使用聚簇索引 | ✅ | 行锁/表锁 | B+Tree/全文索引(5.6+) | `.ibd`(数据+索引)、`.frm`(表结构) | 高并发OLTP(电商交易、金融系统) | +| **MyISAM** | 非事务型,表级锁,支持全文索引和压缩表,查询速度快 | ❌ | 表锁 | B+Tree/全文索引 | `.MYD`(数据)、`.MYI`(索引)、`.frm` | 静态报表、日志分析、只读业务 | +| **Memory** | 数据全内存存储,哈希索引加速查询,重启后数据丢失 | ❌ | 表锁 | Hash/B-Tree | `.frm`(仅表结构) | 临时表、会话缓存、高速缓存层 | +| **Archive** | 仅支持INSERT/SELECT,Zlib压缩存储(压缩率10:1),无索引 | ❌ | 行锁(仅插入) | ❌ | `.ARZ`(数据)、`.ARM`(元数据) | 历史数据归档、审计日志 | +| **CSV** | 数据以CSV格式存储,可直接文本编辑,不支持索引 | ❌ | 表锁 | ❌ | `.CSV`(数据)、`.CSM`(元数据) | 数据导入/导出中间表 | +| **Blackhole** | 写入数据即丢弃,仅保留二进制日志,用于复制链路中继 | ❌ | ❌ | ❌ | `.frm`(仅表结构) | 主从复制中继、性能测试 | +| **Federated** | 代理访问远程表,本地无实际数据存储 | ❌ | 依赖远程表引擎 | 依赖远程表引擎 | `.frm`(仅表结构) | 分布式数据聚合 | +| **NDB** | 集群式存储引擎,支持数据自动分片和高可用性 | ✅ | 行锁 | Hash/B-Tree | 数据存储在集群节点 | MySQL Cluster分布式系统 | +| **Merge** | 聚合多个MyISAM表,逻辑上作为单个表操作 | ❌ | 表锁 | B-Tree | `.MRG`(聚合定义)、底层使用MyISAM文件 | 分库分表聚合查询 | +| **Performance Schema** | 内置性能监控引擎,采集服务器运行时指标 | ❌ | ❌ | ❌ | 内存存储,无物理文件 | 性能监控与诊断 | - +### 2.2 存储引擎架构演进 -### 设置存储引擎 +**1. MySQL 8.0 关键改进** -```mysql --- 建表时指定存储引擎。默认的就是INNODB,不需要设置 -CREATE TABLE t1 (i INT) ENGINE = INNODB; -CREATE TABLE t2 (i INT) ENGINE = CSV; -CREATE TABLE t3 (i INT) ENGINE = MEMORY; +- 原子 DDL:DDL操作(如CREATE TABLE)具备事务性,失败时自动回滚元数据变更 +- 数据字典升级:系统表全部转为InnoDB引擎,替代原有的.frm文件,实现事务化元数据管理 +- Redo日志优化:MySQL 8.0.30+ 引入 `innodb_redo_log_capacity` 参数替代旧版日志配置,支持动态调整redo日志大小 --- 修改存储引擎 -ALTER TABLE t ENGINE = InnoDB; + **2. 物理文件结构变化** --- 修改默认存储引擎,也可以在配置文件my.cnf中修改默认引擎 -SET default_storage_engine=NDBCLUSTER; -``` +| 文件类型 | 5.7及之前版本 | 8.0+版本 | 作用 | +| -------------- | ------------- | ------------------ | ------------------ | +| 表结构定义文件 | .frm | .sdi (JSON格式) | 存储表结构元数据 6 | +| 事务日志 | ibdata1 | undo_001, undo_002 | 独立UNDO表空间 | +| 数据文件 | .ibd | .ibd | 表数据与索引存储 | +| 临时文件 | ibtmp1 | ibtmp1 | 临时表空间 | - 默认情况下,每当CREATE TABLE或ALTER TABLE不能使用默认存储引擎时,都会生成一个警告。为了防止在所需的引擎不可用时出现令人困惑的意外行为,可以启用`NO_ENGINE_SUBSTITUTION SQL`模式。如果所需的引擎不可用,则此设置将产生错误而不是警告,并且不会创建或更改表 +> 示例:通过 `SHOW CREATE TABLE` 可查看SDI元数据,支持 JSON 格式导出 -### 常用存储引擎 +### 2.3 Innodb 引擎的 4 大特性 -#### InnoDB +#### **1. 插入缓冲(Insert Buffer / Change Buffer)** -**InnoDB是MySQL5.7 默认的存储引擎,主要特性有** +- **作用**:优化非唯一二级索引的插入、删除、更新(即 DML 操作)性能,减少磁盘随机 I/O 开销。 +- 原理: + - 当非唯一索引页不在内存中时,操作会被暂存到 Change Buffer(内存区域)中,而非直接写入磁盘。 + - 后续通过合并(Merge)操作,将多个离散的修改批量写入磁盘,减少 I/O 次数。 +- 适用条件: + - 仅针对非唯一二级索引。 + - 可通过参数 `innodb_change_buffer_max_size` 调整缓冲区大小(默认 25% 缓冲池)。 -- InnoDB存储引擎维护自己的缓冲池,在访问数据时将表和索引数据缓存在主内存中 +#### 2. 二次写(Double Write) -- 支持事务 +- 作用:防止因部分页写入(Partial Page Write)导致的数据页损坏,确保崩溃恢复的可靠性。 +- 流程: + - 脏页刷盘时,先写入内存的 Doublewrite Buffer,再分两次(每次 1MB)顺序写入共享表空间的连续磁盘区域。 + - 若数据页写入过程中崩溃,恢复时从共享表空间副本还原损坏页,再通过 Redo Log 恢复。 +- 意义:牺牲少量顺序 I/O 换取数据完整性,避免因随机 I/O 中断导致数据丢失。 -- 支持外键 +#### 3. 自适应哈希索引(Adaptive Hash Index, AHI) -- B-Tree索引 +- 作用:自动为高频访问的索引页创建哈希索引,加速查询速度(尤其等值查询)。 -- 不支持集群 +- 触发条件: -- 聚簇索引 + - 同一索引被连续访问 17 次以上。 + - 某页被访问超过 100 次,且访问模式一致(如固定 WHERE 条件)。 -- 行锁 +- 限制 -- 支持地理位置的数据类型和索引 + :仅对热点数据生效,无法手动指定,可通过参数 `innodb_adaptive_hash_index` 启用或关闭。 - +#### 4. 预读(Read Ahead) -#### MyISAM +- 作用:基于空间局部性原理,异步预加载相邻数据页到缓冲池,减少未来查询的磁盘 I/O。 +- 模式: + - 线性预读:按顺序访问的页超过阈值时,预加载下一批连续页(默认 64 页为一个块)。 + - 随机预读(已废弃):当某块中部分页在缓冲池时,预加载剩余页,但因性能问题被弃用。 -在 5.1 版本之前,MyISAM 是 MySQL 的默认存储引擎,MyISAM 并发性比较差,使用的场景比较少,主要特点是 +#### 其他重要特性补充 -每个MyISAM表存储在磁盘上的三个文件中 。这些文件的名称以表名开头,并有一个扩展名来指示文件类型 。 +尽管上述四点是核心性能优化特性,但 InnoDB 的其他关键能力也值得注意: -`.frm`文件存储表的格式。 `.MYD` (`MYData`) 文件存储表的数据。 `.MYI` (`MYIndex`) 文件存储索引。 +- 事务支持:通过 ACID 特性(原子性、一致性、隔离性、持久性)保障数据一致性。 +- 行级锁与外键约束:支持高并发与数据完整性。 +- **崩溃恢复**:结合 Redo Log 和 Double Write 实现快速恢复 - **MyISAM表具有以下特征** -- 每个MyISAM表最大索引数是64,这可以通过重新编译来改变。每个索引最大的列数是16 -- 每个MyISAM表都支持一个`AUTO_INCREMENT`的内部列。当执行`INSERT`或者`UPDATE`操作的时候,MyISAM自动更新这个列,这使得`AUTO_INCREMENT`列更快。 -- 当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,若下一个块被删除,就扩展到下一块自动完成 -- MyISAM支持**并发插入** -- **可以将数据文件和索引文件放在不同物理设备上的不同目录中**,以更快地使用数据目录和索引目录表选项来创建表 -- BLOB和TEXT列可以被索引 -- **NULL被允许在索引的列中**,这个值占每个键的0~1个字节 -- 每个字符列可以有不同的字符集 -- **`MyISAM` 表使用 B-tree 索引** -- MyISAM表的行最大限制为 (2^32)^2 (1.844E+19) -- 大文件(达到63位文件长度)在支持大文件的文件系统和操作系统上被支持 -- 键的最大长度为1000字节,这也可以通过重新编译来改变,对于键长度超过250字节的情况,一个超过1024字节的键将被用上 +### 2.4 数据的存储 -- VARCHAR支持固定或动态记录长度 -- 表中VARCHAR和CHAR列的长度总和有可能达到64KB -- 任意长度的唯一约束 +在整个数据库体系结构中,我们可以使用不同的存储引擎来存储数据,而绝大多数存储引擎都以二进制的形式存储数据;我们来看下InnoDB 中对数据是如何存储的。 -- All data values are stored with the low byte first. This makes the data machine and operating system independent. +在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page) -- All numeric key values are stored with the high byte first to permit better index compression + - todo:最后两条没搞懂啥意思 + + 同一个数据库实例的所有表空间都有相同的页大小;默认情况下,表空间中的页大小都为 16KB,当然也可以通过改变 `innodb_page_size` 选项对默认大小进行修改,需要注意的是不同的页大小最终也会导致区大小的不同 +对于 16KB 的页来说,连续的 64 个页就是一个区,也就是 1 个区默认占用 1 MB 空间的大小。 -### 存储引擎对比 +#### 数据页结构 -| 对比项 | MyISAM | InnoDB | -| -------- | -------------------------------------------------------- | ------------------------------------------------------------ | -| 主外键 | 不支持 | 支持 | -| 事务 | 不支持 | 支持 | -| 行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作 | -| 缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 | -| 表空间 | 小 | 大 | -| 关注点 | 性能 | 事务 | -| 默认安装 | 是 | 是 | +页是 InnoDB 存储引擎管理数据的最小磁盘单位,一个页的大小一般是 `16KB`。 - +`InnoDB` 为了不同的目的而设计了许多种不同类型的`页`,比如存放表空间头部信息的页,存放 `Insert Buffer` 信息的页,存放 `INODE`信息的页,存放 `undo` 日志信息的页等等等等。 -官方提供的多种引擎对比 - -| Feature | MyISAM | Memory | InnoDB | Archive | NDB | -| ------------------------------------------ | ------------ | ---------------- | ------------ | ------------ | ------------ | -| **B-tree indexes** | Yes | Yes | Yes | No | No | -| **Backup/point-in-time recovery** (note 1) | Yes | Yes | Yes | Yes | Yes | -| **Cluster database support** | No | No | No | No | Yes | -| **Clustered indexes** | No | No | Yes | No | No | -| **Compressed data** | Yes (note 2) | No | Yes | Yes | No | -| **Data caches** | No | N/A | Yes | No | Yes | -| **Encrypted data** | Yes (note 3) | Yes (note 3) | Yes (note 4) | Yes (note 3) | Yes (note 3) | -| **Foreign key support** | No | No | Yes | No | Yes (note 5) | -| **Full-text search indexes** | Yes | No | Yes (note 6) | No | No | -| **Geospatial data type support** | Yes | No | Yes | Yes | Yes | -| **Geospatial indexing support** | Yes | No | Yes (note 7) | No | No | -| **Hash indexes** | No | Yes | No (note 8) | No | Yes | -| **Index caches** | Yes | N/A | Yes | No | Yes | -| **Locking granularity** | Table | Table | Row | Row | Row | -| **MVCC** | No | No | Yes | No | No | -| **Replication support** (note 1) | Yes | Limited (note 9) | Yes | Yes | Yes | -| **Storage limits** | 256TB | RAM | 64TB | None | 384EB | -| **T-tree indexes** | No | No | No | No | Yes | -| **Transactions** | No | No | Yes | No | Yes | -| **Update statistics for data dictionary** | Yes | Yes | Yes | Yes | Yes | - - - -### 数据的存储 - -在整个数据库体系结构中,我们可以使用不同的存储引擎来存储数据,而绝大多数存储引擎都以二进制的形式存储数据;这一节会介绍 InnoDB 中对数据是如何存储的。 + B-Tree 节点就是实际存放表中数据的页面,我们在这里将要介绍页是如何组织和存储记录的;首先,一个 InnoDB 页有以下七个部分: -在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page) + -  +有的部分占用的字节数是确定的,有的部分占用的字节数是不确定的。 - 同一个数据库实例的所有表空间都有相同的页大小;默认情况下,表空间中的页大小都为 16KB,当然也可以通过改变 `innodb_page_size` 选项对默认大小进行修改,需要注意的是不同的页大小最终也会导致区大小的不同 +| 名称 | 中文名 | 占用空间大小 | 简单描述 | +| -------------------- | ------------------ | ------------ | ------------------------ | +| `File Header` | 文件头部 | `38`字节 | 页的一些通用信息 | +| `Page Header` | 页面头部 | `56`字节 | 数据页专有的一些信息 | +| `Infimum + Supremum` | 最小记录和最大记录 | `26`字节 | 两个虚拟的行记录 | +| `User Records` | 用户记录 | 不确定 | 实际存储的行记录内容 | +| `Free Space` | 空闲空间 | 不确定 | 页中尚未使用的空间 | +| `Page Directory` | 页面目录 | 不确定 | 页中的某些记录的相对位置 | +| `File Trailer` | 文件尾部 | `8`字节 | 校验页是否完整 | - +在页的 7 个组成部分中,我们自己存储的记录会按照我们指定的`行格式`存储到 `User Records` 部分。但是在一开始生成页的时候,其实并没有 `User Records` 这个部分,每当我们插入一条记录,都会从 `Free Space` 部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到 `User Records` 部分,当 `Free Space` 部分的空间全部被 `User Records` 部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了,这个过程的图示如下: -从图中可以看出,在 InnoDB 存储引擎中,一个区的大小最小为 1MB,页的数量最少为 64 个。 + #### 如何存储表 -MySQL 使用 InnoDB 存储表时,会将表的定义和数据索引等信息分开存储,其中前者存储在 `.frm` 文件中,后者存储在 `.ibd` 文件中,这一节就会对这两种不同的文件分别进行介绍。 - - +MySQL 使用 InnoDB 存储表时,会将表的定义和数据索引等信息分开存储,其中前者存储在 `.frm` 文件中,后者存储在 `.ibd` 文件中。 #### .frm 文件 无论在 MySQL 中选择了哪个存储引擎,所有的 MySQL 表都会在硬盘上创建一个 `.frm` 文件用来描述表的格式或者说定义;`.frm` 文件的格式在不同的平台上都是相同的。 -```mysql -`CREATE TABLE test_frm(`` ``column1 CHAR(5),`` ``column2 INTEGER``);` -``` - -当我们使用上面的代码创建表时,会在磁盘上的 `datadir` 文件夹中生成一个 `test_frm.frm` 的文件,这个文件中就包含了表结构相关的信息: - - - > MySQL 官方文档中的 [11.1 MySQL .frm File Format](https://dev.mysql.com/doc/internals/en/frm-file-format.html) 一文对于 `.frm` 文件格式中的二进制的内容有着非常详细的表述。 #### .ibd 文件 @@ -193,54 +196,62 @@ InnoDB 中用于存储数据的文件总共有两个部分,一是系统表空 当打开 `innodb_file_per_table` 选项时,`.ibd` 文件就是每一个表独有的表空间,文件存储了当前表的数据和相关的索引数据。 -#### 如何存储记录 +#### 如何存储记录 | InnoDB 行格式 -与现有的大多数存储引擎一样,InnoDB 使用页作为磁盘管理的最小单位;数据在 InnoDB 存储引擎中都是按行存储的,每个 16KB 大小的页中可以存放 2-7992 行的记录。(至少是2条记录,最多是7992条记录) +InnoDB 存储引擎和大多数数据库一样,记录是以行的形式存储的,每个 16KB 大小的页中可以存放多条行记录。 -当 InnoDB 存储数据时,它可以使用不同的行格式进行存储;MySQL 5.7 版本支持以下格式的行存储方式: +它可以使用不同的行格式进行存储。 - +InnoDB 早期的文件格式为 `Antelope`,可以定义两种行记录格式,分别是 `Compact` 和 `Redundant`,InnoDB 1.0.x 版本开始引入了新的文件格式 `Barracuda`。`Barracuda `文件格式下拥有两种新的行记录格式:`Compressed` 和 `Dynamic`。 -Antelope 是 InnoDB 最开始支持的文件格式,它包含两种行格式 Compact 和 Redundant,它最开始并没有名字;Antelope 的名字是在新的文件格式 Barracuda 出现后才起的,Barracuda 的出现引入了两种新的行格式 Compressed 和 Dynamic;InnoDB 对于文件格式都会向前兼容,而官方文档中也对之后会出现的新文件格式预先定义好了名字:Cheetah、Dragon、Elk 等等。 +> [InnoDB Row Formats](https://dev.mysql.com/doc/refman/5.7/en/innodb-row-format.html#innodb-row-format-redundant) -两种行记录格式 Compact 和 Redundant 在磁盘上按照以下方式存储: + - +MySQL 5.7 版本支持以上格式的行存储方式。 -Compact 和 Redundant 格式最大的不同就是记录格式的第一个部分;在 Compact 中,行记录的第一部分倒序存放了一行数据中列的长度(Length),而 Redundant 中存的是每一列的偏移量(Offset),从总体上上看,Compact 行记录格式相比 Redundant 格式能够减少 20% 的存储空间。 +我们可以在创建或修改表的语句中指定行格式: -#### 行溢出数据 +```mysql +CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称 + +ALTER TABLE 表名 ROW_FORMAT=行格式名称 +``` -当 InnoDB 使用 Compact 或者 Redundant 格式存储极长的 VARCHAR 或者 BLOB 这类大对象时,我们并不会直接将所有的内容都存放在数据页节点中,而是将行数据中的前 768 个字节存储在数据页中,后面会通过偏移量指向溢出页。 +`Compact `行记录格式是在 MySQL 5.0 中引入的,其首部是一个非 NULL 变长列长度列表,并且是逆序放置的,其长度为: - +- 若列的长度小于等于 255 字节,用 1 个字节表示; +- 若列的长度大于 255 字节,用 2 个字节表示。 -但是当我们使用新的行记录格式 Compressed 或者 Dynamic 时都只会在行记录中保存 20 个字节的指针,实际的数据都会存放在溢出页面中。 + - +变长字段的长度最大不可以超过 2 字节,这是因为 MySQL 数据库中 VARCHAR 类型的最大长度限制为 65535。变长字段之后的第二个部分是 NULL 标志位,该标志位指示了该行数据中某列是否为 NULL 值,有则用 1 表示,NULL 标志位也是不定长的。接下来是记录头部信息,固定占用 5 字节。 -当然在实际存储中,可能会对不同长度的 TEXT 和 BLOB 列进行优化,不过这就不是本文关注的重点了。 +`Redundant` 是 MySQL 5.0 版本之前 InnoDB 的行记录格式,`Redundant` 行记录格式的首部是每一列长度偏移列表,同样是逆序存放的。从整体上看,`Compact `格式的存储空间减少了约 20%,但代价是某些操作会增加 CPU 的使用。 -> 想要了解更多与 InnoDB 存储引擎中记录的数据格式的相关信息,可以阅读 [InnoDB Record Structure](https://dev.mysql.com/doc/internals/en/innodb-record-structure.html) +`Dynamic` 和 `Compressed `是 `Compact `行记录格式的变种,`Compressed `会对存储在其中的行数据会以 `zlib` 的算法进行压缩,因此对于 BLOB、TEXT、VARCHAR 这类大长度类型的数据能够进行非常有效的存储。 -#### 数据页结构 +> 高版本,比如 8.3 默认使用的是 Dynamic +> +> ```sql +> SELECT @@innodb_default_row_format; +> ``` -页是 InnoDB 存储引擎管理数据的最小磁盘单位,而 B-Tree 节点就是实际存放表中数据的页面,我们在这里将要介绍页是如何组织和存储记录的;首先,一个 InnoDB 页有以下七个部分: - -每一个页中包含了两对 header/trailer:内部的 Page Header/Page Directory 关心的是页的状态信息,而 Fil Header/Fil Trailer 关心的是记录页的头信息。 +#### 行溢出数据 -在页的头部和尾部之间就是用户记录和空闲空间了,每一个数据页中都包含 Infimum 和 Supremum 这两个虚拟的记录(可以理解为占位符),Infimum 记录是比该页中任何主键值都要小的值,Supremum 是该页中的最大值: +当 InnoDB 存储极长的 TEXT 或者 BLOB 这类大对象时,MySQL 并不会直接将所有的内容都存放在数据页中。因为 InnoDB 存储引擎使用 B+Tree 组织索引,每个页中至少应该有两条行记录,因此,如果页中只能存放下一条记录,那么 InnoDB 存储引擎会自动将行数据存放到溢出页中。 - +如果我们使用 `Compact` 或 `Redundant` 格式,那么会将行数据中的前 768 个字节存储在数据页中,后面的数据会通过指针指向 Uncompressed BLOB Page。 -User Records 就是整个页面中真正用于存放行记录的部分,而 Free Space 就是空余空间了,它是一个链表的数据结构,为了保证插入和删除的效率,整个页面并不会按照主键顺序对所有记录进行排序,它会自动从左侧向右寻找空白节点进行插入,行记录在物理存储上并不是按照顺序的,它们之间的顺序是由 `next_record` 这一指针控制的。 +但是如果我们使用新的行记录格式 `Compressed` 或者 `Dynamic` 时只会在行记录中保存 20 个字节的指针,实际的数据都会存放在溢出页面中。 -B+ 树在查找对应的记录时,并不会直接从树中找出对应的行记录,它只能获取记录所在的页,将整个页加载到内存中,再通过 Page Directory 中存储的稀疏索引和 `n_owned`、`next_record` 属性取出对应的记录,不过因为这一操作是在内存中进行的,所以通常会忽略这部分查找的耗时。 -InnoDB 存储引擎中对数据的存储是一个非常复杂的话题,这一节中也只是对表、行记录以及页面的存储进行一定的分析和介绍,虽然作者相信这部分知识对于大部分开发者已经足够了,但是想要真正消化这部分内容还需要很多的努力和实践。 +### 参考与引用: +- https://www.linkedin.com/pulse/leverage-innodb-architecture-optimize-django-model-design-bouslama +- [踏雪无痕-InnoDB存储引擎](https://www.cnblogs.com/chenpingzhao/p/9177324.html) +- [MySQL 与 InnoDB 存储引擎总结](https://wingsxdu.com/posts/database/mysql/innodb/) -> [踏雪无痕-InnoDB存储引擎](https://www.cnblogs.com/chenpingzhao/p/9177324.html) \ No newline at end of file diff --git a/docs/data-management/MySQL/MySQL-Transaction.md b/docs/data-management/MySQL/MySQL-Transaction.md index e9f7a56e40..45e21bd4f4 100644 --- a/docs/data-management/MySQL/MySQL-Transaction.md +++ b/docs/data-management/MySQL/MySQL-Transaction.md @@ -1,64 +1,145 @@ -# MySQL 事务 +--- +title: MySQL 事务 +date: 2022-02-01 +tags: + - MySQL +categories: MySQL +--- -MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务! + +> Hello,我是海星。 +> +> MySQL 事务,最熟悉经典的例子,就是你给我转账的例子了,要经过查询余额,减你的钱,加我的钱,这一系列操作必须保证是一体的,这些数据库操作的集合就构成了一个事务。 +> +> MySQL 事务也是在存储引擎层面实现的,大家用 InnoDB 取代 MyISAM 引擎很重要的一个原因就是 InnoDB 支持事务。 -### ACID — 事务基本要素 -事务是由一组SQL语句组成的逻辑处理单元,具有4个属性,通常简称为事务的ACID属性。 +## 一、事务基本要素 — ACID + +事务是由一组 SQL 语句组成的逻辑处理单元,具有 4 个属性,通常简称为事务的 ACID 属性。 + + - **A (Atomicity) 原子性**:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 - **C (Consistency) 一致性**:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。 -- **I (Isolation)隔离性**:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。 -- **D (Durability) 持久性**:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。 +- **I (Isolation)隔离性**:一个事务所做的修改在最终提交以前,对其他事务是不可见的。这种属性有时称为『串行化』,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。 +- **D (Durability) 持久性**:在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库中,并不会被回滚。 -### 事务隔离级别 +## 二、MySQL 中事务的使用 -**并发事务处理带来的问题** + MySQL 的服务层不管理事务,而是由下层的存储引擎实现。MySQL 提供了两种事务型的存储引擎:InnoDB 和 NDB。 -- 更新丢失(Lost Update): 事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题 -- 脏读(Dirty Reads):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据 -- 不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。 -- 幻读(Phantom Reads):系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。 +**MySQL支持本地事务的语句:** +```mysql +START TRANSACTION | BEGIN [WORK] +COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] +ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] +SET AUTOCOMMIT = {0 | 1} +``` +- START TRANSACTION 或 BEGIN 语句:开始一项新的事务。 +- COMMIT 和 ROLLBACK:用来提交或者回滚事务。 +- CHAIN 和 RELEASE 子句:分别用来定义在事务提交或者回滚之后的操作,CHAIN 会立即启动一个新事物,并且和刚才的事务具有相同的隔离级别,RELEASE 则会断开和客户端的连接。 +- SET AUTOCOMMIT 可以修改当前连接的提交方式, 如果设置了 SET AUTOCOMMIT=0,则设置之后的所有事务都需要通过明确的命令进行提交或者回滚 -**幻读和不可重复读的区别:** +**事务使用注意点:** -- 不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改) -- 幻读的重点在于新增或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除) +- 如果在锁表期间,用 start transaction 命令开始一个新事务,会造成一个隐含的 unlock tables 被执行。 +- 在同一个事务中,最好不使用不同存储引擎的表,否则 ROLLBACK 时需要对非事务类型的表进行特别的处理,因为 COMMIT、ROLLBACK 只能对事务类型的表进行提交和回滚。 +- 和 Oracle 的事务管理相同,所有的 DDL 语句是不能回滚的,并且部分的 DDL 语句会造成隐式的提交。 +- 在事务中可以通过定义 SAVEPOINT(例如:mysql> savepoint test; 定义 savepoint,名称为 test),指定回滚事务的一个部分,但是不能指定提交事务的一个部分。对于复杂的应用,可以定义多个不同的 SAVEPOINT,满足不同的条件时,回滚 + 不同的 SAVEPOINT。需要注意的是,如果定义了相同名字的 SAVEPOINT,则后面定义的SAVEPOINT 会覆盖之前的定义。对于不再需要使用的 SAVEPOINT,可以通过 RELEASE SAVEPOINT 命令删除 SAVEPOINT, 删除后的 SAVEPOINT, 不能再执行 ROLLBACK TO SAVEPOINT命令。 +**自动提交(autocommit):** +Mysql 默认采用自动提交模式,可以通过设置 `autocommit` 变量来启用或禁用自动提交模式 +- **隐式锁定** -**并发事务处理带来的问题的解决办法:** + InnoDB 在事务执行过程中,使用两阶段锁协议: -- “更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。 + 随时都可以执行锁定,InnoDB 会根据隔离级别在需要的时候自动加锁; -- “脏读” 、 “不可重复读”和“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决: + 锁只有在执行 commit 或者 rollback 的时候才会释放,并且所有的锁都是在**同一时刻**被释放。 - - 一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。 - - 另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 **MVCC** 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。 +- **显式锁定** + InnoDB 也支持通过特定的语句进行显示锁定(存储引擎层): +```mysql +select ... lock in share mode //共享锁 +select ... for update //排他锁 +``` -查看当前数据库的事务隔离级别: + MySQL Server 层的显示锁定: ```mysql -show variables like 'tx_isolation' +lock table 和 unlock table ``` -数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。下面通过事例一一阐述在事务的并发操作中可能会出现脏读,不可重复读,幻读和事务隔离级别的联系。 +## 三、事务隔离级别 + +当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。 + +> **并发事务处理带来的问题** +> +> - 更新丢失(Lost Update): 事务 A 和事务 B 选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题 +> - 脏读(Dirty Reads):事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据 +> - 不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。 +> - 幻读(Phantom Reads):系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。 + +> **幻读和不可重复读的区别:** +> +> - 不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改) +> - 幻读的重点在于新增或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除) +> -数据库的事务隔离越严格,并发副作用越小,但付出的代价就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关系数据并发访问的能力。 +> **并发事务处理带来的问题的解决办法:** +> +> - “更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。 +> +> - “脏读” 、 “不可重复读”和“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决: +> +> - 一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。 +> - 另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 **MVCC** 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。 +> + +在谈隔离级别之前,你首先要知道,你隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。 + +数据库事务的隔离级别有 4 种,由低到高分别为 + +- 读未提交(read uncommitted) +- 读提交(read committed) +- 可重复读(repeatable read) +- 串行化(serializable ) + +查看当前数据库的事务隔离级别: + +```mysql +mysql> show variables like 'transaction_isolation'; ++-----------------------+-----------------+ +| Variable_name | Value | ++-----------------------+-----------------+ +| transaction_isolation | REPEATABLE-READ | ++-----------------------+-----------------+ +``` + +> 通俗理解就是 +> +> - 读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。 +> - 读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。 +>- 可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。 +> - 串行:我的事务尚未提交,别人就别想改数据。 #### Read uncommitted -读未提交,就是一个事务可以读取另一个未提交事务的数据。 +**读未提交,就是一个事务可以读取另一个未提交事务的数据**。 事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。 @@ -68,7 +149,7 @@ show variables like 'tx_isolation' #### Read committed -读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 +**读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据**。 事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的… @@ -78,23 +159,44 @@ show variables like 'tx_isolation' #### Repeatable read -重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。 MySQL的默认事务隔离级别 +**可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。 MySQL 的默认事务隔离级别** 事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。 -分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。 +分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即 UPDATE 操作。但是可能还会有幻读问题。因为幻读问题对应的是插入 INSERT 操作,而不是 UPDATE 操作。 **什么时候会出现幻读?** -事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。 +事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增 INSERT 了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。 那怎么解决幻读问题?Serializable! #### Serializable 序列化 -Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。简单来说,Serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用问题。这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。 +串行化是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。 + +简单来说,Serializable会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用问题。这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。 + +#### demo + +```mysql +create table T(t int) engine=InnoDB; +insert into T(t) values(1); +``` + + + +- “读未提交”:则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。 +- “读提交”:则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。 +- “可重复读”:则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。 +- “串行化”:则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。 + +> - 读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。 +> - 读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。 +> - 可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。 +> - 串行:我的事务尚未提交,别人就别想改数据。 | 事务隔离级别 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 | | ---------------------------- | ---------------------------------------- | ---- | ---------- | ---- | @@ -103,152 +205,256 @@ Serializable 是最高的事务隔离级别,在该级别下,事务串行化 | 可重复读(repeatable-read) | 事务级 | 否 | 否 | 是 | | 串行化(serializable) | 最高级别,事务级 | 否 | 否 | 否 | +需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。 -需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。 +## 三、MVCC 多版本并发控制 +#### 核心概念 -### MVCC 多版本并发控制 +1. **快照读(Snapshot Read)**: + - 每个事务在开始时,会获取一个数据快照,事务在读取数据时,总是读取该快照中的数据。 + - 这意味着即使在事务进行期间,其他事务对数据的更新也不会影响当前事务的读取。 +2. **版本链(Version Chain)**: + - 每个数据行都有多个版本,每个版本包含数据和元数据(如创建时间、删除时间等)。 + - 新版本的数据行会被链接到旧版本的数据行,形成一个版本链。 +3. **隐式锁(Implicit Locking)**: + - MVCC 通过版本管理避免了显式锁定,减少了锁争用问题。 + - 对于读取操作,事务读取其开始时的快照数据,不会被写操作阻塞。 -MySQL的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC),包括Oracle、PostgreSQL。只是实现机制各不相同。 +#### MVCC 的底层实现 -可以认为MVCC是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只是锁定必要的行。 +1. **数据行的多版本存储**: + - 每个数据行在物理存储上会有多个版本,每个版本包含该行在特定时间点的值。 + - 数据行版本包含元数据,如事务ID(Transaction ID)、创建时间戳和删除时间戳。 +2. **快照读取**: + - 每个事务在开始时,会记录当前系统的事务ID作为快照ID。 + - 读取数据时,只读取那些创建时间戳早于快照ID,并且删除时间戳为空或晚于快照ID的数据版本。 +3. **事务提交和版本更新**: + - 当一个事务对数据行进行更新时,会创建一个新的数据版本,并将其链接到现有版本链上。 + - 旧版本仍然存在,直到没有任何活动事务需要访问它们。 -MVCC的实现是通过保存数据在某个时间点的快照来实现的。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。 +#### MVCC 在MySQL中的实现 -典型的MVCC实现方式,分为**乐观(optimistic)并发控制和悲观(pressimistic)并发控制**。下边通过InnoDB的简化版行为来说明MVCC是如何工作的。 +MySQL InnoDB 存储引擎使用 MVCC 来实现可重复读(REPEATABLE READ)隔离级别,避免脏读、不可重复读和幻读问题。具体机制如下: -InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 +1. **隐藏列**: + - InnoDB 在每行记录中存储两个隐藏列:`trx_id`(事务ID)和`roll_pointer`(回滚指针)。 + - `trx_id` 记录最后一次修改该行的事务ID,`roll_pointer` 指向该行的上一版本。 -**REPEATABLE READ(可重读)隔离级别下MVCC如何工作:** +> 其实,InnoDB下的 Compact 行结构,有三个隐藏的列 +> +> | 列名 | 是否必须 | 描述 | +> | -------------- | -------- | ------------------------------------------------------------ | +> | row_id | 否 | 行ID,唯一标识一条记录(如果定义主键,它就没有啦) | +> | transaction_id | 是 | 事务ID | +> | roll_pointer | 是 | DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本 | -- SELECT +2. **Undo日志**: -InnoDB会根据以下两个条件检查每行记录: + - 每次数据更新时,InnoDB 会在 Undo 日志中记录旧版本数据。 -1. InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在开始事务之前已经存在要么是事务自身插入或者修改过的 + - 如果需要读取旧版本数据,InnoDB 会通过 `roll_pointer` 找到 Undo 日志中的旧版本。 -2. 行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除 +3. **一致性视图(Consistent Read View)**: - 只有符合上述两个条件的才会被查询出来 + - InnoDB 为每个事务创建一致性视图,记录当前活动的所有事务ID。 -- INSERT + - 读取数据时,会根据一致性视图决定哪些版本的数据对当前事务可见。 - InnoDB为新插入的每一行保存当前系统版本号作为行版本号 -- DELETE - InnoDB为删除的每一行保存当前系统版本号作为行删除标识 +在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。 -- UPDATE +假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。 - InnoDB为插入的一行新纪录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识 + -保存这两个额外系统版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且也能保证只会读取到符合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。 +当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。 -MVCC只在COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。 +同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。 -### 事务日志 +MySQL 的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC),包括Oracle、PostgreSQL。只是实现机制各不相同。 -事务日志可以帮助提高事务效率: +可以认为 MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只是锁定必要的行。 -- 使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。 -- 事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。 -- 事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。 -- 如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。 +MVCC 的实现是通过保存数据在某个时间点的快照来实现的。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。 -目前来说,大多数存储引擎都是这样实现的,我们通常称之为**预写式日志**(Write-Ahead Logging),修改数据需要写两次磁盘。 +典型的 MVCC 实现方式,分为**乐观(optimistic)并发控制和悲观(pressimistic)并发控制**。 + +下边通过 InnoDB 的简化版行为来说明 MVCC 是如何工作的。 +InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 +**REPEATABLE READ(可重读)隔离级别下 MVCC 如何工作:** -### 事务的实现 +- **SELECT** - 事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持程度不一样。mysql中支持事务的存储引擎有innoDB和NDB。 + InnoDB 会根据以下两个条件检查每行记录: -事务的实现就是如何实现ACID特性。 + - InnoDB 只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在开始事务之前已经存在要么是事务自身插入或者修改过的 + - 行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除 -innoDB是mysql默认的存储引擎,默认的隔离级别是RR(Repeatable Read),并且在RR的隔离级别下更进一步,通过多版本**并发控制**(MVCC,Multiversion Concurrency Control )解决不可重复读问题,加上间隙锁(也就是并发控制)解决幻读问题。因此innoDB的RR隔离级别其实实现了串行化级别的效果,而且保留了比较好的并发性能。 + 只有符合上述两个条件的才会被查询出来 +- **INSERT** + InnoDB 为新插入的每一行保存当前系统版本号作为行版本号 -?> 事务的隔离性是通过锁实现,而事务的原子性、一致性和持久性则是通过事务日志实现 。 +- **DELETE** + InnoDB 为删除的每一行保存当前系统版本号作为行删除标识 +- **UPDATE** -**redo log(重做日志**) 实现持久化和原子性。 + InnoDB 为插入的一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识 -在innoDB的存储引擎中,事务日志通过重做(redo)日志和innoDB存储引擎的日志缓冲(InnoDB Log Buffer)实现。事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是DBA们口中常说的“日志先行”(Write-Ahead Logging)。当事务提交之后,在Buffer Pool中映射的数据文件才会慢慢刷新到磁盘。此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据redo log中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。 +保存这两个额外系统版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且也能保证只会读取到符合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。 -在系统启动的时候,就已经为redo log分配了一块连续的存储空间,以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。所有的事务共享redo log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起。 +MVCC 只在 COMMITTED READ(读提交)和 REPEATABLE READ(可重复读)两种隔离级别下工作。 +> 所谓的`MVCC`,就是通过生成一个`ReadView`,然后通过`ReadView`找到符合条件的记录版本(历史版本是由`undo日志`构建的),其实就像是在生成`ReadView`的那个时刻做了一次时间静止(就像用相机拍了一个快照),查询语句只能读到在生成`ReadView`之前已提交事务所做的更改,在生成`ReadView`之前未提交的事务或者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本本身并不冲突,也就是采用`MVCC`时,`读-写`操作并不冲突。 - **undo log** 实现一致性 - undo log主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。 +## 四、事务的实现 +> 事务的隔离性是通过锁实现,而事务的原子性、一致性和持久性则是通过事务日志实现 。 +### 一致性读(Consistent Reads) -二种日志均可以视为一种恢复操作,redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本。二者记录的内容也不同,redo_log是物理日志,记录页的物理修改操作,而undo_log是逻辑日志,根据每行记录进行记录。 +事务利用`MVCC`进行的读取操作称为`一致性读`,或者`一致性无锁读`,有的地方也称之为`快照读`。所有普通的`SELECT`语句(`plain SELECT`)在`READ COMMITTED`、`REPEATABLE READ`隔离级别下都算是`一致性读`,比方说: +```sql +SELECT * FROM t; +SELECT * FROM t1 INNER JOIN t2 ON t1.col1 = t2.col2 +``` +`一致性读`并不会对表中的任何记录做`加锁`操作,其他事务可以自由的对表中的记录做改动。 -### Mysql中的事务使用 +### 事务日志 - MySQL的服务层不管理事务,而是由下层的存储引擎实现。MySQL提供了两种事务型的存储引擎:InnoDB和NDB。 +事务日志可以帮助提高事务效率: -**MySQL支持本地事务的语句:** +- 使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。 +- 事务日志采用的是**追加**的方式,因此写日志的操作是磁盘上一小块区域内的顺序 I/O,而不像随机 I/O 需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。 +- 事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。 +- 如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。 -```mysql -START TRANSACTION | BEGIN [WORK] -COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] -ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] -SET AUTOCOMMIT = {0 | 1} -``` +目前来说,大多数存储引擎都是这样实现的,我们通常称之为**预写式日志**(Write-Ahead Logging),修改数据需要写两次磁盘。 -- START TRANSACTION 或 BEGIN 语句:开始一项新的事务。 -- COMMIT 和 ROLLBACK:用来提交或者回滚事务。 -- CHAIN 和 RELEASE 子句:分别用来定义在事务提交或者回滚之后的操作,CHAIN 会立即启动一个新事物,并且和刚才的事务具有相同的隔离级别,RELEASE 则会断开和客户端的连接。 -- SET AUTOCOMMIT 可以修改当前连接的提交方式, 如果设置了 SET AUTOCOMMIT=0,则设置之后的所有事务都需要通过明确的命令进行提交或者回滚 -**事务使用注意点:** -- 如果在锁表期间,用 start transaction 命令开始一个新事务,会造成一个隐含的 unlock - tables 被执行。 -- 在同一个事务中,最好不使用不同存储引擎的表,否则 ROLLBACK 时需要对非事 - 务类型的表进行特别的处理,因为 COMMIT、ROLLBACK 只能对事务类型的表进行提交和回滚。 -- 和 Oracle 的事务管理相同,所有的 DDL 语句是不能回滚的,并且部分的 DDL 语句会造成隐式的提交。 -- 在事务中可以通过定义 SAVEPOINT(例如:mysql> savepoint test; 定义 savepoint,名称为 test),指定回滚事务的一个部分,但是不能指定提交事务的一个部分。对于复杂的应用,可以定义多个不同的 SAVEPOINT,满足不同的条件时,回滚 - 不同的 SAVEPOINT。需要注意的是,如果定义了相同名字的 SAVEPOINT,则后面定义的SAVEPOINT 会覆盖之前的定义。对于不再需要使用的 SAVEPOINT,可以通过 RELEASE SAVEPOINT 命令删除 SAVEPOINT, 删除后的 SAVEPOINT, 不能再执行 ROLLBACK TO SAVEPOINT命令。 +事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持程度不一样。MySQL 中支持事务的存储引擎有 InnoDB 和 NDB。 -**自动提交(autocommit):** -Mysql默认采用自动提交模式,可以通过设置autocommit变量来启用或禁用自动提交模式 +事务的实现就是如何实现 ACID 特性。 -- **隐式锁定** +- **RR隔离级别下间隙锁才有效,RC隔离级别下没有间隙锁;** +- **RR隔离级别下为了解决“幻读”问题:“快照读”依靠MVCC控制,“当前读”通过间隙锁解决;** +- **间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间;** +- **间隙锁的引入,可能会导致同样语句锁住更大的范围,影响并发度。** - InnoDB在事务执行过程中,使用两阶段锁协议: - 随时都可以执行锁定,InnoDB会根据隔离级别在需要的时候自动加锁; - 锁只有在执行commit或者rollback的时候才会释放,并且所有的锁都是在**同一时刻**被释放。 +### 重做日志 -- **显式锁定** +**redo log(重做日志**) 实现持久化 - InnoDB也支持通过特定的语句进行显示锁定(存储引擎层): +在 InnoDB 的存储引擎中,事务日志通过重做(redo)日志和 InnoDB 存储引擎的日志缓冲(InnoDB Log Buffer)实现。事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是 DBA 们口中常说的“日志先行”(Write-Ahead Logging)。 -```mysql -select ... lock in share mode //共享锁 -select ... for update //排他锁 -``` +当事务提交之后,在 Buffer Pool 中映射的数据文件才会慢慢刷新到磁盘。此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据 redo log 中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。 - MySQL Server层的显示锁定: +在系统启动的时候,就已经为 redo log 分配了一块连续的存储空间,以顺序追加的方式记录 Redo Log,通过顺序 I/O 来改善性能。所有的事务共享 redo log 的存储空间,它们的 redo log 按语句的执行顺序,依次交替的记录在一起。 + +> InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。 +> +>  +> +> write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。 +> +> write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。 +> +> 有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**。 + + + +### 回滚日志 + +**undo log(回滚日志)** 实现原子性 + +undo log 主要为事务的回滚服务。在事务执行的过程中,除了记录 redo log,还会记录一定量的 undo log。undo log 记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据 undo log 进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。 + + + +二种日志均可以视为一种恢复操作,redo_log 是恢复提交事务修改的页操作,而 undo_log 是回滚行记录到特定版本。二者记录的内容也不同,redo_log 是**物理日志**,记录页的物理修改操作,而 undo_log 是**逻辑日志**,根据每行记录进行记录。 + +> 回滚日志可以**理解**为,我们在事务中使用的每一条 `INSERT` 都对应了一条 `DELETE`,每一条 `UPDATE` 也都对应一条相反的 `UPDATE` 语句。 +> +> 假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。 +> +>  +> +> 当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。 +> +> 同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。 +> +> +> +> 在可重复读隔离级别下,事务在启动的时候就“拍了个快照”。注意,这个快照是基于整库的。 +> +> 这时,你会说这看上去不太现实啊。如果一个库有 100G,那么我启动一个事务,MySQL 就要拷贝 100G 的数据出来,这个过程得多慢啊。可是,我平时的事务执行起来很快啊。 +> +> 实际上,我们并不需要拷贝出这 100G 的数据。我们先来看看这个快照是怎么实现的。 +> +> InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。 +> +> 而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。 +> +> 也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。 +> +> 如图 2 所示,就是一个记录被多个事务连续更新后的状态。 +> +>  +> +> 图中虚线框里是同一行数据的 4 个版本,当前最新版本是 V4,k 的值是 22,它是被 transaction id 为 25 的事务更新的,因此它的 row trx_id 也是 25 +> +> 你可能会问,前面的文章不是说,语句更新会生成 undo log(回滚日志)吗?那么,**undo log 在哪呢?** +> +> 实际上,图 2 中的三个虚线箭头,就是 undo log;而 V1、V2、V3 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时候,就是通过 V4 依次执行 U3、U2 算出来。 +> +> 明白了多版本和 row trx_id 的概念后,我们再来想一下,InnoDB 是怎么定义那个“100G”的快照的。 +> +> 按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。 +> +> 因此,一个事务只需要在启动的时候声明说,“**以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本**”。 +> +> 在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。 +> +> 数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。 +> +> 这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。 +> +> 而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的。 +> +> 这个视图数组把所有的 row trx_id 分成了几种不同的情况。 +> +>  +> +> -```mysql -lock table和unlock table -``` + + + + + + +在数据库系统中,事务的原子性和持久性是由事务日志(transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保证两点: + +1. 发生错误或者需要回滚的事务能够成功回滚(原子性); +2. 在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性); @@ -289,7 +495,16 @@ XA {START|BEGIN} xid [JOIN|RESUME] -> [数据库事务与MySQL事务总结](https://zhuanlan.zhihu.com/p/29166694) +## 总结 + + + + + +## References + +- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction/) +- [数据库事务与MySQL事务总结](https://zhuanlan.zhihu.com/p/29166694) diff --git a/docs/data-management/MySQL/MySQL-select.md b/docs/data-management/MySQL/MySQL-select.md index 7831d41f21..ac0c35efdd 100644 --- a/docs/data-management/MySQL/MySQL-select.md +++ b/docs/data-management/MySQL/MySQL-select.md @@ -1,4 +1,121 @@ -## 常见通用的Join查询 +--- +title: MySQL 查询 +date: 2023-03-31 +tags: + - MySQL +categories: MySQL +--- + + + + + +> `SQL`的全称是`Structured Query Language`,翻译后就是`结构化查询语言`。 + +## Order By + +在开发应用的时候,一定会经常碰到需要根据指定的字段排序来显示结果的需求。 + +```mysql +CREATE TABLE `t` ( + `id` int(11) NOT NULL, + `city` varchar(16) NOT NULL, + `name` varchar(16) NOT NULL, + `age` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `city` (`city`) +) ENGINE=InnoDB; +``` + +业务需求来了, + + + +Extra 这个字段中的“Using filesort”表示的就是需要排序,MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer。 + +```mysql +mysql> show variables like 'sort_buffer_size'; ++------------------+--------+ +| Variable_name | Value | ++------------------+--------+ +| sort_buffer_size | 262144 | ++------------------+--------+ +1 row in set (0.00 sec) +``` + +### 全字段排序 + +这个语句的大概执行流程是这样的: + +1. 初始化 sort_buffer,确定放入 name、city、age 这三个字段; +2. 从索引 city 找到第一个满足 city='北京’ 条件的主键 id; +3. 到主键 id 索引取出整行,取 name、city、age 三个字段的值,存入 sort_buffer 中; +4. 从索引 city 取下一个记录的主键 id; +5. 重复步骤 3、4 直到 city 的值不满足查询条件为止; +6. 对 sort_buffer 中的数据按照字段 name 做快速排序; +7. 按照排序结果取前 3 行返回给客户端。 + +我们暂且把这个排序过程,称为『全字段排序』 + +“按 name 排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size。 + +sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。 + +### rowid 排序 + +在上面这个算法过程里面,只对原表的数据读了一遍,剩下的操作都是在 sort_buffer 和临时文件中执行的。但这个算法有一个问题,就是如果查询要返回的字段很多的话,那么 sort_buffer 里面要放的字段数太多,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差。 + +所以如果单行很大,这个方法效率不够好。 + +那么,**如果 MySQL 认为排序的单行长度太大会怎么做呢?** + +接下来,我来修改一个参数,让 MySQL 采用另外一种算法。 + +```mysql +SET max_length_for_sort_data = 16; +``` + +max_length_for_sort_data,是 MySQL 中专门控制用于排序的行数据的长度的一个参数。它的意思是,如果单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法。 + +city、name、age 这三个字段的定义总长度是 36,我把 max_length_for_sort_data 设置为 16,我们再来看看计算过程有什么改变。 + +新的算法放入 sort_buffer 的字段,只有要排序的列(即 name 字段)和主键 id。 + +但这时,排序的结果就因为少了 city 和 age 字段的值,不能直接返回了,整个执行流程就变成如下所示的样子: + +1. 初始化 sort_buffer,确定放入两个字段,即 name 和 id; +2. 从索引 city 找到第一个满足 city='杭州’条件的主键 id; +3. 到主键 id 索引取出整行,取 name、id 这两个字段,存入 sort_buffer 中; +4. 从索引 city 取下一个记录的主键 id; +5. 重复步骤 3、4 直到不满足 city='杭州’条件为止; +6. 对 sort_buffer 中的数据按照字段 name 进行排序; +7. 遍历排序结果,取前 3 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。 + +对全字段排序流程你会发现,rowid 排序多访问了一次表 t 的主键索引,就是步骤 7。 + + + +### 全字段排序 VS rowid 排序 + +如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。 + +如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。 + +这也就体现了 MySQL 的一个设计思想:**如果内存够,就要多利用内存,尽量减少磁盘访问。** + + + +当然,如果我们创建了覆盖索引,就不需要再排序了 + +```mysql +alter table t add index city_user_age(city, name, age); +``` + + + + + +## 常见通用的 Join 查询 ### SQL执行顺序 @@ -32,13 +149,11 @@ - 总结 -  - - +  ### Join图 - + ### demo @@ -111,7 +226,7 @@ INSERT INTO tbl_emp(NAME,deptId) VALUES('s9',51); ``` 6. AB全有 - + **MySQL Full Join的实现 因为MySQL不支持FULL JOIN,替代方法:left join + union(可去除重复数据)+ right join** @@ -131,3 +246,42 @@ INSERT INTO tbl_emp(NAME,deptId) VALUES('s9',51); +## count() + +count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。 + +### count(*) 的实现方式 + +你首先要明确的是,在不同的 MySQL 引擎中,count(*) 有不同的实现方式。 + +- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高; +- 而 InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。 + +> 那**为什么 InnoDB 不跟 MyISAM 一样,也把数字存起来呢?** +> +> 这是因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。 + + + + + +**对于 count(主键 id) 来说**,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。 + +**对于 count(1) 来说**,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。 + +单看这两个用法的差别的话,你能对比出来,count(1) 执行得要比 count(主键 id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。 + +**对于 count(字段) 来说**: + +1. 如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加; +2. 如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。 + + + +按照效率排序的话,count(字段) 数据库的设计三范式: +> +> - 1NF:字段不可分。第一范式要求数据原子化 +> - 2NF:唯一性 一个表只说明一个事物。有主键,非主键字段依赖主键。第二范式消除部分依赖 +> - 3NF:非主键字段不能相互依赖。第三范式消除传递依赖,从而提高数据的完整性和一致性 + +在当今信息化时代,数据库已经成为各行各业中不可或缺的一部分。从小型应用到企业级系统,数据库都在背后默默支撑着数据的存储、管理与分析。而在数据库设计中,**三范式**(3NF)是关系型数据库模型设计中最为基础和重要的规范化原则之一。三范式的核心目标是通过规范化数据库的结构,减少冗余数据,增强数据一致性和完整性,避免更新异常,确保数据的高效存储与访问。 + +### 什么是数据库规范化? + +数据库规范化(Normalization)是指对数据库表的设计进行优化的过程,通过一系列规则来消除冗余数据,并提高数据的组织方式。规范化不仅帮助减少数据的重复性,还确保数据之间的逻辑关系清晰,减少因数据修改引发的不一致问题。 + +规范化的核心思想是通过分解数据库表,降低数据冗余性和依赖性。在实际操作中,数据库规范化通常分为多个阶段,称为**范式**。每一范式都是在前一范式的基础上进一步精化和完善的。三范式是数据库设计中最为基础且常用的范式,它通过消除部分依赖和传递依赖等问题,保证数据库的高效性与一致性。 + +### 第一范式(1NF) + +**第一范式**是数据库规范化的最基础范式,它的要求是:**表中的每个列必须包含原子值,即每个字段只能存储不可再分的单一数据单元**。简而言之,1NF 要求每个列的数据必须是“原子的”,也就是没有重复数据和多值属性。 + +#### 1NF的具体要求: + +1. 每列中的数据必须是不可分割的单一数据项。 +2. 每一行必须唯一,不允许有重复的记录。 +3. 表中的每个字段都必须具有唯一的标识符(即主键)。 + +**示例**: 假设我们有一个学生信息表,如下所示: + +| 学生ID | 姓名 | 电话号码 | +| ------ | ---- | ---------------------- | +| 1 | 张三 | 1234567890, 0987654321 | +| 2 | 李四 | 1357924680, 2468013579 | + +从上表可以看出,"电话号码"字段存储了多个值,违反了1NF。要满足1NF要求,电话号码字段应拆分为多行或多列,像这样: + +| 学生ID | 姓名 | 电话号码 | +| ------ | ---- | ---------- | +| 1 | 张三 | 1234567890 | +| 1 | 张三 | 0987654321 | +| 2 | 李四 | 1357924680 | +| 2 | 李四 | 2468013579 | + +这样,每个字段就变成了原子值,符合了1NF的要求。 + +### 第二范式(2NF) + +第二范式建立在第一范式的基础之上,要求**消除部分依赖**。具体而言,2NF要求表中的所有非主属性(即不参与主键的字段)必须完全依赖于主键,而不能仅依赖于主键的一部分。换句话说,表中的每一个非主属性必须依赖于完整的主键,而不是主键的某一部分。 + +#### 2NF的要求: + +1. 表必须满足1NF。 +2. 表中的每个非主键列必须完全依赖于整个主键。 +3. 如果表有复合主键(由多个列组成的主键),则不能有部分依赖。 + +**示例**: 考虑以下的订单表: + +| 订单ID | 产品ID | 产品名称 | 数量 | 单价 | +| ------ | ------ | -------- | ---- | ---- | +| 1 | 101 | 手机 | 2 | 2000 | +| 1 | 102 | 耳机 | 1 | 500 | +| 2 | 101 | 手机 | 1 | 2000 | + +在这个例子中,复合主键由**订单ID**和**产品ID**组成。虽然**产品名称**只依赖于**产品ID**,但它却被存储在了当前的表中,这种依赖关系违反了2NF。我们可以通过拆分表格来消除部分依赖: + +**订单表:** + +| 订单ID | 产品ID | 数量 | 单价 | +| ------ | ------ | ---- | ---- | +| 1 | 101 | 2 | 2000 | +| 1 | 102 | 1 | 500 | +| 2 | 101 | 1 | 2000 | + +**产品表:** + +| 产品ID | 产品名称 | +| ------ | -------- | +| 101 | 手机 | +| 102 | 耳机 | + +通过这样的拆分,我们确保了所有非主键字段完全依赖于复合主键,符合了2NF。 + +### 第三范式(3NF) + +第三范式是在第二范式的基础上进一步规范化,它要求消除**传递依赖**。传递依赖是指某个非主键列依赖于另一个非主键列,而这个非主键列又依赖于主键。换句话说,第三范式要求表中的每个非主属性都直接依赖于主键,而不是间接依赖。 + +#### 3NF的要求: + +1. 表必须满足2NF。 +2. 表中的每个非主键列必须直接依赖于主键,不能依赖于其他非主键列。 + +**示例**: 考虑以下员工表: + +| 员工ID | 部门ID | 部门名称 | 部门经理 | +| ------ | ------ | -------- | -------- | +| 1 | 101 | 销售部 | 王经理 | +| 2 | 102 | 技术部 | 张经理 | + +在这个例子中,**部门名称**和**部门经理**依赖于**部门ID**,而**部门ID**又依赖于**员工ID**,这就构成了传递依赖。为了满足3NF,我们应该将部门相关信息提取到单独的表中: + +**员工表:** + +| 员工ID | 部门ID | +| ------ | ------ | +| 1 | 101 | +| 2 | 102 | + +**部门表:** + +| 部门ID | 部门名称 | 部门经理 | +| ------ | -------- | -------- | +| 101 | 销售部 | 王经理 | +| 102 | 技术部 | 张经理 | + +通过这样的拆分,我们消除了传递依赖,确保了表符合3NF的要求。 + +### 规范化的优点与实际应用 + +数据库规范化的主要优点是减少冗余数据,提高数据的一致性和完整性。通过规范化,数据的修改操作变得更加简便,不容易出现更新异常,例如**插入异常**、**删除异常**和**更新异常**。 + +然而,在实际应用中,数据库设计并非总是严格遵循最高范式。过度规范化可能会导致表的拆分过多,从而影响查询性能。在这种情况下,数据库设计师可能会选择在一定程度上**反规范化**,即故意增加一些冗余数据,以提高查询效率。 + +### 总结 + +数据库的三范式是关系型数据库设计的基础,它通过消除数据冗余和不必要的依赖关系,帮助提高数据的一致性和完整性。第一范式要求数据原子化,第二范式消除部分依赖,而第三范式消除传递依赖。在实际的数据库设计中,三范式为我们提供了科学的规范化思路,但在一些特定场景下,可能需要适当进行反规范化来优化查询性能。 + +了解并掌握三范式的概念,能帮助开发人员在设计数据库时做出更加高效且一致的决策,提升数据库系统的整体性能和可维护性。 + diff --git a/docs/data-management/MySQL/readMySQL.md b/docs/data-management/MySQL/readMySQL.md index 20e4d4e390..c8f198ff9f 100644 --- a/docs/data-management/MySQL/readMySQL.md +++ b/docs/data-management/MySQL/readMySQL.md @@ -3,9 +3,6 @@ --- - - - @@ -26,7 +23,7 @@ MySQL是个啥,就说一句话——**MySQL是一个关系型数据库管理 - + diff --git "a/docs/data-management/MySQL/Int(4)\345\222\214Int(11) \351\200\211\345\223\252\344\270\252\357\274\237.md" "b/docs/data-management/MySQL/reproduce/Int(4)\345\222\214Int(11) \351\200\211\345\223\252\344\270\252\357\274\237.md" similarity index 100% rename from "docs/data-management/MySQL/Int(4)\345\222\214Int(11) \351\200\211\345\223\252\344\270\252\357\274\237.md" rename to "docs/data-management/MySQL/reproduce/Int(4)\345\222\214Int(11) \351\200\211\345\223\252\344\270\252\357\274\237.md" diff --git a/docs/data-management/MySQL/reproduce/MySQL-count.md b/docs/data-management/MySQL/reproduce/MySQL-count.md new file mode 100644 index 0000000000..aa359617cd --- /dev/null +++ b/docs/data-management/MySQL/reproduce/MySQL-count.md @@ -0,0 +1,181 @@ +> 《MySQL 实战45 讲》 + +在开发系统的时候,你可能经常需要计算一个表的行数,比如一个交易系统的所有变更记录总数。这时候你可能会想,一条 select count(*) from t 语句不就解决了吗? + +但是,你会发现随着系统中记录数越来越多,这条语句执行得也会越来越慢。然后你可能就想了,MySQL 怎么这么笨啊,记个总数,每次要查的时候直接读出来,不就好了吗。 + +那么今天,我们就来聊聊 count(*) 语句到底是怎样实现的,以及 MySQL 为什么会这么实现。然后,我会再和你说说,如果应用中有这种频繁变更并需要统计表行数的需求,业务设计上可以怎么做。 + +# count(*) 的实现方式 + +你首先要明确的是,在不同的 MySQL 引擎中,count(*) 有不同的实现方式。 + +- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高; +- 而 InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。 + +**这里需要注意的是,我们在这篇文章里讨论的是没有过滤条件的 count(*),如果加了 where 条件的话,MyISAM 表也是不能返回得这么快的。** + +在前面的文章中,我们一起分析了为什么要使用 InnoDB,因为不论是在事务支持、并发能力还是在数据安全方面,InnoDB 都优于 MyISAM。我猜你的表也一定是用了 InnoDB 引擎。这就是当你的记录数越来越多的时候,计算一个表的总行数会越来越慢的原因。 + +那**为什么 InnoDB 不跟 MyISAM 一样,也把数字存起来呢?** + +这是因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。这里,我用一个算 count(*) 的例子来为你解释一下。 + +假设表 t 中现在有 10000 条记录,我们设计了三个用户并行的会话。 + +- 会话 A 先启动事务并查询一次表的总行数; +- 会话 B 启动事务,插入一行后记录后,查询表的总行数; +- 会话 C 先启动一个单独的语句,插入一行记录后,查询表的总行数。 + +我们假设从上到下是按照时间顺序执行的,同一行语句是在同一时刻执行的。 + + + +你会看到,在最后一个时刻,三个会话 A、B、C 会同时查询表 t 的总行数,但拿到的结果却不同。 + +这和 InnoDB 的事务设计有关系,可重复读是它默认的隔离级别,在代码上就是通过多版本并发控制,也就是 MVCC 来实现的。每一行记录都要判断自己是否对这个会话可见,因此对于 count(*) 请求来说,InnoDB 只好把数据一行一行地读出依次判断,可见的行才能够用于计算“基于这个查询”的表的总行数。 + +当然,现在这个看上去笨笨的 MySQL,在执行 count(*) 操作的时候还是做了优化的。 + +你知道的,InnoDB 是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。所以,普通索引树比主键索引树小很多。对于 count(*) 这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此,MySQL 优化器会找到最小的那棵树来遍历。**在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。** + +如果你用过 show table status 命令的话,就会发现这个命令的输出结果里面也有一个 TABLE_ROWS 用于显示这个表当前有多少行,这个命令执行挺快的,那这个 TABLE_ROWS 能代替 count(*) 吗? + +实际上,TABLE_ROWS 就是从这个采样估算得来的,因此它也很不准。有多不准呢,官方文档说误差可能达到 40% 到 50%。**所以,show table status 命令显示的行数也不能直接使用。** + +到这里我们小结一下: + +- MyISAM 表虽然 count(*) 很快,但是不支持事务; +- show table status 命令虽然返回很快,但是不准确; +- InnoDB 表直接 count(*) 会遍历全表,虽然结果准确,但会导致性能问题。 + +那么,回到文章开头的问题,如果你现在有一个页面经常要显示交易系统的操作记录总数,到底应该怎么办呢?答案是,我们只能自己计数。 + +接下来,我们讨论一下,看看自己计数有哪些方法,以及每种方法的优缺点有哪些。 + +这里,我先和你说一下这些方法的基本思路:你需要自己找一个地方,把操作记录表的行数存起来。 + +# 用缓存系统保存计数 + +对于更新很频繁的库来说,你可能会第一时间想到,用缓存系统来支持。 + +你可以用一个 Redis 服务来保存这个表的总行数。这个表每被插入一行 Redis 计数就加 1,每被删除一行 Redis 计数就减 1。这种方式下,读和更新操作都很快,但你再想一下这种方式存在什么问题吗? + +没错,缓存系统可能会丢失更新。 + +Redis 的数据不能永久地留在内存里,所以你会找一个地方把这个值定期地持久化存储起来。但即使这样,仍然可能丢失更新。试想如果刚刚在数据表中插入了一行,Redis 中保存的值也加了 1,然后 Redis 异常重启了,重启后你要从存储 redis 数据的地方把这个值读回来,而刚刚加 1 的这个计数操作却丢失了。 + +当然了,这还是有解的。比如,Redis 异常重启以后,到数据库里面单独执行一次 count(*) 获取真实的行数,再把这个值写回到 Redis 里就可以了。异常重启毕竟不是经常出现的情况,这一次全表扫描的成本,还是可以接受的。 + +但实际上,**将计数保存在缓存系统中的方式,还不只是丢失更新的问题。即使 Redis 正常工作,这个值还是逻辑上不精确的。** + +你可以设想一下有这么一个页面,要显示操作记录的总数,同时还要显示最近操作的 100 条记录。那么,这个页面的逻辑就需要先到 Redis 里面取出计数,再到数据表里面取数据记录。 + +我们是这么定义不精确的: + +1. 一种是,查到的 100 行结果里面有最新插入记录,而 Redis 的计数里还没加 1; +2. 另一种是,查到的 100 行结果里没有最新插入的记录,而 Redis 的计数里已经加了 1。 + +这两种情况,都是逻辑不一致的。 + +我们一起来看看这个时序图。 + + + +上图中,会话 A 是一个插入交易记录的逻辑,往数据表里插入一行 R,然后 Redis 计数加 1;会话 B 就是查询页面显示时需要的数据。 + +在上图 的这个时序里,在 T3 时刻会话 B 来查询的时候,会显示出新插入的 R 这个记录,但是 Redis 的计数还没加 1。这时候,就会出现我们说的数据不一致。 + +你一定会说,这是因为我们执行新增记录逻辑时候,是先写数据表,再改 Redis 计数。而读的时候是先读 Redis,再读数据表,这个顺序是相反的。那么,如果保持顺序一样的话,是不是就没问题了?我们现在把会话 A 的更新顺序换一下,再看看执行结果。 + + + + + +你会发现,这时候反过来了,会话 B 在 T3 时刻查询的时候,Redis 计数加了 1 了,但还查不到新插入的 R 这一行,也是数据不一致的情况。 + +在并发系统里面,我们是无法精确控制不同线程的执行时刻的,因为存在图中的这种操作序列,所以,我们说即使 Redis 正常工作,这个计数值还是逻辑上不精确的。 + +# 在数据库保存计数 + +根据上面的分析,用缓存系统保存计数有丢失数据和计数不精确的问题。那么,**如果我们把这个计数直接放到数据库里单独的一张计数表 C 中,又会怎么样呢?** + +首先,这解决了崩溃丢失的问题,InnoDB 是支持崩溃恢复不丢数据的。 + +> 备注:关于 InnoDB 的崩溃恢复,你可以再回顾一下第 2 篇文章[《日志系统:一条 SQL 更新语句是如何执行的?》](https://time.geekbang.org/column/article/68633)中的相关内容。 + +然后,我们再看看能不能解决计数不精确的问题。 + +你会说,这不一样吗?无非就是把图 3 中对 Redis 的操作,改成了对计数表 C 的操作。只要出现图 3 的这种执行序列,这个问题还是无解的吧? + +这个问题还真不是无解的。 + +我们这篇文章要解决的问题,都是由于 InnoDB 要支持事务,从而导致 InnoDB 表不能把 count(*) 直接存起来,然后查询的时候直接返回形成的。 + +所谓以子之矛攻子之盾,现在我们就利用“事务”这个特性,把问题解决掉。 + + + +我们来看下现在的执行结果。虽然会话 B 的读操作仍然是在 T3 执行的,但是因为这时候更新事务还没有提交,所以计数值加 1 这个操作对会话 B 还不可见。 + +因此,会话 B 看到的结果里, 查计数值和“最近 100 条记录”看到的结果,逻辑上就是一致的。 + +# 不同的 count 用法 + +在 select count(?) from t 这样的查询语句里面,count(*)、count(主键 id)、count(字段) 和 count(1) 等不同用法的性能,有哪些差别。 + +需要注意的是,下面的讨论还是基于 InnoDB 引擎的。 + +这里,首先你要弄清楚 count() 的语义。count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。 + +所以,count(*)、count(主键 id) 和 count(1) 都表示返回满足条件的结果集的总行数;而 count(字段),则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。 + +至于分析性能差别的时候,你可以记住这么几个原则: + +1. server 层要什么就给什么; +2. InnoDB 只给必要的值; +3. 现在的优化器只优化了 count(*) 的语义为“取行数”,其他“显而易见”的优化并没有做。 + +这是什么意思呢?接下来,我们就一个个地来看看。 + +**对于 count(主键 id) 来说**,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。 + +**对于 count(1) 来说**,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。 + +单看这两个用法的差别的话,你能对比出来,count(1) 执行得要比 count(主键 id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。 + +**对于 count(字段) 来说**: + +1. 如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加; +2. 如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。 + +也就是前面的第一条原则,server 层要什么字段,InnoDB 就返回什么字段。 + +**但是 count(\*) 是例外**,并不会把全部字段取出来,而是专门做了优化,不取值。count(*) 肯定不是 null,按行累加。 + +看到这里,你一定会说,优化器就不能自己判断一下吗,主键 id 肯定非空啊,为什么不能按照 count(*) 来处理,多么简单的优化啊。 + +当然,MySQL 专门针对这个语句进行优化,也不是不可以。但是这种需要专门优化的情况太多了,而且 MySQL 已经优化过 count(*) 了,你直接使用这种用法就可以了。 + +所以结论是:按照效率排序的话,count(字段) (成绩,课程学分), 表示所有非主键列 (成绩,课程学分)都依赖于主键 (学号,课程)。 但是,表中还存在另外一个依赖:(课程)->(课程学分)。这样非主键列 ‘课程学分‘ 依赖于部分主键列 ’课程‘, 所以上表是不满足第二范式的。 - -我们把它拆成如下2张表: - - - -学生选课表: - -| 学号 | 课程 | 成绩 | -| ----- | ---- | ---- | -| 10001 | 数学 | 100 | -| 10001 | 语文 | 90 | -| 10001 | 英语 | 85 | -| 10002 | 数学 | 90 | -| 10003 | 数学 | 99 | -| 10004 | 语文 | 89 | - -课程信息表: - -| 课程 | 课程学分 | -| ---- | -------- | -| 数学 | 6 | -| 语文 | 3 | -| 英语 | 2 | - -那么上面2个表,学生选课表主键为(学号,课程),课程信息表主键为(课程),表中所有非主键列都完全依赖主键。不仅符合第二范式,还符合第三范式。 - - - -再看这样一个学生信息表: - -| 学号 | 姓名 | 性别 | 班级 | 班主任 | -| ----- | ------ | ---- | ---- | ------ | -| 10001 | 张三 | 男 | 一班 | 小王 | -| 10002 | 李四 | 男 | 一班 | 小王 | -| 10003 | 王五 | 男 | 二班 | 小李 | -| 10004 | 张小三 | 男 | 二班 | 小李 | - -上表中,主键为:(学号),所有字段 (姓名,性别,班级,班主任)都依赖与主键(学号),不存在对主键的部分依赖。所以是满足第二范式。 - - - -#### 第三范式(3NF) - -满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主键字段。就是说,表的信息,如果能够被推导出来,就不应该单独的设计一个字段来存放(能尽量外键join就用外键join)。很多时候,我们为了满足第三范式往往会把一张表分成多张表。 - -即满足第二范式前提,如果某一属性依赖于其他非主键属性,而其他非主键属性又依赖于主键,那么这个属性就是间接依赖于主键,这被称作传递依赖于主属性。 通俗解释就是一张表最多只存两层同类型信息。 - - - -反三范式 - -没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,提高读性能,就必须降低范式标准,适当保留冗余数据。具体做法是: 在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,减少了查询时的关联,提高查询效率,因为在数据库的操作中查询的比例要远远大于DML的比例。但是反范式化一定要适度,并且在原本已满足三范式的基础上再做调整的。 \ No newline at end of file diff --git a/docs/data-management/Redis/.DS_Store b/docs/data-management/Redis/.DS_Store index d4fb639cb0..45706ae4e1 100644 Binary files a/docs/data-management/Redis/.DS_Store and b/docs/data-management/Redis/.DS_Store differ diff --git a/docs/data-management/Redis/Nosql-Overview.md b/docs/data-management/Redis/Nosql-Overview.md index d432e219c0..4e5c48ff95 100644 --- a/docs/data-management/Redis/Nosql-Overview.md +++ b/docs/data-management/Redis/Nosql-Overview.md @@ -1,9 +1,5 @@ - - # NoSQL的前世今生 -> Java大猿帅成长手册,**GitHub** [JavaEgg](https://github.com/Jstarfish/JavaEgg) ,N线互联网开发必备技能兵器谱 - ### 啥玩意: NoSQL(NoSQL = Not Only SQL ),“不仅仅是SQL”,泛指**非关系型的数据库**。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。 @@ -14,7 +10,7 @@ NoSQL(NoSQL = Not Only SQL ),“不仅仅是SQL”,泛指**非关系型的 #### 1. 单机MySQL的美好年代 - 在以前,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。在那个时候,更多的都是静态网页,动态交互类型的网站不多。上述架构下,我们来看看数据存储的瓶颈是什么? +在以前,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。在那个时候,更多的都是静态网页,动态交互类型的网站不多。上述架构下,我们来看看数据存储的瓶颈是什么? - 数据量的总大小 一个机器放不下时 - 数据的索引(B+ Tree)一个机器的内存放不下时 @@ -22,29 +18,29 @@ NoSQL(NoSQL = Not Only SQL ),“不仅仅是SQL”,泛指**非关系型的 #### 2. Memcached(缓存)+MySQL+垂直拆分 - 后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web程序不再仅仅专注在功能上,同时也在追求性能。程序员们开始大量的使用**缓存技术**来缓解数据库的压力,优化数据库的结构和索引。开始比较流行的是通过**文件缓存**来缓解数据库压力,但是当访问量继续增大的时候,多台web机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。 +后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web程序不再仅仅专注在功能上,同时也在追求性能。程序员们开始大量的使用**缓存技术**来缓解数据库的压力,优化数据库的结构和索引。开始比较流行的是通过**文件缓存**来缓解数据库压力,但是当访问量继续增大的时候,多台web机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。 - Memcached作为一个**独立的分布式的缓存服务器**,为多个web服务器提供了一个共享的高性能缓存服务,在Memcached服务器上,又发展了根据hash算法来进行多台Memcached缓存服务的扩展,然后又出现了一致性hash来解决增加或减少缓存服务器导致重新hash带来的大量缓存失效的弊端 +Memcached作为一个**独立的分布式的缓存服务器**,为多个web服务器提供了一个共享的高性能缓存服务,在Memcached服务器上,又发展了根据hash算法来进行多台Memcached缓存服务的扩展,然后又出现了一致性hash来解决增加或减少缓存服务器导致重新hash带来的大量缓存失效的弊端 #### 3. Mysql主从读写分离 - 由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负,大部分网站开始**使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性**。**Mysql的master-slave模式**成为这个时候的网站标配了。 +由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负,大部分网站开始**使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性**。**Mysql的master-slave模式**成为这个时候的网站标配了。 #### 4. 分表分库+水平拆分+mysql集群 - 在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于**MyISAM**使用**表锁**,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用**InnoDB**引擎代替MyISAM。 +在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于**MyISAM**使用**表锁**,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用**InnoDB**引擎代替MyISAM。 - 同时,开始流行**使用分表分库来缓解写压力和数据增长的扩展问题**。这个时候,分表分库成了一个热门技术,是面试的热门问题也是业界讨论的热门技术问题。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。 +同时,开始流行**使用分表分库来缓解写压力和数据增长的扩展问题**。这个时候,分表分库成了一个热门技术,是面试的热门问题也是业界讨论的热门技术问题。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。 #### 5. MySQL的扩展性瓶颈 - MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。 +MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。 #### 6. 为什么用NoSQL - 今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的**访问和抓取数据**(爬虫私密信息有风险哈)。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也不能很好的处理这些大的数据。 +今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的**访问和抓取数据**(爬虫私密信息有风险哈)。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也不能很好的处理这些大的数据。 - + diff --git a/docs/data-management/Redis/ReadRedis.md b/docs/data-management/Redis/ReadRedis.md index 57b9504666..2ecbb031b8 100644 --- a/docs/data-management/Redis/ReadRedis.md +++ b/docs/data-management/Redis/ReadRedis.md @@ -1,4 +1,4 @@ - + @@ -8,6 +8,8 @@ > > 带着问题去系统学习,有一个自己的问题画像,最后梳理成自己的“武功秘籍” > +> 看下极客时间中的一个 Redis 问题画像图: +> >  @@ -16,96 +18,64 @@ Redis: **REmote DIctionary Server**(远程字典服务器)。 -Redis 是一个全开源免费(BSD许可)的,内存中的数据结构存储系统,它可以用作**数据库、缓存和消息中间件**。一般作为一个高性能的(key/value)分布式内存数据库,基于**内存**运行并支持持久化的 NoSQL 数据库,是当前最热门的 NoSql 数据库之一,也被人们称为**数据结构服务器** - - - -## Redis 介绍 - -Redis 是一个开源的、使用 C 语言编写的、支持网络交互的、可基于内存也可持久化的 Key-Value 数据库。 - -Redis 是一个 key-value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括**string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)**。这些数据类型都支持push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步,所以 Redis 也可以被看成是一个数据结构服务器。 - - - -Redis 支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。 - -Redis 的官网地址,非常好记,是 redis.io。(域名后缀io属于国家域名,是 british Indian Ocean territory,即英属印度洋领地)目前,Vmware 在资助着 Redis 项目的开发和维护 - -Redis 的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”);也可以把每一次数据变化都写入到一个 append only file(aof)里面(这称为“全持久化模式”)。这就是 redis 提供的两种持久化的方式,RDB(Redis DataBase)和 AOF(Append Only File)。 - - - -## Redis 特点 +Redis 是一个全开源免费(BSD许可)的,使用 C 语言编写,内存中的数据结构存储系统,它可以用作**数据库、缓存和消息中间件**。一般作为一个高性能的(key/value)分布式内存数据库,基于**内存**运行并支持持久化的 NoSQL 数据库,是当前最热门的 NoSql 数据库之一,也被人们称为**数据结构服务器** -Redis 是一个开源,先进的 key-value 存储,并用于构建高性能,可扩展的Web应用程序的完美解决方案。 +它支持多种数据结构,如字符串(Strings)、哈希(Hashes)、列表(Lists)、集合(Sets)、有序集合(Sorted Sets)、位图(bitmaps)、HyperLogLogs 和地理空间索引(geospatial indexes),并带有半持久化存储的选项。 -Redis从它的许多竞争继承来的三个主要特点: +### 主要特点 -- Redis数据库完全在内存中,使用磁盘仅用于持久性。 +1. **高性能**:Redis 的读写速度非常快,支持每秒百万级的请求处理能力。其所有数据都存储在内存中,保证了极高的读写性能。 +2. **多种数据结构**:Redis 不仅支持简单的键值对,还支持多种高级数据结构,如列表、集合、有序集合、哈希等,可以满足复杂的数据存储需求。 +3. **持久化**:Redis 支持多种持久化机制,如 RDB(快照)和 AOF(追加文件),可以将内存中的数据持久化到磁盘,保证数据的持久性。 +4. **高可用性**:通过 Redis 的复制(Replication)、Sentinel 和 Cluster 特性,可以实现高可用性和自动故障转移。复制机制允许数据从主节点复制到多个从节点,从而提高数据的冗余性和读取的可扩展性。 +5. **Lua脚本**:Redis 内置 Lua 脚本引擎,支持在服务器端运行复杂的脚本,减少了网络往返次数,提高了操作的原子性。 +6. **事务支持**:Redis 支持事务,通过 MULTI、EXEC、DISCARD 和 WATCH 等命令实现事务操作。 +7. **发布/订阅**:Redis 提供了发布/订阅功能,可以实现消息通知和实时消息传递。 +8. **丰富的生态系统**:Redis 拥有丰富的客户端库,支持多种编程语言,包括 C、C++、Java、Python、Go、Node.js等。 -- 相比许多键值数据存储,Redis拥有一套较为丰富的数据类型。 - -- Redis可以将数据复制到任意数量的从服务器。 - - - -## Redis 优势 - -- 异常快速:Redis的速度非常快,每秒能执行约11万集合,每秒约81000+条记录。SET操作每秒钟 110000 次,GET操作每秒钟 81000 次,网站一般使用Redis作为**缓存服务器**。 -- 支持**丰富的数据类型**:Redis 支持大多数开发人员已经知道像列表,集合,有序集合,散列数据类型。这使得它非常容易解决各种各样的问题,因为我们知道哪些问题是可以处理通过它的数据类型更好。 -- 操作都是**原子性**:所有 Redis 操作是原子的,这保证了如果两个客户端同时访问的 Redis 服务器将获得更新后的值。 -- MultiUtility 工具:Redis 是一个多功能实用工具,可以在很多如:缓存,消息传递队列中使用(Redis 原生支持发布/订阅),在应用程序中,如:Web应用程序会话,网站页面点击数等任何短暂的数据; - - - -#### Redis 使用场景 - -- 取最新N个数据的操作 -- 排行榜应用,取TOP N 操作 -- 需要精确设定过期时间的应用 -- 定时器、计数器应用 -- Uniq操作,获取某段时间所有数据排重值 -- 实时系统,反垃圾系统 -- Pub/Sub构建实时消息系统 -- 构建队列系统 -- 缓存 +### 应用场景 +1. **缓存**:Redis 作为缓存系统,可以极大地提高数据读取速度,减轻数据库的压力。 +2. **会话存储**:利用 Redis 的高性能和持久化特性,可以用于存储用户会话信息。 +3. **实时分析**:利用 Redis 的集合和有序集合,可以进行实时数据分析和排名。 +4. **消息队列**:利用 Redis 的列表和发布/订阅特性,可以实现简单的消息队列系统。 +5. **计数器和限流**:利用 Redis 的原子递增操作,可以实现高效的计数器和限流机制。 +> **具体以某一论坛为例:** +> +> - 记录帖子的点赞数、评论数和点击数 (hash)。 +> - 记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表 (zset)。 +> - 记录帖子的标题、摘要、作者和封面信息,用于列表页展示 (hash)。 +> - 记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数 (zset)。 +> - 缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (hash)。 +> - 记录帖子的相关文章 ID,根据内容推荐相关帖子 (list)。 +> - 如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器)。 +> - 收藏集和帖子之间的关系 (zset)。 +> - 记录热榜帖子 ID 列表,总热榜和分类热榜 (zset)。 +> - 缓存用户行为历史,进行恶意行为过滤 (zset,hash)。 -**具体以某一论坛为例:** +Redis 的官网地址,非常好记,是 redis.io。(域名后缀io属于国家域名,是 british Indian Ocean territory,即英属印度洋领地)目前,Vmware 在资助着 Redis 项目的开发和维护。 -- 记录帖子的点赞数、评论数和点击数 (hash)。 -- 记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表 (zset)。 -- 记录帖子的标题、摘要、作者和封面信息,用于列表页展示 (hash)。 -- 记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数 (zset)。 -- 缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (hash)。 -- 记录帖子的相关文章 ID,根据内容推荐相关帖子 (list)。 -- 如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器)。 -- 收藏集和帖子之间的关系 (zset)。 -- 记录热榜帖子 ID 列表,总热榜和分类热榜 (zset)。 -- 缓存用户行为历史,进行恶意行为过滤 (zset,hash)。 - **安装** -``` +```shell $ wget http://download.redis.io/releases/redis-5.0.6.tar.gz $ tar xzf redis-5.0.6.tar.gz $ cd redis-5.0.6 $ make ``` -新版本的编译文件在src中(之前在bin目录),启动server +新版本的编译文件在 src 中(之前在bin目录),启动 server -``` +```sh $ src/redis-server ``` 启动客户端 -``` +```shell $ src/redis-cli redis> set foo bar OK @@ -117,7 +87,7 @@ redis> get foo ## Redis 知识全景 - + “两大维度”就是指系统维度和应用维度,“三大主线”也就是指高性能、高可靠和高可扩展(可以简称为“三高”)。 @@ -133,6 +103,8 @@ Redis 作为庞大的键值数据库,可以说遍地都是知识,一抓一 + + ## 推荐阅读 [《我是如何学习Redis的?高效学习Redis的路径和方法分享》](http://kaito-kidd.com/2020/09/09/how-i-learned-redis/) \ No newline at end of file diff --git "a/docs/data-management/Redis/Redis \346\234\211\347\224\250\346\214\207\344\273\244.md" "b/docs/data-management/Redis/Redis \346\234\211\347\224\250\346\214\207\344\273\244.md" new file mode 100644 index 0000000000..8a096f1a0e --- /dev/null +++ "b/docs/data-management/Redis/Redis \346\234\211\347\224\250\346\214\207\344\273\244.md" @@ -0,0 +1,146 @@ +## Info 指令 + +在使用 Redis 时,时常会遇到很多问题需要诊断,在诊断之前需要了解 Redis 的运行状态,通过强大的 Info 指令,你可以清晰地知道 Redis 内部一系列运行参数。 + +Info 指令显示的信息非常繁多,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是: + +1. Server 服务器运行的环境参数 +2. Clients 客户端相关信息 +3. Memory 服务器运行内存统计数据 +4. Persistence 持久化信息 +5. Stats 通用统计数据 +6. Replication 主从复制相关信息 +7. CPU CPU 使用情况 +8. Cluster 集群信息 +9. KeySpace 键值对统计数量信息 + +Info 可以一次性获取所有的信息,也可以按块取信息。 + +``` +# 获取所有信息 +> info +# 获取内存相关信息 +> info memory +# 获取复制相关信息 +> info replication + +#Redis 连接了多少客户端? +> info clients + +#Redis 内存占用多大 ? +> redis-cli info memory | grep used | grep human +used_memory_human:827.46K # 内存分配器 (jemalloc) 从操作系统分配的内存总量 +used_memory_rss_human:3.61M # 操作系统看到的内存占用 ,top 命令看到的内存 +used_memory_peak_human:829.41K # Redis 内存消耗的峰值 +used_memory_lua_human:37.00K # lua 脚本引擎占用的内存大小 + +#复制积压缓冲区多大? +> redis-cli info replication |grep backlog +repl_backlog_active:0 +repl_backlog_size:1048576 # 这个就是积压缓冲区大小 +repl_backlog_first_byte_offset:0 +repl_backlog_histlen:0 +``` + +考虑到参数非常繁多,一一说明工作量巨大,下面我只挑一些关键性的、非常实用和最常用的参数进行详细讲解。如果读者想要了解所有的参数细节,请参考阅读 [Redis 官网文档](https://link.juejin.cn/?target=https%3A%2F%2Fredis.io%2Fcommands%2Finfo)。 + + + + + +## Redis 每秒执行多少次指令? + + + + + + + +这个信息在 Stats 块里,可以通过 `info stats` 看到。 + +``` +# ops_per_sec: operations per second,也就是每秒操作数 +> redis-cli info stats |grep ops +instantaneous_ops_per_sec:789 +``` + + + + + +以上,表示 ops 是 789,也就是所有客户端每秒会发送 789 条指令到服务器执行。极限情况下,Redis 可以每秒执行 10w 次指令,CPU 几乎完全榨干。如果 qps 过高,可以考虑通过 `monitor` 指令快速观察一下究竟是哪些 key 访问比较频繁,从而在相应的业务上进行优化,以减少 IO 次数。`monitor` 指令会瞬间吐出来巨量的指令文本,所以一般在执行 `monitor` 后立即 `ctrl+c`中断输出。 + + + +``` +> redis-cli monitor +``` + +## Redis 连接了多少客户端? + +这个信息在 Clients 块里,可以通过 `info clients` 看到。 + +``` +> redis-cli info clients +# Clients +connected_clients:124 # 这个就是正在连接的客户端数量 +client_longest_output_list:0 +client_biggest_input_buf:0 +blocked_clients:0 +``` + +这个信息也是比较有用的,通过观察这个数量可以确定是否存在意料之外的连接。如果发现这个数量不对劲,接着就可以使用`client list`指令列出所有的客户端链接地址来确定源头。 + +关于客户端的数量还有个重要的参数需要观察,那就是`rejected_connections`,它表示因为超出最大连接数限制而被拒绝的客户端连接次数,如果这个数字很大,意味着服务器的最大连接数设置的过低需要调整 `maxclients` 参数。 + +``` +> redis-cli info stats |grep reject +rejected_connections:0 +``` + +## Redis 内存占用多大 ? + + + + + + + +这个信息在 Memory 块里,可以通过 `info memory` 看到。 + +``` +> redis-cli info memory | grep used | grep human +used_memory_human:827.46K # 内存分配器 (jemalloc) 从操作系统分配的内存总量 +used_memory_rss_human:3.61M # 操作系统看到的内存占用 ,top 命令看到的内存 +used_memory_peak_human:829.41K # Redis 内存消耗的峰值 +used_memory_lua_human:37.00K # lua 脚本引擎占用的内存大小 +``` + +如果单个 Redis 内存占用过大,并且在业务上没有太多压缩的空间的话,可以考虑集群化了。 + +## 复制积压缓冲区多大? + +这个信息在 Replication 块里,可以通过 `info replication` 看到。 + +``` +> redis-cli info replication |grep backlog +repl_backlog_active:0 +repl_backlog_size:1048576 # 这个就是积压缓冲区大小 +repl_backlog_first_byte_offset:0 +repl_backlog_histlen:0 +``` + +复制积压缓冲区大小非常重要,它严重影响到主从复制的效率。当从库因为网络原因临时断开了主库的复制,然后网络恢复了,又重新连上的时候,这段断开的时间内发生在 master 上的修改操作指令都会放在积压缓冲区中,这样从库可以通过积压缓冲区恢复中断的主从同步过程。 + +积压缓冲区是环形的,后来的指令会覆盖掉前面的内容。如果从库断开的时间过长,或者缓冲区的大小设置的太小,都会导致从库无法快速恢复中断的主从同步过程,因为中间的修改指令被覆盖掉了。这时候从库就会进行全量同步模式,非常耗费 CPU 和网络资源。 + +如果有多个从库复制,积压缓冲区是共享的,它不会因为从库过多而线性增长。如果实例的修改指令请求很频繁,那就把积压缓冲区调大一些,几十个 M 大小差不多了,如果很闲,那就设置为几个 M。 + +``` +> redis-cli info stats | grep sync +sync_full:0 +sync_partial_ok:0 +sync_partial_err:0 # 半同步失败次数 +``` + +通过查看`sync_partial_err`变量的次数来决定是否需要扩大积压缓冲区,它表示主从半同步复制失败的次数。 \ No newline at end of file diff --git a/docs/data-management/Redis/Redis-BloomFilter.md b/docs/data-management/Redis/Redis-BloomFilter.md deleted file mode 100644 index 0a136edecc..0000000000 --- a/docs/data-management/Redis/Redis-BloomFilter.md +++ /dev/null @@ -1,338 +0,0 @@ -## 布隆过滤器是什么? - -布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。 - -当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。打个比方,当它说不认识你时,肯定就不认识;当它说见过你时,可能根本就没见过面,不过因为你的脸跟它认识的人中某脸比较相似 (某些熟脸的系数组合),所以误判以前见过你。 - -套在上面的使用场景中,布隆过滤器能准确过滤掉那些已经看过的内容,那些没有看过的新内容,它也会过滤掉极小一部分 (误判),但是绝大多数新内容它都能准确识别。这样就可以完全保证推荐给用户的内容都是无重复的。 - -## Redis 中的布隆过滤器 - -Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布隆过滤器作为一个插件加载到 Redis Server 中,给 Redis 提供了强大的布隆去重功能。 - -下面我们来体验一下 Redis 4.0 的布隆过滤器,为了省去繁琐安装过程,我们直接用 Docker 吧。 - -``` -> docker pull redislabs/rebloom # 拉取镜像 -> docker run -p6379:6379 redislabs/rebloom # 运行容器 -> redis-cli # 连接容器中的 redis 服务 -``` - -如果上面三条指令执行没有问题,下面就可以体验布隆过滤器了。 - -## 布隆过滤器基本使用 - -布隆过滤器有二个基本指令,`bf.add` 添加元素,`bf.exists` 查询元素是否存在,它的用法和 set 集合的 sadd 和 sismember 差不多。注意 `bf.add` 只能一次添加一个元素,如果想要一次添加多个,就需要用到 `bf.madd` 指令。同样如果需要一次查询多个元素是否存在,就需要用到 `bf.mexists` 指令。 - -``` -127.0.0.1:6379> bf.add codehole user1 -(integer) 1 -127.0.0.1:6379> bf.add codehole user2 -(integer) 1 -127.0.0.1:6379> bf.add codehole user3 -(integer) 1 -127.0.0.1:6379> bf.exists codehole user1 -(integer) 1 -127.0.0.1:6379> bf.exists codehole user2 -(integer) 1 -127.0.0.1:6379> bf.exists codehole user3 -(integer) 1 -127.0.0.1:6379> bf.exists codehole user4 -(integer) 0 -127.0.0.1:6379> bf.madd codehole user4 user5 user6 -1) (integer) 1 -2) (integer) 1 -3) (integer) 1 -127.0.0.1:6379> bf.mexists codehole user4 user5 user6 user7 -1) (integer) 1 -2) (integer) 1 -3) (integer) 1 -4) (integer) 0 -``` - - - -Java 客户端 Jedis-2.x 没有提供指令扩展机制,所以你无法直接使用 Jedis 来访问 Redis Module 提供的 [bf.xxx](http://bf.xxx/) 指令。RedisLabs 提供了一个单独的包 [JReBloom](https://github.com/RedisLabs/JReBloom),但是它是基于 Jedis-3.0,Jedis-3.0 这个包目前还没有进入 release,没有进入 maven 的中央仓库,需要在 Github 上下载。在使用上很不方便,如果怕麻烦,还可以使用 [lettuce](https://github.com/lettuce-io/lettuce-core),它是另一个 Redis 的客户端,相比 Jedis 而言,它很早就支持了指令扩展。 - -``` -public class BloomTest { - - public static void main(String[] args) { - Client client = new Client(); - - client.delete("codehole"); - for (int i = 0; i < 100000; i++) { - client.add("codehole", "user" + i); - boolean ret = client.exists("codehole", "user" + i); - if (!ret) { - System.out.println(i); - break; - } - } - - client.close(); - } - -} -``` - -执行上面的代码后,你会张大了嘴巴发现居然没有输出,塞进去了 100000 个元素,还是没有误判,这是怎么回事?如果你不死心的话,可以将数字再加一个 0 试试,你会发现依然没有误判。 - - 原因就在于布隆过滤器对于已经见过的元素肯定不会误判,它只会误判那些没见过的元素。所以我们要稍微改一下上面的脚本,使用 bf.exists 去查找没见过的元素,看看它是不是以为自己见过了。 - -```java -public class BloomTest { - - public static void main(String[] args) { - Client client = new Client(); - - client.delete("codehole"); - for (int i = 0; i < 100000; i++) { - client.add("codehole", "user" + i); - boolean ret = client.exists("codehole", "user" + (i + 1)); - if (ret) { - System.out.println(i); - break; - } - } - - client.close(); - } - -} -``` - - - -运行后,我们看到了输出是 214,也就是到第 214 的时候,它出现了误判。 - -那如何来测量误判率呢?我们先随机出一堆字符串,然后切分为 2 组,将其中一组塞入布隆过滤器,然后再判断另外一组的字符串存在与否,取误判的个数和字符串总量一半的百分比作为误判率。 - -```java -public class BloomTest { - - private String chars; - { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < 26; i++) { - builder.append((char) ('a' + i)); - } - chars = builder.toString(); - } - - private String randomString(int n) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < n; i++) { - int idx = ThreadLocalRandom.current().nextInt(chars.length()); - builder.append(chars.charAt(idx)); - } - return builder.toString(); - } - - private List randomUsers(int n) { - List users = new ArrayList<>(); - for (int i = 0; i < 100000; i++) { - users.add(randomString(64)); - } - return users; - } - - public static void main(String[] args) { - BloomTest bloomer = new BloomTest(); - List users = bloomer.randomUsers(100000); - List usersTrain = users.subList(0, users.size() / 2); - List usersTest = users.subList(users.size() / 2, users.size()); - - Client client = new Client(); - client.delete("codehole"); - for (String user : usersTrain) { - client.add("codehole", user); - } - int falses = 0; - for (String user : usersTest) { - boolean ret = client.exists("codehole", user); - if (ret) { - falses++; - } - } - System.out.printf("%d %d\n", falses, usersTest.size()); - client.close(); - } - -} -``` - -运行一下,等待大约一分钟,输出: - -``` -total users 100000 -all trained -628 50000 -``` - -可以看到误判率大约 1% 多点。你也许会问这个误判率还是有点高啊,有没有办法降低一点?答案是有的。 - -我们上面使用的布隆过滤器只是默认参数的布隆过滤器,它在我们第一次 add 的时候自动创建。Redis 其实还提供了自定义参数的布隆过滤器,需要我们在 add 之前使用`bf.reserve`指令显式创建。如果对应的 key 已经存在,`bf.reserve`会报错。`bf.reserve`有三个参数,分别是 key, `error_rate`和`initial_size`。错误率越低,需要的空间越大。`initial_size`参数表示预计放入的元素数量,当实际数量超出这个数值时,误判率会上升。 - -所以需要提前设置一个较大的数值避免超出导致误判率升高。如果不使用 bf.reserve,默认的`error_rate`是 0.01,默认的`initial_size`是 100。 - - 接下来我们使用 bf.reserve 改造一下上面的脚本: - -Java 版本: - -``` -public class BloomTest { - - private String chars; - { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < 26; i++) { - builder.append((char) ('a' + i)); - } - chars = builder.toString(); - } - - private String randomString(int n) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < n; i++) { - int idx = ThreadLocalRandom.current().nextInt(chars.length()); - builder.append(chars.charAt(idx)); - } - return builder.toString(); - } - - private List randomUsers(int n) { - List users = new ArrayList<>(); - for (int i = 0; i < 100000; i++) { - users.add(randomString(64)); - } - return users; - } - - public static void main(String[] args) { - BloomTest bloomer = new BloomTest(); - List users = bloomer.randomUsers(100000); - List usersTrain = users.subList(0, users.size() / 2); - List usersTest = users.subList(users.size() / 2, users.size()); - - Client client = new Client(); - client.delete("codehole"); - // 对应 bf.reserve 指令 - client.createFilter("codehole", 50000, 0.001); - for (String user : usersTrain) { - client.add("codehole", user); - } - int falses = 0; - for (String user : usersTest) { - boolean ret = client.exists("codehole", user); - if (ret) { - falses++; - } - } - System.out.printf("%d %d\n", falses, usersTest.size()); - client.close(); - } - -} -``` - -运行一下,等待约 1 分钟,输出如下: - -``` -total users 100000 -all trained -6 50000 -``` - -我们看到了误判率大约 0.012%,比预计的 0.1% 低很多,不过布隆的概率是有误差的,只要不比预计误判率高太多,都是正常现象。 - -## 注意事项 - -布隆过滤器的`initial_size`估计的过大,会浪费存储空间,估计的过小,就会影响准确率,用户在使用之前一定要尽可能地精确估计好元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出估计值很多。 - -布隆过滤器的`error_rate`越小,需要的存储空间就越大,对于不需要过于精确的场合,`error_rate`设置稍大一点也无伤大雅。比如在新闻去重上而言,误判率高一点只会让小部分文章不能让合适的人看到,文章的整体阅读量不会因为这点误判率就带来巨大的改变。 - -## 布隆过滤器的原理 - -学会了布隆过滤器的使用,下面有必要把原理解释一下,不然读者还会继续蒙在鼓里 - - - - - - - -每个布隆过滤器对应到 Redis 的数据结构里面就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。 - -向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。 - -向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在。如果都是 1,这并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,判断正确的概率就会很大,如果这个位数组比较拥挤,判断正确的概率就会降低。具体的概率计算公式比较复杂,感兴趣可以阅读扩展阅读,非常烧脑,不建议读者细看。 - -使用时不要让实际元素远大于初始化大小,当实际元素开始超出初始化大小时,应该对布隆过滤器进行重建,重新分配一个 size 更大的过滤器,再将所有的历史元素批量 add 进去 (这就要求我们在其它的存储器中记录所有的历史元素)。因为 error_rate 不会因为数量超出就急剧增加,这就给我们重建过滤器提供了较为宽松的时间。 - -## 空间占用估计 - -布隆过滤器的空间占用有一个简单的计算公式,但是推导比较繁琐,这里就省去推导过程了,直接引出计算公式,感兴趣的读者可以点击「扩展阅读」深入理解公式的推导过程。 - -布隆过滤器有两个参数,第一个是预计元素的数量 n,第二个是错误率 f。公式根据这两个输入得到两个输出,第一个输出是位数组的长度 l,也就是需要的存储空间大小 (bit),第二个输出是 hash 函数的最佳数量 k。hash 函数的数量也会直接影响到错误率,最佳的数量会有最低的错误率。 - -``` -k=0.7*(l/n) # 约等于 -f=0.6185^(l/n) # ^ 表示次方计算,也就是 math.pow -``` - -从公式中可以看出 - -1. 位数组相对越长 (l/n),错误率 f 越低,这个和直观上理解是一致的 -2. 位数组相对越长 (l/n),hash 函数需要的最佳数量也越多,影响计算效率 -3. 当一个元素平均需要 1 个字节 (8bit) 的指纹空间时 (l/n=8),错误率大约为 2% -4. 错误率为 10%,一个元素需要的平均指纹空间为 4.792 个 bit,大约为 5bit -5. 错误率为 1%,一个元素需要的平均指纹空间为 9.585 个 bit,大约为 10bit -6. 错误率为 0.1%,一个元素需要的平均指纹空间为 14.377 个 bit,大约为 15bit - -你也许会想,如果一个元素需要占据 15 个 bit,那相对 set 集合的空间优势是不是就没有那么明显了?这里需要明确的是,set 中会存储每个元素的内容,而布隆过滤器仅仅存储元素的指纹。元素的内容大小就是字符串的长度,它一般会有多个字节,甚至是几十个上百个字节,每个元素本身还需要一个指针被 set 集合来引用,这个指针又会占去 4 个字节或 8 个字节,取决于系统是 32bit 还是 64bit。而指纹空间只有接近 2 个字节,所以布隆过滤器的空间优势还是非常明显的。 - -如果读者觉得公式计算起来太麻烦,也没有关系,有很多现成的网站已经支持计算空间占用的功能了,我们只要把参数输进去,就可以直接看到结果,比如 [布隆计算器](https://krisives.github.io/bloom-calculator/)。 - - - - - - - -## 实际元素超出时,误判率会怎样变化 - -当实际元素超出预计元素时,错误率会有多大变化,它会急剧上升么,还是平缓地上升,这就需要另外一个公式,引入参数 t 表示实际元素和预计元素的倍数 t - -``` -f=(1-0.5^t)^k # 极限近似,k 是 hash 函数的最佳数量 -``` - -当 t 增大时,错误率,f 也会跟着增大,分别选择错误率为 10%,1%,0.1% 的 k 值,画出它的曲线进行直观观察 - - - - - - - -从这个图中可以看出曲线还是比较陡峭的 - -1. 错误率为 10% 时,倍数比为 2 时,错误率就会升至接近 40%,这个就比较危险了 -2. 错误率为 1% 时,倍数比为 2 时,错误率升至 15%,也挺可怕的 -3. 错误率为 0.1%,倍数比为 2 时,错误率升至 5%,也比较悬了 - -## 用不上 Redis4.0 怎么办? - -Redis 4.0 之前也有第三方的布隆过滤器 lib 使用,只不过在实现上使用 redis 的位图来实现的,性能上也要差不少。比如一次 exists 查询会涉及到多次 getbit 操作,网络开销相比而言会高出不少。另外在实现上这些第三方 lib 也不尽完美,比如 pyrebloom 库就不支持重连和重试,在使用时需要对它做一层封装后才能在生产环境中使用。 - -1. [Python Redis Bloom Filter](https://github.com/robinhoodmarkets/pyreBloom) -2. [Java Redis Bloom Filter](https://github.com/Baqend/Orestes-Bloomfilter) - -## 布隆过滤器的其它应用 - -在爬虫系统中,我们需要对 URL 进行去重,已经爬过的网页就可以不用爬了。但是 URL 太多了,几千万几个亿,如果用一个集合装下这些 URL 地址那是非常浪费空间的。这时候就可以考虑使用布隆过滤器。它可以大幅降低去重存储消耗,只不过也会使得爬虫系统错过少量的页面。 - -布隆过滤器在 NoSQL 数据库领域使用非常广泛,我们平时用到的 HBase、Cassandra 还有 LevelDB、RocksDB 内部都有布隆过滤器结构,布隆过滤器可以显著降低数据库的 IO 请求数量。当用户来查询某个 row 时,可以先通过内存中的布隆过滤器过滤掉大量不存在的 row 请求,然后再去磁盘进行查询。 - -邮箱系统的垃圾邮件过滤功能也普遍用到了布隆过滤器,因为用了这个过滤器,所以平时也会遇到某些正常的邮件被放进了垃圾邮件目录中,这个就是误判所致,概率很低。 \ No newline at end of file diff --git a/docs/data-management/Redis/Redis-Cache-Model.md b/docs/data-management/Redis/Redis-Cache-Model.md new file mode 100644 index 0000000000..9cb60433a4 --- /dev/null +++ b/docs/data-management/Redis/Redis-Cache-Model.md @@ -0,0 +1,191 @@ +### **什么是缓存模式?** + +缓存模式是指在系统中如何设计和实现缓存机制,用以快速存取数据并减轻后端数据存储的负载。Redis 提供了灵活多样的缓存策略,常见模式包括: + +1. **直写模式(Write Through)** +2. **回写模式(Write Back)** +3. **旁路缓存模式(Cache Aside)** +4. **只缓存模式(Read Through)** + +通过合理选择和优化这些模式,可以满足不同业务场景对性能、数据一致性和可用性的需求。 + +------ + +### **Redis 缓存模式详解** + +#### **1. Cache Aside(旁路缓存)** + +Cache Aside 模式又称为 **Lazy Loading**,是使用最广泛的缓存模式之一,通常由业务代码显式管理缓存和数据库,核心思想是: + +- 数据从数据库加载到缓存中,缓存作为数据库的一个“旁路”。 +- 应用程序负责读取缓存,缓存未命中时再从数据库读取并更新缓存。 + +**读请求**: + +- 先从缓存中读取数据; +- 如果缓存中没有数据(缓存未命中),从数据库中获取数据,将数据写入缓存,返回给客户端。 + +**写请求**: + +- 数据写入数据库后,将缓存中的数据清除或更新。 + +**适用场景:** + +- 数据更新较少但读取频率较高的场景,例如商品详情、热搜榜单。 +- 对数据一致性要求不严格的系统。 + +**优缺点:** + +- **优点**:简单易用,缓存与数据库解耦。 +- **缺点**:缓存预热需要时间,容易出现缓存击穿问题。 + + + +#### 2. Write Through(直写模式) + +在直写模式中,应用程序的所有写操作都会同时更新缓存和数据库: + +- 数据写入数据库的同时同步写入缓存。 + +**工作流程:** + +1. 应用将数据同时写入 Redis 和数据库。 +2. 读取时直接从 Redis 获取数据。 + +**适用场景:** + +- 需要缓存和数据库一致性非常高的场景,例如账户余额、订单状态等敏感数据。 + +**优缺点:** + +- **优点**:一致性高,数据实时同步。 +- **缺点**:写入速度较慢,因为每次写操作都需要更新两处存储。 + + + +#### 3. Write Back(回写模式) + +在回写模式下,数据首先写入缓存,之后异步写入数据库: + +- 写入数据库的操作由后台线程或任务队列完成。 + +**工作流程:** + +1. 数据先写入 Redis。 +2. Redis 异步将数据批量写入数据库。 + +**适用场景:** + +- 写频率高、读频率较低且对一致性要求不严格的场景,例如日志系统。 + +**优缺点:** + +- **优点**:写入性能高,因为写数据库是异步的。 +- **缺点**:可能导致数据丢失,如果缓存写入后还未同步到数据库时发生故障。 + + + +#### 4. Read Through(只缓存模式) + +在这种模式中,所有的读写操作都必须通过缓存完成: + +- 缓存未命中时,应用从数据库中加载数据并自动更新缓存。 + +**工作流程:** + +1. 应用读取 Redis,如果未命中,自动从数据库加载并更新。 +2. 写入时同步更新缓存和数据库。 + +**适用场景:** + +- 读多写少且对实时性要求较高的场景。 + +**优缺点:** + +- **优点**:应用层逻辑简单。 +- **缺点**:依赖于缓存层,缓存崩溃可能导致大量数据库请求。 + + + +#### 5、Refresh Ahead(提前刷新缓存) + +Refresh Ahead 模式通过提前异步加载数据,防止缓存失效时查询数据库的性能抖动。 + +- 基于预设的过期时间,在缓存即将失效前,后台异步加载数据并更新缓存。 + + + +#### 6、Singleflight 模式 + +Singleflight 是一种**抑制重复请求**的模式,用于解决缓存未命中时的高并发问题。 + +- 当多个请求同时查询相同的缓存未命中数据时,只有一个请求会执行数据库查询,其余请求等待结果返回。 + +**核心思路** + +- 当多个并发请求同时访问**同一个缓存键**,导致缓存未命中时,**Singleflight** 机制保证只有**一个请求**去执行实际的数据库查询或计算操作,其余请求会等待第一个请求的结果完成后直接返回相同的结果。 +- 这样可以有效避免重复查询或计算,减少对数据库、API 等资源的压力。 + +**流程** + +1. 多个请求到来时,检测是否已有请求正在执行。 + - 如果没有请求在执行,则当前请求负责查询数据。 + - 如果已有请求在执行,其他请求进入等待队列。 +2. **第一个请求执行数据库查询或计算操作,并保存结果。** +3. **等待中的请求获取到第一个请求的结果,直接返回相同数据。** + + + +### Redis 缓存模式的优化策略 + +#### **1. 缓存雪崩** + +缓存雪崩指缓存集中失效时,大量请求直接击穿数据库,导致数据库压力激增甚至崩溃。 + +**解决方案:** + +- **设置随机过期时间**:避免大量缓存同时失效。 +- **多级缓存**:在 Redis 之上增加本地缓存(如 Guava)。 +- **请求限流**:通过限流机制控制瞬时流量。 + +#### **2. 缓存击穿** + +缓存击穿指某个热点数据缓存失效后,短时间内大量请求直接打到数据库。 + +**解决方案:** + +- **热点数据预加载**:提前将热点数据缓存。 +- **加互斥锁**:在缓存未命中时,通过锁机制防止数据库过载。 + +#### 3. 缓存穿透 + +缓存穿透指用户请求的数据既不在缓存中,也不存在于数据库中,导致所有请求打到数据库。 + +**解决方案:** + +- **布隆过滤器**:拦截无效请求,减少数据库查询。 +- **缓存空结果**:将不存在的数据写入缓存,避免重复查询。 + + + +### Redis 缓存模式的应用场景 + +#### **1. 电商秒杀** + +在高并发的秒杀场景中,Redis 通常用于缓存商品库存数据。典型模式为: + +- 使用 `Cache Aside` 模式缓存库存数据,避免频繁访问数据库。 +- 配合分布式锁,防止超卖问题。 + +#### **2. 社交网络** + +在社交平台上,Redis 用于存储用户会话、好友关系等数据: + +- 使用 `Write Through` 模式保证数据一致性。 +- 通过 Redis 的 Set 结构实现快速去重和交集运算。 + +#### **3. 实时排行榜** + +Redis 的 Sorted Set 结构非常适合实现排行榜功能: + +- 使用 `Cache Aside` 模式,定期将缓存中的排行榜数据同步到数据库。 \ No newline at end of file diff --git a/docs/data-management/Redis/Redis-Cluster.md b/docs/data-management/Redis/Redis-Cluster.md index 1901e9907e..856af90b08 100644 --- a/docs/data-management/Redis/Redis-Cluster.md +++ b/docs/data-management/Redis/Redis-Cluster.md @@ -1,3 +1,11 @@ +--- +title: Redis 集群 +date: 2021-10-11 +tags: + - Redis +categories: Redis +--- + ## 一、Redis 集群是啥 我们先回顾下前边介绍的几种 Redis 高可用方案:持久化、主从同步和哨兵机制。但这些方案仍有痛点,其中最主要的问题就是存储能力受单机限制,以及没办法实现写操作的负载均衡。 @@ -10,7 +18,7 @@ Redis 集群刚好解决了上述问题,实现了较为完善的高可用方 集群,即 Redis Cluster,是 Redis 3.0 开始引入的分布式存储方案。 -集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。 +集群由多个节点(Node)组成,Redis 的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。 @@ -22,7 +30,7 @@ Redis 集群刚好解决了上述问题,实现了较为完善的高可用方 2. **高可用**: 集群支持主从复制和主节点的 **自动故障转移** *(与哨兵类似)*,当任一节点发生故障时,集群仍然可以对外提供服务。 - + 上图展示了 **Redis Cluster** 典型的架构图,集群中的每一个 Redis 节点都 **互相两两相连**,客户端任意 **直连** 到集群中的 **任意一台**,就可以对其他 Redis 节点进行 **读写** 的操作。 @@ -30,7 +38,7 @@ Redis 集群刚好解决了上述问题,实现了较为完善的高可用方 ### 1.3 Redis 集群的基本原理 - + Redis 集群中内置了 `16384` 个哈希槽。当客户端连接到 Redis 集群之后,会同时得到一份关于这个 **集群的配置信息**,当客户端具体对某一个 `key` 值进行操作时,会计算出它的一个 Hash 值,然后把结果对 `16384` **求余数**,这样每个 `key` 都会对应一个编号在 `0-16383` 之间的哈希槽,Redis 会根据节点数量 **大致均等** 的将哈希槽映射到不同的节点。 @@ -81,7 +89,7 @@ redis-server cluster_config/redis_7005.conf 然后执行 `ps -ef | grep redis` 查看是否启动成功: - + 可以看到 `6` 个 Redis 节点都以集群的方式成功启动了,**但是现在每个节点还处于独立的状态**,也就是说它们每一个都各自成了一个集群,还没有互相联系起来,我们需要手动地把他们之间建立起联系。 @@ -97,7 +105,7 @@ redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0. 观察控制台输出: - + 看到 `[OK]` 的信息之后,就表示集群已经搭建成功了,可以看到,这里我们正确地创建了三主三从的集群。 @@ -127,7 +135,7 @@ OK 我们再使用 `cluster info` *(查看集群信息)* 和 `cluster nodes` *(查看节点列表)* 来分别看看:*(任意节点输入均可)* - + @@ -147,17 +155,17 @@ Redis 集群最核心的功能就是数据分区,数据分区之后又伴随 不过该方案最大的问题是,**当新增或删减节点时**,节点数量发生变化,系统中所有的数据都需要 **重新计算映射关系**,引发大规模数据迁移。 -这种方式的突出优点是简单性,常用于数据库的分库分表规则,一般采 用预分区的方式,提前根据数据量规划好分区数,比如划分为 512 或1024 张表,保证可支撑未来一段时间的数据量,再根据负载情况将表迁移到其他数 据库中。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况 +这种方式的突出优点是简单性,常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区数,比如划分为 512 或 1024 张表,保证可支撑未来一段时间的数据量,再根据负载情况将表迁移到其他数据库中。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况 #### 方案二:一致性哈希分区 一致性哈希算法将 **整个哈希值空间** 组织成一个虚拟的圆环,范围一般是 0 - $2^{32}$,对于每一个数据,根据 `key` 计算 hash 值,确定数据在环上的位置,然后从此位置沿顺时针行走,找到的第一台服务器就是其应该映射到的服务器: - + -与哈希取余分区相比,一致性哈希分区将 **增减节点的影响限制在相邻节点**。以上图为例,如果在 `node1` 和 `node2` 之间增加 `node5`,则只有 `node2` 中的一部分数据会迁移到 `node5`;如果去掉 `node2`,则原 `node2` 中的数据只会迁移到 `node4` 中,只有 `node4` 会受影响。 +与哈希取余分区相比,一致性哈希分区将 **增减节点的影响限制在相邻节点**。以上图为例,如果在 `node1` 和 `node2` 之间增加 `node5`,则只有 `node2` 中的一部分数据会迁移到 `node5`;如果去掉 `node2`,则原 `node2` 中的数据只会迁移到 `node3` 中,只有 `node3` 会受影响。 -一致性哈希分区的主要问题在于,当 **节点数量较少** 时,增加或删减节点,**对单个节点的影响可能很大**,造成数据的严重不平衡。还是以上图为例,如果去掉 `node2`,`node4` 中的数据由总数据的 `1/4` 左右变为 `1/2` 左右,与其他节点相比负载过高。 +一致性哈希分区的主要问题在于,当 **节点数量较少** 时,增加或删减节点,**对单个节点的影响可能很大**,造成数据的严重不平衡。还是以上图为例,如果去掉 `node2`,`node3` 中的数据由总数据的 `1/4` 左右变为 `1/2` 左右,与其他节点相比负载过高。 #### 方案三:带有虚拟节点的一致性哈希分区 @@ -213,7 +221,7 @@ Redis 集群相对单机在功能上存在一些限制,需要开发人员提 ### 3.3 节点通信 -集群的建立离不开节点之间的通信,例如我们上面启动六个集群节点之后通过 `redis-cli` 命令帮助我们搭建起来了集群,实际上背后每个集群之间的两两连接是通过了 `CLUSTER MEET ` 命令发送 `MEET` 消息完成的。 +集群的建立离不开节点之间的通信,例如我们上面启动六个集群节点之后通过 `redis-cli` 命令帮助我们搭建起来了集群,实际上背后每个集群之间的两两连接是通过了 `CLUSTER MEET` 命令发送 `MEET` 消息完成的。 通信过程说明: @@ -221,7 +229,7 @@ Redis 集群相对单机在功能上存在一些限制,需要开发人员提 2. 每个节点在固定周期内通过特定规则选择几个节点发送 ping 消息 3. 接收到 ping 消息的节点用 pong 消息作为响应 -集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息 变更等事件发生时,通过不断的 `ping/pong` 消息通信,经过一段时间后所有的 节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目 的。 +集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息 变更等事件发生时,通过不断的 `ping/pong` 消息通信,经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。 #### 两个端口 @@ -234,6 +242,10 @@ Redis 集群相对单机在功能上存在一些限制,需要开发人员提 #### Gossip 协议 +> 对于一个分布式集群来说,它的良好运行离不开集群节点信息和节点状态的正常维护。为了实现这一目标,通常我们可以选择**中心化**的方法,使用一个第三方系统,比如 Zookeeper 或 etcd,来维护集群节点的信息、状态等。同时,我们也可以选择**去中心化**的方法,让每个节点都维护彼此的信息、状态,并且使用集群通信协议 Gossip 在节点间传播更新的信息,从而实现每个节点都能拥有一致的信息。下图就展示了这两种集群节点信息维护的方法,你可以看下。 +> +>  + 节点间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip 协议等。重点是广播和 Gossip 的对比。 - 广播是指向集群内所有节点发送消息。**优点** 是集群的收敛速度快(集群收敛是指集群内所有节点获得的集群信息是一致的),**缺点** 是每条消息都要发送给所有节点,CPU、带宽等消耗较大。 @@ -242,9 +254,7 @@ Redis 集群相对单机在功能上存在一些限制,需要开发人员提 (为什么需要随机呢? ) - Gossip 协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播。 - -Gossip 协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的 Gossip 消息,了解这些消息有助于我们理解集群如何完成信息交换。 + Gossip 协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似**流言传播**。Gossip 协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的 Gossip 消息,了解这些消息有助于我们理解集群如何完成信息交换。 #### 消息类型 @@ -252,14 +262,14 @@ Gossip 协议的主要职责就是信息交换。信息交换的载体就是节 节点间发送的消息主要分为 `5` 种:`meet 消息`、`ping 消息`、`pong 消息`、`fail 消息`、`publish 消息`。不同的消息类型,通信协议、发送的频率和时机、接收节点的选择等是不同的: - + - **MEET 消息:** 用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet 消息通信正常完成后,接收节点会加入到集群中并进行周期性的 ping、pong 消息交换。 - **PING 消息:** 集群里每个节点每秒钟会选择部分节点发送 `PING` 消息,接收者收到消息后会回复一个 `PONG` 消息。**PING 消息的内容是自身节点和部分其他节点的状态信息**,作用是彼此交换信息,以及检测节点是否在线。`PING` 消息使用 Gossip 协议发送,接收节点的选择兼顾了收敛速度和带宽成本(内部频繁进行信息交换,而且 ping/pong 消息会携带当前节点和部分其他节点的状态数据,势必会加重带宽和计算的负担,所以选择需要通信的节点列表就很重要了),**具体规则如下**: 1. 随机找 5 个节点,在其中选择最久没有通信的 1 个节点; 2. 扫描节点列表,选择最近一次收到 `PONG` 消息时间大于 `cluster_node_timeout / 2` 的所有节点,防止这些节点长时间未更新。 - + - **PONG消息:** `PONG` 消息封装了自身状态数据。可以分为两种: 1. **第一种** 是在接到 `MEET/PING` 消息后回复的 `PONG` 消息; @@ -299,32 +309,32 @@ typedef struct { 集群内所有的消息都采用相同的**消息头**结构 `clusterMsg`,它包含了发送节点关键信息,如节点 id、槽映射、节点标识(主从角色,是否下线)等。 -**消息体**在 Redis 内部采用 `clusterMsgData` 结构声明,结构如下: +**消息体** 在 Redis 内部采用 `clusterMsgData` 结构声明,结构如下: ```c union clusterMsgData { - /* PING, MEET and PONG */ + //Ping、Pong和Meet消息类型对应的数据结构 struct { /* Array of N clusterMsgDataGossip structures */ clusterMsgDataGossip gossip[1]; } ping; - /* FAIL */ + //Fail消息类型对应的数据结构 struct { clusterMsgDataFail about; } fail; - /* PUBLISH */ + //Publish消息类型对应的数据结构 struct { clusterMsgDataPublish msg; } publish; - /* UPDATE */ + //Update消息类型对应的数据结构 struct { clusterMsgDataUpdate nodecfg; } update; - /* MODULE */ + //Module消息类型对应的数据结构 struct { clusterMsgModule msg; } module; @@ -335,18 +345,18 @@ union clusterMsgData { ```c typedef struct { - char nodename[CLUSTER_NAMELEN]; - uint32_t ping_sent; /* 最后一次向该节点发送ping消息时间 */ - uint32_t pong_received; /* 最后一次接收该节点pong消息时间 */ - char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */ - uint16_t port; /* base port last time it was seen */ - uint16_t cport; /* cluster port last time it was seen */ - uint16_t flags; /* node->flags copy */ - uint32_t notused1; + char nodename[CLUSTER_NAMELEN]; //节点名称 + uint32_t ping_sent; //节点发送Ping的时间 + uint32_t pong_received; //节点收到Pong的时间 + char ip[NET_IP_STR_LEN]; //节点IP + uint16_t port; //节点和客户端的通信端口 + uint16_t cport; //节点用于集群通信的端口 + uint16_t flags; //节点的标记 + uint32_t notused1; //未用字段 } clusterMsgDataGossip; ``` - +从 clusterMsgDataGossip 数据结构中,我们可以看到,它里面包含了节点的基本信息,比如节点名称、IP 和通信端口,以及使用 Ping、Pong 消息发送和接收时间来表示的节点运行状态。 消息交互的过程就是解析消息头和消息体的过程 @@ -473,7 +483,7 @@ Redis 集群自身实现了高可用。高可用首先需要解决集群部分 - 更新配置纪元 - 配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元 (`clusterNode.configEpoch`)标示当前主节点的版本,所有主节点的配置纪元 都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全局的配 置纪元(`clusterState.current Epoch`),用于记录集群内所有主节点配置纪元的最大版本。 + 配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元 (`clusterNode.configEpoch`)标示当前主节点的版本,所有主节点的配置纪元 都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全局的配置纪元(`clusterState.current Epoch`),用于记录集群内所有主节点配置纪元的最大版本。 - 广播选举消息 @@ -487,7 +497,7 @@ Redis 集群自身实现了高可用。高可用首先需要解决集群部分 当从节点收集到 N/2+1 个持有槽的主节点投票时,从节点可以执行替换主节点操作,例如集群内有 5 个持有槽的主节点,主节点 b 故障后还有 4 个, 当其中一个从节点收集到 3 张投票时代表获得了足够的选票可以进行替换主节点操作。 -  + 5. 替换主节点 @@ -528,7 +538,7 @@ Redis 集群自身实现了高可用。高可用首先需要解决集群部分 像 redis-cli 这种客户端又叫 Dummy(傀儡)客户端,它优点是代码实现简单,对客户端协议影响较小,只需要根据重定向信息再次发送请求即可。但是它的弊端很明显,每次执行键命令前都要到 Redis 上进行重定向才能找到要执行命令的节点,额外增加了 IO 开销, 这不是Redis 集群高效的使用方式。正因为如此通常集群客户端都采用另一 种实现:Smart(智能)客户端。 -#### 3.5.2 Smart客户端 +#### 3.5.2 Smart 客户端 大多数开发语言的 Redis 客户端都采用 Smart 客户端支持集群协议。Smart 客户端通过在内部维护 slot→node 的映射关系,本地就可实现键到节点的查找,从而保证 IO 效率的最大化,而 MOVED 重定向负责协助 Smart 客户端更新 slot→node 映射。 @@ -564,7 +574,8 @@ Redis 集群自身实现了高可用。高可用首先需要解决集群部分 ### 参考与来源 -1. https://redis.io/topics/cluster-tutorial -2. 《Redis 设计与实现》 -3. 《Redis 开发与运维》 -4. https://www.cnblogs.com/kismetv/p/9853040.html \ No newline at end of file +1. https://www.mybluelinux.com/redis-explained/ +2. https://redis.io/topics/cluster-tutorial +3. 《Redis 设计与实现》 +4. 《Redis 开发与运维》 +5. https://www.cnblogs.com/kismetv/p/9853040.html \ No newline at end of file diff --git a/docs/data-management/Redis/Redis-Conf.md b/docs/data-management/Redis/Redis-Conf.md index 711108ff58..e9d84b8574 100644 --- a/docs/data-management/Redis/Redis-Conf.md +++ b/docs/data-management/Redis/Redis-Conf.md @@ -1,15 +1,33 @@ ## 简单介绍 -**units单位** +**units 单位** -- 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit +- 配置大小单位,开头定义了一些基本的度量单位,只支持 bytes,不支持 bit - 对大小写不敏感 +> Note on units: when memory size is needed, it is possible to specify +> +> it in the usual form of 1k 5GB 4M and so forth: +> +> 1k => 1000 bytes +> +> 1kb => 1024 bytes +> +> 1m => 1000000 bytes +> +> 1mb => 1024*1024 bytes +> +> 1g => 1000000000 bytes +> +> 1gb => 1024*1024*1024 bytes +> +> units are case insensitive so 1GB 1Gb 1gB are all the same. + ### INCLUDES包含 -- 和我们的Struts2配置文件类似,可以通过includes包含,redis.conf可以作为总闸,包含其他 +- 可以通过 includes 包含,redis.conf 可以作为总闸,包含其他 diff --git a/docs/data-management/Redis/Redis-Database.md b/docs/data-management/Redis/Redis-Database.md index dcb71d3493..9c71d14892 100644 --- a/docs/data-management/Redis/Redis-Database.md +++ b/docs/data-management/Redis/Redis-Database.md @@ -1,10 +1,8 @@ # Redis-Database - - Redis 如何表示一个数据库?数据库操作是如何实现的? -> 这边文章是基于源码来让我们理解 Redis 的,不管是我们自己下载 redis 还是直接在 Github 上看源码,我们先要了解下 redis 更目录下的重要目录 +> 这篇文章是基于源码来让我们理解 Redis 的,不管是我们自己下载 redis 还是直接在 Github 上看源码,我们先要了解下 redis 根目录下的重要目录 > > - `src`:用C编写的Redis实现 > - `tests`:包含在Tcl中实现的单元测试 @@ -18,8 +16,6 @@ Redis 如何表示一个数据库?数据库操作是如何实现的? 理解程序如何工作的最简单方法是理解它使用的数据结构。 从 `redis/src` 目录下可以看到 server 的源码文件(基于 `redis-6.0.5`,redis3.0 叫 `redis.c` 和 `redis.h`)。 - - Redis的主头文件 `server.h` 中定义了各种结构体,比如Redis 对象`redisObject` 、存储结构`redisDb `、客户端`client` 等等。 ```c @@ -118,5 +114,4 @@ Redis 解决哈希碰撞的方式 和 Java 中的 HashMap 类似,采取链表 - -https://redisbook.readthedocs.io/en/latest/index.html \ No newline at end of file +> https://redisbook.readthedocs.io/en/latest/index.html diff --git a/docs/data-management/Redis/Redis-Datatype.md b/docs/data-management/Redis/Redis-Datatype.md index c2438efbc6..fc8ff7ad04 100644 --- a/docs/data-management/Redis/Redis-Datatype.md +++ b/docs/data-management/Redis/Redis-Datatype.md @@ -1,4 +1,10 @@ -# Redis 数据类型篇 +--- +title: Redis 数据类型篇 +date: 2022-08-25 +tags: + - Redis +categories: Redis +--- > 一提到 Redis,我们的脑子里马上就会出现一个词:“快。” > @@ -10,7 +16,7 @@ 我们都知道 Redis 是个 KV 数据库,那 KV 结构的数据在 Redis 中是如何存储的呢? -### 一、KV 如何存储? +## 一、KV 如何存储? 为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。类似我们的 HashMap @@ -20,10 +26,20 @@ 在下图中,可以看到,哈希桶中的 entry 元素中保存了 `*key` 和 `*value` 指针,分别指向了实际的键和值,这样一来,即使值是一个集合,也可以通过 `*value` 指针被查找到。 - + 因为这个哈希表保存了所有的键值对,所以,也把它称为**全局哈希表**。哈希表的最大好处很明显,就是让我们可以用 $O(1)$ 的时间复杂度来快速查找到键值对——我们只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。 +```c +struct redisObject { + unsigned type:4; // 类型 + unsigned encoding:4; // 编码 + unsigned lru:LRU_BITS; // 对象最后一次被访问的时间 + int refcount; //引用计数 + void *ptr; //指向实际值的指针 +}; +``` + 你看,这个查找过程主要依赖于哈希计算,和数据量的多少并没有直接关系。也就是说,不管哈希表里有 10 万个键还是 100 万个键,我们只需要一次计算就能找到相应的键。但是,如果你只是了解了哈希表的 $O(1)$ 复杂度和快速查找特性,那么,当你往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。这其实是因为你忽略了一个潜在的风险点,那就是哈希表的冲突问题和 rehash 可能带来的操作阻塞。 ### 为什么哈希表操作变慢了? @@ -32,15 +48,32 @@ Redis 解决哈希冲突的方式,就是链式哈希。和 JDK7 中的 HahsMap 类似,链式哈希也很容易理解,**就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接**。 -如下图所示:哈希桶 6 上就有 3 个连着的 entry,也叫作哈希冲突链。 - - + 但是,这里依然存在一个问题,哈希冲突链上的元素只能通过指针逐一查找再操作。如果哈希表里写入的数据越来越多,哈希冲突可能也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低。对于追求“快”的 Redis 来说,这是不太能接受的。 所以,Redis 会对哈希表做 rehash 操作。rehash 也就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。那具体怎么做呢? -其实,为了使 rehash 操作更高效,Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空间。随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步: + + +其实,为了使 rehash 操作更高效,Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。 + +```c +struct dict { + dictType *type; + + dictEntry **ht_table[2]; + unsigned long ht_used[2]; + + long rehashidx; /* rehashing not in progress if rehashidx == -1 */ + + /* Keep small vars at end for optimal (minimal) struct padding */ + int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */ + signed char ht_size_exp[2]; /* exponent of size. (size = 1<链式哈希。和 JDK7 到此,我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来的哈希表 1 留作下一次 rehash 扩容备用。这个过程看似简单,但是第二步涉及大量的数据拷贝,如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。为了避免这个问题,Redis 采用了渐进式 rehash。 -简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。如下图所示: - - +简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。 渐进式 rehash 这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。 @@ -62,31 +93,29 @@ Redis 解决哈希冲突的方式,就是链式哈希。和 JDK7 ## 一、Redis 的五种基本数据类型和其数据结构 -由于 Redis 是基于标准 C 写的,只有最基础的数据类型,因此 Redis 为了满足对外使用的 5 种数据类型,开发了属于自己**独有的一套基础数据结构**,使用这些数据结构来实现 5 种数据类型。 + + +由于 Redis 是基于标准 C 写的,只有最基础的数据类型,因此 Redis 为了满足对外使用的 5 种基本数据类型,开发了属于自己**独有的一套基础数据结构**。 **Redis** 有 5 种基础数据类型,它们分别是:**string(字符串)**、**list(列表)**、**hash(字典)**、**set(集合)** 和 **zset(有序集合)**。 -Redis 底层的数据结构包括:**简单动态数组SDS、链表、字典、跳跃链表、整数集合、压缩列表、对象。** +Redis 底层的数据结构包括:**简单动态数组SDS、链表、字典、跳跃链表、整数集合、快速列表、压缩列表、对象。** Redis 为了平衡空间和时间效率,针对 value 的具体类型在底层会采用不同的数据结构来实现,其中哈希表和压缩列表是复用比较多的数据结构,如下图展示了对外数据类型和底层数据结构之间的映射关系: - + 下面我们具体看下各种数据类型的底层实现和操作。 > 安装好 Redis,我们可以使用 `redis-cli` 来对 Redis 进行命令行的操作,当然 Redis 官方也提供了在线的调试器,你也可以在里面敲入命令进行操作:http://try.redis.io/#run -### 1、String(字符串) - -String 是 Redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。 -String 类型是二进制安全的。意思是 Redis 的 String 可以包含任何数据。比如 jpg 图片或者序列化的对象 。 -Redis 的字符串是动态字符串,是可以修改的字符串,**内部结构实现上类似于 Java 的 ArrayList**,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。 - - +### 1、String(字符串) +String 类型是二进制安全的。意思是 Redis 的 String 可以包含任何数据。比如 jpg 图片或者序列化的对象 。 +Redis 的字符串是动态字符串,是可以修改的字符串,**内部结构实现上类似于 Java 的 ArrayList**,采用预分配冗余空间的方式来减少内存的频繁分配,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。 Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将 SDS 用作 Redis 的默认字符串表示。 @@ -94,7 +123,7 @@ Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾 比如说, 下图就展示了一个值为 `"Redis"` 的 C 字符串: - + C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求 @@ -108,7 +137,21 @@ C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 举个例子, 对于下图所示的 SDS 来说, 程序只要访问 SDS 的 `len` 属性, 就可以立即知道 SDS 的长度为 `5` 字节: -  +  + + > **len**:当前字符串的长度(不包括末尾的 null 字符)。这使得 Redis 不需要在每次操作时都遍历整个字符串来获取其长度。 + > + > **alloc**:当前为字符串分配的总空间(包括字符串数据和额外的内存空间)。由于 Redis 使用的是动态分配内存,因此可以避免频繁的内存分配和释放。 + > + > **buf**:实际的字符串数据部分,存储字符串的字符数组。Redis 通过这个区域存储字符串的内容。 + > + > ```c + > struct sdshdr { + > int len; // 当前字符串的长度 + > int alloc; // 为字符串分配的空间, 2.X 版本用一个 free 表示当前字符串缓冲区中未使用的内存量 + > unsigned char buf[]; // 字符串数据 + > }; + > ``` 通过使用 SDS 而不是 C 字符串, Redis 将获取字符串长度所需的复杂度从 $O(N)$ 降低到了 $O(1)$ , 这确保了获取字符串长度的工作不会成为 Redis 的性能瓶颈 @@ -129,11 +172,20 @@ C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 通过未使用空间, SDS 实现了空间预分配和惰性空间释放两种优化策略。 -- **二进制安全** +- **SDS 如何保证二进制安全**: - 因为 C 语言中的字符串必须符合某种编码(比如 ASCII),并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据 + - **不依赖于 null 字符**:**二进制安全**意味着 SDS 字符串可以包含任何数据,包括 null 字节。传统的 C 字符串依赖于 null 字符('\0')来表示字符串的结束,但 Redis 的 SDS 不依赖于 null 字符来确定字符串的结束位置。 + - **动态扩展**:SDS 会根据需要动态地扩展其内部缓冲区。Redis 会使用 `alloc` 字段来记录已分配的内存大小。当你向 SDS 中追加数据时,Redis 会确保分配足够的内存,而不需要担心数据的终止符。 + - **不需要二次编码**:二进制数据直接存储在 SDS 的 `buf` 区域内,不需要进行任何编码或转换。因此,Redis 可以原样存储任意二进制数据。 + > C 语言中的字符串必须符合某种编码(比如 ASCII),并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据 +- **编码方式**: + - **int**:当值可以用整数表示时,Redis 会使用整数编码。这样可以节省内存,并且在执行数值操作时更高效。 + - **embstr**:这是一种特殊的编码方式,用于存储短字符串。当字符串的长度小于或等于 44 字节时,Redis 会使用 embstr 编码。只读,修改后自动转为 raw。这种编码方式将字符串直接存储在 Redis 对象的内部,这样可以减少内存分配和内存拷贝的次数,提高性能。 + - **raw**:当字符串值不是整数时,Redis 会使用 raw 编码。raw 编码就是简单地将字符串存储为字节序列。Redis 会根据客户端发送的字节序列来存储字符串,因此可以存储任何类型的数据,包括二进制数据。 + +> 可以通过 `TYPE KEY_NAME` 查看 key 所存储的值的类型验证下。 ### 2、List(列表) @@ -141,8 +193,6 @@ C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。 - - Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理 **右边进左边出:队列** @@ -188,21 +238,19 @@ Redis 的列表结构常用来做异步队列使用。将需要延后处理的 - 列表中保存的单个数据(有可能是字符串类型的)小于 64 字节; - 列表中数据个数少于 512 个。 ->听到“压缩”两个字,直观的反应就是节省内存。之所以说这种存储结构节省内存,是相较于数组的存储思路而言的。我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是 20 个字节)。那当我们存储小于 20 个字节长度的字符串的时候,便会浪费部分存储空间。听起来有点儿拗口,我画个图解释一下。 -> -> -> ->压缩列表这种存储结构,一方面比较节省内存,另一方面可以支持不同类型数据的存储。而且,因为数据存储在一片连续的内存空间,通过键来获取值为列表类型的数据,读取的效率也非常高。 +从 Redis 3.2 版本开始,列表的底层实现由压缩列表组成的快速列表(quicklist)所取代。 -当列表中存储的数据量比较大的时候,也就是不能同时满足刚刚讲的两个条件的时候,列表就要通过双向循环链表来实现了。 +- 快速列表由多个 ziplist 节点组成,每个节点使用指针连接,形成一个链表。 +- 这种方式结合了压缩列表的内存效率和小元素的快速访问,以及双向链表的灵活性。 -Redis 的这种双向链表的实现方式,非常值得借鉴。它额外定义一个 list 结构体,来组织链表的首、尾指针,还有长度等信息。这样,在使用的时候就会非常方便。 +>听到“压缩”两个字,直观的反应就是节省内存。之所以说这种存储结构节省内存,是相较于数组的存储思路而言的。 +> +>它并不是基础数据结构,而是 Redis 自己设计的一种数据存储结构。它有点儿类似数组,通过一片连续的内存空间,来存储数据。不过,它跟数组不同的一点是,它允许存储的数据大小不同。听到“压缩”两个字,直观的反应就是节省内存。之所以说这种存储结构节省内存,是相较于数组的存储思路而言的。我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是 20 个字节)。那当我们存储小于 20 个字节长度的字符串的时候,便会浪费部分存储空间。压缩列表这种存储结构,一方面比较节省内存,另一方面可以支持不同类型数据的存储。而且,因为数据存储在一片连续的内存空间,通过键来获取值为列表类型的数据,读取的效率也非常高。当列表中存储的数据量比较大的时候,也就是不能同时满足刚刚讲的两个条件的时候,列表就要通过双向循环链表来实现了。 我们可以从 [源码](https://github.com/redis/redis/blob/unstable/src/adlist.h "redis源码") 的 `adlist.h/listNode` 来看到对其的定义: ```c /* Node, List, and Iterator are the only data structures used currently. */ - typedef struct listNode { struct listNode *prev; // 前置节点 struct listNode *next; // 后置节点 @@ -230,23 +278,95 @@ typedef struct list { Redis hash 是一个键值对集合。KV 模式不变,但 V 又是一个键值对。 -字典类型也有两种实现方式。一种是我们刚刚讲到的压缩列表,另一种是散列表。 +#### 1. **Redis Hash 的实现** + +Redis 使用的是 **哈希表(Hash Table)** 来存储 `Hash` 类型的数据。每个 `Hash` 对象都由一个或多个字段(field)和相应的值(value)组成。 + +在 Redis 中,哈希表的数据结构是通过 **`dict`(字典)** 来实现的,`dict` 的结构包括键和值,其中每个键都是哈希表中的字段(field),而值是字段的值。 + +哈希表的基本实现如下: + +- **哈希表**:采用 **开地址法** 来处理哈希冲突。每个 `Hash` 存储为一个哈希表,哈希表根据一定的哈希算法将键映射到一个位置。 +- **动态扩展**:当哈希表存储的元素数量达到一定的阈值时,Redis 会自动进行 **rehash(重哈希)** 操作,重新分配内存并调整哈希表的大小。 + +Redis 字典由 **嵌套的三层结构** 构成,采用链地址法处理哈希冲突: + +```c +// 哈希表结构 +typedef struct dictht { + dictEntry **table; // 二维数组(哈希桶) + unsigned long size; // 总槽位数(2^n 对齐) + unsigned long sizemask; // 槽位掩码(size-1) + unsigned long used; // 已用槽位数量 +} dictht; + +// 字典结构 +typedef struct dict { + dictType *type; // 类型特定函数(实现多态) + void *privdata; // 私有数据 + dictht ht[2]; // 双哈希表(用于渐进式rehash) + long rehashidx; // rehash进度标记(-1表示未进行) +} dict; + +// 哈希节点结构 +typedef struct dictEntry { + void *key; // 键(SDS字符串) + union { + void *val; // 值(Redis对象) + uint64_t u64; + int64_t s64; + } v; + struct dictEntry *next; // 链地址法指针 +} dictEntry; +``` + +#### 2. **Redis Hash 的内部结构** + +Redis 中的 `Hash` 是由 **哈希表** 和 **ziplist** 两种不同的数据结构实现的,具体使用哪一种结构取决于哈希表中键值对的数量和大小。 + +2.1 **哈希表(Hash Table)** + +- 哈希表是 Redis 中实现字典(`dict`)的基础数据结构,使用 **哈希冲突解决方法**(例如链地址法或开地址法)来存储键值对。 +- 每个哈希表由两个数组组成:**键数组(key array)** 和 **值数组(value array)**,它们通过哈希算法映射到表中的相应位置。 + +2.2 **Ziplist(压缩列表)** + +- **ziplist** 是一种内存高效的列表数据结构,在 Redis 中用于存储小型的 `Hash`,当哈希表中的元素个数较少时,Redis 会使用 `ziplist` 来节省内存。 +- Ziplist 是连续内存块,采用压缩存储。它适用于存储小量数据,避免哈希表内存开销。 + +2.3 **切换机制** + +> - 字典中保存的键和值的大小都要小于 64 字节; +> - 字典中键值对的个数要小于 512 个。 + +当 Redis 中 `Hash` 的元素数量较小时,会使用 `ziplist`;当元素数量增多时,会切换到使用哈希表的方式。 -同样,只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:字典中保存的键和值的大小都要小于 64 字节;字典中键值对的个数要小于 512 个。 +- **小型 Hash**:当哈希表的字段数量很少时,Redis 会使用 `ziplist` 来存储 `Hash`,因为它可以节省内存。 +- **大型 Hash**:当字段数量较多时,Redis 会将 `ziplist` 转换为 `哈希表` 来优化性能和内存管理。 -当不能同时满足上面两个条件的时候,Redis 就使用散列表来实现字典类型。Redis 使用MurmurHash2这种运行速度快、随机性好的哈希算法作为哈希函数。对于哈希冲突问题,Redis 使用链表法来解决。除此之外,Redis 还支持散列表的动态扩容、缩容。当数据动态增加之后,散列表的装载因子会不停地变大。为了避免散列表性能的下降,当装载因子大于 1 的时候,Redis 会触发扩容,将散列表扩大为原来大小的 2 倍左右(具体值需要计算才能得到,如果感兴趣,你可以去[阅读源码](https://github.com/redis/redis/blob/unstable/src/dict.c))。 +#### 3. Rehash(重哈希) -扩容缩容要做大量的数据搬移和哈希值的重新计算,所以比较耗时。针对这个问题,Redis 使用我们在散列表(中)讲的渐进式扩容缩容策略,将数据的搬移分批进行,避免了大量数据一次性搬移导致的服务停顿。 +**Rehash** 是 Redis 用来扩展哈希表的一种机制。随着 `Hash` 中元素数量的增加,哈希表的负载因子(load factor)会逐渐增大,最终可能会导致哈希冲突增多,降低查询效率。为了解决这个问题,Redis 会在哈希表负载因子达到一定阈值时,执行 **rehash** 操作,即扩展哈希表。 +3.1 **Rehash 的过程** +- **扩展哈希表**:Redis 会将哈希表的大小翻倍,并将现有的数据重新映射到新的哈希表中。扩展哈希表的目的是减少哈希冲突,提高查找效率。 -Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典, 内部实现结构上同 Java 的 HashMap 也是一致的,同样的**数组 + 链表**二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。 +- **渐进式 rehash(Incremental Rehash)**:Redis 在执行重哈希时,并不会一次性将所有数据都重新映射到新的哈希表中,这样可以避免大量的阻塞操作。Redis 会分阶段地逐步迁移哈希表中的元素。这一过程通过增量的方式进行,逐步从旧哈希表中取出元素,放入新哈希表。 -不同的是,Redis 的字典的值只能是字符串,另外它们 rehash 的方式不一样,因为 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需要一次性全部 rehash。Redis 为了高性能,不能堵塞服务,所以采用了渐进式 rehash 策略。 + 这种增量迁移的方式保证了 **rehash** 操作不会一次性占用过多的 CPU 时间,避免了阻塞。 -渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完成了,就会使用新的 hash 结构取而代之。 +3.2 **Rehash 的触发条件** -当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。 +Redis 会在以下情况下触发 rehash 操作: + +- 当哈希表的元素数量超过哈希表容量的负载因子阈值时(例如,默认阈值为 1),Redis 会开始进行 rehash 操作。 +- 当哈希表的空间变得非常紧张,Redis 会执行扩展操作。 + +扩容就会涉及到键值对的迁移。具体来说,迁移操作会在以下两种情况下进行: + +1. **Lazy Rehashing(懒惰重哈希):** Redis 采用了懒惰重哈希的策略,即在进行哈希表扩容时,并不会立即将所有键值对都重新散列到新的存储桶中。而是在有需要的时候,例如进行读取操作时,才会将相应的键值对从旧存储桶迁移到新存储桶中。这种方式避免了一次性大规模的迁移操作,减少了扩容期间的阻塞时间。 +2. **Redis 事件循环(Event Loop):** Redis 会在事件循环中定期执行一些任务,包括一些与哈希表相关的操作。在事件循环中,Redis会检查是否有需要进行迁移的键值对,并将它们从旧存储桶迁移到新存储桶中。这样可以保证在系统负载较轻的时候进行迁移,减少对服务性能的影响。 @@ -288,15 +408,56 @@ typedef struct dict { ### 4、Set(集合) -集合这种数据类型用来存储一组不重复的数据。这种数据类型也有两种实现方法,一种是基于有序数组,另一种是基于散列表。当要存储的数据,同时满足下面这样两个条件的时候,Redis 就采用有序数组,来实现集合这种数据类型。存储的数据都是整数;存储的数据元素个数不超过 512 个。当不能同时满足这两个条件的时候,Redis 就使用散列表来存储集合中的数据。 +集合这种数据类型用来存储一组不重复的数据。这种数据类型也有两种实现方法,一种是基于整数集合,另一种是基于散列表。当要存储的数据,同时满足下面这样两个条件的时候,Redis 就采用整数集合(intset),来实现集合这种数据类型。 -Redis 的 Set 是 String 类型的无序集合。它是通过 HashTable 实现的, 相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值`NULL`。 +- 存储的数据都是整数; +- 存储的数据元素个数不超过 512 个。 + +当不能同时满足这两个条件的时候,Redis 就使用散列表来存储集合中的数据。 + +Redis 的 Set 是 String 类型的无序集合。它是通过 HashTable 实现的, 相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 `NULL`。 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。 +```c +// 定义整数集合结构 +typedef struct intset { + uint32_t encoding; // 编码方式 + uint32_t length; // 集合长度 + int8_t contents[]; // 元素数组 +} intset; + +// 定义哈希表节点结构 +typedef struct dictEntry { + void *key; // 键 + union { + void *val; // 值 + uint64_t u64; + int64_t s64; + double d; + } v; + struct dictEntry *next; // 指向下一个节点的指针 +} dictEntry; + +// 定义哈希表结构 +typedef struct dictht { + dictEntry **table; // 存储桶数组 + unsigned long size; // 存储桶数量 + unsigned long sizemask; // 存储桶数量掩码 + unsigned long used; // 已使用存储桶数量 +} dictht; +// 定义集合结构 +typedef struct { + uint32_t encoding; // 编码方式 + dictht *ht; // 哈希表 + intset *is; // 整数集合 +} set; +``` -### 5、zset(sorted set:有序集合) + + +### 5、Zset(sorted set:有序集合) zset 和 set 一样也是 String 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。 @@ -313,33 +474,48 @@ zset 中最后一个 value 被移除后,数据结构自动删除,内存被 -为什么 Redis 要用跳表来实现有序集合,而不是红黑树? - -Redis 中的有序集合是通过跳表来实现的,严格点讲,其实还用到了散列表。不过散列表我们后面才会讲到,所以我们现在暂且忽略这部分。如果你去查看 Redis 的开发手册,就会发现,Redis 中的有序集合支持的核心操作主要有下面这几个:插入一个数据;删除一个数据;查找一个数据;按照区间查找数据(比如查找值在[100, 356]之间的数据);迭代输出有序序列。其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。当然,Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。 +> **为什么 Redis 要用跳表来实现有序集合,而不是红黑树?** +> +> Redis 中的有序集合是通过跳表来实现的,严格点讲,其实还用到了散列表。不过散列表我们后面才会讲到,所以我们现在暂且忽略这部分。如果你去查看 Redis 的开发手册,就会发现,Redis 中的有序集合支持的核心操作主要有下面这几个: +> +> - 插入一个数据; +> - 删除一个数据; +> - 查找一个数据; +> - 按照区间查找数据(比如查找值在[100, 356]之间的数据); +> - 迭代输出有序序列。 +> +> 其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。对于按照区间查找数据这个操作,跳表可以做到 $O(logn)$ 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。当然,Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。 ## 二、其他数据类型 -### bitmaps +### Bitmap -##### ☆☆位图: +Redis 的 Bitmap 数据结构是一种基于 String 类型的位数组,它允许用户将字符串当作位向量来使用,并对这些位执行位操作。Bitmap 并不是 Redis 中的一个独立数据类型,而是通过在 String 类型上定义的一组位操作命令来实现的。由于 Redis 的 String 类型是二进制安全的,最大长度可以达到 512 MB,因此可以表示最多 $2^{32}$ 个不同的位。 -在我们平时开发过程中,会有一些 bool 型数据需要存取,比如用户一年的签到记录,签了是 1,没签是 0,要记录 365 天。如果使用普通的 key/value,每个用户要记录 365 个,当用户上亿的时候,需要的存储空间是惊人的。 +Bitmap 在 Redis 中的使用场景包括但不限于: -为了解决这个问题,Redis 提供了位图数据结构,这样每天的签到记录只占据一个位,365 天就是 365 个位,46 个字节 (一个稍长一点的字符串) 就可以完全容纳下,这就大大节约了存储空间。 +1. **集合表示**:当集合的成员对应于整数 0 到 N 时,Bitmap 可以高效地表示这种集合。 +2. **对象权限**:每个位代表一个特定的权限,类似于文件系统存储权限的方式。 +3. **签到系统**:记录用户在特定时间段内的签到状态。 +4. **用户在线状态**:跟踪大量用户的在线或离线状态。 - +Bitmap 操作的基本命令包括: +- `SETBIT key offset value`:设置或清除 key 中 offset 位置的位值(只能是 0 或 1)。 +- `GETBIT key offset`:获取 key 中 offset 位置的位值,如果 key 不存在,则返回 0。 +Bitmap 还支持更复杂的位操作,如: -位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是 byte 数组。我们可以使用普通的 get/set 直接获取和设置整个位图的内容,也可以使用位图操作 getbit/setbit 等将 byte 数组看成「位数组」来处理 +- `BITOP operation destkey key [key ...]`:对一个或多个 key 的 Bitmap 进行位操作(AND、OR、NOT、XOR)并将结果保存到 destkey。 +- `BITCOUNT key [start] [end]`:计算 key 中位数为 1 的数量,可选地在指定的 start 和 end 范围内进行计数。 - Redis 的位数组是自动扩展,如果设置了某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充。 +Bitmap 在存储空间方面非常高效,例如,表示一亿个用户的登录状态,每个用户用一个位来表示,总共只需要 12 MB 的内存空间。 - +在实际应用中,Bitmap 可以用于实现诸如亿级数据统计、用户行为跟踪等大规模数据集的高效管理。 -接下来我们使用 redis-cli 设置第一个字符,也就是位数组的前 8 位,我们只需要设置值为 1 的位,如上图所示,h 字符只有 1/2/4 位需要设置,e 字符只有 9/10/13/15 位需要设置。值得注意的是位数组的顺序和字符的位顺序是相反的。 +总的来说,Redis 的 Bitmap 是一种非常节省空间且功能强大的数据结构,适用于需要对大量二进制数据进行操作的场景。 ```sh 127.0.0.1:6379> setbit s 1 1 @@ -362,43 +538,37 @@ Redis 中的有序集合是通过跳表来实现的,严格点讲,其实还 上面这个例子可以理解为「零存整取」,同样我们还也可以「零存零取」,「整存零取」。「零存」就是使用 setbit 对位值进行逐个设置,「整存」就是使用字符串一次性填充所有位数组,覆盖掉旧值。 -bitcount 和 bitop, bitpos, bitfield 都是操作位图的指令。 - ### HyperLogLog Redis 在 2.8.9 版本添加了 HyperLogLog 结构。 -场景:可以用来统计站点的UV... +Redis HyperLogLog 是一种用于基数统计的数据结构,它提供了一个近似的、不精确的解决方案来估算集合中唯一元素的数量,即集合的基数。HyperLogLog 特别适用于需要处理大量数据并且对精度要求不是特别高的场景,因为它使用非常少的内存(通常每个 HyperLogLog 实例只需要 12.4KB 左右,无论集合中有多少元素)。 -Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。但是会有误差。 - -| 命令 | 用法 | 描述 | -| ------- | ------------------------------------------ | ----------------------------------------- | -| pfadd | [PFADD key element [element ...] | 添加指定元素到 HyperLogLog 中 | -| pfcount | [PFCOUNT key [key ...] | 返回给定 HyperLogLog 的基数估算值。 | -| pfmerge | [PFMERGE destkey sourcekey [sourcekey ...] | 将多个 HyperLogLog 合并为一个 HyperLogLog | - -```java -public class JedisTest { - public static void main(String[] args) { - Jedis jedis = new Jedis(); - for (int i = 0; i < 100000; i++) { - jedis.pfadd("codehole", "user" + i); - } - long total = jedis.pfcount("codehole"); - System.out.printf("%d %d\n", 100000, total); - jedis.close(); - } -} -``` +HyperLogLog 的主要特点包括: + +1. **近似统计**:HyperLogLog 不保证精确计算基数,但它提供了一个非常接近真实值的近似值。 +2. **内存效率**:HyperLogLog 能够使用固定大小的内存来估算基数,这使得它在处理大规模数据集时非常有用。 +3. **可合并性**:多个 HyperLogLog 实例可以合并,以估算多个集合的并集基数。 + +HyperLogLog 的主要命令包括: -[HyperLogLog图解](http://content.research.neustar.biz/blog/hll.html ) +- `PFADD key element [element ...]`:向 HyperLogLog 数据结构添加元素。如果 key 不存在,它将被创建。 +- `PFCOUNT key`:返回给定 HyperLogLog 的近似基数。 +- `PFMERGE destkey sourcekey [sourcekey ...]`:将多个 HyperLogLog 结构合并到一个单独的 HyperLogLog 结构中。 + +HyperLogLog 的工作原理基于一个数学算法,它使用一个固定大小的位数组和一些哈希函数。当添加一个新元素时,它被哈希函数映射到一个位数组的索引,并根据哈希值的前几位来设置位数组中的位。基数估算是通过分析位数组中 0 的位置来完成的。 + +由于 HyperLogLog 提供的是近似值,它有一个标准误差率,通常在 0.81% 左右。这意味着如果实际基数是 1000,HyperLogLog 估算的基数可能在 992 到 1008 之间。 + +HyperLogLog 是处理大数据集基数统计的理想选择,尤其是当数据集太大而无法在内存中完全加载时。它在数据挖掘、日志分析、用户行为分析等领域有着广泛的应用。 + +场景:可以用来统计站点的UV... +> [HyperLogLog图解](http://content.research.neustar.biz/blog/hll.html ) -### Geo diff --git a/docs/data-management/Redis/Reids-Lock.md b/docs/data-management/Redis/Redis-Lock.md similarity index 97% rename from docs/data-management/Redis/Reids-Lock.md rename to docs/data-management/Redis/Redis-Lock.md index b4c977d506..5719d23d85 100644 --- a/docs/data-management/Redis/Reids-Lock.md +++ b/docs/data-management/Redis/Redis-Lock.md @@ -1,6 +1,12 @@ -# 分布式锁 +--- +title: 分布式锁 +date: 2021-10-09 +tags: + - Redis +categories: Redis +--- - + > 分布式锁的文章其实早就烂大街了,但有些“菜鸟”写的太浅,或者自己估计都没搞明白,没用过,看完后我更懵逼了,有些“大牛”写的吧,又太高级,只能看懂前半部分,后边就开始讲论文了,也比较懵逼,所以还得我这个中不溜的来总结下 > @@ -9,12 +15,13 @@ > - 什么是分布式锁 > - 分布式锁的实现要求 > - 基于 Redisson 实现的 Redis 分布式锁 +> - 再简单说下 RedLock ## 一、什么是分布式锁 **分布式~~锁**,要这么念,首先得是『分布式』,然后才是『锁』 -- 分布式:这里的分布式指的是分布式系统,涉及到好多技术和理论,包括CAP 理论、分布式存储、分布式事务、分布式锁... +- 分布式:这里的分布式指的是分布式系统,涉及到好多技术和理论,包括 CAP 理论、分布式存储、分布式事务、分布式锁... > 分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。 > @@ -38,7 +45,7 @@ 知道了什么是分布式锁,接下来就到了技术选型环节 - + ## 二、分布式锁要怎么搞 @@ -106,7 +113,7 @@ SET resource_name my_random_value NX PX 30000 > - `NX` :只在键不存在时,才对键进行设置操作。 `SET key value NX` 效果等同于 `SETNX key value` 。 > - `XX` :只在键已经存在时,才对键进行设置操作。 -这条指令的意思:当 key——resource_name 不存在时创建这样的key,设值为 my_random_value,并设置过期时间 30000 毫秒。 +这条指令的意思:当 key——resource_name 不存在时创建这样的 key,设值为 my_random_value,并设置过期时间 30000 毫秒。 别看这干了两件事,因为 Redis 是单线程的,这一条指令不会被打断,所以是原子性的操作。 @@ -139,7 +146,7 @@ end 1. 获取锁时,过期时间要设置多少合适呢? - 预估一个合适的时间,其实没那么容易,比如操作资源的时间最慢可能要 10 s,而我们只设置了 5 s 就过期,那就存在锁提前过期的风险。这个问题先记下,我们先看下 Javaer 要怎么在代码中用 Redis 锁。 + 预估一个合适的时间,其实没那么容易,比如操作资源的时间最慢可能要 10s,而我们只设置了 5s 就过期,那就存在锁提前过期的风险。这个问题先记下,我们一会看下 Javaer 要怎么在代码中用 Redis 锁。 2. 容错性如何保证呢? @@ -151,13 +158,13 @@ end ### Redisson 实现代码 -redisson 是 Redis 官方的分布式锁组件。GitHub 地址:[https://github.com/redisson/redisson](https://zhuanlan.zhihu.com/write) +Redisson 是 Redis 官方的分布式锁组件。GitHub 地址:[https://github.com/redisson/redisson](https://zhuanlan.zhihu.com/write) > Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。Redisson 提供了使用 Redis 的最简单和最便捷的方法。Redisson 的宗旨是促进使用者对 Redis 的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。 -redisson 现在已经很强大了,github 的 wiki 也很详细,分布式锁的介绍直接戳 [Distributed locks and synchronizers](https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers) +Redisson 现在已经很强大了,github 的 wiki 也很详细,分布式锁的介绍直接戳 [Distributed locks and synchronizers](https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers) -Redisson 支持单点模式、主从模式、哨兵模式、集群模式,只是配置的不同,我们以单点模式来看下怎么使用,代码很简单,都已经为我们封装好了,直接拿来用就好,详细的demo,我放在了 github: starfish-learn-redisson 上,这里就不一步步来了 +Redisson 支持单点模式、主从模式、哨兵模式、集群模式,只是配置的不同,我们以单点模式来看下怎么使用,代码很简单,都已经为我们封装好了,直接拿来用就好,详细的 demo,我放在了 github: starfish-learn-redisson 上,这里就不一步步来了 ```java RLock lock = redisson.getLock("myLock"); @@ -169,7 +176,7 @@ RLock 提供了各种锁方法,我们来解读下这个接口方法, #### RLock - + ```java public interface RLock extends Lock, RLockAsync { @@ -267,7 +274,7 @@ try { 先看下 RLock 的类关系 - + 跟着源码,可以发现 RedissonLock 是 RLock 的直接实现,也是我们加锁、解锁操作的核心类 @@ -659,7 +666,7 @@ Redisson 提供了看门狗,每获得一个锁时,只设置一个很短的 - + ## 四、RedLock diff --git a/docs/data-management/Redis/Redis-MQ.md b/docs/data-management/Redis/Redis-MQ.md index deafdd8972..64527748f9 100644 --- a/docs/data-management/Redis/Redis-MQ.md +++ b/docs/data-management/Redis/Redis-MQ.md @@ -1,8 +1,12 @@ +--- +title: Redis 消息队列的三种方案(List、Streams、Pub/Sub) +date: 2022-2-9 +tags: + - Redis +categories: Redis +--- - -# Redis 消息队列的三种方案(List、Streams、Pub/Sub) - - + 现如今的互联网应用大都是采用 **分布式系统架构** 设计的,所以 **消息队列** 已经逐渐成为企业应用系统 **内部通信** 的核心手段, @@ -28,7 +32,7 @@ > > 通过提供 **消息传递** 和 **消息排队** 模型,它可以在 **分布式环境** 下提供 **应用解耦**、**弹性伸缩**、**冗余存储**、**流量削峰**、**异步通信**、**数据同步** 等等功能,其作为 **分布式系统架构** 中的一个重要组件,有着举足轻重的地位。 - + @@ -100,7 +104,7 @@ Redis 提供了好几对 List 指令,先大概看下这些命令,混个眼 127.0.0.1:6379> ``` - + @@ -138,7 +142,7 @@ Redis 提供了好几对 List 指令,先大概看下这些命令,混个眼 -因为 Redis 单线程的特点,所以在消费数据时,同一个消息会不会同时被多个 `consumer` 消费掉,但是需要我们考虑消费不成功的情况。 +因为 Redis 单线程的特点,所以在消费数据时,同一个消息不会同时被多个 `consumer` 消费掉,但是需要我们考虑消费不成功的情况。 #### 可靠队列模式 | ack 机制 @@ -168,7 +172,7 @@ Redis 提供了好几对 List 指令,先大概看下这些命令,混个眼 1) "three" ``` - + @@ -189,7 +193,7 @@ Redis 提供了好几对 List 指令,先大概看下这些命令,混个眼 List 实现方式其实就是点对点的模式,下边我们再看下 Redis 的发布订阅模式(消息多播),这才是“根正苗红”的 Redis MQ - + "发布/订阅"模式同样可以实现进程间的消息传递,其原理如下: @@ -203,11 +207,11 @@ Redis 通过 `PUBLISH` 、 `SUBSCRIBE` 等命令实现了订阅与发布模式 我们启动三个 Redis 客户端看下效果: - + 先启动两个客户端订阅(subscribe) 名字叫 framework 的频道,然后第三个客户端往 framework 发消息,可以看到前两个客户端都会接收到对应的消息: - + 我们可以看到订阅的客户端每次可以收到一个 3 个参数的消息,分别为: @@ -217,11 +221,11 @@ Redis 通过 `PUBLISH` 、 `SUBSCRIBE` 等命令实现了订阅与发布模式 再来看下订阅符合给定**模式**的频道,这回订阅的命令是 `PSUBSCRIBE` - + 我们往 `java.framework` 这个频道发送了一条消息,不止订阅了该频道的 Consumer1 和 Consumer2 可以接收到消息,订阅了模式 `java.*` 的 Consumer3 和 Consumer4 也可以接收到消息。 - + #### Pub/Sub 常用命令: @@ -246,7 +250,9 @@ Redis 5.0 版本新增了一个更强大的数据结构——**Stream**。它提 它就像是个仅追加内容的**消息链表**,把所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容。而且消息是持久化的。 - + + + @@ -254,7 +260,7 @@ Redis 5.0 版本新增了一个更强大的数据结构——**Stream**。它提 -Streams 是 Redis 专门为消息队列设计的数据类型,所以提供了丰富的消息队列操作命令。 +Stream 是 Redis 专门为消息队列设计的数据类型,所以提供了丰富的消息队列操作命令。 #### Stream 常用命令 @@ -362,7 +368,7 @@ Streams 是 Redis 专门为消息队列设计的数据类型,所以提供了 `xread` 虽然可以扇形分发到 N 个客户端,然而,在某些问题中,我们想要做的不是向许多客户端提供相同的消息流,而是从同一流向许多客户端提供不同的消息子集。比如下图这样,三个消费者按轮训的方式去消费一个 Stream。 - + Redis Stream 借鉴了很多 Kafka 的设计。 @@ -371,15 +377,15 @@ Redis Stream 借鉴了很多 Kafka 的设计。 - **last_delivered_id** :每个消费组会有个游标 last_delivered_id 在数组之上往前移动,表示当前消费组已经消费到哪条消息了 - **pending_ids** :消费者的状态变量,作用是维护消费者的未确认的 id。 pending_ids 记录了当前已经被客户端读取的消息,但是还没有 ack。如果客户端没有 ack,这个变量里面的消息 ID 会越来越多,一旦某个消息被 ack,它就开始减少。这个 pending_ids 变量在 Redis 官方被称之为 `PEL`,也就是 `Pending Entries List`,这是一个很核心的数据结构,它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理。 - + -Stream 不像 Kafak 那样有分区的概念,如果想实现类似分区的功能,就要在客户端使用一定的策略将消息写到不同的 Stream。 +Stream 不像 Kafka 那样有分区的概念,如果想实现类似分区的功能,就要在客户端使用一定的策略将消息写到不同的 Stream。 - `xgroup create`:创建消费者组 - `xgreadgroup`:读取消费组中的消息 - `xack`:ack 掉指定消息 -.jpg) + ```shell # 创建消费者组的时候必须指定 ID, ID 为 0 表示从头开始消费,为 $ 表示只消费新的消息,也可以自己指定 @@ -476,7 +482,7 @@ Stream 提供了 `xreadgroup` 指令可以进行消费组的组内消费,需 > 以梦为马,越骑越傻。诗和远方,越走越慌。不忘初心是对的,但切记要出发,加油吧,程序员。 -> 在路上的你,可以微信搜「 **JavaKeeper** 」一起前行,无套路领取 500+ 本电子书和 30+ 视频教学和源码,本文 **GitHub** [github.com/JavaKeeper)](https://github.com/Jstarfish/JavaKeeper)已经收录,服务端开发、面试必备技能兵器谱,有你想要的! + diff --git a/docs/data-management/Redis/Redis-Master-Slave.md b/docs/data-management/Redis/Redis-Master-Slave.md index fc54867cd1..bce755ff33 100644 --- a/docs/data-management/Redis/Redis-Master-Slave.md +++ b/docs/data-management/Redis/Redis-Master-Slave.md @@ -1,4 +1,12 @@ -## Redis 主从同步 +--- +title: Redis 主从复制 +date: 2021-10-08 +tags: + - Redis +categories: Redis +--- + + > 我们总说的 Redis 具有高可靠性,其实,这里有两层含义:一是数据尽量少丢失,二是服务尽量少中断。 > @@ -6,7 +14,7 @@ > > 这就是 Redis 的主从模式,主从库之间采用的是读写分离的方式。 - + ### 一、主从复制是啥 @@ -123,7 +131,7 @@ repl_backlog_histlen:0 OK ``` -#### + ### 四、主从复制的工作过程 @@ -137,7 +145,7 @@ Redis 主从库之间的同步,在不同阶段有不同的处理方式,我 #### 4.1 全量复制 | 快照同步 - + 为了节省篇幅,我把主要的步骤都 **浓缩** 在了上图中,其实也可以 **简化成三个阶段:建立连接阶段-数据同步阶段-命令传播阶段**。 @@ -148,7 +156,7 @@ Redis 主从库之间的同步,在不同阶段有不同的处理方式,我 - runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“ ?”。 - offset,此时设为 -1,表示第一次复制。 - 主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。 + 主库收到 psync 命令后,会用 **FULLRESYNC** 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。 这里有个地方需要注意,**FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库**。 @@ -163,6 +171,17 @@ Redis 主从库之间的同步,在不同阶段有不同的处理方式,我 3. 最后,也就是第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。 具体的操作是,当主库完成 RDB 文件发送后,就会把此时 `replication buffer` 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。 + + > 主节点在生成 RDB 文件时,会将新的写命令(例如 `SET`、`DEL` 等)追加到**复制积压缓冲区**中,同时这些命令也会通过网络直接发送到所有已连接的从节点。 + > + > 这些写操作是以 **Redis 协议格式(RESP)** 逐条发送的。 + > + > - 双管齐下(RDB + 命令传播) + > + > ##### **数据传输协议:RESP**(Redis Serialization Protocol) + > + > - Redis 的所有数据通信,包括 RDB 文件和增量数据的发送,均基于其内部协议 **RESP(Redis Serialization Protocol)**。 + > - 增量数据是以 Redis 命令流的形式,序列化为 RESP 格式后通过 TCP 连接发送的 @@ -184,7 +203,7 @@ replicaof 所选从库的IP 6379 再看下文章开头的图。 - + ##### 无盘复制 @@ -200,16 +219,16 @@ replicaof 所选从库的IP 6379 ##### 心跳机制 -在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。 +在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING 和 REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。 - 每隔指定的时间,**从节点会向主节点发送 PING 命令**, 并报告复制流的处理情况。 PING 发送的频率由 `repl-ping-slave-period` 参数控制,单位是秒,默认值是 10s。 -- 在命令传播阶段,**从节点会向主节点发送 REPLCONF ACK**命令,频率是每秒1次;命令格式为:`REPLCONF ACK {offset}`,其中offset 指从节点保存的复制偏移量。REPLCONF ACK命令的作用包括: +- 在命令传播阶段,**从节点会向主节点发送 REPLCONF ACK** 命令,频率是每秒 1 次;命令格式为:`REPLCONF ACK {offset}`,其中 offset 指从节点保存的复制偏移量。REPLCONF ACK 命令的作用包括: - 实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用 info Replication,可以看到其从节点的状态中的 lag 值,代表的是主节点上次收到该 REPLCONF ACK 命令的时间间隔,在正常情况下,该值应该是 0 或 1 - 检测命令丢失:从节点发送了自身的 offset,主节点会与自己的 offset 对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。**注意**,offset 和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。 - - 辅助保证从节点的数量和延迟:Redis 主节点中使用 `min-slaves-to-write` 和 `min-slaves-max-lag` 参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如 `min-slaves-to-write` 和 `min-slaves-max-lag` 分别是 3 和 10,含义是如果从节点数量小于 3 个,或所有从节点的延迟值都大于 10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到 REPLCONF ACK 命令的时间来判断的,即前面所说的 info Replication 中的lag 值。 + - 辅助保证从节点的数量和延迟:Redis 主节点中使用 `min-slaves-to-write` 和 `min-slaves-max-lag` 参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如 `min-slaves-to-write` 和 `min-slaves-max-lag` 分别是 3 和 10,含义是如果从节点数量小于 3 个,或所有从节点的延迟值都大于 10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到 REPLCONF ACK 命令的时间来判断的,即前面所说的 info Replication 中的 lag 值。 不过, 因为 Redis 主从使用异步复制, 这就意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延迟特别大,那么丢失的数据就可能会特别多。 @@ -235,13 +254,11 @@ replicaof 所选从库的IP 6379 主库对应的偏移量是 `master_repl_offset`,从库的偏移量 `slave_repl_offset` 。正常情况下,这两个偏移量基本相等。 - - -.jpg) + 在网络断连阶段,主库可能会收到新的写操作命令,这时,`master_repl_offset` 会大于 `slave_repl_offset`。此时,主库只用把 `master_repl_offset` 和 `slave_repl_offset` 之间的命令操作同步给从库就可以了。 - + > PS:因为 repl_backlog_buffer 是一个环形缓冲区(可以理解为是一个定长的环形数组),所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。**如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致**。如果从库和主库**断连时间过长**,造成它在主库 repl_backlog_buffer 的 slave_repl_offset 位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制。 > @@ -254,7 +271,7 @@ replicaof 所选从库的IP 6379 -### 六、小结 +### 五、小结 Redis 的主从库同步的基本原理,总结来说,有三种模式:全量复制、基于长连接的命令传播,以及增量复制。 diff --git a/docs/data-management/Redis/Redis-Persistence.md b/docs/data-management/Redis/Redis-Persistence.md index 54f2d79134..9bb9414067 100644 --- a/docs/data-management/Redis/Redis-Persistence.md +++ b/docs/data-management/Redis/Redis-Persistence.md @@ -3,9 +3,10 @@ title: Redis的持久化机制 date: 2020-12-20 tags: - Redis +categories: Redis --- -# Redis的持久化机制 + > 带着疑问,或者是面试问题去看 Redis 的持久化,或许会有不一样的视角,这几个问题你废了吗? > @@ -47,11 +48,11 @@ RDB 的缺点是最后一次持久化后的数据可能丢失。 **配置位置**: SNAPSHOTTING - + rdb 默认保存的是 **dump.rdb** 文件,如下(不可读) - + 你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。 @@ -70,7 +71,20 @@ rdb 默认保存的是 **dump.rdb** 文件,如下(不可读) > 简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作(例如图中的键值对K1),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 K3),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。 > ->  +>  +> +> 当 Redis 触发 RDB 快照时,它会通过 `fork` 系统调用来创建一个子进程,子进程负责将内存中的数据写入到磁盘,而父进程则继续响应客户端请求。**写时复制(COW)** 机制使得在 fork 后,父子进程共享同一份内存页,只有当父进程或子进程对内存进行修改时,操作系统才会复制出新的内存页,从而减少了内存复制的开销。 +> +> 尽管如此,`fork` 子进程的操作本身仍然是有一定开销的,尤其在数据量非常大的时候,尽管采用了 COW,但以下因素可能会导致 **性能下降**: +> +> - **大量数据**:当 Redis 中的数据量非常大时(几千万或几亿条键值对),fork 子进程仍然需要花费一定的时间来生成 RDB 文件,即使是写时复制也会带来一定的延迟。 +> - **系统资源压力**:如果机器的内存不足,频繁的 fork 和写时复制操作可能导致系统的 **内存不足** 和 **CPU 高负载**,尤其是在数据量较大的时候。 +> - **磁盘 I/O**:即便使用写时复制,子进程最终需要将数据写入磁盘,磁盘 I/O 的性能也会影响快照的时间。如果磁盘写入速度较慢,生成 RDB 文件的过程可能会很慢。 +> +> 如果 Redis 在执行一次 RDB 快照时,快照尚未完成,新的写操作又触发了一个新的 RDB 快照,那么会发生 **重叠触发** 的问题。 +> +> - **新的 RDB 快照会等待前一个快照完成**。 +> - 如果 **`save` 配置的触发条件**(如 `save 900 1`)满足且当前快照未完成,Redis 会 **阻塞新的快照请求**,直到当前的快照操作完成。这是为了避免对系统资源的过度消耗,防止多次快照操作同时进行。 @@ -88,7 +102,7 @@ rdb 默认保存的是 **dump.rdb** 文件,如下(不可读) 将备份文件 (dump.rdb) 移动到 Redis 安装目录并启动服务即可(`CONFIG GET dir` 获取目录) - + @@ -115,7 +129,7 @@ rdb 默认保存的是 **dump.rdb** 文件,如下(不可读) #### 小总结 - + - RDB 是一个非常紧凑的文件 @@ -139,7 +153,7 @@ AOF 默认保存的是 **appendonly.aof ** 文件 **配置位置**: APPEND ONLY MODE - + @@ -155,7 +169,7 @@ AOF 默认保存的是 **appendonly.aof ** 文件 - 启动:修改默认的 appendonly no,改为 yes - 备份被写坏的 AOF 文件 - - 修复:**redis-check-aof --fix** 进行修复 + AOF文件 + - 修复:**redis-check-aof --fix** 进行修复 + AOF 文件 - 恢复:重启 redis 然后重新加载 @@ -166,7 +180,7 @@ AOF 默认保存的是 **appendonly.aof ** 文件 不过,AOF 日志正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示: - + > Tip:日志先行的方式,如果宕机后,还可以通过之前保存的日志恢复到之前的数据状态。可是 AOF 后写日志的方式,如果宕机后,不就会把写入到内存的数据丢失吗? > @@ -178,7 +192,7 @@ AOF 默认保存的是 **appendonly.aof ** 文件 例如,`*2` 表示有两个部分,`$6` 表示 6 个字节,也就是下边的 “SELECT” 命令,`$1` 表示 1 个字节,也就是下边的 “0” 命令,合起来就是 `SELECT 0`,选择 0 库。下边的指令同理,就很好理解了 `SET K1 V1`。 - + 但是,为了避免额外的检查开销,**Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。而写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错**。所以,Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。除此之外,AOF 还有一个好处:它是在命令执行后才记录日志,所以**不会阻塞当前的写操作**。 @@ -230,19 +244,26 @@ AOF 默认保存的是 **appendonly.aof ** 文件 #### rewrite(AOF 重写) -- 是什么:AOF 采用文件追加方式,文件会越来越大,为了避免出现这种情况,新增了重写机制,当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令 `bgrewriteaof`,这个操作相当于对 AOF 文件“瘦身”。在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。 - -  +- 是什么:AOF 采用文件追加方式,文件会越来越大,为了避免出现这种情况,新增了重写机制,当 AOF 文件的大小超过所设定的阈值时,**Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集**,可以使用命令 `bgrewriteaof`,这个操作相当于对 AOF 文件“瘦身”。在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。 -- 重写原理:AOF 文件持续增长而过大时,会 fork 出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,转换成一条条的操作指令,再序列化到一个新的 AOF 文件中。 +  - PS: 重写 AOF 文件的操作,并没有读取旧的 AOF 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 AOF 文件,这点和快照有点类似。 +- **重写原理**:AOF 文件持续增长而过大时,会 fork 出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,转换成一条条的操作指令,再序列化到一个新的 AOF 文件中。 -- 触发机制:Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发 + > PS: 重写 AOF 文件的操作,并没有读取旧的 AOF 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 AOF 文件,这点和快照有点类似。 - 我们在客户端输入两次 `set k1 v1` ,然后比较 `bgrewriteaof` 前后两次的 appendonly.aof 文件(先要关闭混合持久化) +- 触发机制: - + - **AOF 文件增长比例超过一定阈值**:Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的**一倍**且文件大于 64M 时触发 + + ``` + auto-aof-rewrite-min-size 64mb //指定触发重写的 AOF 文件最小大小(默认为 64MB) + auto-aof-rewrite-percentage 100 //指定 AOF 文件增长的百分比(默认为 100%) + ``` + + - **手动触发 AOF 重写**: 通过执行 `BGREWRITEAOF` 命令,用户可以手动触发 AOF 重写 + + 我们在客户端输入两次 `set k1 v1` ,然后比较 `bgrewriteaof` 前后两次的 appendonly.aof 文件(先要关闭混合持久化) @@ -270,13 +291,22 @@ AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制 以下是 AOF 重写的执行步骤: -1. Redis 执行 `fork()` ,现在同时拥有父进程和子进程。 -2. 子进程开始将新 AOF 文件的内容写入到临时文件。 -3. 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。 -4. 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。 -5. 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。 +1. **创建一个新的 AOF 文件**:Redis 执行 `fork()` ,它会**异步地**在后台创建一个新的 AOF 文件(通常命名为 `appendonly.aof.new`)。现在同时拥有父进程和子进程。 + +2. **重写的内容——只保留当前数据库状态**:子进程开始将新 AOF 文件的内容写入到临时文件。 + + 在执行 AOF 重写时,Redis 不会记录每个命令的详细内容,而是通过以下方式来确保重写后的 AOF 文件有效: + + - **仅记录当前数据集的重建命令**:Redis 会根据数据库当前的状态生成一系列 `SET`、`HSET`、`LPUSH` 等命令,这些命令能够重建当前数据库的状态。这些命令是可以直接执行的、能够恢复数据的命令。 + - **压缩冗余命令**:在重写过程中,Redis 会去除一些无意义或重复的命令。例如,如果有大量重复的 `SET key value` 操作,Redis 只会保留最新的 `SET` 命令,而忽略之前的操作。 + +3. **复制 AOF文件**:对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件(即 `appendonly.aof`)的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的,这个过程称为 **追加操作**。 + +4. **重写完成后的替换**:当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。 + +5. **删除过时的 AOF 文件**:搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。 - + #### 优势 @@ -287,12 +317,12 @@ AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制 #### 劣势 -- 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。恢复速度慢于rdb。 -- 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。 +- 对于相同数量的数据集而言,AOF 文件通常要大于 RDB 文件。恢复速度慢于 RDB。 +- 根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和 RDB 一样高效。 #### 总结 - + - AOF 文件是一个只进行追加的日志文件 - Redis 可以在 AOF 文件体积变得过大时,自动在后台对 AOF 进行重写 @@ -355,7 +385,7 @@ Redis 4.0 中提出了一个**混合使用 AOF 日志和内存快照的方法** 同样我们执行 3 次 `set k1 v1`,然后手动瘦身 `bgrewriteaof` 后,查看 appendonly.aof 文件: - + 这样做的好处是可以结合 rdb 和 aof 的优点,快速加载同时避免丢失过多的数据,缺点是 aof 里面的 rdb 部分就是压缩格式不再是 aof 格式,可读性差。 @@ -365,7 +395,7 @@ Redis 4.0 中提出了一个**混合使用 AOF 日志和内存快照的方法** 如下图所示,两次快照中间时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。 - + 这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势,有点“鱼和熊掌可以兼得”的意思。 diff --git a/docs/data-management/Redis/Redis-Sentinel.md b/docs/data-management/Redis/Redis-Sentinel.md index d3eedde99a..fcab77ecf2 100644 --- a/docs/data-management/Redis/Redis-Sentinel.md +++ b/docs/data-management/Redis/Redis-Sentinel.md @@ -1,4 +1,12 @@ -# Redis 哨兵模式 +--- +title: Redis 哨兵模式 +date: 2021-10-08 +tags: + - Redis +categories: Redis +--- + + > 我们知道 Reids 提供了主从模式的机制,来保证可用性,可是如果主库发生故障了,那就直接会影响到从库的同步,怎么办呢? > @@ -14,7 +22,7 @@ ### 一、Redis Sentinel 哨兵 - + 上图 展示了一个典型的哨兵架构图,它由两部分组成,哨兵节点和数据节点: @@ -35,7 +43,7 @@ - **配置提供者(Configuration provider):** 客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。 - 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。 + 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。 其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移。而配置提供者和通知功能,则需要在与客户端的交互中才能体现。 @@ -154,7 +162,7 @@ master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3 我们先看下我们启动的 redis 进程,3 个数据节点,3 个哨兵节点 - + 使用 `kill` 命令来杀掉主节点,**同时** 在哨兵节点中执行 `info Sentinel` 命令来观察故障节点的过程: @@ -264,9 +272,18 @@ master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3 每个实例都会有一个 runid,这个 ID 就类似于这里的从库的编号。目前,Redis 在选主库时,有一个默认的规定:在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高,会被选为新主库。 -.jpg) - + +> ##### **哨兵模式是否会出现脑裂问题?** +> +> - **哨兵模式下存在脑裂风险。** +> - 当网络分区或通信异常时,可能导致旧主节点未完全下线,新的主节点被选出,导致两个主节点同时存在,形成**脑裂**问题。 +> +> #### **解决方法:** +> +> 1. **主节点心跳检测**:通过哨兵的客观下线判断,多数哨兵节点确认主节点下线,减少误判。 +> 2. **客户端重连机制**:客户端连接断开后,需要重新通过哨兵获取正确的主节点地址。 +> 3. **配置防止脑裂**:`quorum` 参数设置哨兵节点的投票数量,避免少数节点误判主节点下线。 ### 四、哨兵集群的原理 @@ -315,7 +332,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2 在下图中,哨兵 sentinel_26379 把自己的 IP(127.0.0.1)和端口(26379)发布到频道上,哨兵 26380 和 26381 订阅了该频道。那么此时,其他哨兵就可以从这个频道直接获取哨兵 sentinel_26379 的 IP 地址和端口号。通过这个方式,各个哨兵之间就可以建立网络连接,哨兵集群就形成了。它们相互间可以通过网络连接进行通信,比如说对主库有没有下线这件事儿进行判断和协商。 - + #### 4.2 哨兵和从库的连接 @@ -327,7 +344,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2 就像下图所示,哨兵 sentinel_26380 给主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵。接着,哨兵就可以根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续地对从库进行监控。senetinel_26379 和 senetinel_26381 可以通过相同的方法和从库建立连接。 - + #### 4.3 哨兵和客户端的连接 @@ -357,7 +374,7 @@ PSUBSCRIBE * ### 五、小结 -> Redis 哨兵是 Redis 的高可用实现方案:故障发现、故障自动转移、配置中心、客户端通知。 +Redis 哨兵是 Redis 的高可用实现方案:故障发现、故障自动转移、配置中心、客户端通知。 #### 5.1 哨兵机制其实就有三大功能: @@ -403,7 +420,7 @@ PSUBSCRIBE * -### 参考与来源 +### References - 《Redis 开发与运维》 - 《Redis 核心技术与实战》 diff --git a/docs/data-management/Redis/Redis-Transaction.md b/docs/data-management/Redis/Redis-Transaction.md index a178259949..97c95bcb8c 100644 --- a/docs/data-management/Redis/Redis-Transaction.md +++ b/docs/data-management/Redis/Redis-Transaction.md @@ -1,6 +1,14 @@ +--- +title: Redis 事务 +date: 2021-10-09 +tags: + - Redis +categories: Redis +--- + > 文章收录在 GitHub [JavaKeeper](https://github.com/Jstarfish/JavaKeeper) ,N线互联网开发必备技能兵器谱 - + > 假设现在有这样一个业务,用户获取的某些数据来自第三方接口信息,为避免频繁请求第三方接口,我们往往会加一层缓存,缓存肯定要有时效性,假设我们要存储的结构是 hash(没有String的'**SET anotherkey "will expire in a minute" EX 60**'这种原子操作),我们既要批量去放入缓存,又要保证每个 key 都加上过期时间(以防 key 永不过期),这时候事务操作是个比较好的选择 @@ -26,7 +34,7 @@ Redis 在形式上看起来也差不多,分为三个阶段 2. 命令入队(业务操作) 3. 执行事务(exec)或取消事务(discard) -``` +```sh > multi OK > incr star @@ -38,7 +46,7 @@ QUEUED (integer) 2 ``` -上面的指令演示了一个完整的事务过程,所有的指令在 exec 之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到 exec 指令,才开执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。 +上面的指令演示了一个完整的事务过程,所有的指令在 exec 之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到 exec 指令,才开始执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。 @@ -79,11 +87,11 @@ MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令 **正常执行**(可以批处理,挺爽,每条操作成功的话都会各取所需,互不影响) - + **放弃事务**(discard 操作表示放弃事务,之前的操作都不算数) - + @@ -104,11 +112,11 @@ Redis 针对如上两种错误采用了不同的处理策略,对于发生在 ` **全体连坐**(某一条操作记录报错的话,exec 后所有操作都不会成功) - + **冤头债主**(示例中 k1 被设置为 String 类型,decr k1 可以放入操作队列中,因为只有在执行的时候才可以判断出语句错误,其他正确的会被正常执行) - + @@ -133,7 +141,7 @@ Redis 针对如上两种错误采用了不同的处理策略,对于发生在 ` `WATCH` 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务将被打断,不再执行, 直接返回失败。 -WATCH命令可以被调用多次。 对键的监视从 WATCH 执行之后开始生效, 直到调用 EXEC 为止。 +WATCH 命令可以被调用多次。 对键的监视从 WATCH 执行之后开始生效, 直到调用 EXEC 为止。 用户还可以在单个 WATCH 命令中监视任意多个键, 就像这样: @@ -146,23 +154,25 @@ OK 我们看个简单的例子,用 watch 监控我的账号余额(一周100零花钱的我),正常消费 - + 但这个卡,还绑定了我媳妇的支付宝,如果在我消费的时候,她也消费了,会怎么样呢? 犯困的我去楼下 711 买了包烟,买了瓶水,这时候我媳妇在超市直接刷了 100,此时余额不足的我还在挑口香糖来着,,, - + 这时候我去结账,发现刷卡失败(事务中断),尴尬的一批 - + 你可能没看明白 watch 有啥用,我们再来看下,如果还是同样的场景,我们没有 `watch balance` ,事务不会失败,储蓄卡成负数,是不不太符合业务呢 - +> 当然,这里也会出现只要你媳妇刷了你的卡,就没办法刷成功的问题,这时候可以先查下余额,重新开启事务继续刷 + + @@ -180,7 +190,9 @@ OK > > **乐观锁** > -> 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。乐观锁策略:提交版本必须大于记录当前版本才能执行更新 +> 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。 +> +> 乐观锁策略:提交版本必须大于记录当前版本才能执行更新 @@ -188,7 +200,7 @@ OK 在代表数据库的 `server.h/redisDb` 结构类型中, 都保存了一个 `watched_keys` 字典, 字典的键是这个数据库被监视的键, 而字典的值是一个链表, 链表中保存了所有监视这个键的客户端,如下图。 - + ```c typedef struct redisDb { @@ -210,7 +222,7 @@ list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */ 举个例子, 如果当前客户端为 `client99` , 那么当客户端执行 `WATCH key2 key3` 时, 前面展示的 `watched_keys` 将被修改成这个样子: - + 通过 `watched_keys` 字典, 如果程序想检查某个键是否被监视, 那么它只要检查字典中是否存在这个键即可; 如果程序要获取监视某个键的所有客户端, 那么只要取出键的值(一个链表), 然后对链表进行遍历即可。 @@ -218,7 +230,7 @@ list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */ 在任何对数据库键空间(key space)进行修改的命令成功执行之后 (比如 FLUSHDB、SET 、DEL、LPUSH、 SADD,诸如此类), `multi.c/touchWatchedKey` 函数都会被调用 —— 它会去 `watched_keys` 字典, 看是否有客户端在监视已经被命令修改的键, 如果有的话, 程序将所有监视这个/这些被修改键的客户端的 `REDIS_DIRTY_CAS` 选项打开: - + ```c void multiCommand(client *c) { diff --git a/docs/data-management/Redis/Redis-clients.md b/docs/data-management/Redis/Redis-clients.md deleted file mode 100644 index e4d8e4051a..0000000000 --- a/docs/data-management/Redis/Redis-clients.md +++ /dev/null @@ -1,3 +0,0 @@ - https://redis.io/clients#java - - \ No newline at end of file diff --git a/docs/data-management/Redis/reproduce/.DS_Store b/docs/data-management/Redis/reproduce/.DS_Store new file mode 100644 index 0000000000..d342460f02 Binary files /dev/null and b/docs/data-management/Redis/reproduce/.DS_Store differ diff --git a/docs/data-management/Redis/Cache-Design.md b/docs/data-management/Redis/reproduce/Cache-Design.md similarity index 100% rename from docs/data-management/Redis/Cache-Design.md rename to docs/data-management/Redis/reproduce/Cache-Design.md diff --git "a/docs/data-management/Redis/Key \345\257\273\345\235\200\347\256\227\346\263\225.md" "b/docs/data-management/Redis/reproduce/Key \345\257\273\345\235\200\347\256\227\346\263\225.md" similarity index 100% rename from "docs/data-management/Redis/Key \345\257\273\345\235\200\347\256\227\346\263\225.md" rename to "docs/data-management/Redis/reproduce/Key \345\257\273\345\235\200\347\256\227\346\263\225.md" diff --git "a/docs/data-management/Redis/reproduce/Redis\344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206-\345\270\270\350\247\201\345\273\266\350\277\237\351\227\256\351\242\230\345\256\232\344\275\215\344\270\216\345\210\206\346\236\220.md" "b/docs/data-management/Redis/reproduce/Redis\344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206-\345\270\270\350\247\201\345\273\266\350\277\237\351\227\256\351\242\230\345\256\232\344\275\215\344\270\216\345\210\206\346\236\220.md" new file mode 100755 index 0000000000..73778eece2 --- /dev/null +++ "b/docs/data-management/Redis/reproduce/Redis\344\270\272\344\273\200\344\271\210\345\217\230\346\205\242\344\272\206-\345\270\270\350\247\201\345\273\266\350\277\237\351\227\256\351\242\230\345\256\232\344\275\215\344\270\216\345\210\206\346\236\220.md" @@ -0,0 +1,248 @@ +> Redis作为内存数据库,拥有非常高的性能,单个实例的QPS能够达到10W左右。但我们在使用Redis时,经常时不时会出现访问延迟很大的情况,如果你不知道Redis的内部实现原理,在排查问题时就会一头雾水。 +> +> 很多时候,Redis出现访问延迟变大,都与我们的使用不当或运维不合理导致的。 +> +> 这篇文章我们就来分析一下Redis在使用过程中,经常会遇到的延迟问题以及如何定位和分析。 + + + +## 使用复杂度高的命令 + +如果在使用Redis时,发现访问延迟突然增大,如何进行排查? + +首先,第一步,建议你去查看一下Redis的慢日志。Redis提供了慢日志命令的统计功能,我们通过以下设置,就可以查看有哪些命令在执行时延迟比较大。 + +首先设置Redis的慢日志阈值,只有超过阈值的命令才会被记录,这里的单位是微秒,例如设置慢日志的阈值为5毫秒,同时设置只保留最近1000条慢日志记录: + +``` +# 命令执行超过5毫秒记录慢日志 +CONFIG SET slowlog-log-slower-than 5000 +# 只保留最近1000条慢日志 +CONFIG SET slowlog-max-len 1000 +``` + +设置完成之后,所有执行的命令如果延迟大于5毫秒,都会被Redis记录下来,我们执行`SLOWLOG get 5`查询最近5条慢日志: + +``` +127.0.0.1:6379> SLOWLOG get 5 +1) 1) (integer) 32693 # 慢日志ID + 2) (integer) 1593763337 # 执行时间 + 3) (integer) 5299 # 执行耗时(微妙) + 4) 1) "LRANGE" # 具体执行的命令和参数 + 2) "user_list_2000" + 3) "0" + 4) "-1" +2) 1) (integer) 32692 + 2) (integer) 1593763337 + 3) (integer) 5044 + 4) 1) "GET" + 2) "book_price_1000" +... +``` + +通过查看慢日志记录,我们就可以知道在什么时间执行哪些命令比较耗时,如果你的业务**经常使用`O(N)`以上复杂度的命令**,例如`sort`、`sunion`、`zunionstore`,或者在执行`O(N)`命令时操作的数据量比较大,这些情况下Redis处理数据时就会很耗时。 + +如果你的服务请求量并不大,但Redis实例的CPU使用率很高,很有可能是使用了复杂度高的命令导致的。 + +解决方案就是,不使用这些复杂度较高的命令,并且一次不要获取太多的数据,每次尽量操作少量的数据,让Redis可以及时处理返回。 + +## 存储bigkey + +> 在Redis中,一个字符串最大512MB,一个二级数据结构(例如hash、list、set、zset)可以存储大约40亿个(2^32-1)个元素,但实际上中如果下面两种情况,我就会认为它是bigkey。 +> +> - **字符串类型**:它的big体现在单个value值很大,一般认为超过10KB就是bigkey。 +> - **非字符串类型**:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。 + +如果查询慢日志发现,并不是复杂度较高的命令导致的,例如都是`SET`、`DELETE`操作出现在慢日志记录中,那么你就要怀疑是否存在Redis写入了bigkey的情况。 + +Redis在写入数据时,需要为新的数据分配内存,当从Redis中删除数据时,它会释放对应的内存空间。 + +如果一个key写入的数据非常大,Redis在**分配内存时也会比较耗时**。同样的,当删除这个key的数据时,**释放内存也会耗时比较久**。 + +你需要检查你的业务代码,是否存在写入bigkey的情况,需要评估写入数据量的大小,业务层应该避免一个key存入过大的数据量。 + +那么有没有什么办法可以扫描现在Redis中是否存在bigkey的数据吗? + +Redis也提供了扫描bigkey的方法: + +``` +redis-cli -h $host -p $port --bigkeys -i 0.01 +``` + +使用上面的命令就可以扫描出整个实例key大小的分布情况,它是以类型维度来展示的。 + +需要注意的是当我们在线上实例进行bigkey扫描时,Redis的QPS会突增,为了降低扫描过程中对Redis的影响,我们需要控制扫描的频率,使用`-i`参数控制即可,它表示扫描过程中每次扫描的时间间隔,单位是秒。 + +使用这个命令的原理,其实就是Redis在内部执行`scan`命令,遍历所有key,然后针对不同类型的key执行`strlen`、`llen`、`hlen`、`scard`、`zcard`来获取字符串的长度以及容器类型(list/dict/set/zset)的元素个数。 + +而对于容器类型的key,只能扫描出元素最多的key,但元素最多的key不一定占用内存最多,这一点需要我们注意下。不过使用这个命令一般我们是可以对整个实例中key的分布情况有比较清晰的了解。 + +针对bigkey的问题,Redis官方在4.0版本推出了`lazy-free`的机制,用于异步释放bigkey的内存,降低对Redis性能的影响。即使这样,我们也不建议使用bigkey,bigkey在集群的迁移过程中,也会影响到迁移的性能,这个后面在介绍集群相关的文章时,会再详细介绍到。 + +## 集中过期 + +有时你会发现,平时在使用Redis时没有延时比较大的情况,但在某个时间点突然出现一波延时,而且**报慢的时间点很有规律,例如某个整点,或者间隔多久就会发生一次**。 + +如果出现这种情况,就需要考虑是否存在大量key集中过期的情况。 + +如果有大量的key在某个固定时间点集中过期,在这个时间点访问Redis时,就有可能导致延迟增加。 + +Redis的过期策略采用主动过期+懒惰过期两种策略: + +- 主动过期:Redis内部维护一个定时任务,默认每隔100毫秒会从过期字典中随机取出20个key,删除过期的key,如果过期key的比例超过了25%,则继续获取20个key,删除过期的key,循环往复,直到过期key的比例下降到25%或者这次任务的执行耗时超过了25毫秒,才会退出循环 +- 懒惰过期:只有当访问某个key时,才判断这个key是否已过期,如果已经过期,则从实例中删除 + +注意,**Redis的主动过期的定时任务,也是在Redis主线程中执行的**,也就是说如果在执行主动过期的过程中,出现了需要大量删除过期key的情况,那么在业务访问时,必须等这个过期任务执行结束,才可以处理业务请求。此时就会出现,业务访问延时增大的问题,最大延迟为25毫秒。 + +而且这个访问延迟的情况,**不会记录在慢日志里**。慢日志中**只记录真正执行某个命令的耗时**,Redis主动过期策略执行在操作命令之前,如果操作命令耗时达不到慢日志阈值,它是不会计算在慢日志统计中的,但我们的业务却感到了延迟增大。 + +此时你需要检查你的业务,是否真的存在集中过期的代码,一般集中过期使用的命令是`expireat`或`pexpireat`命令,在代码中搜索这个关键字就可以了。 + +如果你的业务确实需要集中过期掉某些key,又不想导致Redis发生抖动,有什么优化方案? + +解决方案是,**在集中过期时增加一个随机时间,把这些需要过期的key的时间打散即可。** + +伪代码可以这么写: + +``` +# 在过期时间点之后的5分钟内随机过期掉 +redis.expireat(key, expire_time + random(300)) +``` + +这样Redis在处理过期时,不会因为集中删除key导致压力过大,阻塞主线程。 + +另外,除了业务使用需要注意此问题之外,还可以通过运维手段来及时发现这种情况。 + +做法是我们需要把Redis的各项运行数据监控起来,执行`info`可以拿到所有的运行数据,在这里我们需要重点关注`expired_keys`这一项,它代表整个实例到目前为止,累计删除过期key的数量。 + +我们需要对这个指标监控,当在**很短时间内这个指标出现突增**时,需要及时报警出来,然后与业务报慢的时间点对比分析,确认时间是否一致,如果一致,则可以认为确实是因为这个原因导致的延迟增大。 + +## 实例内存达到上限 + +有时我们把Redis当做纯缓存使用,就会给实例设置一个内存上限`maxmemory`,然后开启LRU淘汰策略。 + +当实例的内存达到了`maxmemory`后,你会发现之后的每次写入新的数据,有可能变慢了。 + +导致变慢的原因是,当Redis内存达到`maxmemory`后,每次写入新的数据之前,必须先踢出一部分数据,让内存维持在`maxmemory`之下。 + +这个踢出旧数据的逻辑也是需要消耗时间的,而具体耗时的长短,要取决于配置的淘汰策略: + +- allkeys-lru:不管key是否设置了过期,淘汰最近最少访问的key +- volatile-lru:只淘汰最近最少访问并设置过期的key +- allkeys-random:不管key是否设置了过期,随机淘汰 +- volatile-random:只随机淘汰有设置过期的key +- allkeys-ttl:不管key是否设置了过期,淘汰即将过期的key +- noeviction:不淘汰任何key,满容后再写入直接报错 +- allkeys-lfu:不管key是否设置了过期,淘汰访问频率最低的key(4.0+支持) +- volatile-lfu:只淘汰访问频率最低的过期key(4.0+支持) + +具体使用哪种策略,需要根据业务场景来决定。 + +我们最常使用的一般是`allkeys-lru`或`volatile-lru`策略,它们的处理逻辑是,每次从实例中随机取出一批key(可配置),然后淘汰一个最少访问的key,之后把剩下的key暂存到一个池子中,继续随机取出一批key,并与之前池子中的key比较,再淘汰一个最少访问的key。以此循环,直到内存降到`maxmemory`之下。 + +如果使用的是`allkeys-random`或`volatile-random`策略,那么就会快很多,因为是随机淘汰,那么就少了比较key访问频率时间的消耗了,随机拿出一批key后直接淘汰即可,因此这个策略要比上面的LRU策略执行快一些。 + +但以上这些逻辑都是在访问Redis时,**真正命令执行之前执行的**,也就是它会影响我们访问Redis时执行的命令。 + +另外,如果此时Redis实例中有存储bigkey,那么**在淘汰bigkey释放内存时,这个耗时会更加久,延迟更大**,这需要我们格外注意。 + +如果你的业务访问量非常大,并且必须设置`maxmemory`限制实例的内存上限,同时面临淘汰key导致延迟增大的的情况,要想缓解这种情况,除了上面说的避免存储bigkey、使用随机淘汰策略之外,也可以考虑**拆分实例**的方法来缓解,拆分实例可以把一个实例淘汰key的压力**分摊到多个实例**上,可以在一定程度降低延迟。 + +## fork耗时严重 + +如果你的Redis开启了自动生成RDB和AOF重写功能,那么有可能在后台生成RDB和AOF重写时导致Redis的访问延迟增大,而等这些任务执行完毕后,延迟情况消失。 + +遇到这种情况,一般就是执行生成RDB和AOF重写任务导致的。 + +生成RDB和AOF都需要父进程`fork`出一个子进程进行数据的持久化,**在`fork`执行过程中,父进程需要拷贝内存页表给子进程,如果整个实例内存占用很大,那么需要拷贝的内存页表会比较耗时,此过程会消耗大量的CPU资源,在完成`fork`之前,整个实例会被阻塞住,无法处理任何请求,如果此时CPU资源紧张,那么`fork`的时间会更长,甚至达到秒级。这会严重影响Redis的性能**。 + +具体原理也可以参考我之前写的文章:[Redis持久化是如何做的?RDB和AOF对比分析](http://kaito-kidd.com/2020/06/29/redis-persistence-rdb-aof/)。 + +我们可以执行`info`命令,查看最后一次`fork`执行的耗时`latest_fork_usec`,单位微妙。这个时间就是整个实例阻塞无法处理请求的时间。 + +除了因为备份的原因生成RDB之外,**在主从节点第一次建立数据同步时**,主节点也会生成RDB文件给从节点进行一次全量同步,这时也会对Redis产生性能影响。 + +要想避免这种情况,我们需要规划好数据备份的周期,建议在**从节点上执行备份,而且最好放在低峰期执行**。如果对于丢失数据不敏感的业务,那么不建议开启AOF和AOF重写功能。 + +另外,`fork`的耗时也与系统有关,如果把Redis部署在虚拟机上,那么这个时间也会增大。所以使用Redis时建议部署在物理机上,降低`fork`的影响。 + +## 绑定CPU + +很多时候,我们在部署服务时,为了提高性能,降低程序在使用多个CPU时上下文切换的性能损耗,一般会采用进程绑定CPU的操作。 + +但在使用Redis时,我们不建议这么干,原因如下。 + +**绑定CPU的Redis,在进行数据持久化时,`fork`出的子进程,子进程会继承父进程的CPU使用偏好,而此时子进程会消耗大量的CPU资源进行数据持久化,子进程会与主进程发生CPU争抢,这也会导致主进程的CPU资源不足访问延迟增大。** + +所以在部署Redis进程时,如果需要开启RDB和AOF重写机制,一定不能进行CPU绑定操作! + +## AOF配置不合理 + +上面提到了,当执行AOF文件重写时会因为`fork`执行耗时导致Redis延迟增大,除了这个之外,如果开启AOF机制,设置的策略不合理,也会导致性能问题。 + +开启AOF后,Redis会把写入的命令实时写入到文件中,但写入文件的过程是先写入内存,等内存中的数据超过一定阈值或达到一定时间后,内存中的内容才会被真正写入到磁盘中。 + +AOF为了保证文件写入磁盘的安全性,提供了3种刷盘机制: + +- `appendfsync always`:每次写入都刷盘,对性能影响最大,占用磁盘IO比较高,数据安全性最高 +- `appendfsync everysec`:1秒刷一次盘,对性能影响相对较小,节点宕机时最多丢失1秒的数据 +- `appendfsync no`:按照操作系统的机制刷盘,对性能影响最小,数据安全性低,节点宕机丢失数据取决于操作系统刷盘机制 + +当使用第一种机制`appendfsync always`时,Redis每处理一次写命令,都会把这个命令写入磁盘,而且**这个操作是在主线程中执行的**。 + +内存中的的数据写入磁盘,这个会加重磁盘的IO负担,操作磁盘成本要比操作内存的代价大得多。如果写入量很大,那么每次更新都会写入磁盘,此时机器的磁盘IO就会非常高,拖慢Redis的性能,因此我们不建议使用这种机制。 + +与第一种机制对比,`appendfsync everysec`会每隔1秒刷盘,而`appendfsync no`取决于操作系统的刷盘时间,安全性不高。因此我们推荐使用`appendfsync everysec`这种方式,在最坏的情况下,只会丢失1秒的数据,但它能保持较好的访问性能。 + +当然,对于有些业务场景,对丢失数据并不敏感,也可以不开启AOF。 + +## 使用Swap + +如果你发现Redis突然变得非常慢,**每次访问的耗时都达到了几百毫秒甚至秒级**,那此时就检查Redis是否使用到了Swap,这种情况下Redis基本上已经无法提供高性能的服务。 + +我们知道,操作系统提供了Swap机制,目的是为了当内存不足时,可以把一部分内存中的数据换到磁盘上,以达到对内存使用的缓冲。 + +但当内存中的数据被换到磁盘上后,访问这些数据就需要从磁盘中读取,这个速度要比内存慢太多! + +**尤其是针对Redis这种高性能的内存数据库来说,如果Redis中的内存被换到磁盘上,对于Redis这种性能极其敏感的数据库,这个操作时间是无法接受的。** + +我们需要检查机器的内存使用情况,确认是否确实是因为内存不足导致使用到了Swap。 + +如果确实使用到了Swap,要及时整理内存空间,释放出足够的内存供Redis使用,然后释放Redis的Swap,让Redis重新使用内存。 + +释放Redis的Swap过程通常要重启实例,为了避免重启实例对业务的影响,一般先进行主从切换,然后释放旧主节点的Swap,重新启动服务,待数据同步完成后,再切换回主节点即可。 + +可见,当Redis使用到Swap后,此时的Redis的高性能基本被废掉,所以我们需要提前预防这种情况。 + +**我们需要对Redis机器的内存和Swap使用情况进行监控,在内存不足和使用到Swap时及时报警出来,及时进行相应的处理。** + +## 网卡负载过高 + +如果以上产生性能问题的场景,你都规避掉了,而且Redis也稳定运行了很长时间,但在某个时间点之后开始,访问Redis开始变慢了,而且一直持续到现在,这种情况是什么原因导致的? + +之前我们就遇到这种问题,**特点就是从某个时间点之后就开始变慢,并且一直持续**。这时你需要检查一下机器的网卡流量,是否存在网卡流量被跑满的情况。 + +**网卡负载过高,在网络层和TCP层就会出现数据发送延迟、数据丢包等情况。Redis的高性能除了内存之外,就在于网络IO,请求量突增会导致网卡负载变高。** + +如果出现这种情况,你需要排查这个机器上的哪个Redis实例的流量过大占满了网络带宽,然后确认流量突增是否属于业务正常情况,如果属于那就需要及时扩容或迁移实例,避免这个机器的其他实例受到影响。 + +运维层面,我们需要对机器的各项指标增加监控,包括网络流量,在达到阈值时提前报警,及时与业务确认并扩容。 + +## 总结 + +以上我们总结了Redis中常见的可能导致延迟增大甚至阻塞的场景,这其中既涉及到了业务的使用问题,也涉及到Redis的运维问题。 + +可见,要想保证Redis高性能的运行,其中涉及到CPU、内存、网络,甚至磁盘的方方面面,其中还包括操作系统的相关特性的使用。 + +作为开发人员,我们需要了解Redis的运行机制,例如各个命令的执行时间复杂度、数据过期策略、数据淘汰策略等,使用合理的命令,并结合业务场景进行优化。 + +作为DBA运维人员,需要了解数据持久化、操作系统`fork`原理、Swap机制等,并对Redis的容量进行合理规划,预留足够的机器资源,对机器做好完善的监控,才能保证Redis的稳定运行。 + + + +**来源** + +> [《Redis为什么变慢了?常见延迟问题定位与分析》](http://kaito-kidd.com/2020/07/03/redis-latency-analysis/) +> +> 作者:Kaito + diff --git a/docs/data-structure-algorithms/.DS_Store b/docs/data-structure-algorithms/.DS_Store new file mode 100644 index 0000000000..661b6ce50c Binary files /dev/null and b/docs/data-structure-algorithms/.DS_Store differ diff --git a/docs/data-structure-algorithms/Binary-Tree.md b/docs/data-structure-algorithms/BTree.md similarity index 100% rename from docs/data-structure-algorithms/Binary-Tree.md rename to docs/data-structure-algorithms/BTree.md diff --git a/docs/data-structure-algorithms/Linked-List.md b/docs/data-structure-algorithms/Linked-List.md deleted file mode 100644 index 002084b2d8..0000000000 --- a/docs/data-structure-algorithms/Linked-List.md +++ /dev/null @@ -1,418 +0,0 @@ -# 链表 - -与数组相似,链表也是一种`线性`数据结构。 - -链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个域是指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为**结点**(node)。 - - - -## 单链表 - - - -一种最简单的结点结构如上图所示,它是构成单链表的基本结点结构。在结点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的结点。 - -单链表中的每个结点不仅包含值,还包含链接到下一个结点的`引用字段`。通过这种方式,单链表将所有结点按顺序组织起来。 - - - -链表的第一个结点和最后一个结点,分别称为链表的**首结点**和**尾结点**。尾结点的特征是其 next 引用为空(null)。链表中每个结点的 next 引用都相当于一个指针,指向另一个结点,借助这些 next 引用,我们可以从链表的首结点移动到尾结点。如此定义的结点就称为**单链表**(single linked list)。 - -上图蓝色箭头显示单个链接列表中的结点是如何组合在一起的。 - -在单链表中通常使用 head 引用来指向链表的首结点,由 head 引用可以完成对整个链表中所有节点的访问。有时也可以根据需要使用指向尾结点的 tail 引用来方便某些操作的实现。 - -在单链表结构中还需要注意的一点是,由于每个结点的数据域都是一个 Object 类的对象,因此,每个数据元素并非真正如图中那样,而是在结点中的数据域通过一个 Object 类的对象引用来指向数据元素的。 - -与数组类似,单链表中的结点也具有一个线性次序,即如果结点 P 的 next 引用指向结点 S,则 P 就是 S 的**直接前驱**,S 是 P 的**直接后续**。单链表的一个重要特性就是只能通过前驱结点找到后续结点,而无法从后续结点找到前驱结点。 - -接着我们来看下单链表的 CRUD: - -以下是单链表中结点的典型定义: - -```java -// Definition for singly-linked list. -public class SinglyListNode { - int val; - SinglyListNode next; - SinglyListNode(int x) { val = x; } -} -``` - -### 查找 - -与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 $O(N)$ 时间,其中 N 是链表的长度。 - -例如需要在单链表中查找是否包含某个数据元素 e,则方法是使用一个循环变量 p,起始时从单链表的头结点开始,每次循环判断 p 所指结点的数据域是否和 e 相同,如果相同则可以返回 true,否则继续循环直到链表中所有结点均被访问,此时 p 为 null。 - -使用 Java 语言实现整个过程的关键语句是: - -```java -p=head; -while (p!=null) -if (strategy.equal( e , p.getData() )) return true; -return false; -``` - - - -### 添加 - -单链表中数据元素的插入,是通过在链表中插入数据元素所属的结点来完成的。对于链表的不同位置,插入的过程会有细微的差别。 - - - -除了单链表的首结点由于没有直接前驱结点,所以可以直接在首结点之前插入一个新的结点之外,在单链表中的其他任何位置插入一个新结点时,都只能是在已知某个特定结点引用的基础上在其后面插入一个新结点。并且在已知单链表中某个结点引用的基础上,完成结点的插入操作需要的时间是 $O(1)$。 - -> 思考:如果是带头结点的单链表进行插入操作,是什么样子呢? - - - -### 删除 - -类似的,在单链表中数据元素的删除也是通过结点的删除来完成的。在链表的不同位置删除结点,其操作过程也会有一些差别。 - - - -在单链表中删除一个结点时,除首结点外都必须知道该结点的直接前驱结点的引用。并且在已知单链表中某个结点引用的基础上,完成其后续结点的删除操作需要的时间是 $O(1)$。 - -> 在使用单链表实现线性表的时候,为了使程序更加简洁,我们通常在单链表的最前面添加一个**哑元结点**,也称为头结点。在头结点中不存储任何实质的数据对象,其 next 域指向线性表中 0 号元素所在的结点,头结点的引入可以使线性表运算中的一些边界条件更容易处理。 -> -> 对于任何基于序号的插入、删除,以及任何基于数据元素所在结点的前面或后面的插入、删除,在带头结点的单链表中均可转化为在某个特定结点之后完成结点的插入、删除,而不用考虑插入、删除是在链表的首部、中间、还是尾部等不同情况。 - - - -## 双向链表 - -单链表的一个优点是结构简单,但是它也有一个缺点,即在单链表中只能通过一个结点的引用访问其后续结点,而无法直接访问其前驱结点,要在单链表中找到某个结点的前驱结点,必须从链表的首结点出发依次向后寻找,但是需要 $Ο(n)$ 时间。 - -所以我们在单链表结点结构中新增加一个域,该域用于指向结点的直接前驱结点。 - - - -双向链表是通过上述定义的结点使用 pre 以及 next 域依次串联在一起而形成的。一个双向链表的结构如下图所示。 - - - -接着我们来看下双向链表的 CRUD: - -以下是双链表中结点的典型定义: - -```java -// Definition for doubly-linked list. -class DoublyListNode { - int val; - DoublyListNode next, prev; - DoublyListNode(int x) {val = x;} -} -``` - -### 查找 - -在双向链表中进行查找与在单链表中类似,只不过在双向链表中查找操作可以从链表的首结点开始,也可以从尾结点开始,但是需要的时间和在单链表中一样。 - -### 添加 - -单链表的插入操作,除了首结点之外必须在某个已知结点后面进行,而在双向链表中插入操作在一个已知的结点之前或之后都可以进行,如下表示在结点 p(11) 之前 插入 s(9)。 - - - -使用 Java 语言实现整个过程的关键语句是 - -```java -s.setPre (p.getPre()); -p.getPre().setNext(s); -s.setNext(p); -p.setPre(s); -``` - -在结点 p 之后插入一个新结点的操作与上述操作对称,这里不再赘述。 - -插入操作除了上述情况,还可以在双向链表的首结点之前、双向链表的尾结点之后进行,此时插入操作与上述插入操作相比更为简单。 - -### 删除 - -单链表的删除操作,除了首结点之外必须在知道待删结点的前驱结点的基础上才能进行,而在双向链表中在已知某个结点引用的前提下,可以完成该结点自身的删除。如下表示删除 p(16) 的过程。 - - - -使用 Java 语言实现整个过程的关键语句是 - -```java -p.getPre().setNext(p.getNext()); -p.getNext().setPre(p.getPre()); -``` - - - -对线性表的操作,无非就是排序、加法、减法、反转,说的好像很简单,我们开始刷题。 - - - -## 刷题 - -### 反转链表(206) - ->反转一个单链表。 -> ->**示例:** -> ->``` ->输入: 1->2->3->4->5->NULL ->输出: 5->4->3->2->1->NULL ->``` - -**进阶:** 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? - -**题目解析** - -设置三个节点`pre`、`cur`、`next` - -1. 每次查看`cur`节点是否为`NULL`,如果是,则结束循环,获得结果 -2. 如果`cur`节点不是为`NULL`,则先设置临时变量`next`为`cur`的下一个节点 -3. 让`cur`的下一个节点变成指向`pre`,而后`pre`移动`cur`,`cur`移动到`next` -4. 重复(1)(2)(3) - -**动画描述** - - - -```java - public ListNode reverseList(ListNode head) { - if (head == null || head.next == null) { - return head; - } - - ListNode prev = null; - ListNode next = null; - while (head.next != null) { - next = head.next; //保存下一个节点 - head.next = prev; //重置next - prev = head; //保存当前节点 - head = next; - } - head.next = prev; - return head; - } -``` - - - -### 环形链表(141) - -> 给定一个链表,判断链表中是否有环。 -> -> 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。 -> -> ``` -> 输入:head = [3,2,0,-4], pos = 1 -> 输出:true -> 解释:链表中有一个环,其尾部连接到第二个节点。 -> ``` -> ->  - -**题目解析** - -这道题是快慢指针的**经典应用**。 - -设置两个指针,一个每次走一步的**慢指针**和一个每次走两步的**快指针**。 - -- 如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环 -- 如果含有环,快指针会超慢指针一圈,和慢指针相遇,说明链表含有环。 - - - -```java -public class linkedlistcycle_141 { - - public boolean hasCycle(ListNode head) { - - if (head == null || head.next == null) { - return false; - } - // 龟兔起跑 - ListNode fast = head; - ListNode slow = head; - - while (fast != null && fast.next != null) { - // 龟走一步 - slow = slow.next; - // 兔走两步 - fast = fast.next.next; - if (slow == fast) { - return true; - } - } - return false; - } -} -``` - - - - - -### 相交链表(160) - ->  -> -> 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 -> 输出:Reference of the node with value = 8 -> 输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 - -**题目解析** - -为满足题目时间复杂度和空间复杂度的要求,我们可以使用双指针法。 - -- 创建两个指针 pA 和 pB 分别指向链表的头结点 headA 和 headB。 -- 当 pA 到达链表的尾部时,将它重新定位到链表B的头结点 headB,同理,当 pB 到达链表的尾部时,将它重新定位到链表 A 的头结点 headA。 -- 当 pA 与 pB 相等时便是两个链表第一个相交的结点。 这里其实就是相当于把两个链表拼在一起了。pA 指针是按 B 链表拼在 A 链表后面组成的新链表遍历,而 pB 指针是按A链表拼在B链表后面组成的新链表遍历。举个简单的例子: A链表:{1,2,3,4} B链表:{6,3,4} pA按新拼接的链表{1,2,3,4,6,3,4}遍历 pB按新拼接的链表{6,3,4,1,2,3,4}遍历 - - - -```java -public ListNode getIntersectionNode(ListNode headA, ListNode headB) { - if (headA == null || headB == null) { - return null; - } - ListNode pA = headA, pB = headB; - while (pA != pB) { - pA = pA == null ? headB : pA.next; - pB = pB == null ? headA : pB.next; - } - return pA; -} -``` - - - -### 合并两个有序链表(21) - -> 将两个升序链表合并为一个新的 **升序** 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 -> -> **示例:** -> -> ``` -> 输入:1->2->4, 1->3->4 -> 输出:1->1->2->3->4->4 -> ``` - -如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。 - -```java -public ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) { - return l2; - } else if (l2 == null) { - return l1; - } else if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1, l2.next); - return l2; - } -} -``` - - - -### 回文链表(234) - -> 请判断一个链表是否为回文链表。 -> -> **示例 1:** -> -> ``` -> 输入: 1->2 -> 输出: false -> ``` -> -> **示例 2:** -> -> ``` -> 输入: 1->2->2->1 -> 输出: true -> ``` - -**解法1:** - -1. 复制链表值到数组列表中。 -2. 使用双指针法判断是否为回文。 - - - -**解法2:** - -我们先找到链表的中间结点,然后将中间结点后面的链表进行反转,反转之后再和前半部分链表进行比较,如果相同则表示该链表属于回文链表,返回true;否则,否则返回false - - - -### 两数相加(2) - -> 给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 -> -> 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 -> -> 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 -> -> 示例: -> -> 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) -> 输出:7 -> 0 -> 8 -> 原因:342 + 465 = 807 - - - -### 删除链表的倒数第N个节点(19) - -> 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 -> -> 示例: -> -> 给定一个链表: 1->2->3->4->5, 和 n = 2. -> -> 当删除了倒数第二个节点后,链表变为 1->2->3->5. -> - -**方法一:两次遍历算法** - -我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)(L−n+1) 个结点,其中 LL 是列表的长度。只要我们找到列表的长度 LL,这个问题就很容易解决。 - -首先我们将添加一个哑结点作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n)(L−n) 个结点那里。我们把第 (L - n)(L−n) 个结点的 next 指针重新链接至第 (L - n + 2)(L−n+2) 个结点,完成这个算法。 - - - -**方法二:一次遍历算法** - -上述算法可以优化为只使用一次遍历。我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被 nn 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 nn 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。 - - - - - -### 排序链表() - -> 在 *O*(*n* log *n*) 时间复杂度和常数级空间复杂度下,对链表进行排序。 -> -> **示例 1:** -> -> ``` -> 输入: 4->2->1->3 -> 输出: 1->2->3->4 -> ``` - -**解答一:归并排序(递归法)** - -**解答二:归并排序(从底至顶直接合并)** - - - - - - - -## 参考与感谢 - -- https://aleej.com/2019/09/16/数据结构与算法之美学习笔记 \ No newline at end of file diff --git a/docs/data-structure-algorithms/README.md b/docs/data-structure-algorithms/README.md index 4c1ddae0ab..c306a84679 100644 --- a/docs/data-structure-algorithms/README.md +++ b/docs/data-structure-algorithms/README.md @@ -1,119 +1,546 @@ -# 数据结构开篇 +--- +title: 数据结构与算法:Java开发者的必备修炼 +date: 2025-05-09 +categories: Algorithm +--- + +# 🚀 数据结构与算法开篇 + + + +> 💡 **关于怎么刷题的帖子**: +> +> - 📖 《论如何4个月高效刷满 500 题并形成长期记忆》 https://leetcode-cn.com/circle/discuss/jq9Zke/ + +--- + +## 🎯 专栏介绍 + +本专栏致力于为Java开发者提供全面的数据结构与算法学习资源,涵盖从基础概念到高级应用的完整知识体系。通过系统性的学习和实践,帮助开发者提升编程能力,掌握解决复杂问题的核心技能。 + +### 📚 专栏特色 + +- 🎯 **系统性学习**:从基础到进阶,循序渐进 +- 💻 **Java实现**:所有代码示例均使用Java语言 +- 🔥 **实战导向**:结合LeetCode经典题目 +- 📊 **可视化理解**:丰富的图表和动画演示 +- 🎨 **美观排版**:精心设计的文档格式 + +--- + +## 📊 第一部分:数据结构分类 + +数据结构是计算机存储、组织数据的方式,是算法的基础。掌握各种数据结构的特点和应用场景,是成为优秀程序员的必经之路。 + +### 📏 线性数据结构 + +线性数据结构是指数据元素之间存在一对一关系的数据结构,元素按线性顺序排列。 + +#### 📋 1. 数组 (Array) +``` +数组结构示意图: +Index: [0] [1] [2] [3] [4] +Value: [12][45][78][23][56] + ↑ ↑ ↑ ↑ ↑ + 连续的内存地址空间 +``` +- **特点**:连续存储,支持随机访问 +- **时间复杂度**:访问O(1),插入/删除O(n) +- **Java实现**:`int[]`、`ArrayList` + +#### 🔗 2. 链表 (Linked List) +``` +单链表结构示意图: +[Data|Next] -> [Data|Next] -> [Data|Next] -> null + | | | + 节点1 节点2 节点3 + +双链表结构示意图: +null <- [Prev|Data|Next] <-> [Prev|Data|Next] <-> [Prev|Data|Next] -> null +``` +- **特点**:动态存储,插入删除高效 +- **时间复杂度**:访问O(n),插入/删除O(1) +- **Java实现**:`LinkedList` + +#### 📚 3. 栈 (Stack) +``` +栈结构示意图: + | | <- top (栈顶) + | C | + | B | + | A | + |_____| <- bottom (栈底) + +操作:LIFO (后进先出) +``` +- **特点**:后进先出(LIFO) +- **时间复杂度**:push/pop/peek都是O(1) +- **Java实现**:`Stack`、`Deque` + +#### 🚶 4. 队列 (Queue) +``` +队列结构示意图: +出队 <- [A][B][C][D] <- 入队 + front rear + +操作:FIFO (先进先出) +``` +- **特点**:先进先出(FIFO) +- **时间复杂度**:enqueue/dequeue都是O(1) +- **Java实现**:`Queue`、`LinkedList` + +### 🌳 非线性数据结构 + +非线性数据结构是指数据元素之间存在一对多或多对多关系的数据结构,形成复杂的层次或网状结构。 + +#### 🌲 5. 二叉树 (Binary Tree) +``` +二叉树结构示意图: + A (根节点) + / \ + B C + / \ / \ + D E F G (叶子节点) + +层次: +第0层: A +第1层: B C +第2层: D E F G +``` +- **特点**:每个节点最多有两个子节点 +- **时间复杂度**:搜索/插入/删除O(logn)~O(n) +- **Java实现**:`TreeMap`、`TreeSet` + +#### 🏔️ 6. 堆 (Heap) +``` +最大堆示意图: + 90 + / \ + 80 70 + / \ / \ + 60 50 40 30 + +特点:父节点 >= 子节点 (最大堆) +数组表示:[90,80,70,60,50,40,30] +``` +- **特点**:完全二叉树,堆序性质 +- **时间复杂度**:插入/删除O(logn),查找最值O(1) +- **Java实现**:`PriorityQueue` + +#### 🕸️ 7. 图 (Graph) +``` +无向图示意图: + A --- B + /| |\ + / | | \ + D | | C + | | / + E --- F + +邻接矩阵表示: + A B C D E F +A [ 0 1 0 1 1 0 ] +B [ 1 0 1 0 0 1 ] +C [ 0 1 0 0 0 1 ] +D [ 1 0 0 0 1 0 ] +E [ 1 0 0 1 0 1 ] +F [ 0 1 1 0 1 0 ] +``` +- **特点**:顶点和边的集合,表示复杂关系 +- **时间复杂度**:遍历O(V+E),最短路径O(V²) +- **Java实现**:`Map>` + +#### 🗂️ 8. 哈希表 (Hash Table) +``` +哈希表结构示意图: +Hash函数:key → hash(key) % size → index + +Key: "apple" → hash("apple") = 5 → index: 5 % 8 = 5 +Key: "banana" → hash("banana")= 3 → index: 3 % 8 = 3 + +Table: +[0] → null +[1] → null +[2] → null +[3] → "banana" → value +[4] → null +[5] → "apple" → value +[6] → null +[7] → null + +冲突处理(链地址法): +[3] → ["banana",value1] → ["grape",value2] → null +``` +- **特点**:基于键值对,快速查找 +- **时间复杂度**:平均O(1),最坏O(n) +- **Java实现**:`HashMap`、`HashSet` + +--- + +## ⚡ 第二部分:常见算法 + +算法是解决问题的步骤和方法,是程序设计的核心。掌握各种算法的思想和实现,能够帮助我们高效地解决各种复杂问题。 + +### 🔢 1. 排序算法 + +排序算法是计算机科学中最基础也是最重要的算法之一,掌握各种排序算法的特点和应用场景至关重要。 + +| 算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 空间复杂度 | 稳定性 | +|------|-------------------|-------------------|------------|--------| +| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | +| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | +| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | +| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 | +| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 | +| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 | +| 计数排序 | O(n+k) | O(n+k) | O(k) | 稳定 | +| 基数排序 | O(nk) | O(nk) | O(n+k) | 稳定 | + +### 🔍 2. 搜索算法 + +搜索算法用于在数据集合中查找特定元素,不同的搜索算法适用于不同的场景。 + +- **线性搜索**:O(n) - 适用于无序数组 +- **二分搜索**:O(logn) - 适用于有序数组 +- **哈希查找**:O(1) - 适用于HashMap/HashSet +- **树搜索**:O(logn) - 适用于二叉搜索树 +- **图搜索**:DFS/BFS - O(V+E) + +### 🕸️ 3. 图算法 + +图算法是处理图结构数据的重要工具,广泛应用于网络分析、路径规划等领域。 + +#### 🔍 3.1 图遍历 +- **深度优先搜索(DFS)**:O(V+E) +- **广度优先搜索(BFS)**:O(V+E) + +#### 🛣️ 3.2 最短路径 +- **Dijkstra算法**:O((V+E)logV) - 单源最短路径 +- **Floyd-Warshall算法**:O(V³) - 全源最短路径 +- **Bellman-Ford算法**:O(VE) - 含负权边 + +#### 🌲 3.3 最小生成树 +- **Prim算法**:O(ElogV) +- **Kruskal算法**:O(ElogE) - +#### 📋 3.4 拓扑排序 +- **Kahn算法**:O(V+E) +- **DFS算法**:O(V+E) -## 概念 +### 🎯 4. 动态规划 -**数据**(data)是描述客观事物的数值、字符以及能输入机器且能被处理的各种符号集合。 数据的含义非常广泛,除了通常的数值数据、字符、字符串是数据以外,声音、图像等一切可以输入计算机并能被处理的都是数据。例如除了表示人的姓名、身高、体重等的字符、数字是数据,人的照片、指纹、三维模型、语音指令等也都是数据。 +动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 +#### 🏗️ 4.1 基础DP +- **斐波那契数列**:O(n) +- **爬楼梯**:O(n) +- **最大子序和**:O(n) +#### 📝 4.2 序列DP +- **最长递增子序列(LIS)**:O(nlogn) +- **最长公共子序列(LCS)**:O(mn) +- **编辑距离**:O(mn) -**数据元素**(data element)是数据的基本单位,是数据集合的个体,在计算机程序中通 常作为一个整体来进行处理。例如一条描述一位学生的完整信息的数据记录就是一个数据元素;空间中一点的三维坐标也可以是一个数据元素。数据元素通常由若干个数据项组成,例如描述学生相关信息的姓名、性别、学号等都是数据项;三维坐标中的每一维坐标值也是数据项。数据项具有原子性,是不可分割的最小单位。 +#### 🎒 4.3 背包问题 +- **0-1背包**:O(nW) +- **完全背包**:O(nW) +- **多重背包**:O(nWlogM) +#### 📏 4.4 区间DP +- **最长回文子串**:O(n²) +- **矩阵链乘法**:O(n³) +### 🎯 5. 贪心算法 -**数据对象**(data object)是性质相同的数据元素的集合,是数据的子集。例如一个学校的所有学生的集合就是数据对象,空间中所有点的集合也是数据对象。 +贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。 +- **活动选择问题**:O(nlogn) +- **分数背包**:O(nlogn) +- **最小生成树(Prim/Kruskal)**:O(ElogV) +- **霍夫曼编码**:O(nlogn) +- **区间调度**:O(nlogn) +### 🔄 6. 分治算法 -**数据结构**(data structure)是指相互之间存在一种或多种特定关系的数据元素的集合。是组织并存储数据以便能够有效使用的一种专门格式,它用来反映一个数据的内部构成,即一个数据由哪些成分数据构成,以什么方式构成,呈什么结构。 +分治算法是一种很重要的算法,字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题。 -由于信息可以存在于逻辑思维领域,也可以存在于计算机世界,因此作为信息载体的数据同样存在于两个世界中。表示一组数据元素及其相互关系的数据结构同样也有两种不同的表现形式,一种是数据结构的逻辑层面,即数据的逻辑结构;一种是存在于计算机世界的物理层面,即数据的存储结构 +- **归并排序**:O(nlogn) +- **快速排序**:O(nlogn) +- **二分搜索**:O(logn) +- **最大子数组和**:O(nlogn) +- **最近点对**:O(nlogn) +### 🔙 7. 回溯算法 +回溯算法是一种通过穷举所有可能情况来找到所有解的算法。当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择。 -## 逻辑结构和物理结构 +- **N皇后问题**:O(N!) +- **数独求解**:O(9^(n*n)) +- **全排列**:O(n!) +- **子集生成**:O(2^n) +- **组合问题**:O(C(n,k)) -按照视点的不同,我们把数据结构分为逻辑结构和物理结构。 +### 📝 8. 字符串算法 -### 逻辑结构 +字符串算法是处理文本数据的重要工具,广泛应用于文本搜索、模式匹配等领域。 -是指数据对象中数据元素之间的相互关系。其实这也是我们今后最需要关注的问题。分为以下四种: +- **KMP算法**:O(n+m) - 字符串匹配 +- **Rabin-Karp算法**:O(n+m) - 字符串匹配 +- **最长公共前缀**:O(S) - S为所有字符串长度和 +- **字典树(Trie)**:插入/查找 O(m) +- **后缀数组**:O(nlogn) -- 集合结构:集合结构中的数据元素除了同属于一个集合外,他们之间没有其他关系 +### 🧮 9. 数学算法 -  +数学算法是解决数学问题的计算方法,在编程中经常需要用到各种数学算法。 -- 线性结构:数据之间是一对一关系 +- **最大公约数(GCD)**:O(logn) +- **快速幂**:O(logn) +- **素数筛选**:O(nloglogn) +- **模运算**:O(1) +- **组合数学**:O(nlogn) -  +### 🔢 10. 位运算算法 -- 树形结构:数据之间存在一对多的层次关系 +位运算是计算机中最底层的运算,掌握位运算技巧可以写出更高效的代码。 -  +- **位运算基础**:O(1) +- **状态压缩DP**:O(n*2^m) +- **子集枚举**:O(2^n) +- **位操作技巧**:O(1) -- 图形结构:数据之间多对多的关系 +### 🏗️ 11. 高级数据结构算法 -  +高级数据结构算法是建立在基础数据结构之上的复杂算法,能够解决更复杂的问题。 -### 物理结构 +- **并查集**:O(α(n)) - 接近常数时间 +- **线段树**:O(logn) - 区间查询/更新 +- **树状数组**:O(logn) - 前缀和查询 +- **平衡树(AVL/红黑树)**:O(logn) +- **跳表**:O(logn) -是指数据的逻辑结构在计算机中的存储形式。(有时也被叫存储结构) +--- -数据是数据元素的集合,根据物理结构的定义,实际上就是如何把数据元素存储到计算机的存储器中。存储器主要是针对内存而言的,像硬盘、软盘、光盘等外部存储器的数据组织通常用文件结构来描述。 +## 🎯 第三部分:LeetCode经典题目 -数据元素的存储结构形式有两种:顺序存储和链式存储。 +LeetCode是程序员刷题的重要平台,通过系统性的刷题练习,可以快速提升算法能力。以下是按类型分类的经典题目。 -- 顺序存储:把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系一致 +### 📋 1. 数组类题目 -  +数组是最基础的数据结构,掌握数组的各种操作技巧是算法学习的基础。 -- 链式存储:把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。 +#### 🔧 基础操作 +- **1. 两数之和** - 哈希表优化 +- **26. 删除排序数组中的重复项** - 双指针 +- **27. 移除元素** - 双指针 +- **88. 合并两个有序数组** - 双指针 -  +#### 🔍 搜索与查找 +- **33. 搜索旋转排序数组** - 二分搜索 +- **34. 在排序数组中查找元素的第一个和最后一个位置** - 二分搜索 +- **35. 搜索插入位置** - 二分搜索 +- **153. 寻找旋转排序数组中的最小值** - 二分搜索 +#### 👆 双指针技巧 +- **15. 三数之和** - 排序+双指针 +- **16. 最接近的三数之和** - 排序+双指针 +- **18. 四数之和** - 排序+双指针 +- **42. 接雨水** - 双指针 +- **11. 盛最多水的容器** - 双指针 +#### 🪟 滑动窗口 +- **3. 无重复字符的最长子串** - 滑动窗口 +- **76. 最小覆盖子串** - 滑动窗口 +- **209. 长度最小的子数组** - 滑动窗口 +- **438. 找到字符串中所有字母异位词** - 滑动窗口 -## 抽象数据结构类型 +### 🔗 2. 链表类题目 -**数据类型**(data type)是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。例如 Java 语言中就有许多不同的数据类型,包括数值型的数据类型、字符串、布尔型等数据类型。以 Java 中的 int 型为例,int 型的数据元素的集合是[-2147483648,2147483647] 间的整数,定义在其上的操作有加、减、乘、除四则运算,还有模运算等。 +链表是动态数据结构,掌握链表的操作技巧对于理解指针和递归非常重要。 -数据类型是按照值得不同进行划分的。在高级语言中,每个变量、常量和表达式都有各自的取值范围。类型就用来说明变量或表达式取值范围和所能进行的操作。 +#### 🔧 基础操作 +- **206. 反转链表** - 迭代/递归 +- **21. 合并两个有序链表** - 双指针 +- **83. 删除排序链表中的重复元素** - 单指针 +- **82. 删除排序链表中的重复元素 II** - 双指针 -定义数据类型的作用一个是隐藏计算机硬件及其特性和差别,使硬件对于用户而言是透明的,即用户可以不关心数据类型是怎么实现的而可以使用它。定义数据类型的另一个作用是,用户能够使用数据类型定义的操作,方便的实现问题的求解。例如,用户可以使用 Java 定义在 int 型的加法操作完成两个整数的加法运算,而不用关心两个整数的加法在计算机中到底是如何实现的。这样不但加快了用户解决问题的速度,也使得用户可以在更高的层面上 考虑问题。 +#### 👆 双指针技巧 +- **141. 环形链表** - 快慢指针 +- **142. 环形链表 II** - 快慢指针 +- **160. 相交链表** - 双指针 +- **19. 删除链表的倒数第 N 个结点** - 快慢指针 -**抽象数据类型**(abstract data type, 简称 ADT)由一种数据模型和在该数据模型上的一组操作组成。 +#### 🔄 复杂操作 +- **234. 回文链表** - 快慢指针+反转 +- **143. 重排链表** - 找中点+反转+合并 +- **148. 排序链表** - 归并排序 +- **23. 合并K个排序链表** - 分治/优先队列 + +### 📚 3. 栈与队列类题目 + +栈和队列是重要的线性数据结构,在算法中有着广泛的应用。 -抽象数据类型一方面使得使用它的人可以只关心它的逻辑特征,不需要了解它的实现方式。另一方面可以使我们更容易描述现实世界,使得我们可以在更高的层面上来考虑问题。 例如可以使用树来描述行政区划,使用图来描述通信网络。 - - - -## 数据结构分类 - -- 数组 -- 栈 -- 链表 -- 队列 -- 树 -- 图 -- 堆 -- 散列表 - - - -# 算法 - -算法设计是最具创造性的工作之一,人们解决任何问题的思想、方法和步骤实际上都可以认为是算法。人们解决问题的方法有好有坏,因此算法在性能上也就有高低之分。 - -## 概念 - -算法(algorithm)是指令的集合,是为解决特定问题而规定的一系列操作。它是明确定义的可计算过程,以一个数据集合作为输入,并产生一个数据集合作为输出。一个算法通常来说具有以下五个特性: - -- 输入:一个算法应以待解决的问题的信息作为输入。 -- 输出:输入对应指令集处理后得到的信息。 -- 可行性:算法是可行的,即算法中的每一条指令都是可以实现的,均能在有限的时间内完成。 -- 有穷性:算法执行的指令个数是有限的,每个指令又是在有限时间内完成的,因此 整个算法也是在有限时间内可以结束的。 -- 确定性:算法对于特定的合法输入,其对应的输出是唯一的。即当算法从一个特定 输入开始,多次执行同一指令集结果总是相同的。 对于随机算法,该特性应当被放宽 - - - -## 算法设计要求 - -- 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义、能正确反映问题的需求、能得到问题的正确答案 -- 可读性:算法设计的另一目的是为了便于阅读、理解和交流 -- 健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或错误结果 -- 时间效率高和存储量低 - - - - \ No newline at end of file +#### 📚 栈的应用 +- **20. 有效的括号** - 栈匹配 +- **155. 最小栈** - 辅助栈 +- **225. 用队列实现栈** - 设计问题 +- **232. 用栈实现队列** - 设计问题 + +#### 📈 单调栈 +- **496. 下一个更大元素 I** - 单调栈 +- **503. 下一个更大元素 II** - 单调栈 +- **739. 每日温度** - 单调栈 +- **84. 柱状图中最大的矩形** - 单调栈 + +### 4. 树类题目 + +#### 二叉树遍历 +- **94. 二叉树的中序遍历** - 递归/迭代 +- **144. 二叉树的前序遍历** - 递归/迭代 +- **145. 二叉树的后序遍历** - 递归/迭代 +- **102. 二叉树的层序遍历** - BFS + +#### 二叉树性质 +- **104. 二叉树的最大深度** - DFS +- **111. 二叉树的最小深度** - BFS/DFS +- **226. 翻转二叉树** - DFS +- **101. 对称二叉树** - DFS + +#### 二叉搜索树 +- **98. 验证二叉搜索树** - 中序遍历 +- **700. 二叉搜索树中的搜索** - 二分搜索 +- **701. 二叉搜索树中的插入操作** - 递归 +- **450. 删除二叉搜索树中的节点** - 递归 + +#### 树的路径问题 +- **112. 路径总和** - DFS +- **113. 路径总和 II** - DFS+回溯 +- **124. 二叉树中的最大路径和** - DFS +- **257. 二叉树的所有路径** - DFS+回溯 + +### 5. 图类题目 + +#### 图的遍历 +- **200. 岛屿数量** - DFS/BFS +- **695. 岛屿的最大面积** - DFS/BFS +- **994. 腐烂的橘子** - BFS +- **130. 被围绕的区域** - DFS/BFS + +#### 拓扑排序 +- **207. 课程表** - 拓扑排序 +- **210. 课程表 II** - 拓扑排序 + +#### 联通分量 +- **547. 省份数量** - 并查集/DFS +- **684. 冗余连接** - 并查集 +- **721. 账户合并** - 并查集 + +### 6. 动态规划类题目 + +#### 基础DP +- **70. 爬楼梯** - 基础DP +- **198. 打家劫舍** - 线性DP +- **213. 打家劫舍 II** - 环形DP +- **53. 最大子序和** - Kadane算法 + +#### 序列DP +- **300. 最长递增子序列** - LIS +- **1143. 最长公共子序列** - LCS +- **72. 编辑距离** - 字符串DP +- **5. 最长回文子串** - 区间DP + +#### 背包问题 +- **416. 分割等和子集** - 0-1背包 +- **494. 目标和** - 0-1背包 +- **322. 零钱兑换** - 完全背包 +- **518. 零钱兑换 II** - 完全背包 + +#### 状态机型DP +- **121. 买卖股票的最佳时机** - 状态机DP +- **122. 买卖股票的最佳时机 II** - 状态机DP +- **123. 买卖股票的最佳时机 III** - 状态机DP +- **188. 买卖股票的最佳时机 IV** - 状态机DP + +### 7. 回溯算法类题目 + +#### 排列组合 +- **46. 全排列** - 回溯 +- **47. 全排列 II** - 回溯+去重 +- **77. 组合** - 回溯 +- **78. 子集** - 回溯 + +#### 分割问题 +- **131. 分割回文串** - 回溯+动态规划 +- **93. 复原IP地址** - 回溯 + +#### 棋盘问题 +- **51. N 皇后** - 回溯 +- **37. 解数独** - 回溯 + +### 8. 贪心算法类题目 + +#### 区间问题 +- **435. 无重叠区间** - 贪心 +- **452. 用最少数量的箭引爆气球** - 贪心 +- **55. 跳跃游戏** - 贪心 +- **45. 跳跃游戏 II** - 贪心 + +### 9. 字符串类题目 + +#### 字符串匹配 +- **28. 实现 strStr()** - KMP算法 +- **459. 重复的子字符串** - KMP算法 + +#### 回文串 +- **125. 验证回文串** - 双指针 +- **5. 最长回文子串** - 中心扩展 +- **647. 回文子串** - 中心扩展 + +### 10. 位运算类题目 + +- **136. 只出现一次的数字** - 异或运算 +- **191. 位1的个数** - 位运算 +- **338. 比特位计数** - 位运算+DP +- **461. 汉明距离** - 位运算 + + + +### 🎯 刷题建议 +1. **由易到难**:从Easy开始,逐步提升到Hard +2. **分类练习**:按数据结构和算法类型分类刷题 +3. **反复练习**:重要题目要反复练习,形成肌肉记忆 +4. **总结模板**:每种题型都要总结解题模板 +5. **时间管理**:每天固定时间刷题,保持连续性 +6. **记录总结**:建立错题本,定期回顾总结 + +--- + +## 🛠️ 学习工具推荐 + +### 📚 在线平台 +- [LeetCode中国](https://leetcode-cn.com/) - 主要刷题平台 +- [牛客网](https://www.nowcoder.com/) - 面试真题 +- [AcWing](https://www.acwing.com/) - 算法竞赛 +- [洛谷](https://www.luogu.com.cn/) - 算法学习 + +### 🎥 学习资源 +- [算法可视化](https://visualgo.net/) - 算法动画演示 +- [VisuAlgo](https://visualgo.net/) - 数据结构可视化 +- [算法导论](https://mitpress.mit.edu/books/introduction-algorithms) - 经典教材 +- [算法4](https://algs4.cs.princeton.edu/) - Java实现 + +### 💻 开发工具 +- **IDE**:IntelliJ IDEA、Eclipse +- **调试**:LeetCode插件、本地调试 +- **版本控制**:Git管理代码 +- **笔记**:Markdown记录学习心得 + +--- + +> 💡 **记住**:数据结构和算法是程序员的内功,需要持续练习和积累 +> +> 🚀 **建议**:每天至少刷一道LeetCode,坚持100天必有收获 +> +> 📚 **资源**:[LeetCode中国](https://leetcode-cn.com/) | [算法可视化](https://visualgo.net/) +> +> 🎉 **加油**:相信通过系统性的学习,你一定能够掌握数据结构和算法的精髓! \ No newline at end of file diff --git a/docs/data-structure-algorithms/Recursion.md b/docs/data-structure-algorithms/Recursion.md deleted file mode 100644 index b139d44ce7..0000000000 --- a/docs/data-structure-algorithms/Recursion.md +++ /dev/null @@ -1,646 +0,0 @@ - - - - -文章目录: - -1. 什么是递归 -2. - - - -**什么是递归** - -递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。递归和枚举的区别在于:枚举是横向地把问题划分,然后依次求解子问题,而递归是把问题逐级分解,是纵向的拆分。 - -**简单地说,就是如果在函数中存在着调用函数本身的情况,这种现象就叫递归。** - -你以前肯定写过递归,只是不知道这就是递归罢了。 - - - -以阶乘函数为例,如下, 在 factorial 函数中存在着 factorial(n - 1) 的调用,所以此函数是递归函数 - -``` -public int factorial(int n) { - if (n < =1) { - return1; - } - return n * factorial(n - 1) -} -int fibonacci(int n) { - // Base case - if (n == 0 || n == 1) return n; - - // Recursive step - return fibonacci(n-1) + fibonacci(n-2); -} -``` - -进一步剖析「递归」,先有「递」再有「归」,「递」的意思是将问题拆解成子问题来解决, 子问题再拆解成子子问题,...,直到被拆解的子问题无需再拆分成更细的子问题(即可以求解),「归」是说最小的子问题解决了,那么它的上一层子问题也就解决了,上一层的子问题解决了,上上层子问题自然也就解决了,....,直到最开始的问题解决,文字说可能有点抽象,那我们就以阶层 f(6) 为例来看下它的「递」和「归」。 - - - -求解问题 f(6), 由于 f(6) = n * f(5), 所以 f(6) 需要拆解成 f(5) 子问题进行求解,同理 f(5) = n * f(4) ,也需要进一步拆分,... ,直到 f(1), 这是「递」,f(1) 解决了,由于 f(2) = 2 f(1) = 2 也解决了,.... f(n)到最后也解决了,这是「归」,所以递归的本质是能把问题拆分成具有**相同解决思路**的子问题,。。。直到最后被拆解的子问题再也不能拆分,解决了最小粒度可求解的子问题后,在「归」的过程中自然顺其自然地解决了最开始的问题。 - - - - 递归原理 - ------- - -> 递归是一种解决问题的有效方法,在递归过程中,函数将自身作为子例程调用 - -你可能想知道如何实现调用自身的函数。诀窍在于,每当递归函数调用自身时,它都会将给定的问题拆解为子问题。递归调用继续进行,直到到子问题无需进一步递归就可以解决的地步。 - -为了确保递归函数不会导致无限循环,它应具有以下属性: - -1. 一个简单的`基本案例(basic case)`(或一些案例) —— 能够不使用递归来产生答案的终止方案。 -2. 一组规则,也称作`递推关系(recurrence relation)`,可将所有其他情况拆分到基本案例。 - -注意,函数可能会有多个位置进行自我调用。 - - - -递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。递归和枚举的区别在于:枚举是横向地把问题划分,然后依次求解子问题,而递归是把问题逐级分解,是纵向的拆分。 - -递归代码最重要的两个特征:结束条件和自我调用。自我调用是在解决子问题,而结束条件定义了最简子问题的答案。 - -``` -int func(你今年几岁) { - // 最简子问题,结束条件 - if (你1999年几岁) return 我0岁; - // 自我调用,缩小规模 - return func(你去年几岁) + 1; -} -``` - - - - - -### 反转字符串(344) - -> 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。 -> -> 不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。 -> -> 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 -> -> **示例 1:** -> -> ``` -> 输入:["h","e","l","l","o"] -> 输出:["o","l","l","e","h"] -> ``` - - - - - - - - - -# 递归 - -递归实在计算机科学、数学等领域运用非常广泛的一种方法。使用递归的方法解决问题,一般具有这样的特征:我们在寻找一个复杂问题的解时,不能立即给出答案,然后从一个规模较小的相同问题的答案开始,却可以较为容易的求解复杂的问题。 - -我们主要介绍两种基于递归的算法设计技术,即基于归纳的递归和分治法。 - - - -## 概念 - -递归(recursion)是指在定义自身的同时又出现了对自身的引用。如果一个算法直接或间接的调用自己,则称这个算法是一个递归算法。 - -递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。 - -任何一个有意义的递归算法总是两部分组成:**递归调用**和**递归终止条件**。 - - - -## 如何理解递归 - -递归是一种应用非常广泛的算法或者编程技巧。很多数据结构和算法的编码实现都要用到递归,比如DFS深度优先搜索、前中后序二叉树遍历等等。所以搞懂递归对学习一些复杂的数据结构和算法是非常有必要的。 - -案例:*周末带着女朋友去电影院看电影,女朋友问,咱们现在坐在第几排啊?电影院里面太黑了,看不清,没法数,现在怎么办?* - -于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也问他前面的人。就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。直到你前面的人告诉你他在哪一排,于是你就知道答案了。 - -这就是一个非常标准的递归求解问题的分解过程,去的过程叫“递”,回来的过程叫“归”。 - -基本上,所有的递归问题都可以用递推公式来表示。比如上面的案例我们用递推公式将它表示出来就是这样: - -``` -f(n) = f(n-1) + 1 //其中 f(1) = 1 -``` - -f(n) 表示想知道自己在哪一排,f(n-1) 表示前面一排所在的排数,f(1) = 1表示第一排的人知道自己在第一排。有了这个递推公式,我们就可以很轻松地将它改为递归代码: - -``` -int f(int n) { - if (n == 1) return 1; - return f(n - 1) + 1; -} -``` - - - -## 递归需要满足的三个条件 - -只要同时满足以下三个条件,就可以用递归来解决。 - -1. **一个问题的解可以分解为几个子问题的解** - - 何为子问题?子问题就是数据规模更小的问题。比如前面的案例,要知道“自己在哪一排”,可以分解为“前一排的人在哪一排”这样的一个子问题。 - -2. **这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样** - - 如案例所示,求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路是一模一样的。 - -3. **存在递归终止条件** - - 把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能存在无限循环,这就需要有终止条件。前面的案例:第一排的人知道自己在哪一排,不需要再问别人,f(1) = 1就是递归的终止条件。 - - - -## 怎样编写递归代码 - -写递归代码,可以按三步走: - -**第一要素:明确你这个函数想要干什么** - -对于递归,我觉得很重要的一个事就是,**这个函数的功能是什么**,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。 - -例如,我定义了一个函数 - -``` -// 算 n 的阶乘(假设n不为0) -int f(int n){ - -} -``` - -这个函数的功能是算 n 的阶乘。好了,我们已经定义了一个函数,并且定义了它的功能是什么,接下来我们看第二要素。 - -**第二要素:寻找递归结束条件** - -所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出**递归的结束条件**,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出**当参数为啥时,递归结束,之后直接把结果返回**,请注意,这个时候我们必须能根据这个参数的值,能够**直接**知道函数的结果是什么。 - -例如,上面那个例子,当 n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面,如下 - -``` -// 算 n 的阶乘(假设n不为0) -int f(int n){ - if(n == 1){ - return 1; - } -} -``` - -有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗? - -当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件,所以下面这段代码也是可以的。 - -``` -// 算 n 的阶乘(假设n>=2) -int f(int n){ - if(n == 2){ - return 2; - } -} -``` - -注意我代码里面写的注释,假设 n >= 2,因为如果 n = 1时,会被漏掉,当 n <= 2时,f(n) = n,所以为了更加严谨,我们可以写成这样: - -``` -// 算 n 的阶乘(假设n不为0) -int f(int n){ - if(n <= 2){ - return n; - } -} -``` - -**第三要素:找出函数的等价关系式** - -第三要素就是,我们要**不断缩小参数的范围**,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。 - -例如,f(n) 这个范围比较大,我们可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。 - -说白了,就是要找到原函数的一个等价关系式,f(n) 的等价关系式为 n * f(n-1),即 - -f(n) = n * f(n-1)。 - - - -写递归代码最关键的是**写出递推公式,找到终止条件**,剩下就是将递推公式转化为代码。 - -案例:*假如这里有 n 个台阶,每次你可以跨 1 个台阶或者 2 个台阶,请问走这 n 个台阶有多少种走法?如果有 7 个台阶,你可以 2,2,2,1 这样子上去,也可以 1,2,1,1,2 这样子上去,总之走法有很多,那如何用编程求得总共有多少种走法呢?* - -我们可以根据第一步的走法把所有走法分为两类,第一类是第一步走了1个台阶,另一类是第一步走了2个台阶。所以n个台阶的走法就等于先走1阶后,n-1个台阶的走法 加上先走2阶后,n-2个台阶的走法,用公式表示: - -``` -f(n) = f(n-1) + f(n-2) -``` - -再来看下终止条件。当有一个台阶时,我们不需要再继续递归,就只有一种走法。所以f(1) = 1。这个递归终止条件足够吗?我们试试用n = 2, n = 3这样比较小的数实验一下。 - -n = 2时,f(2) = f(1) + f(0)。如果递归终止条件只有一个f(1) = 1,那f(2)就无法求解了。所以除了f(1) = 1这一个递归终止条件外,还要有f(0) = 1,表示走0个台阶有一种走法,不过这样看起来不符合正常的逻辑思维。所以,我们可以把f(2) = 2作为一种终止条件,表示走2个台阶,只有两种走法,一步走完或者分两步走。 - -所以,递归终止条件就是f(1) = 1,f(2) = 2。这个时候,可以再拿n = 3,n = 4来验证下,这个终止条件是否足够并且正确。 - -我们把递归终止条件和刚刚得到的递推公式放在一起就是这样: - -``` -f(1) = 1; -f(2) = 2; -f(n) = f(n - 1) + f(n - 2); -``` - -最终的递归代码就是这样: - -``` -int f(int n) { - if (n == 1) return 1; - if (n == 2) return 2; - return f(n -1) + f(n - 2); -} -``` - -**写递归代码的关键就是找到如何将大问题分解为小问题的规律,请求基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码**。 - -> 当我们面对一个问题需要分解为多个子问题的时候,递归代码往往没那么好理解,比如第二个案例,人脑几乎没办法把整个“递”和“归”的过程一步一步都想清楚。 -> -> 计算机擅长做重复的事情,所以递归正符合它的胃口。而我们人脑更喜欢平铺直叙的思维方式。当我们看到递归时,我们总想把递归平铺展开,脑子里就会循环,一层一层往下调,然后再一层一层返回,试图想搞清楚计算机每一步都是怎么执行的,这样就很容易被绕进去。 -> -> 对于递归代码,这种试图想清楚整个递和归过程的做法,实际上是进入了一个思维误区。很多时候,我们理解起来比较吃力,主要原因就是自己给自己制造了这种理解障碍。那正确的思维方式应该是怎样的呢? -> -> 如果一个问题 A 可以分解为若干子问题 B、C、D,可以假设子问题 B、C、D 已经解决,在此基础上思考如何解决问题 A。而且,只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。 - -换句话说就是:千万不要跳进这个函数里面企图探究更多细节,否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。 - -所以,编写递归代码的关键是:**只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤**。 - - - -## 递归代码要警惕堆栈溢出 - -在实际开发中,编写递归代码我们通常会遇到很多问题,比如堆栈溢出。而堆栈溢出会造成系统性崩溃,后果非常严重。为什么递归代码容易造成堆栈溢出呢? - -我们知道在函数调用时,会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险,出现`java.lang.StackOverflowError`。 - -如何避免出现堆栈溢出? - -可以通过在代码中限制递归调用的最大深度的方式来解决这个问题。递归调用超过一定深度(比如1000)之后,我们就不再继续往下递归了,直接返回报错。比如前面电影院的案例,改造后的伪代码如下: - -```c -// 全局变量,表示递归的深度。 -int depth = 0; - -int f(int n) { - ++depth; - if (depth > 1000) throw exception; - - if (n == 1) return 1; - return f(n-1) + 1; -} -``` - -但这种做法并不能完全解决问题,因为最大允许的递归深度跟当前线程剩余的栈空间大小有关,事先无法计算。如果实时计算,代码又会过于复杂,影响到代码的可读性。所以如果最大深度比较小,比如10、50,还可以用这种方法,否则这种方法不是很实用。 - - - -## 递归代码要警惕重复计算 - -使用递归时要注意重复计算的问题,比如案例二,我们把整个递归过程分解一下,那就是这样的: - - - -从图中,我们可以看到,想要计算f(5),需要先计算f(4)和f(3),而计算f(4)还需要计算f(3),因此,f(3)就被计算了很多次,这就是重复计算的问题。 - -为了解决重复计算,我们可以通过散列表等数据结构来保存已经求解过的f(k)。当递归调用到f(k)时,先看下是否已经求解过了。如果是,则直接从散列表中取值返回,就不再重复计算了。 - -如上思路,改造下刚才的代码: - -``` -public int f(int n) { - if (n == 1) return 1; - if (n == 2) return 2; - - // hasSolvedList 可以理解成一个 Map,key 是 n,value 是 f(n) - if (hasSolvedList.containsKey(n)) { - return hasSovledList.get(n); - } - - int ret = f(n-1) + f(n-2); - hasSovledList.put(n, ret); - return ret; -} -``` - -除了堆栈溢出、重复计算这两个常见的问题,递归代码还有很多别的问题。 - -在时间效率上,递归代码里多了很多函数调用,当这些函数调用的数量较大时,就会积累成一个可观的时间成本。在空间复杂度上,因为递归调用一次就会在内存栈中保存一次现场数据,所以在分析递归代码空间复杂度时,需要额外考虑这部分的开销,比如前面的案例一的递归代码,空间复杂度并不是O(1),而是O(n)。 - - - -## 案例 - -### 案例1:斐波那契数列 - -> 斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34....,即第一项 f(1) = 1,第二项 f(2) = 1.....,第 n 项目为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。 - -**1、第一递归函数功能** - -假设 f(n) 的功能是求第 n 项的值,代码如下: - -``` -int f(int n){ - -} -``` - -**2、找出递归结束的条件** - -显然,当 n = 1 或者 n = 2 ,我们可以轻易着知道结果 f(1) = f(2) = 1。所以递归结束条件可以为 n <= 2。代码如下: - -``` -int f(int n){ - if(n <= 2){ - return 1; - } -} -``` - -**第三要素:找出函数的等价关系式** - -题目已经把等价关系式给我们了,所以我们很容易就能够知道 f(n) = f(n-1) + f(n-2)。我说过,等价关系式是最难找的一个,而这个题目却把关系式给我们了,这也太容易,好吧,我这是为了兼顾几乎零基础的读者。 - -所以最终代码如下: - -``` -int f(int n){ - // 1.先写递归结束条件 - if(n <= 2){ - return 1; - } - // 2.接着写等价关系式 - return f(n-1) + f(n - 2); -} -``` - -搞定,是不是很简单? - - - -### 案例2:小青蛙跳台阶 - -> 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 - -**1、第一递归函数功能** - -假设 f(n) 的功能是求青蛙跳上一个n级的台阶总共有多少种跳法,代码如下: - -``` -int f(int n){ - -} -``` - -**2、找出递归结束的条件** - -我说了,求递归结束的条件,你直接把 n 压缩到很小很小就行了,因为 n 越小,我们就越容易直观着算出 f(n) 的多少,所以当 n = 1时,你知道 f(1) 为多少吧?够直观吧?即 f(1) = 1。代码如下: - -``` -int f(int n){ - if(n == 1){ - return 1; - } -} -``` - -**第三要素:找出函数的等价关系式** - -每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。 - -第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。 - -第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。 - -所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了。于是写出代码: - -``` -int f(int n){ - if(n == 1){ - return 1; - } - ruturn f(n-1) + f(n-2); -} -``` - -大家觉得上面的代码对不对? - -答是不大对,当 n = 2 时,显然会有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,按道理是递归结束,不用继续往下调用的,但我们上面的代码逻辑中,会继续调用 f(0) = f(-1) + f(-2)。这会导致无限调用,进入**死循环**。 - -这也是我要和你们说的,关于**递归结束条件是否够严谨问题**,有很多人在使用递归的时候,由于结束条件不够严谨,导致出现死循环。也就是说,当我们在第二步找出了一个递归结束条件的时候,可以把结束条件写进代码,然后进行第三步,但是**请注意**,当我们第三步找出等价函数之后,还得再返回去第二步,根据第三步函数的调用关系,会不会出现一些漏掉的结束条件。就像上面,f(n-2)这个函数的调用,有可能出现 f(0) 的情况,导致死循环,所以我们把它补上。代码如下: - -``` -int f(int n){ - //f(0) = 0,f(1) = 1,等价于 n<=1时,f(n) = n。 - if(n <= 1){ - return n; - } - ruturn f(n-1) + f(n-2); -} -``` - -有人可能会说,我不知道我的结束条件有没有漏掉怎么办?别怕,多练几道就知道怎么办了。 - -看到这里有人可能要吐槽了,这两道题也太容易了吧??能不能被这么敷衍。少侠,别走啊,下面出道难一点的。 - -### 案例3:反转单链表。 - -> 反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1 - -链表的节点定义如下: - -``` -class Node{ - int date; - Node next; -} -``` - -虽然是 Java语言,但就算你没学过 Java,我觉得也是影响不大,能看懂。 - -还是老套路,三要素一步一步来。 - -**1、定义递归函数功能** - -假设函数 reverseList(head) 的功能是反转但链表,其中 head 表示链表的头节点。代码如下: - -``` -Node reverseList(Node head){ - -} -``` - -**2. 寻找结束条件** - -当链表只有一个节点,或者如果是空表的话,你应该知道结果吧?直接啥也不用干,直接把 head 返回呗。代码如下: - -``` -Node reverseList(Node head){ - if(head == null || head.next == null){ - return head; - } -} -``` - -**3. 寻找等价关系** - -这个的等价关系不像 n 是个数值那样,比较容易寻找。但是我告诉你,它的等价条件中,一定是范围不断在缩小,对于链表来说,就是链表的节点个数不断在变小,所以,如果你实在找不出,你就先对 reverseList(head.next) 递归走一遍,看看结果是咋样的。例如链表节点如下 - - - -我们就缩小范围,先对 2->3->4递归下试试,即代码如下 - -``` -Node reverseList(Node head){ - if(head == null || head.next == null){ - return head; - } - // 我们先把递归的结果保存起来,先不返回,因为我们还不清楚这样递归是对还是错。, - Node newList = reverseList(head.next); -} -``` - -我们在第一步的时候,就已经定义了 reverseLis t函数的功能可以把一个单链表反转,所以,我们对 2->3->4反转之后的结果应该是这样: - - - -我们把 2->3->4 递归成 4->3->2。不过,1 这个节点我们并没有去碰它,所以 1 的 next 节点仍然是连接这 2。 - -接下来呢?该怎么办? - -其实,接下来就简单了,我们接下来只需要**把节点 2 的 next 指向 1,然后把 1 的 next 指向 null,不就行了?**,即通过改变 newList 链表之后的结果如下: - - - -也就是说,reverseList(head) 等价于 ** reverseList(head.next)** + **改变一下1,2两个节点的指向**。好了,等价关系找出来了,代码如下(有详细的解释): - -``` -//用递归的方法反转链表 -public static Node reverseList2(Node head){ - // 1.递归结束条件 - if (head == null || head.next == null) { - return head; - } - // 递归反转 子链表 - Node newList = reverseList2(head.next); - // 改变 1,2节点的指向。 - // 通过 head.next获取节点2 - Node t1 = head.next; - // 让 2 的 next 指向 2 - t1.next = head; - // 1 的 next 指向 null. - head.next = null; - // 把调整之后的链表返回。 - return newList; - } -``` - -这道题的第三步看的很懵?正常,因为你做的太少了,可能没有想到还可以这样,多练几道就可以了。但是,我希望通过这三道题,给了你以后用递归做题时的一些思路,你以后做题可以按照我这个模式去想。通过一篇文章是不可能掌握递归的,还得多练,我相信,只要你认真看我的这篇文章,多看几次,一定能找到一些思路!! - -> 我已经强调了好多次,多练几道了,所以呢,后面我也会找大概 10 道递归的练习题供大家学习,不过,我找的可能会有一定的难度。不会像今天这样,比较简单,所以呢,初学者还得自己多去找题练练,相信我,掌握了递归,你的思维抽象能力会更强! - -接下来我讲讲有关递归的一些优化。 - -### 有关递归的一些优化思路 - -**1. 考虑是否重复计算** - -告诉你吧,如果你使用递归的时候不进行优化,是有非常非常非常多的**子问题**被重复计算的。 - -> 啥是子问题? f(n-1),f(n-2)....就是 f(n) 的子问题了。 - -例如对于案例2那道题,f(n) = f(n-1) + f(n-2)。递归调用的状态图如下: - - - -看到没有,递归计算的时候,重复计算了两次 f(5),五次 f(4)。。。。这是非常恐怖的,n 越大,重复计算的就越多,所以我们必须进行优化。 - -如何优化?一般我们可以把我们计算的结果保证起来,例如把 f(4) 的计算结果保证起来,当再次要计算 f(4) 的时候,我们先判断一下,之前是否计算过,如果计算过,直接把 f(4) 的结果取出来就可以了,没有计算过的话,再递归计算。 - -用什么保存呢?可以用数组或者 HashMap 保存,我们用数组来保存把,把 n 作为我们的数组下标,f(n) 作为值,例如 arr[n] = f(n)。f(n) 还没有计算过的时候,我们让 arr[n] 等于一个特殊值,例如 arr[n] = -1。 - -当我们要判断的时候,如果 arr[n] = -1,则证明 f(n) 没有计算过,否则, f(n) 就已经计算过了,且 f(n) = arr[n]。直接把值取出来就行了。代码如下: - -``` -// 我们实现假定 arr 数组已经初始化好的了。 -int f(int n){ - if(n <= 1){ - return n; - } - //先判断有没计算过 - if(arr[n] != -1){ - //计算过,直接返回 - return arr[n]; - }else{ - // 没有计算过,递归计算,并且把结果保存到 arr数组里 - arr[n] = f(n-1) + f(n-1); - reutrn arr[n]; - } -} -``` - -也就是说,使用递归的时候,必要 -须要考虑有没有重复计算,如果重复计算了,一定要把计算过的状态保存起来。 - -**2. 考虑是否可以自底向上** - -对于递归的问题,我们一般都是**从上往下递归**的,直到递归到最底,再一层一层着把值返回。 - -不过,有时候当 n 比较大的时候,例如当 n = 10000 时,那么必须要往下递归10000层直到 n <=1 才将结果慢慢返回,如果n太大的话,可能栈空间会不够用。 - -对于这种情况,其实我们是可以考虑自底向上的做法的。例如我知道 - -f(1) = 1; - -f(2) = 2; - -那么我们就可以推出 f(3) = f(2) + f(1) = 3。从而可以推出f(4),f(5)等直到f(n)。因此,我们可以考虑使用自底向上的方法来取代递归,代码如下: - -``` -public int f(int n) { - if(n <= 2) - return n; - int f1 = 1; - int f2 = 2; - int sum = 0; - - for (int i = 3; i <= n; i++) { - sum = f1 + f2; - f1 = f2; - f2 = sum; - } - return sum; - } -``` - -这种方法,其实也被称之为**递推**。 - - - - - - - -来源: - -https://aleej.com/2019/10/09/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E4%B9%8B%E7%BE%8E%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ - -https://www.cnblogs.com/kubidemanong/p/10538799.html - diff --git a/docs/data-structure-algorithms/Stack.md b/docs/data-structure-algorithms/Stack.md deleted file mode 100644 index 3b072cb431..0000000000 --- a/docs/data-structure-algorithms/Stack.md +++ /dev/null @@ -1,279 +0,0 @@ -# 栈 - -## 一、概述 - -### 定义 - -注意:本文所说的栈是数据结构中的栈,而不是内存模型中栈 - -栈(stack)是限定仅在表尾一端进行插入或删除操作的**特殊线性表**。又称为堆栈。 - -对于栈来说, 允许进行插入或删除操作的一端称为栈顶(top),而另一端称为栈底(bottom)。不含元素栈称为空栈,向栈中插入一个新元素称为入栈或压栈, 从栈中删除一个元素称为出栈或退栈。 - -假设有一个栈S=(a1, a2, …, an),a1先进栈, an最后进栈。称 a1 为栈底元素,an 为栈顶元素。出栈时只允许在栈顶进行,所以 an 先出栈,a1最后出栈。因此又称栈为后进先出(Last In First Out,LIFO)的线性表。 - -栈(stack),是一种线性存储结构,它有以下几个特点: - -- 栈中数据是按照"后进先出(LIFO, Last In First Out)"方式进出栈的。 -- 向栈中添加/删除数据时,只能从栈顶进行操作。 - - - -在上图中,当 ABCD 均已入栈后,出栈时得到的序列为 DCBA,这就是后进先出。 - - - -### 基本操作 - -栈的基本操作除了进栈 `push()`,出栈 `pop()` 之外,还有判空 `isEmpty()`、取栈顶元素 `peek()` 等操作。 - -抽象成接口如下: - -```java -public interface MyStack { - - /** - * 返回堆栈的大小 - */ - public int getSize(); - - /** - * 判断堆栈是否为空 - */ - public boolean isEmpty(); - - /** - * 入栈 - */ - public void push(Object e); - - /** - * 出栈,并删除 - */ - public Object pop(); - - /** - * 返回栈顶元素 - */ - public Object peek(); -} -``` - - - -和线性表类似,栈也有两种存储结构:顺序存储和链式存储。 - -## 二、栈的顺序存储与实现 - -顺序栈是使用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放栈中的数据元素。由于栈是一种特殊的线性表,因此在线性表的顺序存储结构的基础上,选择线性表的一端作为栈顶即可。那么根据数组操作的特性,选择数组下标大的一端,即线性表顺序存储的表尾来作为栈顶,此时入栈、出栈操作可以 $O(1)$ 时间完成。 - -由于栈的操作都是在栈顶完成,因此在顺序栈的实现中需要附设一个指针 top 来动态地指示栈顶元素在数组中的位置。通常 top 可以用栈顶元素所在的数组下标来表示,top=-1时表示空栈。 - -栈在使用过程中所需的最大空间难以估计,所以,一般构造栈的时候不应设定最大容量。一种合理的做法和线性表类似,先为栈分配一个基本容量,然后在实际的使用过程中,当栈的空间不够用时再倍增存储空间。 - -```java -public class MyArrayStack implements MyStack { - - private final int capacity = 2; //默认容量 - private Object[] arrs; //数据元素数组 - private int top; //栈顶指针 - - MyArrayStack(){ - top = -1; - arrs = new Object[capacity]; - } - - public int getSize() { - return top + 1; - } - - public boolean isEmpty() { - return top < 0; - } - - public void push(Object e) { - if(getSize() >= arrs.length){ - expandSapce(); //扩容 - } - arrs[++top]=e; - } - - private void expandSapce() { - Object[] a = new Object[arrs.length * 2]; - for (int i = 0; i < arrs.length; i++) { - a[i] = arrs[i]; - } - arrs = a; - } - - public Object pop() { - if(getSize()<1){ - throw new RuntimeException("栈为空"); - } - Object obj = arrs[top]; - arrs[top--] = null; - return obj; - } - - public Object peek() { - if(getSize()<1){ - throw new RuntimeException("栈为空"); - } - return arrs[top]; - } -} -``` - -以上基于数据实现的栈代码并不难理解。由于有 top 指针的存在,所以`size()`、`isEmpty()`方法均可在 $O(1) $ 时间内完成。`push()`、`pop()`和`peek()`方法,除了需要`ensureCapacity()`外,都执行常数基本操作,因此它们的运行时间也是 $O(1)$ - - - -## 三、栈的链式存储与实现 - -栈的链式存储即采用链表实现栈。当采用单链表存储线性表后,根据单链表的操作特性选择单链表的头部作为栈顶,此时,入栈和出栈等操作可以在 $O(1)$ 时间内完成。 - -由于栈的操作只在线性表的一端进行,在这里使用带头结点的单链表或不带头结点的单链表都可以。使用带头结点的单链表时,结点的插入和删除都在头结点之后进行;使用不带头结点的单链表时,结点的插入和删除都在链表的首结点上进行。 - -下面以不带头结点的单链表为例实现栈,如下示意图所示: - - - -在上图中,top 为栈顶结点的引用,始终指向当前栈顶元素所在的结点。若 top 为null,则表示空栈。入栈操作是在 top 所指结点之前插入新的结点,使新结点的 next 域指向 top,top 前移即可;出栈则直接让 top 后移即可。 - -```java -public class MyLinkedStack implements MyStack { - - class Node { - private Object element; - private Node next; - - public Node() { - this(null, null); - } - - public Node(Object ele, Node next) { - this.element = ele; - this.next = next; - } - - public Node getNext() { - return next; - } - - public void setNext(Node next) { - this.next = next; - } - - public Object getData() { - return element; - } - - public void setData(Object obj) { - element = obj; - } - } - - private Node top; - private int size; - - public MyLinkedStack() { - top = null; - size = 0; - } - - public int getSize() { - return size; - } - - public boolean isEmpty() { - return size == 0; - } - - public void push(Object e) { - Node node = new Node(e, top); - top = node; - size++; - } - - public Object pop() { - if (size < 1) { - throw new RuntimeException("堆栈为空"); - } - Object obj = top.getData(); - top = top.getNext(); - size--; - return obj; - } - - public Object peek() { - if (size < 1) { - throw new RuntimeException("堆栈为空"); - } - return top.getData(); - } -} -``` - -上述 `MyLinkedStack` 类中有两个成员变量,其中 `top` 表示首结点,也就是栈顶元素所在的结点;`size` 指示栈的大小,即栈中数据元素的个数。不难理解,所有的操作均可以在 $O(1)$ 时间内完成。 - - - -## 四、JDK 中的栈实现 Stack - -Java 工具包中的 Stack 是继承于 Vector(矢量队列)的,由于 Vector 是通过数组实现的,这就意味着,Stack 也是通过数组实现的,而非链表。当然,我们也可以将 LinkedList 当作栈来使用。 - -### Stack的继承关系 - -```java -java.lang.Object - java.util.AbstractCollection - java.util.AbstractList - java.util.Vector - java.util.Stack - -public class Stack extends Vector {} -``` - - - - - -## 五、栈应用 - -栈有一个很重要的应用,在程序设计语言里实现了递归。 - -### 有效的括号 - ->给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断字符串是否有效。 -> ->有效字符串需满足: -> ->1. 左括号必须用相同类型的右括号闭合。 ->2. 左括号必须以正确的顺序闭合。 -> ->注意空字符串可被认为是有效字符串。 -> ->``` ->输入: "{[]}" ->输出: true ->输入: "([)]" ->输出: false ->``` - - - - - - - ->请根据每日 `气温` 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 `0` 来代替。 -> ->例如,给定一个列表 `temperatures = [73, 74, 75, 71, 69, 72, 76, 73]`,你的输出应该是 `[1, 1, 4, 2, 1, 1, 0, 0]`。 -> ->**提示:**`气温` 列表长度的范围是 `[1, 30000]`。每个气温的值的均为华氏度,都是在 `[30, 100]` 范围内的整数。 - - - - - -> 逆波兰表达式求值 \ No newline at end of file diff --git a/docs/data-structure-algorithms/TwoSum_1.md b/docs/data-structure-algorithms/TwoSum_1.md deleted file mode 100644 index 0b8454c746..0000000000 --- a/docs/data-structure-algorithms/TwoSum_1.md +++ /dev/null @@ -1,107 +0,0 @@ - - -# 1. 两数之和 - -题目来源于 LeetCode 上第 1 号问题:两数之和。题目难度为 Easy 。 - -### 题目描述 - -给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 - -你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 - -**示例:** - -``` -给定 nums = [2, 7, 11, 15], target = 9 - -因为 nums[0] + nums[1] = 2 + 7 = 9 -所以返回 [0, 1] -``` - -### 代码实现 - -#### 方法一:暴力法 - -暴力法很简单,遍历每个元素 x,并查找是否存在一个值与`xtarget−x` 相等的目标元素。 - -```java -class Solution { - public int[] twoSum(int[] nums, int target) { - for (int i = 0; i < nums.length; i++) { - for (int j = i + 1; j < nums.length; j++) { - if (nums[j] == target - nums[i]) { - return new int[] { i, j }; - } - } - } - throw new IllegalArgumentException("No two sum solution"); - } -} -``` - -复杂度分析: - -- 时间复杂度 $O(n^2)$: - 对于每个元素,我们试图通过遍历数组的其余部分来寻找它所对应的目标元素,这将耗费 $O(n)$ 的时间。因此时间复杂度为 $O(n^2)$ -- 空间复杂度:$O(1)$ - -#### 方法二:两遍哈希表 - -为了对运行时间复杂度进行优化,我们需要一种更有效的方法来检查数组中是否存在目标元素。如果存在,我们需要找出它的索引。保持数组中的每个元素与其索引相互对应的最好方法是什么?哈希表。 - -通过以空间换取速度的方式,我们可以将查找时间从 $O(n)$降低到 $O(1)$。哈希表正是为此目的而构建的,它支持以近似恒定的时间进行快速查找。我用“近似”来描述,是因为一旦出现冲突,查找用时可能会退化到 $O(n)$。 - -一个简单的实现使用了两次迭代。在第一次迭代中,我们将每个元素的值和它的索引添加到表中。然后,在第二次迭代中,我们将检查每个元素所对应的目标元素$(target - nums[i])$是否存在于表中。注意,该目标元素不能是 $nums[i]$ 本身! - -```java -class Solution { - public int[] twoSum(int[] nums, int target) { - Map map = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - map.put(nums[i], i); - } - for (int i = 0; i < nums.length; i++) { - int complement = target - nums[i]; - if (map.containsKey(complement) && map.get(complement) != i) { - return new int[] { i, map.get(complement) }; - } - } - throw new IllegalArgumentException("No two sum solution"); - } -} -``` - -复杂度分析: - -- 时间复杂度:$O(n)$, - 我们把包含有 nn 个元素的列表遍历两次。由于哈希表将查找时间缩短到 $O(1)$ ,所以时间复杂度为 $O(n)$。 -- 空间复杂度:$O(n)$, - 所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 nn 个元素。 - -#### 方法三:一遍哈希表 - -事实证明,我们可以一次完成。在进行迭代并将元素插入到表中的同时,我们还会回过头来检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。 - -```java -class Solution { - public int[] twoSum(int[] nums, int target) { - Map map = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - int complement = target - nums[i]; - if (map.containsKey(complement)) { - return new int[] { map.get(complement), i }; - } - map.put(nums[i], i); - } - throw new IllegalArgumentException("No two sum solution"); - } -} -``` - -复杂度分析: - -- 时间复杂度:$O(n)$ - 我们只遍历了包含有 nn 个元素的列表一次。在表中进行的每次查找只花费 $O(1)$的时间。 -- 空间复杂度:$O(n)$ - 所需的额外空间取决于哈希表中存储的元素数量,该表最多需要存储 n 个元素。 \ No newline at end of file diff --git a/docs/data-structure-algorithms/algorithm/Backtracking.md b/docs/data-structure-algorithms/algorithm/Backtracking.md new file mode 100755 index 0000000000..bdce9eaa4f --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/Backtracking.md @@ -0,0 +1,834 @@ +--- +title: 回溯算法 +date: 2023-05-09 +tags: + - back tracking +categories: Algorithm +--- + + + +> 🔍 **回溯算法**是解决很多算法问题的常见思想,它也是传统的人工智能的方法,其本质是 **在树形问题中寻找解** 。 +> +> 回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在**搜索尝试**过程中寻找问题的解,当发现已不满足求解条件时,就"**回溯**"返回,尝试别的路径。所以也可以叫做**回溯搜索法**。 +> +> 💡 回溯是递归的副产品,只要有递归就会有回溯。 + +# 一、回溯算法 + +回溯算法是一种**深度优先搜索**(DFS)的算法,它通过递归的方式,逐步建立解空间树,从根节点开始,逐层深入,直到找到一个解或路径不可行时回退到上一个状态(即回溯)。每一步的尝试可能会有多个选择,回溯算法通过剪枝来减少不必要的计算。 + +> 🌲 **深度优先搜索** 算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会 **尽可能深** 的搜索树的分支。当结点 `v` 的所在边都己被探寻过,搜索将 **回溯** 到发现结点 `v` 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被访问为止。 + +回溯的基本步骤通常包含以下几个要素: + +- 🎯 **选择**:在当前状态下,做出一个选择,进入下一个状态。 +- ⚖️ **约束**:每一步的选择必须满足问题的约束条件。 +- 🎪 **目标**:找到一个解或判断是否无法继续。 +- ↩️ **回溯**:如果当前的选择不符合目标,撤销当前的选择,回到上一状态继续尝试其他可能的选择。 + +### 🧠 核心思想 + +**回溯法** 采用试错的思想,它尝试分步的去解决一个问题。 + +1. 🔍 **穷举所有可能的解**:回溯法通过在每个状态下尝试不同的选择,来遍历解空间树。每个分支代表着做出的一个选择,每次递归都尝试不同的路径,直到找到一个解或回到根节点。 +2. ✂️ **剪枝**:在回溯过程中,我们可能会遇到一些不符合约束条件的选择,这时候可以及时退出当前分支,避免无谓的计算,这被称为"剪枝"。剪枝是提高回溯算法效率的关键,能减少不必要的计算。 +3. 🌲 **深度优先搜索(DFS)**:回溯算法在解空间树中深度优先遍历,尝试选择每个分支。直到走到树的叶子节点或回溯到一个不满足条件的节点。 + + + +### 🏗️ 基本框架 + +回溯算法的基本框架可以用递归来实现,通常包含以下几个步骤: + +1. 🎯 **选择和扩展**:选择一个可行的扩展步骤,扩展当前的解。 +2. ✅ **约束检查**:检查当前扩展后的解是否满足问题的约束条件。 +3. 🔄 **递归调用**:如果当前解满足约束条件,则递归地尝试扩展该解。 +4. ↩️ **回溯**:如果当前解不满足约束条件,或所有扩展步骤都已经尝试,则回溯到上一步,尝试其他可能的扩展步骤。 + +以下是回溯算法的一般伪代码: + +```java +result = [] +function backtrack(solution, candidates): //入参可以理解为 路径, 选择列表 + if solution 是一个完整解: //满足结束条件 + result.add(solution) // 处理当前完整解 + return + for candidate in candidates: + if candidate 满足约束条件: + solution.add(candidate) // 扩展解 + backtrack(solution, new_candidates) // 递归调用 + solution.remove(candidate) // 回溯,撤销选择 + +``` + +对应到 java 的一般框架如下: + +```java +public void backtrack(List tempList, int start, int[] nums) { + // 1. 终止条件 + if (tempList.size() == nums.length) { + // 找到一个解 + result.add(new ArrayList<>(tempList)); + return; + } + + for (int i = start; i < nums.length; i++) { + // 2. 剪枝:跳过相同的数字,避免重复 + if (i > start && nums[i] == nums[i - 1]) { + continue; + } + + // 3. 做出选择 + tempList.add(nums[i]); + + // 4. 递归 + backtrack(tempList, i + 1, nums); + + // 5. 撤销选择 + tempList.remove(tempList.size() - 1); + } +} +``` + +**其实就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」** + +> 💡 **关键理解**:回溯算法 = 递归 + 选择 + 撤销选择 + + + +### 🎯 常见题型 + +回溯法,一般可以解决如下几种问题: + +- 🎲 **组合问题**:N个数里面按一定规则找出k个数的集合 + + - **题目示例**:`LeetCode 39. 组合总和`,`LeetCode 40. 组合总和 II`,`LeetCode 77. 组合` + - **解题思路**: 组合问题要求我们在给定的数组中选取若干个数字,组合成目标值或某种形式的子集。回溯算法的基本思路是从一个起点开始,选择当前数字或者跳过当前数字,直到找到一个合法的组合。组合问题通常有**去重**的要求,避免重复的组合。 + +- 🔄 **排列问题**:N个数按一定规则全排列,有几种排列方式 + + - **题目示例**:`LeetCode 46. 全排列`,`LeetCode 47. 全排列 II`,`LeetCode 31. 下一个排列` + - **解题思路**: 排列问题要求我们通过给定的数字生成所有可能的排列。回溯算法通过递归生成所有排列,通过交换位置来改变元素的顺序。对于全排列 II 这类题目,必须处理重复元素的问题,确保生成的排列不重复。 + +- 📦 **子集问题**:一个N个数的集合里有多少符合条件的子集 + + - **题目示例**:`LeetCode 78. 子集`,`LeetCode 90. 子集 II` + - **解题思路**: 子集问题要求我们生成数组的所有子集,回溯算法通过递归生成每个可能的子集。在生成子集时,每个元素有两种选择——要么包含它,要么不包含它。因此,回溯法通过逐步选择来生成所有的子集。 + +- ✂️ **切割问题**:一个字符串按一定规则有几种切割方式 + + - 题目实例:` LeetCode 416. Partition Equal Subset Sum`,`LeetCode 698. Partition to K Equal Sum Subsets` + - 解题思路:回溯法适用于切割问题中的"探索所有可能的分割方式"的场景。特别是在无法直接通过动态规划推导出最优解时,回溯法通过递归尝试所有可能的分割方式,并通过剪枝减少不必要的计算 + +- ♟️ **棋盘问题**: + + - **题目示例**:`LeetCode 37. 解数独`,`LeetCode 51. N 皇后`,`LeetCode 52. N 皇后 II` + + **解题思路**: 棋盘问题常常涉及到在二维数组中进行回溯搜索,比如在数独中填入数字,或者在 N 皇后问题中放置皇后。回溯法在这里用于逐步尝试每个位置,满足棋盘的约束条件,直到找到一个解或者回溯到一个合法的状态。 + +- 🗺️ **图的遍历问题**: + + - **题目示例**:`LeetCode 79. 单词搜索`,`LeetCode 130. 被围绕的区域` + - **解题思路**: 回溯算法在图遍历中的应用主要是通过递归搜索路径。常见的问题是从某个起点出发,寻找是否存在某个目标路径。通过回溯算法,可以逐步尝试每一个可能的路径,直到找到符合条件的解。 + + + + +### ⚡ 回溯算法的优化技巧 + +1. ✂️ **剪枝**:在递归过程中,如果当前路径不符合问题约束,就提前返回,避免继续深入。例如在排列问题中,遇到重复的数字时可以跳过该分支。 +2. 📊 **排序**:对输入数据进行排序,有助于我们在递归时判断是否可以剪枝,尤其是在去重的场景下。 +3. 🗜️ **状态压缩**:在一些问题中,使用位运算或其他方式对状态进行压缩,可以显著减少存储空间和计算时间。例如在解决旅行商问题时,常常使用状态压缩来存储已经访问的节点。 +4. 🛑 **提前终止**:如果在递归过程中发现某条路径不可能达到目标(例如目标已经超过了剩余可用值),可以直接结束该分支,节省时间。 + + + +# 二、热门面试题 + +## 🎲 排列、组合类 + +> 无论是排列、组合还是子集问题,简单说无非就是让你从序列 `nums` 中以给定规则取若干元素,主要有以下几种变体: +> +> **元素无重不可复选,即 `nums` 中的元素都是唯一的,每个元素最多只能被使用一次,这也是最基本的形式**。 +> +> - 以组合为例,如果输入 `nums = [2,3,6,7]`,和为 7 的组合应该只有 `[7]`。 +> +> **元素可重不可复选,即 `nums` 中的元素可以存在重复,每个元素最多只能被使用一次**。 +> +> - 以组合为例,如果输入 `nums = [2,5,2,1,2]`,和为 7 的组合应该有两种 `[2,2,2,1]` 和 `[5,2]`。 +> +> **元素无重可复选,即 `nums` 中的元素都是唯一的,每个元素可以被使用若干次**。 +> +> - 以组合为例,如果输入 `nums = [2,3,6,7]`,和为 7 的组合应该有两种 `[2,2,3]` 和 `[7]`。 +> +> 当然,也可以说有第四种形式,即元素可重可复选。但既然元素可复选,那又何必存在重复元素呢?元素去重之后就等同于形式三,所以这种情况不用考虑。 +> +> 上面用组合问题举的例子,但排列、组合、子集问题都可以有这三种基本形式,所以共有 9 种变化。 +> +> 除此之外,题目也可以再添加各种限制条件,比如让你求和为 `target` 且元素个数为 `k` 的组合,那这么一来又可以衍生出一堆变体,怪不得面试笔试中经常考到排列组合这种基本题型。 +> +> **但无论形式怎么变化,其本质就是穷举所有解,而这些解呈现树形结构,所以合理使用回溯算法框架,稍改代码框架即可把这些问题一网打尽**。 + + + +### 🎯 一、元素无重不可复选 + +#### 📦 [子集_78](https://leetcode.cn/problems/subsets/) + +> 给你一个整数数组 `nums` ,数组中的元素 **互不相同** 。返回该数组所有可能的子集(幂集)。 +> +> 解集 **不能** 包含重复的子集。你可以按 **任意顺序** 返回解集。 +> +> ``` +> 输入:nums = [1,2,3] +> 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] +> ``` + +**💡 思路**: + +**子集的特性**: + +- 对于给定的数组 `[1, 2, 3]`,它的所有子集应该包括空集、单个元素的子集、两个元素的组合和完整数组。 +- 每个元素都有两种选择:要么加入子集,要么不加入子集。 + +**回溯算法**: + +- 使用回溯的方式可以从空集开始,逐步添加元素来生成所有子集。 +- 从当前的元素出发,尝试包含它或者不包含它,然后递归地处理下一个元素。 + +参数定义: + +- `res`:一个列表,存储最终的所有子集,类型是 `List>`。 + +- `track`:一个临时列表,记录当前路径(即当前递归中形成的子集)。 +- `start`:当前递归要开始的位置(即考虑从哪个位置开始生成子集)。这个 `start` 是非常重要的,它确保了我们在递归时不会重复生成相同的子集。 + +完成回溯树的遍历就收集了所有子集。 + + + +```java +public List> subsets(int[] nums) { + List> res = new ArrayList<>(); + // 记录回溯算法的递归路径 + List track = new ArrayList<>(); + + if (nums.length == 0) { + return res; + } + + backtrack(nums, 0, res, track); + return res; +} + +private void backtrack(int[] nums, int start, List> res, List track) { + + res.add(new ArrayList<>(track)); + + // 回溯算法标准框架 + for (int i = start; i < nums.length; i++) { + // 做选择 + track.add(nums[i]); + // 通过 start 参数控制树枝的遍历,避免产生重复的子集 + backtrack(nums, i + 1, res, track); + // 撤销选择 + track.remove(track.size() - 1); + } +} +``` + + + +#### 🎲 [组合_77](https://leetcode.cn/problems/combinations/) + +> 给定两个整数 `n` 和 `k`,返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。你可以按 **任何顺序** 返回答案。 +> +> ``` +> 输入:n = 4, k = 2 +> 输出: +> [ +> [2,4], +> [3,4], +> [2,3], +> [1,2], +> [1,3], +> [1,4], +> ] +> ``` + +**💡 思路**:翻译一下就变成子集问题了:**给你输入一个数组 `nums = [1,2..,n]` 和一个正整数 `k`,请你生成所有大小为 `k` 的子集**。 + + + +反映到代码上,只需要稍改 base case,控制算法仅仅收集第 `k` 层节点的值即可: + +```java +public List> combine(int n, int k) { + List> res = new ArrayList<>(); + // 记录回溯算法的递归路径 + List track = new ArrayList<>(); + // start 从 1 开始即可 + backtrack(n, k, 1, track, res); + return res; +} + +private void backtrack(int n, int k, int start, List track, List> res) { + // 遍历到了第 k 层,收集当前节点的值 + if (track.size() == k) { + res.add(new ArrayList<>(track)); // 深拷贝 + return; + } + + // 从当前数字开始尝试 + for (int i = start; i <= n; i++) { + track.add(i); + // 通过 start 参数控制树枝的遍历,避免产生重复的子集 + backtrack(n, k, i + 1, track, res); // 递归选下一个数字 + track.remove(track.size() - 1); // 撤销选择 + } +} +``` + + + +#### 🔄 [全排列_46](https://leetcode.cn/problems/permutations/description/) + +> 给定一个不含重复数字的数组 `nums` ,返回其 *所有可能的全排列* 。你可以 **按任意顺序** 返回答案。 +> +> ``` +> 输入:nums = [1,2,3] +> 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +> ``` + +**💡 思路**:组合/子集问题使用 `start` 变量保证元素 `nums[start]` 之后只会出现 `nums[start+1..]`中的元素,通过固定元素的相对位置保证不出现重复的子集。 + +**但排列问题本身就是让你穷举元素的位置,`nums[i]` 之后也可以出现 `nums[i]` 左边的元素,所以之前的那一套玩不转了,需要额外使用 `used` 数组来标记哪些元素还可以被选择**。 + +全排列共有 `n!` 个,我们可以按阶乘举例的思想,画出「回溯树」 + + + +> 回溯树是一种树状结构,树的每个节点表示一个状态(即当前的选择或部分解),树的每条边表示一次决策的选择。在回溯过程中,我们从根节点开始,递归地选择下一个数字,每次递归都相当于进入树的下一层。 + +> **labuladong 称为 决策树,你在每个节点上其实都在做决策**。因为比如你选了 2 之后,只能再选 1 或者 3,全排列是不允许重复使用数字的。**`[2]` 就是「路径」,记录你已经做过的选择;`[1,3]` 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层叶子节点,这里也就是选择列表为空的时候**。 + +```java +public class Solution { + public List> permute(int[] nums) { + List> res = new ArrayList<>(); + // 记录「路径」 + List track = new ArrayList<>(); + boolean[] used = new boolean[nums.length]; // 标记数字是否被使用过 + backtrack(nums, used, track, res); + return res; + } + + private void backtrack(int[] nums, boolean[] used, List track, List> res) { + // 当排列的大小达到nums.length时,说明当前排列完成 + if (track.size() == nums.length) { + res.add(new ArrayList<>(track)); // 将当前排列加入结果 + return; + } + + // 尝试每一个数字 + for (int i = 0; i < nums.length; i++) { + if (used[i]) continue; // 如果当前数字已经被使用过,则跳过,剪枝操作 + + // 做选择 + track.add(nums[i]); + used[i] = true; // 标记当前数字为已使用 + + // 递归进入下一层 + backtrack(nums, used, track, res); + + // 撤销选择 + track.remove(track.size() - 1); + used[i] = false; // 回溯时将当前数字标记为未使用 + } + } +} +``` + + + +### 🔄 二、元素可重不可复选 + +#### 📦 [子集 II_90](https://leetcode.cn/problems/subsets-ii/) + +> 给你一个整数数组 `nums` ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。 +> +> 解集 **不能** 包含重复的子集。返回的解集中,子集可以按 **任意顺序** 排列。 +> +> ``` +> 输入:nums = [1,2,2] +> 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] +> ``` + +**💡 思路**:该问题的关键是**去重**(剪枝),**体现在代码上,需要先进行排序,让相同的元素靠在一起,如果发现 `nums[i] == nums[i-1]`,则跳过** + +> LeetCode 78 **Subsets** 问题并没有重复的子集。我们生成的是所有可能的子集,并且不需要考虑去除重复的子集,因为给定的数组 `nums` 不含重复元素。而在 **Subsets II** 中,由于输入数组可能包含重复元素,所以我们需要特殊处理来避免生成重复的子集。 + +```java +public class Solution { + public List> subsetsWithDup(int[] nums) { + List> res = new ArrayList<>(); + Arrays.sort(nums); // 排序,确保相同的元素相邻 + backtrack(nums, 0, new ArrayList<>(), res); + return res; + } + + private void backtrack(int[] nums, int start, List track, List> res) { + // 每次递归时,将当前的track添加到结果中 + res.add(new ArrayList<>(track)); + + // 从start位置开始遍历 + for (int i = start; i < nums.length; i++) { + // 如果当前元素与前一个元素相同,并且前一个元素没有被选择,跳过当前元素 + if (i > start && nums[i] == nums[i - 1]) { + continue; // 剪枝 + } + + // 做选择 + track.add(nums[i]); + // 递归进入下一层 + backtrack(nums, i + 1, track, res); + // 撤销选择 + track.remove(track.size() - 1); + } + } +} +``` + + + +#### 🎯 [组合总和 II_40](https://leetcode.cn/problems/combination-sum-ii/) + +> 给定一个候选人编号的集合 `candidates` 和一个目标数 `target` ,找出 `candidates` 中所有可以使数字和为 `target` 的组合。 +> +> `candidates` 中的每个数字在每个组合中只能使用 **一次** 。 +> +> **注意:**解集不能包含重复的组合。 +> +> ``` +> 输入: candidates = [10,1,2,7,6,1,5], target = 8, +> 输出: +> [ +> [1,1,6], +> [1,2,5], +> [1,7], +> [2,6] +> ] +> ``` + +**💡 思路**:说这是一个组合问题,其实换个问法就变成子集问题了:请你计算 `candidates` 中所有和为 `target` 的子集。 + +1. **排序**:首先对 `candidates` 数组进行排序,排序后的数组方便处理重复数字。 +2. **递归选择**:在递归过程中,确保如果当前数字和上一个数字相同,且上一个数字没有被选择过,则跳过当前数字,从而避免重复组合。 +3. **递归终止条件**:如果 `target` 变为 0,表示找到了一个符合条件的组合;如果 `target` 小于 0,表示当前路径不合法,应该回溯。 + +```java +public class Solution { + public List> combinationSum2(int[] candidates, int target) { + List> res = new ArrayList<>(); + Arrays.sort(candidates); // 排序,便于后续去重 + backtrack(candidates, target, 0, new ArrayList<>(), res); + return res; + } + + private void backtrack(int[] candidates, int target, int start, List track, List> res) { + // 当目标值为0时,找到一个符合条件的组合 + if (target == 0) { + res.add(new ArrayList<>(track)); // 复制当前组合并加入结果 + return; + } + + // 遍历候选数组 + for (int i = start; i < candidates.length; i++) { + // 剪枝:当前数字大于目标值,后续不可能有合法的组合 + if (candidates[i] > target) { + break; + } + // 剪枝:跳过重复的数字 + if (i > start && candidates[i] == candidates[i - 1]) { + continue; + } + + // 做选择:选择当前数字 + track.add(candidates[i]); + // 递归,注意i + 1表示下一个位置,确保每个数字只使用一次 + backtrack(candidates, target - candidates[i], i + 1, track, res); + // 撤销选择 + track.remove(track.size() - 1); + } + } +} + +``` + + + +#### 🔄 [全排列 II_47](https://leetcode.cn/problems/permutations-ii/) + +> 给定一个可包含重复数字的序列 `nums` ,***按任意顺序*** 返回所有不重复的全排列。 +> +> ``` +> 输入:nums = [1,1,2] +> 输出: +> [[1,1,2], +> [1,2,1], +> [2,1,1]] +> ``` + +**💡 思路**:典型的回溯 + +1. **排序**:排序的目的是为了能够在回溯时做出剪枝选择。如果两个数字相同,并且前一个数字未被使用过,那么就可以跳过当前数字,避免产生重复的排列 +2. **回溯过程**:`backtrack` 是核心的递归函数。它每次递归时尝试将 `nums` 中的元素添加到 `track` 列表中。当 `track` 的大小等于 `nums` 的长度时,说明我们找到了一个排列,加入到结果 `res` 中。 + - **标记数字是否被使用**:用一个布尔数组 `used[]` 来标记当前数字是否已经在某一层递归中被使用过,避免重复排列。 + - **剪枝条件**:如果当前数字和前一个数字相同,并且前一个数字还没有被使用过,那么跳过当前数字,因为此时使用相同的数字会导致重复的排列。 +3. **递归回溯的选择与撤销**: + - **做选择**:选择当前数字 `nums[i]`,将其标记为已用,并添加到当前排列中。 + - **递归**:继续递归地选择下一个数字,直到 `track` 的大小等于 `nums` 的大小。 + - **撤销选择**:递归回到上一层时,撤销刚刚的选择,将当前数字从 `track` 中移除,并将其标记为未使用。 + +> 剪枝的逻辑: +> +> - **第一轮回溯**:选择第一个 `1`,然后继续递归选择,生成一个排列。 +> +> - **第二轮回溯**:当我们尝试选择第二个 `1` 时,假如第一个 `1` 没有被使用,第二个 `1` 会被选择,这时就会生成一个和第一轮相同的排列。为避免这种情况,我们需要 **剪枝**。 + +```java +public class Solution { + public List> permuteUnique(int[] nums) { + List> res = new ArrayList<>(); + Arrays.sort(nums); // 排序,确保相同元素相邻 + backtrack(nums, new boolean[nums.length], new ArrayList<>(), res); + return res; + } + + private void backtrack(int[] nums, boolean[] used, List track, List> res) { + // 当排列的大小达到nums.length时,找到一个合法排列 + if (track.size() == nums.length) { + res.add(new ArrayList<>(track)); // 加入当前排列 + return; + } + + // 遍历数组,递归生成排列 + for (int i = 0; i < nums.length; i++) { + // 剪枝:当前元素已经被使用过,跳过 + if (used[i]) continue; + // 剪枝:跳过相同的元素,避免生成重复排列 + if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; + + // 做选择 + track.add(nums[i]); + used[i] = true; // 标记当前元素已使用 + + // 递归 + backtrack(nums, used, track, res); + + // 撤销选择 + track.remove(track.size() - 1); + used[i] = false; // 标记当前元素未使用 + } + } +} + +``` + + + +### ♻️ 三、元素无重复可复选 + +输入数组无重复元素,但每个元素可以被无限次使用 + +#### 🎯 [组合总和_39](https://leetcode.cn/problems/combination-sum/) + +> 给你一个 **无重复元素** 的整数数组 `candidates` 和一个目标整数 `target` ,找出 `candidates` 中可以使数字和为目标数 `target` 的 所有 **不同组合** ,并以列表形式返回。你可以按 **任意顺序** 返回这些组合。 +> +> `candidates` 中的 **同一个** 数字可以 **无限制重复被选取** 。如果至少一个数字的被选数量不同,则两种组合是不同的。 +> +> 对于给定的输入,保证和为 `target` 的不同组合数少于 `150` 个。 +> +> ``` +> 输入:candidates = [2,3,6,7], target = 7 +> 输出:[[2,2,3],[7]] +> 解释: +> 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 +> 7 也是一个候选, 7 = 7 。 +> 仅有这两种组合。 +> ``` + +**💡 思路**:**元素无重可复选,即 `nums` 中的元素都是唯一的,每个元素可以被使用若干次**,只要删掉去重逻辑即可 + +```java +public class Solution { + public List> combinationSum(int[] candidates, int target) { + List> res = new ArrayList<>(); + List track = new ArrayList<>(); + backtrack(candidates, target, 0, track, res); + return res; + } + + private void backtrack(int[] candidates, int target, int start, List track, List> res) { + // 如果目标值为0,表示当前组合符合条件 + if (target == 0) { + res.add(new ArrayList<>(track)); // 将当前组合加入结果 + return; + } + + // 遍历候选数组 + for (int i = start; i < candidates.length; i++) { + // 如果当前数字大于目标值,跳过 + if (candidates[i] > target) continue; + + // 做选择:选择当前数字 + track.add(candidates[i]); + + // 递归,注意这里传入 i,因为可以重复选择当前数字 + backtrack(candidates, target - candidates[i], i, track, res); + + // 撤销选择 + track.remove(track.size() - 1); + } + } +} +``` + + + +## 其他问题 + +### 📞 [电话号码的字母组合_17](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) + +> 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 +> +> 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 +> +>  +> +> ``` +> 输入:digits = "23" +> 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"] +> ``` + +**💡 思路**:回溯,递归地尝试每一位数字对应的所有字母,直到找出所有有效的组合 + +首先,我们需要将每个数字 2 到 9 映射到其对应的字母,可以用 Map , 也可以用数组。然后就是递归处理。 + +**递归终止条件**:当当前组合的长度与输入的数字字符串长度相同,就说明我们已经得到了一个有效的组合,可以将其加入结果集。 + + + +```java +public class Solution { + public List letterCombinations(String digits) { + List res = new ArrayList<>(); + if (digits == null || digits.length() == 0) { + return res; // 如果输入为空,返回空结果 + } + + // 数字到字母的映射 + String[] mapping = { + "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" + }; + + // 使用回溯法生成字母组合 + backtrack(digits, 0, mapping, new StringBuilder(), res); + return res; + } + + private void backtrack(String digits, int index, String[] mapping, StringBuilder current, List res) { + // 如果当前组合的长度等于输入的长度,说明已经生成了一个有效的字母组合 + if (index == digits.length()) { + res.add(current.toString()); + return; + } + + // 获取当前数字对应的字母 + String letters = mapping[digits.charAt(index) - '0']; + + // 递归选择字母 + for (char letter : letters.toCharArray()) { + current.append(letter); // 选择一个字母 + backtrack(digits, index + 1, mapping, current, res); // 递归处理下一个数字 + current.deleteCharAt(current.length() - 1); // 撤销选择 + } + } +} +``` + + + +### 🔗 [括号生成_22](https://leetcode.cn/problems/generate-parentheses/) + +> 数字 `n` 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 **有效的** 括号组合。 +> +> ``` +> 输入:n = 3 +> 输出:["((()))","(()())","(())()","()(())","()()()"] +> ``` + +**💡 思路**: + + + +```java + +public List generateParenthesis(int n) { + List res = new ArrayList<>(); + // 回溯过程中的路径 + StringBuilder track = new StringBuilder(); + if (n == 0) { + return res; + } + trackback(n, n, res, track); + return res; + } + + // 可用的左括号数量为 left 个,可用的右括号数量为 right 个 + private void trackback(int left, int right, List res, StringBuilder track) { + //如果剩余的左括号数量大于右括号数量 + if (left < 0 || right < 0 || left > right) { + return; + } + + // 当所有括号都恰好用完时,得到一个合法的括号组合 + if (left == 0 && right == 0) { + res.add(track.toString()); + return; + } + + // 做选择,尝试放一个左括号 + track.append('('); + trackback(left - 1, right, res, track); + // 撤销选择 + track.deleteCharAt(track.length() - 1); + + track.append(')'); + trackback(left, right - 1, res, track); + track.deleteCharAt(track.length() - 1); + + } +``` + + + +### ♛ [N 皇后_51](https://leetcode.cn/problems/n-queens/) + +> 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 +> +> **n 皇后问题** 研究的是如何将 `n` 个皇后放置在 `n×n` 的棋盘上,并且使皇后彼此之间不能相互攻击。 +> +> 给你一个整数 `n` ,返回所有不同的 **n 皇后问题** 的解决方案。 +> +> 每一种解法包含一个不同的 **n 皇后问题** 的棋子放置方案,该方案中 `'Q'` 和 `'.'` 分别代表了皇后和空位。 +> +>  +> +> ``` +>输入:n = 4 +> 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] +> 解释:如上图所示,4 皇后问题存在两个不同的解法。 +> ``` + +**💡 思路**:通过回溯算法逐行放置皇后,每次递归时确保当前行、列和对角线不被其他皇后攻击。通过标记已占用的列和对角线,避免重复搜索,最终生成所有合法的解。 + +- 如果在某一列或对角线处已有皇后,就不能在该位置放置皇后。我们可以使用三个辅助数组来追踪列和对角线的使用情况: + - `cols[i]`:表示第 `i` 列是否已经放置了皇后。 + - `diag1[i]`:表示从左上到右下的对角线(`row - col`)是否已经有皇后。 + - `diag2[i]`:表示从右上到左下的对角线(`row + col`)是否已经有皇后。 + +- 主对角线是从左上角到右下角的对角线、副对角线是从右上角到左下角的对角线 + +```java +public class NQueens { + + public List> solveNQueens(int N) { + List> result = new ArrayList<>(); + char[][] board = new char[N][N]; + + // 初始化棋盘,每个位置设为'.' + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + board[i][j] = '.'; + } + } + + // 用来记录列、主对角线、副对角线是否已被占用 + boolean[] cols = new boolean[N]; // 列占用标记 + boolean[] diag1 = new boolean[2 * N - 1]; // 主对角线占用标记 + boolean[] diag2 = new boolean[2 * N - 1]; // 副对角线占用标记 + + backtrack(N, 0, board, cols, diag1, diag2, result); + return result; + } + + // 回溯函数 + private void backtrack(int N, int row, char[][] board, boolean[] cols, boolean[] diag1, boolean[] diag2, List> result) { + if (row == N) { // 如果已经放置了 N 个皇后 + List solution = new ArrayList<>(); + for (int i = 0; i < N; i++) { + solution.add(new String(board[i])); // 将每一行转化为字符串并添加到结果中 + } + result.add(solution); + return; + } + + // 遍历每一列,尝试放置皇后 + for (int col = 0; col < N; col++) { + // 判断当前位置是否可以放置皇后 + if (cols[col] || diag1[row - col + (N - 1)] || diag2[row + col]) { + continue; // 如果列、主对角线或副对角线已被占用,跳过当前列 + } + + // 放置皇后 + board[row][col] = 'Q'; + cols[col] = true; // 标记该列已被占用 + diag1[row - col + (N - 1)] = true; // 标记主对角线已被占用 + diag2[row + col] = true; // 标记副对角线已被占用 + + // 递归放置下一行的皇后 + backtrack(N, row + 1, board, cols, diag1, diag2, result); + + // 回溯,撤销当前位置的选择 + board[row][col] = '.'; + cols[col] = false; + diag1[row - col + (N - 1)] = false; + diag2[row + col] = false; + } + } + + // 打印结果 + public void printSolutions(List> solutions) { + for (List solution : solutions) { + for (String row : solution) { + System.out.println(row); + } + System.out.println(); + } + } + + public static void main(String[] args) { + NQueens nq = new NQueens(); + List> solutions = nq.solveNQueens(4); + nq.printSolutions(solutions); + } +} + +``` + + + + + +## 📚 参考与感谢: + +- https://yuminlee2.medium.com/combinations-and-combination-sum-3ed2accc8d12 +- https://medium.com/@sunshine990316/leetcode-python-backtracking-summary-medium-1-e8ae88839e85 +- https://blog.devgenius.io/10-daily-practice-problems-day-18-f7293b55224d +- [hello 算法- 回溯算法](https://www.hello-algo.com/chapter_backtracking/backtracking_algorithm/#1312) + +--- + +> 🎉 **恭喜你完成了回溯算法的学习!** 回溯算法是解决很多复杂问题的强大工具,掌握了它,你就拥有了解决排列、组合、子集等问题的钥匙。记住:**选择 → 递归 → 撤销选择** 是回溯的核心思想! diff --git a/docs/data-structure-algorithms/algorithm/Binary-Search.md b/docs/data-structure-algorithms/algorithm/Binary-Search.md new file mode 100755 index 0000000000..9fb5f32679 --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/Binary-Search.md @@ -0,0 +1,1030 @@ +--- +title: 二分查找 +date: 2023-02-09 +tags: + - binary-search + - algorithms +categories: algorithms +--- + + + +> 二分查找【折半查找】,一种简单高效的搜索算法,一般是利用有序数组的特性,通过逐步比较中间元素来快速定位目标值。 +> +> 二分查找并不简单,Knuth 大佬(发明 KMP 算法的那位)都说二分查找:**思路很简单,细节是魔鬼**。比如二分查找让人头疼的细节问题,到底要给 `mid` 加一还是减一,while 里到底用 `<=` 还是 `<`。 + +## 一、二分查找基础框架 + +```java +int binarySearch(int[] nums, int target) { + int left = 0, right = ...; + + while(...) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + ... + } else if (nums[mid] < target) { + left = ... + } else if (nums[mid] > target) { + right = ... + } + } + return ...; +} +``` + +**分析二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节**。本文都会使用 else if,旨在讲清楚,读者理解后可自行简化。 + +其中 `...` 标记的部分,就是可能出现细节问题的地方,当你见到一个二分查找的代码时,首先注意这几个地方。 + +**另外提前说明一下,计算 `mid` 时需要防止溢出**,代码中 `left + (right - left) / 2` 就和 `(left + right) / 2` 的结果相同,但是有效防止了 `left` 和 `right` 太大,直接相加导致溢出的情况。 + + + +## 二、二分查找性能分析 + +**时间复杂度**:二分查找的时间复杂度为 $O(log n)$,其中 n 是数组的长度。这是因为每次比较后,搜索范围都会减半,非常高效。 + +> logn 是一个非常“恐怖”的数量级,即便 n 非常非常大,对应的 logn 也很小。比如 n 等于 2 的 32 次方,这个数很大了吧?大约是 42 亿。也就是说,如果我们在 42 亿个数据中用二分查找一个数据,最多需要比较 32 次。 + +**空间复杂度**: + +- 迭代法:$O(1)$,因为只需要常数级别的额外空间。 +- 递归法:$O(log n)$,因为递归调用会占用栈空间。 + +**最坏情况**:最坏情况下,目标值位于数组两端或不存在,需要$log n$次比较才能确定。 + +**二分查找与其他搜索算法的比较**: + +- 线性搜索:线性搜索简单,时间复杂度为$O(n)$,但在大规模数据集上效率较低。 + +- 哈希:哈希在查找上能提供平均$O(1)$的时间复杂度,但需要额外空间存储哈希表,且对数据有序性无要求。 + + + +## 三、刷刷热题 + +- 二分查找,可以用循环(迭代)实现,也可以用递归实现 +- 二分查找依赖的是顺序表结构(也就是数组) +- 二分查找针对的是有序数组 +- 数据量太小太大都不是很适用二分(太小直接顺序遍历就够了,太大的话对连续内存空间要求更高) + +### [二分查找『704』](https://leetcode.cn/problems/binary-search/)(基本的二分搜索) + +> 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 +> + +```java +int binarySearch(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; // 注意 + + while(left <= right) { + int mid = left + (right - left) / 2; + if(nums[mid] == target) + return mid; + else if (nums[mid] < target) + left = mid + 1; // 注意 + else if (nums[mid] > target) + right = mid - 1; // 注意 + } + return -1; +} +``` + +**时间复杂度**:O(log n),每次都将搜索范围缩小一半 +**空间复杂度**:O(1),只使用常量级别的额外空间 + +**1、为什么 while 循环的条件中是 <=,而不是 <**? + +答:因为初始化 `right` 的赋值是 `nums.length - 1`,即最后一个元素的索引,而不是 `nums.length`。 + +这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 `[left, right]`,后者相当于左闭右开区间 `[left, right)`。因为索引大小为 `nums.length` 是越界的,所以我们把 `right` 这一边视为开区间。 + +我们这个算法中使用的是前者 `[left, right]` 两端都闭的区间。**这个区间其实就是每次进行搜索的区间**。 + +**2、为什么 `left = mid + 1`,`right = mid - 1`?我看有的代码是 `right = mid` 或者 `left = mid`,没有这些加加减减,到底怎么回事,怎么判断**? + +答:这也是二分查找的一个难点,不过只要你能理解前面的内容,就能够很容易判断。 + +刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即 `[left, right]`。那么当我们发现索引 `mid` 不是要找的 `target` 时,下一步应该去搜索哪里呢? + +当然是去搜索区间 `[left, mid-1]` 或者区间 `[mid+1, right]` 对不对?**因为 `mid` 已经搜索过,应该从搜索区间中去除**。 + +> ##### 1. **左闭右闭区间 `[left, right]`** +> +> - **循环条件**:`while (left <= right)`,因为 `left == right` 时区间仍有意义。 +> - 边界调整: +> - `nums[mid] < target` → `left = mid + 1`(排除 `mid` 左侧) +> - `nums[mid] > target` → `right = mid - 1`(排除 `mid` 右侧) +> - 适用场景:明确目标值存在于数组时,直接返回下标。 +> +> ##### 2. **左闭右开区间 `[left, right)`** +> +> - **初始化**:`right = nums.length`。 +> - **循环条件**:`while (left < right)`,因为 `left == right` 时区间为空。 +> - 边界调整: +> - `nums[mid] < target` → `left = mid + 1` +> - `nums[mid] > target` → `right = mid`(右开,不包含 `mid`) +> - **适用场景**:需要处理目标值可能不在数组中的情况,例如插入位置问题 + +> 比如说给你有序数组 `nums = [1,2,2,2,3]`,`target` 为 2,此算法返回的索引是 2,没错。但是如果我想得到 `target` 的左侧边界,即索引 1,或者我想得到 `target` 的右侧边界,即索引 3,这样的话此算法是无法处理的。 +> +> 所以又有了一些含有重复元素,带有边界问题的二分。 + +### 寻找左侧边界的二分搜索 + +```java +public int leftBound(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; + while (left <= right) { + int mid = (right - left) / 2 + left; + if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + //mid 是第一个元素,或者前一个元素不等于查找值,锁定,且返回的是mid + if (mid == 0 || nums[mid - 1] != target) return mid; + else right = mid - 1; + } + } + return -1; +} +``` + +### 寻找右侧边界的二分查找 + +```java +public int rightBound(int[] nums, int target){ + int left = 0; + int right = nums.length - 1; + while(left <= right){ + int mid = left + (right - left)/2; + if(nums[mid] > target){ + right = mid - 1; + }else if(nums[mid] < target){ + left = mid +1; + }else{ + if(mid == nums.length - 1 || nums[mid +1] != target) return mid; + else left = mid + 1; + } + } + return -1; +} +``` + +### 查找第一个大于等于给定值的元素 + +```JAVA +//查找第一个大于等于给定值的元素 1,3,5,7,9 找出第一个大于等于5的元素 +public int firstNum(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] >= target) { + if (mid == 0 || nums[mid - 1] < target) return mid; + else right = mid - 1; + } else { + left = mid + 1; + } + } + return -1; +} +``` + + + +### [搜索旋转排序数组『33』](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) + +> 整数数组 nums 按升序排列,数组中的值 互不相同 。 +> +> 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 +> +> 给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。 +> +> ``` +> 输入:nums = [4,5,6,7,0,1,2], target = 0 +> 输出:4 +> ``` +> +> ``` +> 输入:nums = [4,5,6,7,0,1,2], target = 3 +> 输出:-1 +> ``` + +**思路**: + +对于有序数组(部分有序也可以),可以使用二分查找的方法查找元素。 + +旋转数组后,依然是局部有序,从数组中间分成左右两部分后,一定有一部分是有序的 + +- 如果 [L, mid - 1] 是有序数组,且 target 的大小满足 [nums[L],nums[mid],则我们应该将搜索范围缩小至 [L, mid - 1],否则在 [mid + 1, R] 中寻找。 +- 如果 [mid, R] 是有序数组,且 target 的大小满足 ({nums}[mid+1],{nums}[R]],则我们应该将搜索范围缩小至 [mid + 1, R],否则在 [l, mid - 1] 中寻找。 + +```java +public int search(int[] nums, int target) { + if (nums == null || nums.length == 0) return -1; + + int left = 0, right = nums.length - 1; + + while (left <= right) { + int mid = left + (right - left) / 2; + + if (nums[mid] == target) { + return mid; // 找到目标 + } + + // 判断左半部分是否有序 + if (nums[left] <= nums[mid]) { + // 左半部分有序 + if (nums[left] <= target && target < nums[mid]) { + right = mid - 1; // 目标在左半部分 + } else { + left = mid + 1; // 目标在右半部分 + } + } else { + // 右半部分有序 + if (nums[mid] < target && target <= nums[right]) { + left = mid + 1; // 目标在右半部分 + } else { + right = mid - 1; // 目标在左半部分 + } + } + } + + return -1; // 未找到目标 +} +``` + +**时间复杂度**:$O(log n) $,二分查找的时间复杂度 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +### [在排序数组中查找元素的第一个和最后一个位置『34』](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/) + +> 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 +> +> 如果数组中不存在目标值 target,返回 [-1, -1]。 +> +> 你可以设计并实现时间复杂度为 $O(log n) $ 的算法解决此问题吗? +> +> ``` +> 输入:nums = [5,7,7,8,8,10], target = 8 +> 输出:[3,4] +> ``` +> +> ``` +> 输入:nums = [5,7,7,8,8,10], target = 6 +> 输出:[-1,-1] +> ``` + +**思路**:二分法寻找左右边界值 + +```java +public int[] searchRange(int[] nums, int target) { + int first = binarySearch(nums, target, true); + int last = binarySearch(nums, target, false); + return new int[]{first, last}; +} + +public int binarySearch(int[] nums, int target, boolean findLast) { + int length = nums.length; + int left = 0, right = length - 1; + //结果,因为可能有多个值,所以需要先保存起来 + int index = -1; + while (left <= right) { + //取中间值 + int middle = left + (right - left) / 2; + + //找到相同的值(只有这个地方和普通二分查找有不同) + if (nums[middle] == target) { + //先赋值一下,肯定是找到了,只是不知道这个值是不是在区域的边界内 + index = middle; + //如果是查找最后的 + if (findLast) { + //那我们将浮标移动到下一个值试探一下后面的值还是否有target + left = middle + 1; + } else { + //否则,就是查找第一个值,也是同理,移动指针到上一个值去试探一下上一个值是不是等于target + right = middle - 1; + } + + //下面2个就是普通的二分查找流程,大于小于都移动指针 + } else if (nums[middle] < target) { + left = middle + 1; + } else { + right = middle - 1; + } + + } + return index; +} +``` + +**时间复杂度**:$O(log n) $,需要进行两次二分查找 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +### [搜索插入位置『35』](https://leetcode.cn/problems/search-insert-position/) + +> 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 `O(log n)` 的算法。 +> +> ``` +> 输入: nums = [1,3,5,6], target = 2 +> 输出: 1 +> ``` + +```java +public int searchInsert(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; + //注意:特例处理 + if (nums[left] > target) return 0; + if (nums[right] < target) return right + 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + return mid; + } + } + //注意:这里如果没有查到,返回left,也就是需要插入的位置 + return left; + } +``` + +**时间复杂度**:$O(log n) $,标准的二分查找时间复杂度 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +### [寻找旋转排序数组中的最小值『153』](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) + +> 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到: +> 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2] 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7] +> 注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。 +> +>给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。 +> +>你必须设计一个时间复杂度为 $O(log n)$ 的算法解决此问题。 +> +>``` +> 输入:nums = [3,4,5,1,2] +> 输出:1 +> 解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。 +> ``` +> + +**思路**: + +升序数组+旋转,仍然是部分有序,考虑用二分查找。 + + + +> 我们先搞清楚题目中的数组是通过怎样的变化得来的,基本上就是等于将整个数组向右平移 + +> 这种二分查找难就难在,arr[mid] 跟谁比。 +> +> 我们的目的是:当进行一次比较时,一定能够确定答案在 mid 的某一侧。一次比较为 arr[mid] 跟谁比的问题。 +> 一般的比较原则有: +> +> - 如果有目标值 target,那么直接让 arr[mid] 和 target 比较即可。 +> - 如果没有目标值,一般可以考虑 **端点** +> +> 如果中值 < 右值,则最小值在左半边,可以收缩右边界。 +> 如果中值 > 右值,则最小值在右半边,可以收缩左边界。 + +旋转数组,最小值右侧的元素肯定都小于或等于数组中的最后一个元素 `nums[n-1]`,左侧元素都大于 `num[n-1]` + +```java +public static int findMin(int[] nums) { + int left = 0; + int right = nums.length - 1; + //左闭右开 + while (left < right) { + int mid = left + (right - left) / 2; + //疑问:为什么right = mid;而不是 right = mid-1; + //解答:{4,5,1,2,3},如果right = mid-1,则丢失了最小值1 + if (nums[mid] < nums[right]) { + right = mid; + } else { + left = mid + 1; + } + } + //循环结束条件,left = right,最小值输出nums[left]或nums[right]均可 + return nums[left]; +} +``` + +**时间复杂度**:$O(log n) $,每次都将搜索范围缩小一半 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + +**如果是求旋转数组中的最大值呢** + +```java +public static int findMax(int[] nums) { + int left = 0; + int right = nums.length - 1; + + while (left < right) { + int mid = left + (right - left) >> 1; + + //因为向下取整,left可能会等于mid,所以要考虑 + if (nums[left] < nums[right]) { + return nums[right]; + } + + //[left,mid] 是递增的,最大值只会在[mid,right]中 + if (nums[left] < nums[mid]) { + left = mid; + } else { + //[mid,right]递增,最大值只会在[left, mid-1]中 + right = mid - 1; + } + } + return nums[left]; +} +``` + +### [寻找重复数『287』](https://leetcode-cn.com/problems/find-the-duplicate-number/) + +> 长度为 n+1 的数组,元素在 1~n 之间,有且仅有一个重复数(可能重复多次)。要求不修改数组且只用 O (1) 空间。 +> +> ``` +>输入:nums = [1,3,4,2,2] +> 输出:2 +>``` +> +> ``` +> 输入:nums = [3,1,3,4,2] +> 输出:3 +>``` + +**思路**: + +- 统计小于等于 mid 的元素个数 +- 若 count > mid,说明重复数在 [1, mid] 区间 +- 否则在 [mid+1, n] 区间 + +> 抽屉原理:把 `10` 个苹果放进 `9` 个抽屉,至少有一个抽屉里至少放 `2` 个苹果。 + +```java +public int findDuplicate(int[] nums) { + int left = 0; + int right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + + // nums 中小于等于 mid 的元素的个数 + int count = 0; + for (int num : nums) { + //看这里,是 <= mid,而不是 nums[mid] + if (num <= mid) { + count += 1; + } + } + + // 根据抽屉原理,小于等于 4 的个数如果严格大于 4 个,此时重复元素一定出现在 [1..4] 区间里 + if (count > mid) { + // 重复元素位于区间 [left..mid] + right = mid - 1; + } else { + // if 分析正确了以后,else 搜索的区间就是 if 的反面区间 [mid + 1..right] + left = mid + 1; + } + } + return left; +} +``` + +**时间复杂度**:$O(n log n)$,每次需要遍历数组O(n) +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +### [寻找峰值『162』](https://leetcode-cn.com/problems/find-peak-element/) + +> 峰值元素是指其值严格大于左右相邻值的元素。 +> +> 给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。 +> +> 你可以假设 nums[-1] = nums[n] = -∞ 。 +> +> 你必须实现时间复杂度为 $O(log n) $的算法来解决此问题。 +> +> ``` +> 输入:nums = [1,2,3,1] +> 输出:2 +> 解释:3 是峰值元素,你的函数应该返回其索引 2。 +> ``` +> +> ``` +> 输入:nums = [1,2,1,3,5,6,4] +> 输出:1 或 5 +> 解释:你的函数可以返回索引 1,其峰值元素为 2; +> 或者返回索引 5, 其峰值元素为 6。 +> ``` + +**思路**: + +- 比较 `nums[mid]` 和 `nums[mid+1]` +- 如果 `nums[mid] < nums[mid+1]`,说明右侧一定有峰值 +- 否则左侧一定有峰值 + +**为什么有效**: + +- 峰值条件 `nums[i] > nums[i+1]` 保证单调性 +- 二分查找每次可以缩小一半搜索范围 + +```java +public int findPeakElement(int[] nums) { + int left = 0, right = nums.length - 1; + + while (left < right) { + int mid = left + (right - left) / 2; + + if (nums[mid] < nums[mid + 1]) { + // 右侧有峰值 + left = mid + 1; + } else { + // 左侧有峰值 + right = mid; + } + } + + return left; // left == right,即为峰值位置 +} +``` + +**时间复杂度**:$O(log n) $,二分查找的时间复杂度 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +### [搜索二维矩阵『74』](https://leetcode.cn/problems/search-a-2d-matrix/) + +> 给你一个满足以下两点的 `m x n` 矩阵: +> +> - 每行从左到右递增 +> - 每行第一个数大于上一行最后一个数 +> +> 判断目标值 `target` 是否在矩阵中。 +> +> 给你一个整数 `target` ,如果 `target` 在矩阵中,返回 `true` ;否则,返回 `false` 。 +> +>  +> +> ``` +> 输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 +> 输出:true +> ``` + +**思路**:由于每行的第一个元素都大于前一行的最后一个元素,整个矩阵可以被看作一个**完全有序的一维数组**。 + +例如,上面的示例矩阵可以被 “拉直” 为:`[1, 3, 5, 7, 10, 11, 16, 20, 23, 30, 34, 60]` + +要实现 “一次二分”,必须能将一维数组的索引 `mid`(二分查找中的中间位置)转换回二维矩阵的 `(行号, 列号)`,公式如下: + +- **行号 = mid /n**(整除,因为每一行有 `n` 个元素,商表示当前元素在第几行) +- **列号 = mid % n**(取余,余数表示当前元素在该行的第几列) + +举例: + +- 一维索引 `mid = 5`,`n = 4`(列数): + - 行号 = 5 / 4 = 1(第 2 行,索引从 0 开始) + - 列号 = 5 % 4 = 1(第 2 列) + - 对应矩阵值:`matrix[1][1] = 11`,与一维数组索引 5 的值一致。 + +```java +public boolean searchMatrix(int[][] matrix, int target) { + // 1. 处理边界情况:矩阵为空(行数为 0) + int m = matrix.length; + if (m == 0) { + return false; + } + + // 2. 处理边界情况:矩阵列数为空(每行没有元素) + int n = matrix[0].length; + if (n == 0) { + return false; + } + + // 3. 初始化二分查找的左右指针(对应一维数组的起始和末尾索引) + int left = 0; + int right = m * n - 1; // 总元素数 = 行数 × 列数,末尾索引 = 总元素数 - 1 + + // 4. 二分查找循环:left <= right 确保不遗漏元素 + while (left <= right) { + // 计算中间索引:避免 left + right 直接相加导致整数溢出 + int mid = left + (right - left) / 2; + + // 关键:将一维索引 mid 转换为二维矩阵的 (row, col) 坐标 + int row = mid / n; // 行号 = 中间索引 ÷ 列数(整除) + int col = mid % n; // 列号 = 中间索引 % 列数(取余) + + // 获取当前中间位置的矩阵值 + int midValue = matrix[row][col]; + + // 5. 比较 midValue 与 target,调整二分范围 + if (midValue == target) { + // 找到目标值,直接返回 true + return true; + } else if (midValue < target) { + // 中间值比目标小:目标在右半部分,left 移到 mid + 1 + left = mid + 1; + } else { + // 中间值比目标大:目标在左半部分,right 移到 mid - 1 + right = mid - 1; + } + } + + // 6. 循环结束仍未找到,说明目标不在矩阵中 + return false; +} +``` + +**时间复杂度**:$O(log(mn)) $:二分查找的时间复杂度是 `O(log(总元素数))`,总元素数为 `m×n`,因此复杂度为 `O(log(m×n))` +**空间复杂度**:$O(1)$:仅使用了 `m、n、left、right、mid、row、col、midValue` 等常数个变量,没有额外占用空间 + + + +### [搜索二维矩阵 II『240』](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/) + +> [剑指 Offer 04. 二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) 一样的题目 +> +> 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 +> +> 现有矩阵 matrix 如下: +> +>  +> +> ``` +> 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5 +> 输出:true +> ``` + +**思路**: + +站在左下角或者右上角看。这个矩阵其实就像是一个Binary Search Tree。然后,聪明的大家应该知道怎么做了。 + + + +有序的数组,我们首先应该想到二分 + +```java +public boolean searchMatrix(int[][] matrix, int target) { + // 1. 处理边界情况:矩阵为空、行数为0、或列数为0 + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return false; + } + + // 2. 获取矩阵的行数 m 和列数 n + int m = matrix.length; + int n = matrix[0].length; + + // 3. 初始化指针,从左下角开始 + int row = m - 1; // 行指针指向最后一行 + int col = 0; // 列指针指向第一列 + + // 4. 循环条件:行指针不能越界(>= 0),列指针不能越界(< n) + while (row >= 0 && col < n) { + // 获取当前指针指向的元素值 + int current = matrix[row][col]; + + // 5. 比较 current 与 target + if (current == target) { + // 找到了目标值,直接返回 true + return true; + } else if (current < target) { + // 当前值小于目标值:目标值不可能在当前行(因为行是递增的) + // 将列指针向右移动,去更大的区域查找 + col++; + } else { // current > target + // 当前值大于目标值:目标值不可能在当前列(因为列是递增的) + // 将行指针向上移动,去更小的区域查找 + row--; + } + } + + // 6. 如果循环结束仍未找到,说明目标值不存在于矩阵中 + return false; + } +} +``` + +**时间复杂度**:$O(m + n)$,其中m是行数,n是列数,最坏情况下需要遍历m+n个元素 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +### [最长递增子序列『300』](https://leetcode.cn/problems/longest-increasing-subsequence/) + +> 给你一个整数数组 `nums` ,找到其中最长严格递增子序列的长度。 +> +> **子序列** 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,`[3,6,2,7]`是数组 `[0,3,1,6,2,2,7]` 的子序列。 +> +> ``` +> 输入:nums = [10,9,2,5,3,7,101,18] +> 输出:4 +> 解释:最长递增子序列是 [2,3,7,101],因此长度为 4。 +> ``` + +**思路**: + +**动态规划解法**: + +```java +public int lengthOfLIS(int[] nums) { + if (nums == null || nums.length == 0) return 0; + int[] dp = new int[nums.length]; + //初始时,每个元素自身构成一个长度为1的子序列 + Arrays.fill(dp, 1); + // 记录全局最长递增子序列的长度,初始为1 + int maxLen = 1; + + for (int i = 1; i < nums.length; i++) { + // 对于每个i,检查所有j < i的元素 + for (int j = 0; j < i; j++) { + // 如果nums[j] < nums[i],说明nums[i]可以接在nums[j]后面 + if (nums[j] < nums[i]) { + // 更新dp[i]为dp[j] + 1和当前dp[i]的较大值 + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + // 更新全局最大值 + maxLen = Math.max(maxLen, dp[i]); + } + return maxLen; +} +``` + +**二分查找优化解法**: +```java +public int lengthOfLIS(int[] nums) { + if (nums == null || nums.length == 0) return 0; + + List tails = new ArrayList<>(); + + for (int num : nums) { + // 二分查找第一个大于等于num的位置 + int left = 0, right = tails.size(); + while (left < right) { + int mid = left + (right - left) / 2; + if (tails.get(mid) < num) { + left = mid + 1; + } else { + right = mid; + } + } + + // 如果找到末尾,说明num比所有元素都大,直接添加 + if (left == tails.size()) { + tails.add(num); + } else { + // 否则替换找到的位置 + tails.set(left, num); + } + } + + return tails.size(); +} +``` + +**时间复杂度**: +- 动态规划:$O(n²)$ +- 二分查找优化:$O(n log n)$ + +**空间复杂度**:$O(n)$,需要额外的数组空间 + + + +### [寻找两个正序数组的中位数『4』](https://leetcode.cn/problems/median-of-two-sorted-arrays/) + +> 给定两个大小分别为 `m` 和 `n` 的正序(从小到大)数组 `nums1` 和 `nums2`。请你找出并返回这两个正序数组的 **中位数** 。 +> +> 算法的时间复杂度应该为 `O(log (m+n))` 。 +> +> ``` +> 输入:nums1 = [1,3], nums2 = [2] +> 输出:2.00000 +> 解释:合并数组 = [1,2,3] ,中位数 2 +> ``` + +**思路**: + +中位数是指将一组数据从小到大排序后,位于中间位置的数值。如果数据集中的元素数量是奇数,中位数就是中间的那个元素;如果是偶数,则中位数是中间两个元素的平均值。 + +这道题要求时间复杂度为 O(log(m+n)),提示我们使用二分查找。关键思路是: + +1. **问题转化**:寻找第 k 小的元素,其中 k = (m+n+1)/2(奇数情况)或需要找第k和第k+1小的元素(偶数情况) +2. **二分搜索**:在较短的数组上进行二分,确保左半部分元素个数等于右半部分 +3. **分割线性质**:左半部分的最大值 ≤ 右半部分的最小值 + +```java +public double findMedianSortedArrays(int[] nums1, int[] nums2) { + // 确保 nums1 是较短的数组 + if (nums1.length > nums2.length) { + return findMedianSortedArrays(nums2, nums1); + } + + int m = nums1.length; + int n = nums2.length; + int left = 0, right = m; + + while (left <= right) { + // nums1的分割点 + int cut1 = (left + right) / 2; + // nums2的分割点 + int cut2 = (m + n + 1) / 2 - cut1; + + // 处理边界情况 + int left1 = (cut1 == 0) ? Integer.MIN_VALUE : nums1[cut1 - 1]; + int left2 = (cut2 == 0) ? Integer.MIN_VALUE : nums2[cut2 - 1]; + int right1 = (cut1 == m) ? Integer.MAX_VALUE : nums1[cut1]; + int right2 = (cut2 == n) ? Integer.MAX_VALUE : nums2[cut2]; + + // 找到正确的分割 + if (left1 <= right2 && left2 <= right1) { + // 总长度为偶数 + if ((m + n) % 2 == 0) { + return (Math.max(left1, left2) + Math.min(right1, right2)) / 2.0; + } else { + // 总长度为奇数 + return Math.max(left1, left2); + } + } + // nums1分割点太靠右 + else if (left1 > right2) { + right = cut1 - 1; + } + // nums1分割点太靠左 + else { + left = cut1 + 1; + } + } + + return 0.0; +} +``` + +**算法步骤详解**: +1. 确保在较短数组上进行二分搜索,减少搜索空间 +2. 计算两个数组的分割点,使得左半部分元素个数 = 右半部分元素个数(或多1个) +3. 检查分割是否正确:左半部分最大值 ≤ 右半部分最小值 +4. 根据总长度奇偶性计算中位数 + +**时间复杂度**:$O(log(min(m,n)))$,在较短数组上进行二分搜索 +**空间复杂度**:$O(1)$,只使用常量级别的额外空间 + + + +## 四、常见题目补充 + + +### [x 的平方根『69』](https://leetcode.cn/problems/sqrtx/) + +> 给你一个非负整数 `x`,计算并返回 `x` 的平方根的整数部分。 + +**思路**:在区间 `[1, x/2]` 上二分,寻找最大的 `mid` 使得 `mid*mid <= x`。注意用 `long` 防止乘法溢出。 + +```java +public int mySqrt(int x) { + if (x < 2) return x; + int left = 1, right = x / 2, ans = 0; + while (left <= right) { + int mid = left + (right - left) / 2; + if ((long) mid * mid <= x) { + ans = mid; + left = mid + 1; + } else { + right = mid - 1; + } + } + return ans; +} +``` + +**时间复杂度**:$O(log x)$ +**空间复杂度**:$O(1)$ + + +### [第一个错误的版本『278』](https://leetcode.cn/problems/first-bad-version/) + +> 有 `n` 个版本,从 1 到 n,给你 `isBadVersion(version)` 接口。找出第一个错误版本。 + +**思路**:典型“找左边界”。`isBad(mid)` 为真时收缩到左侧,并记录答案。 + +```java +// isBadVersion(version) 由系统提供 +public int firstBadVersion(int n) { + int left = 1, right = n, ans = n; + while (left <= right) { + int mid = left + (right - left) / 2; + if (isBadVersion(mid)) { + ans = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return ans; +} +``` + +**时间复杂度**:$O(log n) $ +**空间复杂度**:$O(1)$ + + +### [有序数组中的单一元素『540』](https://leetcode.cn/problems/single-element-in-a-sorted-array/) + +> 一个按升序排列的数组,除了某个元素只出现一次外,其余每个元素均出现两次。找出这个元素。 + +**思路**:利用“成对对齐”的性质。让 `mid` 偶数化(`mid -= mid % 2`),若 `nums[mid] == nums[mid+1]`,唯一元素在右侧;否则在左侧(含 `mid`)。 + +```java +public int singleNonDuplicate(int[] nums) { + int left = 0, right = nums.length - 1; + while (left < right) { + int mid = left + (right - left) / 2; + if (mid % 2 == 1) mid--; // 偶数化 + if (nums[mid] == nums[mid + 1]) { + left = mid + 2; + } else { + right = mid; + } + } + return nums[left]; +} +``` + +**时间复杂度**:$O(log n) $ +**空间复杂度**:$O(1)$ + + +### [爱吃香蕉的珂珂『875』](https://leetcode.cn/problems/koko-eating-bananas/) + +> 给定每堆香蕉数量 `piles` 和总小时数 `h`,最小化吃速 `k` 使得能在 `h` 小时内吃完。 + +**思路**:对答案 `k` 二分。检验函数为以速率 `k` 需要的总小时数是否 `<= h`。 + +```java +public int minEatingSpeed(int[] piles, int h) { + int left = 1, right = 0; + for (int p : piles) right = Math.max(right, p); + while (left < right) { + int mid = left + (right - left) / 2; + long hours = 0; + for (int p : piles) { + hours += (p + mid - 1) / mid; // 向上取整 + } + if (hours <= h) right = mid; + else left = mid + 1; + } + return left; +} +``` + +**时间复杂度**:$O(n log max(piles))$ +**空间复杂度**:$O(1)$ + + +### [在 D 天内送达包裹的能力『1011』](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) + +> 给定包裹重量数组 `weights` 与天数 `days`,求最小运力使得能按顺序在 `days` 天内送达。 + +**思路**:对答案(运力)二分。下界是最大单件重量,上界是总重量。检验函数为用给定运力需要的天数是否 `<= days`。 + +```java +public int shipWithinDays(int[] weights, int days) { + int left = 0, right = 0; + for (int w : weights) { + left = Math.max(left, w); + right += w; + } + while (left < right) { + int mid = left + (right - left) / 2; + int need = 1, cur = 0; + for (int w : weights) { + if (cur + w > mid) { + need++; + cur = 0; + } + cur += w; + } + if (need <= days) right = mid; + else left = mid + 1; + } + return left; +} +``` + +**时间复杂度**:$O(n log(sum(weights)))$ +**空间复杂度**:$O(1)$ diff --git a/docs/data-structure-algorithms/algorithm/DFS-BFS.md b/docs/data-structure-algorithms/algorithm/DFS-BFS.md new file mode 100644 index 0000000000..658a24b592 --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/DFS-BFS.md @@ -0,0 +1,627 @@ +--- +title: DFS 与 BFS +date: 2025-03-09 +tags: + - Algorithm +categories: BFS DFS +--- + + + +> 在线性结构中,按照顺序一个一个地看到所有的元素,称为线性遍历。在非线性结构中,由于元素之间的组织方式变得复杂,就有了不同的遍历行为。其中最常见的遍历有:**深度优先遍历**(Depth-First-Search)和**广度优先遍历**(Breadth-First-Search)。它们的思想非常简单,但是在算法的世界里发挥着巨大的作用,也是面试高频考点。 +> + +「遍历」和「搜索」可以看作是两个等价概念,通过遍历 **所有** 的可能的情况达到搜索的目的。遍历是手段,搜索是目的。因此「优先遍历」也叫「优先搜索」。 + + + +## 一、DFS 与 BFS的核心原理 + + + +1. **DFS(深度优先搜索)** + + - 核心思想:优先沿一条路径深入探索,直到无法继续再回溯到上一个节点继续搜索,类似“不撞南墙不回头”。 + - **适用场景**:寻找所有可能路径、拓扑排序、连通性问题等。 + - **实现方式**:递归(隐式栈)或显式栈。 + + ```Java + void dfs(TreeNode node) { + if (node == null) return; + // 处理当前节点 + dfs(node.left); // 深入左子树 + dfs(node.right); // 深入右子树 + } + ``` + +2. **BFS(广度优先搜索)** + + - 核心思想:按层次逐层遍历,优先访问同一层的所有节点,常用于最短路径问题。 + - **适用场景**:层序遍历、最短路径(无权图)、扩散类问题。 + - **实现方式**:队列(FIFO)。 + + ```Java + void bfs(TreeNode root) { + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + // 处理当前节点 + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + } + } + ``` + +只是比较两段代码的话,最直观的感受就是:DFS 遍历的代码比 BFS 简洁太多了!这是因为递归的方式隐含地使用了系统的 栈,我们不需要自己维护一个数据结构。如果只是简单地将二叉树遍历一遍,那么 DFS 显然是更方便的选择。 + + + +## 二、勇往直前的深度优先搜索 + +### 2.1 深度优先遍历的形象描述 + +「一条路走到底,不撞南墙不回头」是对「深度优先遍历」的最直观描述。 + +说明: + +- 深度优先遍历只要前面有可以走的路,就会一直向前走,直到无路可走才会回头; +- 「无路可走」有两种情况:① 遇到了墙;② 遇到了已经走过的路; +- 在「无路可走」的时候,沿着原路返回,直到回到了还有未走过的路的路口,尝试继续走没有走过的路径; +- 有一些路径没有走到,这是因为找到了出口,程序就停止了; +- 「深度优先遍历」也叫「深度优先搜索」,遍历是行为的描述,搜索是目的(用途); +- 遍历不是很深奥的事情,把 **所有** 可能的情况都看一遍,才能说「找到了目标元素」或者「没找到目标元素」。遍历也称为 **穷举**,穷举的思想在人类看来虽然很不起眼,但借助 **计算机强大的计算能力**,穷举可以帮助我们解决很多专业领域知识不能解决的问题。 + + + +### 2.2 树的深度优先遍历 + +我们以「二叉树」的深度优先遍历为例,介绍树的深度优先遍历。 + +二叉树的深度优先遍历从「根结点」开始,依次 「递归地」 遍历「左子树」的所有结点和「右子树」的所有结点。 + + + +> 事实上,「根结点 → 右子树 → 左子树」也是一种深度优先遍历的方式,为了符合人们「先左再右」的习惯。如果没有特别说明,树的深度优先遍历默认都按照 「根结点 → 左子树 → 右子树」 的方式进行。 + +**二叉树深度优先遍历的递归终止条件**:遍历完一棵树的 **所有** 叶子结点,等价于遍历到 **空结点**。 + +二叉树的深度优先遍历可以分为:前序遍历、中序遍历和后序遍历。 + + + +- 前序遍历:根节点 → 左子树 → 右子树 +- 中序遍历: 左子树 → 根节点 → 右子树 +- 后序遍历:左子树 → 右子树 → 根节点 + +> 友情提示:后序遍历是非常重要的遍历方式,解决很多树的问题都采用了后序遍历的思想,请大家务必重点理解「后序遍历」一层一层向上传递信息的遍历方式。并在做题的过程中仔细体会「后序遍历」思想的应用。 + +**为什么前、中、后序遍历都是深度优先遍历** + +可以把树的深度优先遍历想象成一只蚂蚁,从根结点绕着树的外延走一圈。每一个结点的外延按照下图分成三个部分:前序遍历是第一部分,中序遍历是第二部分,后序遍历是第三部分。 + + + +**重要性质** + +根据定义不难得到以下性质。 + + - 性质 1:二叉树的 前序遍历 序列,根结点一定是 最先 访问到的结点; + - 性质 2:二叉树的 后序遍历 序列,根结点一定是 最后 访问到的结点; + - 性质 3:根结点把二叉树的 中序遍历 序列划分成两个部分,第一部分的所有结点构成了根结点的左子树,第二部分的所有结点构成了根结点的右子树。 + +> 根据这些性质,可以完成「力扣」第 105 题、第 106 题 + + + +### 2.3 图的深度优先遍历 + +深度优先遍历有「回头」的过程,在树中由于不存在「环」(回路),对于每一个结点来说,每一个结点只会被递归处理一次。而「图」中由于存在「环」(回路),就需要 记录已经被递归处理的结点(通常使用布尔数组或者哈希表),以免结点被重复遍历到。 + +**说明**:深度优先遍历的结果通常与图的顶点如何存储有关,所以图的深度优先遍历的结果并不唯一。 + +#### [课程表](https://leetcode.cn/problems/course-schedule/) + +> 你这个学期必须选修 `numCourses` 门课程,记为 `0` 到 `numCourses - 1` 。 +> +> 在选修某些课程之前需要一些先修课程。 先修课程按数组 `prerequisites` 给出,其中 `prerequisites[i] = [ai, bi]` ,表示如果要学习课程 `ai` 则 **必须** 先学习课程 `bi` 。 +> +> - 例如,先修课程对 `[0, 1]` 表示:想要学习课程 `0` ,你需要先完成课程 `1` 。 +> +> 请你判断是否可能完成所有课程的学习?如果可以,返回 `true` ;否则,返回 `false` 。 +> +> ``` +> 输入:numCourses = 2, prerequisites = [[1,0],[0,1]] +> 输出:false +> 解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。 +> ``` + +思路:对于课程表问题,实际上是在寻找一个有向无环图(DAG)的环,以确定是否存在一个有效的课程学习顺序。 + +```java +public class Solution { + private boolean hasCycle = false; + + public boolean canFinish(int numCourses, int[][] prerequisites) { + // 构建图的邻接表表示 + List> graph = new ArrayList<>(); + for (int i = 0; i < numCourses; i++) { + graph.add(new ArrayList<>()); + } + for (int[] prerequisite : prerequisites) { + int course = prerequisite[0]; + int prerequisiteCourse = prerequisite[1]; + graph.get(prerequisiteCourse).add(course); + } + + // 初始化访问状态数组 + boolean[] visited = new boolean[numCourses]; + boolean[] recursionStack = new boolean[numCourses]; + + // 对每个节点进行DFS遍历 + for (int i = 0; i < numCourses; i++) { + if (!visited[i]) { + dfs(graph, visited, recursionStack, i); + } + // 如果在DFS过程中检测到了环,则提前返回false + if (hasCycle) { + return false; + } + } + + // 如果没有检测到环,则返回true + return true; + } + + private void dfs(List> graph, boolean[] visited, boolean[] recursionStack, int node) { + // 将当前节点标记为已访问 + visited[node] = true; + // 将当前节点加入递归栈 + recursionStack[node] = true; + + // 遍历当前节点的所有邻接节点 + for (int neighbor : graph.get(node)) { + // 如果邻接节点未被访问,则递归访问 + if (!visited[neighbor]) { + dfs(graph, visited, recursionStack, neighbor); + } else if (recursionStack[neighbor]) { + // 如果邻接节点已经在递归栈中,说明存在环 + hasCycle = true; + } + } + + // 将当前节点从递归栈中移除 + recursionStack[node] = false; + } +} +``` + + + +### 2.4 深度优先遍历的两种实现方式 + +在深度优先遍历的过程中,需要将 当前遍历到的结点 的相邻结点 暂时保存 起来,以便在回退的时候可以继续访问它们。遍历到的结点的顺序呈现「后进先出」的特点,因此 深度优先遍历可以通过「栈」实现。 + +再者,深度优先遍历有明显的递归结构。我们知道支持递归实现的数据结构也是栈。因此实现深度优先遍历有以下两种方式: + +- 编写递归方法; +- 编写栈,通过迭代的方式实现。 + + + +### 2.5 DFS 算法框架 + +#### **1. 基础模板(以全排列为例)** + +```java +// 全局变量:记录结果和路径 +List> result = new ArrayList<>(); +List path = new ArrayList<>(); +boolean[] visited; // 访问标记数组 + +void dfs(int[] nums, int depth) { + // 终止条件:路径长度达到要求 + if (depth == nums.length) { + result.add(new ArrayList<>(path)); + return; + } + + // 遍历选择列表 + for (int i = 0; i < nums.length; i++) { + if (!visited[i]) { + // 做选择:标记已访问,加入路径 + visited[i] = true; + path.add(nums[i]); + // 递归进入下一层 + dfs(nums, depth + 1); + // 撤销选择:回溯 + visited[i] = false; + path.remove(path.size() - 1); + } + } +} +``` + +**关键点**: + +- **路径记录**:通过`path`列表保存当前路径。 +- **访问标记**:使用`visited`数组避免重复访问。 +- **递归与回溯**:递归调用后必须撤销选择以恢复状态 + +#### **2. 二维矩阵遍历框架(如岛屿问题)** + +```java +void dfs(int[][] grid, int i, int j) { + // 边界检查 + if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length) return; + // 终止条件:遇到非陆地或已访问 + if (grid[i][j] != '1') return; + + // 标记为已访问(直接修改原矩阵) + grid[i][j] = '0'; + // 四个方向递归 + dfs(grid, i + 1, j); // 下 + dfs(grid, i - 1, j); // 上 + dfs(grid, i, j + 1); // 右 + dfs(grid, i, j - 1); // 左 +} +``` + +**适用场景**:矩阵中的连通性问题(如岛屿数量、迷宫路径) + + + +### 2.6 练习 + +> https://leetcode.cn/problem-list/depth-first-search/ + +请大家通过这些问题体会 「**如何设计递归函数的返回值**」 帮助我们解决问题。并理解这些简单的问题其实都是「深度优先遍历」的思想中「后序遍历」思想的体现,真正程序在执行的时候,是通过「一层一层向上汇报」的方式,最终在根结点汇总整棵树遍历的结果。 + +1. 完成「力扣」第 104 题:二叉树的最大深度(简单):设计递归函数的返回值; +2. 完成「力扣」第 111 题:二叉树的最小深度(简单):设计递归函数的返回值; +3. 完成「力扣」第 112 题:路径总和(简单):设计递归函数的返回值; +4. 完成「力扣」第 226 题:翻转二叉树(简单):前中后序遍历、广度优先遍历均可,中序遍历有一个小小的坑; +5. 完成「力扣」第 100 题:相同的树(简单):设计递归函数的返回值; +6. 完成「力扣」第 101 题:对称二叉树(简单):设计递归函数的返回值; +7. 完成「力扣」第 129 题:求根到叶子节点数字之和(中等):设计递归函数的返回值。 +8. 完成「力扣」第 236 题:二叉树的最近公共祖先(中等):使用后序遍历的典型问题。 + +请大家完成下面这些树中的问题,加深对前序遍历序列、中序遍历序列、后序遍历序列的理解。 + +9. 完成「力扣」第 105 题:从前序与中序遍历序列构造二叉树(中等); +10. 完成「力扣」第 106 题:从中序与后序遍历序列构造二叉树(中等); +11. 完成「力扣」第 1008 题:前序遍历构造二叉搜索树(中等); + +12. 完成「力扣」第 1028 题:从先序遍历还原二叉树(困难)。 + +> 友情提示:需要用到后序遍历思想的一些经典问题,这些问题可能有一些难度,可以不用急于完成。先做后面的问题,见多了类似的问题以后,慢慢理解「后序遍历」一层一层向上汇报,在根结点汇总的遍历思想。 + + + +### 2.7 总结 + +- 遍历可以用于搜索,思想是穷举,遍历是实现搜索的手段; +- 树的「前、中、后」序遍历都是深度优先遍历; +- 树的后序遍历很重要; +- 由于图中存在环(回路),图的深度优先遍历需要记录已经访问过的结点,以避免重复访问; +- 遍历是一种简单、朴素但是很重要的算法思想,很多树和图的问题就是在树和图上执行一次遍历,在遍历的过程中记录有用的信息,得到需要结果,区别在于为了解决不同的问题,在遍历的时候传递了不同的 与问题相关 的数据。 + + + + + +## 三、齐头并进的广度优先搜索 + +> DFS(深度优先搜索)和 BFS(广度优先搜索)就像孪生兄弟,提到一个总是想起另一个。然而在实际使用中,我们用 DFS 的时候远远多于 BFS。那么,是不是 BFS 就没有什么用呢? +> +> 如果我们使用 DFS/BFS 只是为了遍历一棵树、一张图上的所有结点的话,那么 DFS 和 BFS 的能力没什么差别,我们当然更倾向于更方便写、空间复杂度更低的 DFS 遍历。不过,某些使用场景是 DFS 做不到的,只能使用 BFS 遍历。 +> + + + +「广度优先遍历」的思想在生活中随处可见: + +如果我们要找一个医生或者律师,我们会先在自己的一度人脉中遍历(查找),如果没有找到,继续在自己的二度人脉中遍历(查找),直到找到为止。 + +### 3.1 广度优先遍历借助「队列」实现 + +广度优先遍历呈现出「一层一层向外扩张」的特点,**先看到的结点先遍历,后看到的结点后遍历**,因此「广度优先遍历」可以借助「队列」实现。 + + + +**说明**:遍历到一个结点时,如果这个结点有左(右)孩子结点,依次将它们加入队列。 + +> 友情提示:广度优先遍历的写法相对固定,我们不建议大家背代码、记模板。在深刻理解广度优先遍历的应用场景(找无权图的最短路径),借助「队列」实现的基础上,多做练习,写对代码就是自然而然的事情了 + +我们先介绍「树」的广度优先遍历,再介绍「图」的广度优先遍历。事实上,它们是非常像的。 + + + +### 3.2 树的广度优先遍历 + +二叉树的层序遍历 + +> 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 +> + +思路分析: + +- 题目要求我们一层一层输出树的结点的值,很明显需要使用「广度优先遍历」实现; +- 广度优先遍历借助「队列」实现; + +- 注意: + - 这样写 `for (int i = 0; i < queue.size(); i++) { `代码是不能通过测评的,这是因为 `queue.size()` 在循环中是变量。正确的做法是:每一次在队列中取出元素的个数须要先暂存起来; + - 子结点入队的时候,非空的判断很重要:在队列的队首元素出队的时候,一定要在左(右)子结点非空的时候才将左(右)子结点入队。 +- 树的广度优先遍历的写法模式相对固定: + - 使用队列; + - 在队列非空的时候,动态取出队首元素; + - 取出队首元素的时候,把队首元素相邻的结点(非空)加入队列。 + +大家在做题的过程中需要多加练习,融汇贯通,不须要死记硬背。 + + + +```java +public List> levelOrder(TreeNode root) { + List> result = new ArrayList<>(); // 存储遍历结果的列表 + if (root == null) { + return result; // 如果树为空,直接返回空列表 + } + + Queue queue = new LinkedList<>(); // 创建一个队列用于层序遍历 + queue.offer(root); // 将根节点加入队列 + + while (!queue.isEmpty()) { // 当队列不为空时继续遍历 + int levelSize = queue.size(); // 当前层的节点数 + List currentLevel = new ArrayList<>(); // 存储当前层节点值的列表 + + for (int i = 0; i < levelSize; i++) { + TreeNode currentNode = queue.poll(); // 从队列中取出一个节点 + currentLevel.add(currentNode.val); // 将节点值加入当前层列表 + + // 如果左子节点不为空,将其加入队列 + if (currentNode.left != null) { + queue.offer(currentNode.left); + } + // 如果右子节点不为空,将其加入队列 + if (currentNode.right != null) { + queue.offer(currentNode.right); + } + } + + result.add(currentLevel); // 将当前层列表加入结果列表 + } + + return result; // 返回遍历结果 +} +``` + + + +### 3.3 BFS 算法框架 + +**基础模板(队列+访问标记)** + +```java +public int bfs(Node start, Node target) { + Queue queue = new LinkedList<>(); // 核心队列结构 + Set visited = new HashSet<>(); // 防止重复访问 + int step = 0; // 记录扩散步数 + + queue.offer(start); + visited.add(start); + + while (!queue.isEmpty()) { + int levelSize = queue.size(); // 当前层节点数 + for (int i = 0; i < levelSize; i++) { // 遍历当前层所有节点 + Node cur = queue.poll(); + // 终止条件(根据问题场景调整) + if (cur.equals(target)) return step; + + // 扩散相邻节点(根据数据结构调整) + for (Node neighbor : getNeighbors(cur)) { + if (!visited.contains(neighbor)) { + queue.offer(neighbor); + visited.add(neighbor); + } + } + } + step++; // 步数递增 + } + return -1; // 未找到目标 +} +``` + +**关键点** : + +- **队列控制层次**:通过 `levelSize` 逐层遍历,保证找到最短路径。 +- **访问标记**:避免重复访问(如矩阵问题可改为修改原数据)。 +- **扩散逻辑**:`getNeighbors()` 需根据具体数据结构实现(如二叉树、图、网格等)。 + + + +### 3.4 使用广度优先遍历得到无权图的最短路径 + +在 无权图 中,由于广度优先遍历本身的特点,假设源点为 source,只有在遍历到 所有 距离源点 source 的距离为 d 的所有结点以后,才能遍历到所有 距离源点 source 的距离为 d + 1 的所有结点。也可以使用「两点之间、线段最短」这条经验来辅助理解如下结论:从源点 source 到目标结点 target 走直线走过的路径一定是最短的。 + +> 在一棵树中,一个结点到另一个结点的路径是唯一的,但在图中,结点之间可能有多条路径,其中哪条路最近呢?这一类问题称为最短路径问题。最短路径问题也是 BFS 的典型应用,而且其方法与层序遍历关系密切。 +> +> 在二叉树中,BFS 可以实现一层一层的遍历。在图中同样如此。从源点出发,BFS 首先遍历到第一层结点,到源点的距离为 1,然后遍历到第二层结点,到源点的距离为 2…… 可以看到,用 BFS 的话,距离源点更近的点会先被遍历到,这样就能找到到某个点的最短路径了。 +> +>  +> +> 小贴士: +> +> 很多同学一看到「最短路径」,就条件反射地想到「Dijkstra 算法」。为什么 BFS 遍历也能找到最短路径呢? +> +> 这是因为,Dijkstra 算法解决的是带权最短路径问题,而我们这里关注的是无权最短路径问题。也可以看成每条边的权重都是 1。这样的最短路径问题,用 BFS 求解就行了。 +> +> 在面试中,你可能更希望写 BFS 而不是 Dijkstra。毕竟,敢保证自己能写对 Dijkstra 算法的人不多。 +> +> 最短路径问题属于图算法。由于图的表示和描述比较复杂,本文用比较简单的网格结构代替。网格结构是一种特殊的图,它的表示和遍历都比较简单,适合作为练习题。在 LeetCode 中,最短路径问题也以网格结构为主。 + + + +### 3.5 图论中的最短路径问题概述 + +在图中,由于 图中存在环,和深度优先遍历一样,广度优先遍历也需要在遍历的时候记录已经遍历过的结点。特别注意:将结点添加到队列以后,一定要马上标记为「已经访问」,否则相同结点会重复入队,这一点在初学的时候很容易忽略。如果很难理解这样做的必要性,建议大家在代码中打印出队列中的元素进行调试:在图中,如果入队的时候不马上标记为「已访问」,相同的结点会重复入队,这是不对的。 + +另外一点还需要强调,广度优先遍历用于求解「无权图」的最短路径,因此一定要认清「无权图」这个前提条件。如果是带权图,就需要使用相应的专门的算法去解决它们。事实上,这些「专门」的算法的思想也都基于「广度优先遍历」的思想,我们为大家例举如下: + +- 带权有向图、且所有权重都非负的单源最短路径问题:使用 Dijkstra 算法; +- 带权有向图的单源最短路径问题:Bellman-Ford 算法; + +- 一个图的所有结点对的最短路径问题:Floy-Warshall 算法。 + +这里列出的以三位计算机科学家的名字命名的算法,大家可以在《算法导论》这本经典著作的第 24 章、第 25 章找到相关知识的介绍。值得说明的是:应用任何一种算法,都需要认清使用算法的前提,不满足前提直接套用算法是不可取的。深刻理解应用算法的前提,也是学习算法的重要方法。例如我们在学习「二分查找」算法、「滑动窗口」算法的时候,就可以问自己,这个问题为什么可以使用「二分查找」,为什么可以使用「滑动窗口」。我们知道一个问题可以使用「优先队列」解决,是什么样的需求促使我们想到使用「优先队列」,而不是「红黑树(平衡二叉搜索树)」,想清楚使用算法(数据结构)的前提更重要。 + +> [无向图中连通分量的数目『323』](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/) +> +> 你有一个包含 `n` 个节点的图。给定一个整数 `n` 和一个数组 `edges` ,其中 `edges[i] = [ai, bi]` 表示图中 `ai` 和 `bi` 之间有一条边。 +> +> 返回 *图中已连接分量的数目* 。 +> +>  +> +> ``` +> 输入: n = 5, edges = [[0, 1], [1, 2], [3, 4]] +> 输出: 2 +> ``` +> +> 思路分析: +> +> 首先需要对输入数组进行处理,由于 n 个结点的编号从 0 到 n - 1 ,因此可以使用「嵌套数组」表示邻接表,具体实现请见参考代码; +> 然后遍历每一个顶点,对每一个顶点执行一次广度优先遍历,注意:在遍历的过程中使用 visited 布尔数组记录已经遍历过的结点。 +> +> ```java +> public int countComponents(int n, int[][] edges) { +> // 第 1 步:构建图 +> List[] adj = new ArrayList[n]; +> for (int i = 0; i < n; i++) { +> adj[i] = new ArrayList<>(); +> } +> // 无向图,所以需要添加双向引用 +> for (int[] edge : edges) { +> adj[edge[0]].add(edge[1]); +> adj[edge[1]].add(edge[0]); +> } +> +> // 第 2 步:开始广度优先遍历 +> int res = 0; +> boolean[] visited = new boolean[n]; +> for (int i = 0; i < n; i++) { +> if (!visited[i]) { +> bfs(adj, i, visited); +> res++; +> } +> } +> return res; +> } +> +> /** +> * @param adj 邻接表 +> * @param u 从 u 这个顶点开始广度优先遍历 +> * @param visited 全局使用的 visited 布尔数组 +> */ +> private void bfs(List[] adj, int u, boolean[] visited) { +> Queue queue = new LinkedList<>(); +> queue.offer(u); +> visited[u] = true; +> +> while (!queue.isEmpty()) { +> Integer front = queue.poll(); +> // 获得队首结点的所有后继结点 +> List successors = adj[front]; +> for (int successor : successors) { +> if (!visited[successor]) { +> queue.offer(successor); +> // 特别注意:在加入队列以后一定要将该结点标记为访问,否则会出现结果重复入队的情况 +> visited[successor] = true; +> } +> } +> } +> } +> ``` +> +> 复杂度分析: +> +> - 时间复杂度:O(V + E)O(V+E),这里 EE 是边的条数,即数组 edges 的长度,初始化的时候遍历数组得到邻接表。这里 VV 为输入整数 n,遍历的过程是每一个结点执行一次深度优先遍历,时间复杂度为 O(V)O(V); +> - 空间复杂度:O(V + E)O(V+E),综合考虑邻接表 O(V + E)O(V+E)、visited 数组 O(V)O(V)、队列的长度 O(V)O(V) 三者得到。 +> 说明:和深度优先遍历一样,图的广度优先遍历的结果并不唯一,与每个结点的相邻结点的访问顺序有关。 + +### 3.6 练习 + +> 友情提示:第 1 - 4 题是广度优先遍历的变形问题,写对这些问题有助于掌握广度优先遍历的代码编写逻辑和细节。 + +1. 完成「力扣」第 107 题:二叉树的层次遍历 II(简单); +2. 完成《剑指 Offer》第 32 - I 题:从上到下打印二叉树(中等); +3. 完成《剑指 Offer》第 32 - III 题:从上到下打印二叉树 III(中等); +4. 完成「力扣」第 103 题:二叉树的锯齿形层次遍历(中等); +5. 完成「力扣」第 429 题:N 叉树的层序遍历(中等); +6. 完成「力扣」第 993 题:二叉树的堂兄弟节点(中等); + + + +#### 二维矩阵遍历(岛屿问题) + +```java +void bfs(char[][] grid, int x, int y) { + int[][] dirs = {{1,0}, {-1,0}, {0,1}, {0,-1}}; + Queue queue = new LinkedList<>(); + queue.offer(new int[]{x, y}); + grid[x][y] = '0'; // 直接修改矩阵代替visited + + while (!queue.isEmpty()) { + int[] pos = queue.poll(); + for (int[] d : dirs) { + int nx = pos[0] + d[0], ny = pos[1] + d[1]; + if (nx >=0 && ny >=0 && nx < grid.length && ny < grid[0].length + && grid[nx][ny] == '1') { + queue.offer(new int[]{nx, ny}); + grid[nx][ny] = '0'; // 标记为已访问 + } + } + } +} +``` + +**优化点** : + +- 直接修改原矩阵代替`visited`集合,节省空间。 +- 四方向扩散的坐标计算。 + +#### 图的邻接表遍历 + +```java +public void bfsGraph(Map> graph, int start) { + Queue queue = new LinkedList<>(); + boolean[] visited = new boolean[graph.size()]; + queue.offer(start); + visited[start] = true; + + while (!queue.isEmpty()) { + int node = queue.poll(); + System.out.print(node + " "); + for (int neighbor : graph.get(node)) { + if (!visited[neighbor]) { + queue.offer(neighbor); + visited[neighbor] = true; + } + } + } +} +``` + +**数据结构** : + +- 使用`Map`或邻接表存储图结构。 +- 通过布尔数组标记访问状态。 + + + +## Reference + +- https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/bfs-de-shi-yong-chang-jing-zong-jie-ceng-xu-bian-l/ diff --git a/docs/data-structure-algorithms/algorithm/Double-Pointer.md b/docs/data-structure-algorithms/algorithm/Double-Pointer.md new file mode 100755 index 0000000000..69bcd7b253 --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/Double-Pointer.md @@ -0,0 +1,1466 @@ +--- +title: 双指针 +date: 2023-05-17 +tags: + - pointers + - algorithms +categories: algorithms +--- + + + +> 在数组中并没有真正意义上的指针,但我们可以把索引当做数组中的指针 +> +> 归纳下双指针算法,其实总共就三类 +> +> - 左右指针,数组和字符串问题 +> - 快慢指针,主要是成环问题 +> - 滑动窗口,针对子串问题 + + + +## 一、左右指针 + + + +左右指针在数组中其实就是两个索引值,两个指针相向而行或者相背而行 + +Javaer 一般这么表示: + +```java +int left = 0; +int right = arr.length - 1; +while(left < right) + *** +``` + +这两个指针 **相向交替移动**, 看着像二分查找是吧,二分也属于左右指针。 + + + +### [反转字符串](https://leetcode.cn/problems/reverse-string/) + +> 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 +> +> 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 +> +> 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 +> +> ``` +> 输入:["h","e","l","l","o"] +> 输出:["o","l","l","e","h"] +> ``` +> + +思路: + +- 因为要反转,所以就不需要相向移动了,如果用双指针思路的话,其实就是遍历中交换左右指针的字符 + +```java +public void reverseString(char[] s) { + int left = 0; + int right = s.length - 1; + while (left < right){ + char tmp = s[left]; + s[left] = s[right]; + s[right] = tmp; + left++; + right--; + } +} +``` + + + +### [两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) + +> 给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。 +> +> 以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。 +> +> 你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。 +> +> 你所设计的解决方案必须只使用常量级的额外空间。 +> +> ``` +> 输入:numbers = [2,7,11,15], target = 9 +> 输出:[1,2] +> 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。 +> ``` + +直接用左右指针套就可以 + +```java +public int[] twoSum(int[] nums, int target) { + int left = 0; + int rigth = nums.length - 1; + while (left < rigth) { + int tmp = nums[left] + nums[rigth]; + if (target == tmp) { + //数组下标是从1开始的 + return new int[]{left + 1, rigth + 1}; + } else if (tmp > target) { + rigth--; //右移 + } else { + left++; //左移 + } + } + return new int[]{-1, -1}; +} +``` + + + +### [三数之和](https://leetcode.cn/problems/3sum/) + +> 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 +> +> 注意:答案中不可以包含重复的三元组。 +> + +思路:**排序、双指针、去重** + +第一个想法是,这三个数,两个指针? + +- 对数组排序,固定一个数 $nums[i]$ ,然后遍历数组,并移动左右指针求和,判断是否有等于 0 的情况 + +- 特例: + - 排序后第一个数就大于 0,不干了 + + - 有三个需要去重的地方 + - `nums[i] == nums[i - 1]` 直接跳过本次遍历 + + > **避免重复三元组:** + > + > - 我们从第一个元素开始遍历数组,逐步往后移动。如果当前的 `nums[i]` 和前一个 `nums[i - 1]` 相同,说明我们已经处理过以 `nums[i - 1]` 为起点的组合(即已经找过包含 `nums[i - 1]` 的三元组),此时再处理 `nums[i]` 会导致生成重复的三元组,因此可以跳过。 + > - 如果我们检查 `nums[i] == nums[i + 1]`,由于 `nums[i + 1]` 还没有被处理,这种方式无法避免重复,并且会产生错误的逻辑。 + + - `nums[left] == nums[left + 1]` 移动指针,即去重 + + - `nums[right] == nums[right - 1]` 移动指针 + + > **避免重复的配对:** + > + > 在每次固定一个 `nums[i]` 后,剩下的两数之和问题通常使用双指针法来解决。双指针的左右指针 `left` 和 `right` 分别从数组的两端向中间逼近,寻找合适的配对。 + > + > 为了**避免相同的数字被重复使用**,导致重复的三元组,双指针法中也需要跳过相同的元素。 + > + > - 左指针跳过重复元素: + > - 如果 `nums[left] == nums[left + 1]`,说明接下来的数字与之前处理过的数字相同。为了避免生成相同的三元组,我们将 `left` 向右移动跳过这个重复的数字。 + > - 右指针跳过重复元素: + > - 同样地,`nums[right] == nums[right - 1]` 也会导致重复的配对,因此右指针也要向左移动,跳过这个重复数字。 + +```java +public List> threeSum(int[] nums) { + //存放结果list + List> result = new ArrayList<>(); + int length = nums.length; + //特例判断 + if (length < 3) { + return result; + } + Arrays.sort(nums); + for (int i = 0; i < length; i++) { + //排序后的第一个数字就大于0,就说明没有符合要求的结果 + if (nums[i] > 0) break; + + //去重, 不能是 nums[i] == nums[i +1 ],因为顺序遍历的逻辑使得前一个元素已经被处理过,而后续的元素还没有处理 + if (i > 0 && nums[i] == nums[i - 1]) continue; + //左右指针 + int l = i + 1; + int r = length - 1; + while (l < r) { + int sum = nums[i] + nums[l] + nums[r]; + if (sum == 0) { + result.add(Arrays.asList(nums[i], nums[l], nums[r])); + //去重(相同数字的话就移动指针) + //在将左指针和右指针移动的时候,先对左右指针的值,进行判断,以防[0,0,0]这样的造成数组越界 + //不要用成 if 判断,只跳过 1 条,还会有重复的,且需要再加上 l 0) r--; + } + } + return result; +} +``` + + + +### 盛最多水的容器 + +> 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 +> +> ``` +> 输入:[1,8,6,2,5,4,8,3,7] +> 输出:49 +> 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +> ``` +> +>  + +**思路**: + +- 求得是水量,水量 = 两个指针指向的数字中较小值 * 指针之间的距离(水桶原理,最短的板才不会漏水) +- 为了求最大水量,我们需要存储所有条件的水量,进行比较才行 +- **双指针相向移动**,循环收窄,直到两个指针相遇 +- 往哪个方向移动,需要考虑清楚,如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会更小,所以我们移动**数字较小的那个指针** + +```java +public int maxArea(int[] height){ + int left = 0; + int right = height.length - 1; + //需要保存各个阶段的值 + int result = 0; + while(left < right){ + //水量 = 两个指针指向的数字中较小值∗指针之间的距离 + int area = Math.min(height[left],height[right]) * (right - left); + result = Math.max(result,area); + //移动数字较小的指针 + if(height[left] <= height[right]){ + left ++; + }else{ + right--; + } + } + return result; +} +``` + + + +### [验证回文串](https://leetcode.cn/problems/valid-palindrome/) + +> 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 +> +> 字母和数字都属于字母数字字符。 +> +> 给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。 +> +> ``` +> 输入: "A man, a plan, a canal: Panama" +> 输出: true +> 解释:"amanaplanacanalpanama" 是回文串 +> ``` + +思路: + +- 没看题解前,因为这个例子中有各种逗号、空格啥的,我第一想到的其实就是先遍历放在一个数组里,然后再去判断,看题解可以在原字符串完成,降低了空间复杂度 +- 首先需要知道三个 API + - `Character.isLetterOrDigit` 确定指定的字符是否为字母或数字 + - `Character.toLowerCase` 将大写字符转换为小写 + - `public char charAt(int index)` String 中的方法,用于返回指定索引处的字符 +- 双指针,每移动一步,判断这两个值是不是相同 +- 两个指针相遇,则是回文串 + +```java +public boolean isPalindrome(String s) { + // 转换为小写并去掉非字母和数字的字符 + int left = 0, right = s.length() - 1; + + while (left < right) { + // 忽略左边非字母和数字字符 + while (left < right && !Character.isLetterOrDigit(s.charAt(left))) { + left++; + } + // 忽略右边非字母和数字字符 + while (left < right && !Character.isLetterOrDigit(s.charAt(right))) { + right--; + } + // 比较两边字符 + if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) { + return false; + } + left++; + right--; + } + return true; +} +``` + + + +### 二分查找 + +有重复数字的话,返回的其实就是最右匹配 + +```java +public static int search(int[] nums, int target) { + int left = 0; + int right = nums.length - 1; + while (left <= right) { + //不直接使用(right+left)/2 是考虑数据大的时候溢出 + int mid = (right - left) / 2 + left; + int tmp = nums[mid]; + if (tmp == target) { + return mid; + } else if (tmp > target) { + //右指针移到中间位置 - 1,也避免不存在的target造成死循环 + right = mid - 1; + } else { + // + left = mid + 1; + } + } + return -1; +} +``` + + + +## 二、快慢指针 + +「快慢指针」,也称为「同步指针」,所谓快慢指针,就是两个指针同向而行,一快一慢。快慢指针处理的大都是链表问题。 + + + +### [环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) + +> 给你一个链表的头节点 head ,判断链表中是否有环。 +> +> 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 +> +> 如果链表中存在环 ,则返回 true 。 否则,返回 false 。 +> +>  + +思路: + +- 快慢指针,两个指针,一快一慢的话,慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。 + +```java +public boolean hasCycle(ListNode head) { + if (head == null || head.next == null) { + return false; + } + // 龟兔起跑 + ListNode fast = head; + ListNode slow = head; + + while (fast != null && fast.next != null) { + // 龟走一步 + slow = slow.next; + // 兔走两步 + fast = fast.next.next; + if (slow == fast) { + return true; + } + } + return false; +} +``` + + + +### [环形链表II](https://leetcode-cn.com/problems/linked-list-cycle-ii) + +> 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。 +> +> ``` +> 输入:head = [3,2,0,-4], pos = 1 +> 输出:返回索引为 1 的链表节点 +> 解释:链表中有一个环,其尾部连接到第二个节点。 +> ``` + +思路: + +- 最初,我就把有环理解错了,看题解觉得快慢指针相交的地方就是入环的节点 + +- 假设环是这样的,slow 指针进入环后,又走了 b 的距离与 fast 相遇 + +  + +1. **检测是否有环**:通过快慢指针来判断链表中是否存在环。慢指针一次走一步,快指针一次走两步。如果链表中有环,两个指针最终会相遇;如果没有环,快指针会到达链表末尾。 + +2. **找到环的起点**: +- 当快慢指针相遇时,我们已经确认链表中存在环。 + +- 从相遇点开始,慢指针保持不动,快指针回到链表头部,此时两个指针每次都走一步。两个指针会在环的起点再次相遇。 + +```java +public ListNode detectCycle(ListNode head) { + if (head == null || head.next == null) { + return null; + } + + ListNode slow = head; + ListNode fast = head; + + // 判断是否有环 + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + // 快慢指针相遇,说明有环 + if (slow == fast) { + break; + } + } + + // 如果没有环 + if (fast == null || fast.next == null) { + return null; + } + + // 快指针回到起点,慢指针保持在相遇点 + fast = head; + while (fast != slow) { + fast = fast.next; + slow = slow.next; + } + + // 此时快慢指针相遇的地方就是环的起点 + return slow; +} +``` + + + +### [链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) + +> 给定一个头结点为 `head` 的非空单链表,返回链表的中间结点。 +> +> 如果有两个中间结点,则返回第二个中间结点。(给定链表的结点数介于 `1` 和 `100` 之间。) +> +> ``` +> 输入:[1,2,3,4,5] +> 输出:此列表中的结点 3 (序列化形式:[3,4,5]) +> 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 +> 注意,我们返回了一个 ListNode 类型的对象 ans,这样: +> ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. +> ``` + +思路: + +- 快慢指针遍历,当 `fast` 到达链表的末尾时,`slow` 必然位于中间 + +```java +public ListNode middleNode(ListNode head) { + ListNode fast = head; + ListNode slow = head; + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; +} +``` + + + +### [ 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) + +> 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false +> +> ``` +> 输入:head = [1,2,2,1] +> 输出:true +> ``` + +思路: + +- 双指针:将值复制到数组中后用双指针法 +- 或者使用快慢指针来确定中间结点,然后反转后半段链表,将前半部分链表和后半部分进行比较 + +```java +public boolean isPalindrome(ListNode head) { + List vals = new ArrayList(); + + // 将链表的值复制到数组中 + ListNode currentNode = head; + while (currentNode != null) { + vals.add(currentNode.val); + currentNode = currentNode.next; + } + + // 使用双指针判断是否回文 + int front = 0; + int back = vals.size() - 1; + while (front < back) { + if (!vals.get(front).equals(vals.get(back))) { + return false; + } + front++; + back--; + } + return true; +} +``` + + + +### 删除链表的倒数第 N 个结点 + +> 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 +> +> ``` +> 输入:head = [1,2,3,4,5], n = 2 +> 输出:[1,2,3,5] +> ``` + +思路: + +1. 计算链表长度:从头节点开始对链表进行一次遍历,得到链表的长度,随后我们再从头节点开始对链表进行一次遍历,当遍历到第 L−n+1 个节点时,它就是我们需要删除的节点(为了与题目中的 n 保持一致,节点的编号从 1 开始) +2. 栈:根据栈「先进后出」的原则,我们弹出栈的第 n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点 +3. 双指针:由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。 + +```java +public ListNode removeNthFromEnd(ListNode head, int n) { + ListNode dummy = new ListNode(0, head); + ListNode first = head; + ListNode second = dummy; + + //让 first 指针先移动 n 步 + for (int i = 0; i < n; ++i) { + first = first.next; + } + while (first != null) { + first = first.next; + second = second.next; + } + second.next = second.next.next; + return dummy.next; +} +``` + + + +### [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) + +> 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 +> +> 由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。 +> +> 将最终结果插入 nums 的前 k 个位置后返回 k 。 +> +> 不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 +> +> ``` +> 输入:nums = [1,1,2] +> 输出:2, nums = [1,2,_] +> 解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 +> ``` + +**思路**: + +- 数组有序,那相等的元素在数组中的下标一定是连续的 +- 使用快慢指针,快指针表示遍历数组到达的下标位置,慢指针表示下一个不同元素要填入的下标位置 +- 第一个元素不需要删除,所有快慢指针都从下标 1 开始 + +```java +public static int removeDuplicates(int[] nums) { + if (nums == null) { + return 0; + } + int fast = 1; + int slow = 1; + while (fast < nums.length) { + //和前一个值比较 + if (nums[fast] != nums[fast - 1]) { + //不一样的话,把快指针的值放在慢指针上,实现了去重,并往前移动慢指针 + nums[slow] = nums[fast]; + ++slow; + } + //相等的话,移动快指针就行 + ++fast; + } + //慢指针的位置就是不重复的数量 + return slow; +} +``` + + + +### 最长连续递增序列 + +> 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 +> +> 连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。 +> +> ``` +> 输入:nums = [1,3,5,4,7] +> 输出:3 +> 解释:最长连续递增序列是 [1,3,5], 长度为3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 +> ``` + +思路分析: + +- 这个题的思路和删除有序数组中的重复项,很像 + +```java +public int findLengthOfLCIS(int[] nums) { + int result = 0; + int fast = 0; + int slow = 0; + while (fast < nums.length) { + //前一个数大于后一个数的时候 + if (fast > 0 || nums[fast - 1] > nums[fast]) { + slow = fast; + } + fast++; + result = Math.max(result, fast - slow); + } + return result; +} +``` + + + +## 三、滑动窗口 + +有一类数组上的问题,需要使用两个指针变量(我们称为左指针和右指针),同向、交替向右移动完成任务。这样的过程像极了一个窗口在平面上滑动的过程,因此我们将解决这一类问题的算法称为「滑动窗口」问题 + + + +滑动窗口,就是两个指针齐头并进,好像一个窗口一样,不断往前滑。 + +滑动窗口算法通过维护一个动态调整的窗口范围,高效解决子串、子数组、限流等场景问题。其核心逻辑可概括为以下步骤: + +1. **初始化窗口** 使用双指针 `left` 和 `right` 定义窗口边界,初始状态均指向起点。 +2. **扩展右边界** 移动 `right` 指针,将新元素加入窗口,并根据问题需求更新状态(如统计字符频率或请求计数)。 +3. **收缩左边界** 当窗口满足特定条件(如达到限流阈值或包含冗余元素),逐步移动 `left` 指针缩小窗口,直至不满足条件为止。 +4. **记录结果** 在窗口状态变化的每个阶段,捕获符合要求的解(如最长子串长度或限流通过状态)。 + + + +子串问题,几乎都是滑动窗口。滑动窗口算法技巧的思路,就是维护一个窗口,不断滑动,然后更新答案,该算法的大致逻辑如下: + +```java +int left = 0, right = 0; + +while (right < s.size()) { + // 增大窗口 + window.add(s[right]); + right++; + + while (window needs shrink) { + // 缩小窗口 + window.remove(s[left]); + left++; + } +} +``` + +> 以下是适用于字符串处理、限流等场景的通用框架: +> +> ```java +> public class SlidingWindow { +> +> // 核心双指针定义 +> int left = 0, right = 0; +> // 窗口状态容器(如哈希表、数组) +> Map window = new HashMap<>(); +> // 结果记录 +> List result = new ArrayList<>(); +> +> public void slidingWindow(String s, String target) { +> // 初始化目标状态(如字符频率) +> Map need = new HashMap<>(); +> for (char c : target.toCharArray()) { +> need.put(c, need.getOrDefault(c, 0) + 1); +> } +> +> while (right < s.length()) { +> char c = s.charAt(right); +> right++; +> // 更新窗口状态 +> window.put(c, window.getOrDefault(c, 0) + 1); +> +> // 窗口收缩条件 +> while (window.get(c) > need.getOrDefault(c, 0)) { +> char d = s.charAt(left); +> left++; +> // 更新窗口状态 +> window.put(d, window.get(d) - 1); +> } +> +> // 记录结果(如最小覆盖子串长度) +> if (right - left == target.length()) { +> result.add(left); +> } +> } +> } +> } +> ``` +> +> + +### 3.1 同向交替移动的两个变量 + +有一类数组上的问题,问我们固定长度的滑动窗口的性质,这类问题还算相对简单。 + +#### [子数组最大平均数 I(643)](https://leetcode-cn.com/problems/maximum-average-subarray-i/) + +> 给定 `n` 个整数,找出平均数最大且长度为 `k` 的连续子数组,并输出该最大平均数。 +> +> ``` +> 输入:[1,12,-5,-6,50,3], k = 4 +> 输出:12.75 +> 解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75 +> ``` + +**思路**: + +- 长度为固定的 K,想到用滑动窗口 +- 保存每个窗口的值,取这 k 个数的最大和就可以得出最大平均数 +- 怎么保存每个窗口的值,这一步 + +```java +public static double getMaxAverage(int[] nums, int k) { + int sum = 0; + //先求出前k个数的和 + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + } + //目前最大的数是前k个数 + int result = sum; + //然后从第 K 个数开始移动,保存移动中的和值,返回最大的 + for (int i = k; i < nums.length; i++) { + sum = sum - nums[i - k] + nums[i]; + result = Math.max(result, sum); + } + //返回的是double + return 1.0 * result / k; +} +``` + + + +### 3.2 不定长度的滑动窗口 + +#### [无重复字符的最长子串_3](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) + +> 给定一个字符串 `s` ,请你找出其中不含有重复字符的 **最长子串** 的长度。 +> +> ``` +> 输入: s = "abcabcbb" +> 输出: 3 +> 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 +> ``` + +思路: + +- 滑动窗口,其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列 +- 如何移动?我们只要把队列的左边的元素移出就行了,直到满足题目要求! +- 一直维持这样的队列,找出队列出现最长的长度时候,求出解! + +```java +int lengthOfLongestSubstring(String s) { + Map window = new HashMap<>(); + + int left = 0, right = 0; + int res = 0; // 记录结果 + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + window.put(c, window.getOrDefault(c, 0) + 1); + // 判断左侧窗口是否要收缩,字符串重复时收缩,注意这里是 while 不是 if + while (window.get(c) > 1) { + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + window.put(d, window.get(d) - 1); + } + // 在这里更新答案,我们窗口是左闭右开的,所以窗口实际包含的字符数是 right - left,无需 +1 + res = Math.max(res, right - left); + } + return res; +} +``` + + + +#### [最小覆盖子串(76)](https://leetcode-cn.com/problems/minimum-window-substring/) + +> 给你一个字符串 `s` 、一个字符串 `t` 。返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 `""` 。 +> +> ``` +> 输入:s = "ADOBECODEBANC", t = "ABC" +> 输出:"BANC" +> ``` + +思路: + +1. 我们在字符串 `S` 中使用双指针中的左右指针技巧,初始化 `left = right = 0`,把索引**左闭右开**区间 `[left, right)` 称为一个「窗口」。 + + > [!IMPORTANT] + > + > 为什么要「左闭右开」区间 + > + > **理论上你可以设计两端都开或者两端都闭的区间,但设计为左闭右开区间是最方便处理的**。 + > + > 因为这样初始化 `left = right = 0` 时区间 `[0, 0)` 中没有元素,但只要让 `right` 向右移动(扩大)一位,区间 `[0, 1)` 就包含一个元素 `0` 了。 + > + > 如果你设置为两端都开的区间,那么让 `right` 向右移动一位后开区间 `(0, 1)` 仍然没有元素;如果你设置为两端都闭的区间,那么初始区间 `[0, 0]` 就包含了一个元素。这两种情况都会给边界处理带来不必要的麻烦。 + +2. 我们先不断地增加 `right` 指针扩大窗口 `[left, right)`,直到窗口中的字符串符合要求(包含了 `T` 中的所有字符)。 + +3. 此时,我们停止增加 `right`,转而不断增加 `left` 指针缩小窗口 `[left, right)`,直到窗口中的字符串不再符合要求(不包含 `T` 中的所有字符了)。同时,每次增加 `left`,我们都要更新一轮结果。 + +4. 重复第 2 和第 3 步,直到 `right` 到达字符串 `S` 的尽头。 + +```java +public String minWindow(String s, String t) { + // 两个map,window 记录窗口中的字符频率,need 记录t中字符的频率 + HashMap window = new HashMap<>(); + HashMap need = new HashMap<>(); + + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + //算出每个字符的数量,有可能有重复的 + need.put(c, need.getOrDefault(c, 0) + 1); + } + + //左开右闭的区间,然后创建移动窗口 + int left = 0, right = 0; + // 窗口中满足need条件的字符个数, valid == need.size 说明窗口满足条件 + int valid = 0; + //记录最小覆盖子串的开始索引和长度 + int start = 0, len = Integer.MAX_VALUE; + while (right < s.length()) { + // c 代表将移入窗口的字符 + char c = s.charAt(right); + //扩大窗口 + right++; + + //先判断当前滑动窗口右端(right 指针处)的字符 c 是否是目标字符串 t 中的一个字符 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + //检查当前字符 c 的频率在滑动窗口中是否达到了目标字符串 t 中所要求的频率 + if (window.get(c).equals(need.get(c))) { + valid++; + } + } + //判断左窗口是否需要收缩 + while (valid == need.size()) { + //如果当前滑动窗口的长度比已记录的最小长度 len 更短,则说明找到了一个更小的符合条件的覆盖子串 + if (right - left < len) { + start = left; + len = right - left; + } + //d 是将移除窗口的字符 + char d = s.charAt(left); + left++; //缩小窗口 + + //更新窗口,收缩,更新窗口中的字符频率并检查是否还满足覆盖条件 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { + valid--; + window.put(d, window.get(d) - 1); + } + } + } + } + //返回最小覆盖子串 + return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); +} +``` + + + +#### [字符串的排列(567)](https://leetcode.cn/problems/permutation-in-string/description/) + +> 给你两个字符串 `s1` 和 `s2` ,写一个函数来判断 `s2` 是否包含 `s1` 的排列。如果是,返回 `true` ;否则,返回 `false` 。 +> +> 换句话说,`s1` 的排列之一是 `s2` 的 **子串** 。 +> +> ``` +> 输入:s1 = "ab" s2 = "eidbaooo" +> 输出:true +> 解释:s2 包含 s1 的排列之一 ("ba"). +> ``` + +思路: + +通过滑动窗口(Sliding Window)和字符频率统计来解决 + +和上一题基本一致,只是 移动 `left` 缩小窗口的时机是窗口大小大于 `t.length()` 时,当发现 `valid == need.size()` 时,就说明窗口中就是一个合法的排列 + +```java +// 判断 s 中是否存在 t 的排列 +public boolean checkInclusion(String t, String s) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) { + need.put(c, need.getOrDefault(c, 0) + 1); + } + + int left = 0, right = 0; + int valid = 0; + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).intValue() == need.get(c).intValue()) + valid++; + } + + // 判断左侧窗口是否要收缩 + while (right - left >= t.length()) { + // 在这里判断是否找到了合法的子串 + if (valid == need.size()) + return true; + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).intValue() == need.get(d).intValue()) + valid--; + window.put(d, window.get(d) - 1); + } + } + } + // 未找到符合条件的子串 + return false; +} +``` + + + +#### [替换后的最长重复字符(424)](https://leetcode-cn.com/problems/longest-repeating-character-replacement/) + +> 给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。 +> +> 注意:字符串长度 和 k 不会超过 10^4 +> +> ``` +> 输入:s = "ABAB", k = 2 +> 输出:4 +> 解释:用两个'A'替换为两个'B',反之亦然。 +> ``` +> +> ``` +> 输入:s = "AABABBA", k = 1 +> 输出:4 +> 解释:将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。子串 "BBBB" 有最长重复字母, 答案为 4。 +> ``` + +思路: + +```java +public int characterReplacement(String s, int k) { + int len = s.length(); + if (len < 2) { + return len; + } + + char[] charArray = s.toCharArray(); + int left = 0; + int right = 0; + + int res = 0; + int maxCount = 0; + int[] freq = new int[26]; + // [left, right) 内最多替换 k 个字符可以得到只有一种字符的子串 + while (right < len){ + freq[charArray[right] - 'A']++; + // 在这里维护 maxCount,因为每一次右边界读入一个字符,字符频数增加,才会使得 maxCount 增加 + maxCount = Math.max(maxCount, freq[charArray[right] - 'A']); + right++; + + if (right - left > maxCount + k){ + // 说明此时 k 不够用 + // 把其它不是最多出现的字符替换以后,都不能填满这个滑动的窗口,这个时候须要考虑左边界向右移动 + // 移出滑动窗口的时候,频数数组须要相应地做减法 + freq[charArray[left] - 'A']--; + left++; + } + res = Math.max(res, right - left); + } + return res; +} +``` + + + +### 3.3 计数问题 + +#### 至多包含两个不同字符的最长子串 + +> 给定一个字符串 `s`,找出 **至多** 包含两个不同字符的最长子串 `t` ,并返回该子串的长度。 +> +> ``` +> 输入: "eceba" +> 输出: 3 +> 解释: t 是 "ece",长度为3。 +> ``` + + + +```java +public int lengthOfLongestSubstringTwoDistinct(String s) { + if (s == null || s.length() == 0) { + return 0; + } + + // 滑动窗口的左指针 + int left = 0; + // 记录滑动窗口内的字符及其出现的频率 + Map map = new HashMap<>(); + // 记录最长子串的长度 + int maxLen = 0; + + // 遍历整个字符串 + for (int right = 0; right < s.length(); right++) { + // 右指针的字符 + char c = s.charAt(right); + // 将字符 c 加入到窗口中,并更新其出现的次数 + map.put(c, map.getOrDefault(c, 0) + 1); + + // 当窗口内的不同字符数超过 2 时,开始缩小窗口 + while (map.size() > 2) { + // 左指针的字符 + char leftChar = s.charAt(left); + // 减少左指针字符的频率 + map.put(leftChar, map.get(leftChar) - 1); + // 如果左指针字符的频率为 0,则从窗口中移除该字符 + if (map.get(leftChar) == 0) { + map.remove(leftChar); + } + // 移动左指针,缩小窗口 + left++; + } + + // 更新最大长度 + maxLen = Math.max(maxLen, right - left + 1); + } + + return maxLen; +} + + +``` + + + +#### 至多包含 K 个不同字符的最长子串_340 + +> 给定一个字符串 `s`,找出 **至多** 包含 `k` 个不同字符的最长子串 `T`。 +> +> ``` +> 输入: s = "eceba", k = 2 +> 输出: 3 +> 解释: 则 T 为 "ece",所以长度为 3。 +> ``` + +```java +public int lengthOfLongestSubstringKDistinct(String s, int k) { + if (s == null || s.length() == 0 || k == 0) { + return 0; + } + + // 滑动窗口的左指针 + int left = 0; + // 记录滑动窗口内的字符及其出现的频率 + Map map = new HashMap<>(); + // 记录最长子串的长度 + int maxLen = 0; + + // 遍历整个字符串 + for (int right = 0; right < s.length(); right++) { + // 右指针的字符 + char c = s.charAt(right); + // 将字符 c 加入到窗口中,并更新其出现的次数 + map.put(c, map.getOrDefault(c, 0) + 1); + + // 当窗口内的不同字符数超过 K 时,开始缩小窗口 + while (map.size() > k) { + // 左指针的字符 + char leftChar = s.charAt(left); + // 减少左指针字符的频率 + map.put(leftChar, map.get(leftChar) - 1); + // 如果左指针字符的频率为 0,则从窗口中移除该字符 + if (map.get(leftChar) == 0) { + map.remove(leftChar); + } + // 移动左指针,缩小窗口 + left++; + } + + // 更新最大长度 + maxLen = Math.max(maxLen, right - left + 1); + } + + return maxLen; +} +``` + + + +#### [ 区间子数组个数_795](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum/) + +> 给定一个元素都是正整数的数组`A` ,正整数 `L` 以及 `R` (`L <= R`)。 +> +> 求连续、非空且其中最大元素满足大于等于`L` 小于等于`R`的子数组个数。 +> +> ``` +> 例如 : +> 输入: +> A = [2, 1, 4, 3] +> L = 2 +> R = 3 +> 输出: 3 +> 解释: 满足条件的子数组: [2], [2, 1], [3]. +> ``` + +```java +public int numSubarrayBoundedMax(int[] nums, int left, int right) { + int count = 0; + int start = -1; // 记录大于right的元素下标 + int last_bounded = -1; // 记录符合[Left, Right]区间的元素下标 + + for (int i = 0; i < nums.length; i++) { + if (nums[i] > right) { + // 遇到大于right的元素,重置窗口起始点 + start = i; + } + if (nums[i] >= left && nums[i] <= right) { + // 记录符合区间条件的元素下标 + last_bounded = i; + } + // 计算当前有效子数组的数量 + count += last_bounded - start; + } + + return count; +} +``` + + + + + +### 3.4 使用数据结构维护窗口性质 + +有一类问题只是名字上叫「滑动窗口」,但解决这一类问题需要用到常见的数据结构。这一节给出的问题可以当做例题进行学习,一些比较复杂的问题是基于这些问题衍生出来的。 + +#### 滑动窗口最大值 + +#### 滑动窗口中位数 + + + + + +## 四、其他双指针问题 + +#### [最长回文子串_5](https://leetcode.cn/problems/longest-palindromic-substring/) + +> 给你一个字符串 `s`,找到 `s` 中最长的 回文子串。 + +```java +public static String longestPalindrome(String s){ + //处理边界 + if(s == null || s.length() < 2){ + return s; + } + + //初始化start和maxLength变量,用来记录最长回文子串的起始位置和长度 + int start = 0, maxLength = 0; + + //遍历每个字符 + for (int i = 0; i < s.length(); i++) { + //以当前字符为中心的奇数长度回文串 + int len1 = centerExpand(s, i, i); + //以当前字符和下一个字符之间的中心的偶数长度回文串 + int len2 = centerExpand(s, i, i+1); + + int len = Math.max(len1, len2); + + //当前找到的回文串大于之前的记录,更新start和maxLength + if(len > maxLength){ + // i 是当前扩展的中心位置, len 是找到的回文串的总长度,我们要用这两个值计算出起始位置 start + // (len - 1)/2 为什么呢,计算中心到回文串起始位置的距离, 为什么不用 len/2, 这里考虑的是奇数偶数的通用性,比如'abcba' 和 'abba' 或者 'cabbad',巧妙的同时处理两种,不需要分别考虑 + start = i - (len - 1)/2; + maxLength = len; + } + + } + + return s.substring(start, start + maxLength); +} + +private static int centerExpand(String s, int left, int right){ + while(left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){ + left --; + right ++; + } + //这个的含义: 假设扩展过程中,left 和 right 已经超出了回文返回, 此时回文范围是 (left+1,right-1), 那么回文长度= (right-1)-(left+1)+1=right-left-1 + return right - left - 1; +} +``` + + + +#### [合并两个有序数组_88](https://leetcode-cn.com/problems/merge-sorted-array/) + + + +#### [下一个排列_31](https://leetcode.cn/problems/next-permutation/) + +> 整数数组的一个 **排列** 就是将其所有成员以序列或线性顺序排列。 +> +> - 例如,`arr = [1,2,3]` ,以下这些都可以视作 `arr` 的排列:`[1,2,3]`、`[1,3,2]`、`[3,1,2]`、`[2,3,1]` 。 +> +> 整数数组的 **下一个排列** 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 **下一个排列** 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。 +> +> - 例如,`arr = [1,2,3]` 的下一个排列是 `[1,3,2]` 。 +> - 类似地,`arr = [2,3,1]` 的下一个排列是 `[3,1,2]` 。 +> - 而 `arr = [3,2,1]` 的下一个排列是 `[1,2,3]` ,因为 `[3,2,1]` 不存在一个字典序更大的排列。 +> +> 给你一个整数数组 `nums` ,找出 `nums` 的下一个排列。 +> +> 必须**[ 原地 ](https://baike.baidu.com/item/原地算法)**修改,只允许使用额外常数空间。 + +**Approach**: + +1. 我们希望下一个数 比当前数大,这样才满足 “下一个排列” 的定义。因此只需要 将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465。 +2. 我们还希望下一个数 增加的幅度尽可能的小,这样才满足“下一个排列与当前排列紧邻“的要求。为了满足这个要求,我们需要: + - 在 尽可能靠右的低位 进行交换,需要 从后向前 查找 + - 将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换 + - 将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序 + +该算法可以分为三个步骤: + +1. **从右向左找到第一个升序对**(即`nums[i] < nums[i + 1]`),记为`i`。如果找不到,则说明数组已经是最大的排列,直接将数组反转为最小的排列。 +2. **从右向左找到第一个比`nums[i]`大的数**,记为`j`,然后交换`nums[i]`和`nums[j]`。 +3. **反转`i + 1`之后的数组**,使其变成最小的排列。 + +```java +public void nextPermutation(int[] nums){ + + //为什么从倒数第二个元素开始,因为我们第一步要从右往左找到第一个“升序对”, + int i = nums.length - 2; + //step 1: 找到第一个下降的元素 + while (i >= 0 && nums[i] >= nums[i + 1]) { + i--; + } + + //step2 : 如果找到了 i, 找到第一个比 nums[i] 大的元素 j + if (i > 0) { + int j = nums.length - 1; + while (j >= 0 && nums[j] <= nums[i]) { + j--; + } + //交换i 和 j 的位置 + swap(nums, i, j); + + } + + // step3: 反转从 start 开始到末尾的部分(不需要重新排序,是因为这半部分再交换前就是降序的,我们第一步找的升序对) + reverse(nums, i+1); +} + +private void swap(int[] nums, int i, int j){ + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; +} + +private void reverse(int[] nums, int start){ + int end = nums.length - 1; + while(start < end){ + swap(nums, start, end); + start ++; + end --; + } +} +``` + + + +#### [颜色分类_75](https://leetcode.cn/problems/sort-colors/) + +> 给定一个包含红色、白色和蓝色、共 `n` 个元素的数组 `nums` ,**[原地](https://baike.baidu.com/item/原地算法)** 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 +> +> 我们使用整数 `0`、 `1` 和 `2` 分别表示红色、白色和蓝色。 +> +> 必须在不使用库内置的 sort 函数的情况下解决这个问题。 +> +> ``` +> 输入:nums = [2,0,2,1,1,0] +> 输出:[0,0,1,1,2,2] +> ``` + +**Approach**: + +荷兰国旗问题 + +我们可以使用三个指针:`low`、`mid` 和 `high`,分别用来处理 0、1 和 2 的排序问题。 + +- `low` 表示红色 (0) 的边界,指向的元素是 1 的位置,即把所有 0 放在 `low` 的左边。 +- `mid` 表示当前处理的元素索引。 +- `high` 表示蓝色 (2) 的边界,指向的元素是 2 的位置,把所有 2 放在 `high` 的右边。 + +**算法步骤:** + +1. 初始化:`low = 0`,`mid = 0`,`high = nums.length - 1`。 +2. 当 `mid <= high` 时,进行以下判断: + - 如果 `nums[mid] == 0`,将其与 `nums[low]` 交换,并将 `low` 和 `mid` 都加 1。 + - 如果 `nums[mid] == 1`,只需将 `mid` 加 1,因为 1 已经在正确的位置。 + - 如果 `nums[mid] == 2`,将其与 `nums[high]` 交换,并将 `high` 减 1,但 `mid` 不动,因为交换过来的数还未处理。 + +```java +public void sortColors(int[] nums) { + int low = 0, mid = 0, high = nums.length - 1; + + while (mid <= high) { + if (nums[mid] == 0) { + // 交换 nums[mid] 和 nums[low] + swap(nums, low, mid); + low++; + mid++; + } else if (nums[mid] == 1) { + mid++; + } else if (nums[mid] == 2) { + // 交换 nums[mid] 和 nums[high] + swap(nums, mid, high); + high--; + } + } +} + +private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; +} + +``` + +- 时间复杂度:O(n),每个元素只遍历一次。 + +- 空间复杂度:O(1),不需要额外的空间,只在原数组中进行操作。 + +双指针方法的话,就是两次遍历。 + +```java +public void sortColors(int[] nums) { + int left = 0; + int right = nums.length - 1; + + // 第一次遍历,把 0 移动到数组的左边 + for (int i = 0; i <= right; i++) { + if (nums[i] == 0) { + swap(nums, i, left); + left++; + } + } + + // 第二次遍历,把 2 移动到数组的右边 + for (int i = nums.length - 1; i >= left; i--) { + if (nums[i] == 2) { + swap(nums, i, right); + right--; + } + } +} + +private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; +} + +``` + + + +#### [排序链表_148](https://leetcode.cn/problems/sort-list/description/) + +> 给你链表的头结点 `head` ,请将其按 **升序** 排列并返回 **排序后的链表** 。 +> +> ``` +> 输入:head = [4,2,1,3] +> 输出:[1,2,3,4] +> ``` + +**Approach**: 要将链表排序,并且时间复杂度要求为 O(nlogn)O(n \log n)O(nlogn),这提示我们需要使用 **归并排序**。归并排序的特点就是时间复杂度是 O(nlogn)O(n \log n)O(nlogn),并且它在链表上的表现很好,因为链表的分割和合并操作相对容易。 + +具体实现步骤: + +1. **分割链表**:我们可以使用 **快慢指针** 来找到链表的中点,从而将链表一分为二。 +2. **递归排序**:分别对左右两部分链表进行排序。 +3. **合并有序链表**:最后将两个已经排序好的链表合并成一个有序链表。 + +```java + +public ListNode sortList(ListNode head) { + // base case: if the list is empty or contains a single element, it's already sorted + if (head == null || head.next == null) { + return head; + } + + // Step 1: split the linked list into two halves + ListNode mid = getMiddle(head); + // right 为链表右半部分的头结点 + ListNode right = mid.next; + mid.next = null; //断开 + + // Step 2: recursively sort both halves + ListNode leftSorted = sortList(head); + ListNode rightSorted = sortList(right); + + // Step 3: merge the sorted halves + return mergeTwoLists(leftSorted, rightSorted); +} + +// Helper method to find the middle node of the linked list +private ListNode getMiddle(ListNode head) { + ListNode slow = head; + ListNode fast = head; + + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + + return slow; +} + +// Helper method to merge two sorted linked lists +private ListNode mergeTwoLists(ListNode l1, ListNode l2) { + ListNode dummy = new ListNode(-1); + ListNode current = dummy; + + while (l1 != null && l2 != null) { + if (l1.val < l2.val) { + current.next = l1; + l1 = l1.next; + } else { + current.next = l2; + l2 = l2.next; + } + current = current.next; + } + + // Append the remaining elements of either list + if (l1 != null) { + current.next = l1; + } else { + current.next = l2; + } + + return dummy.next; +} +``` + + + +### 总结 + +区间不同的定义决定了不同的初始化逻辑、遍历过程中的逻辑。 + +- 移除元素 +- 删除排序数组中的重复项 II +- 移动零 + + + diff --git a/docs/data-structure-algorithms/Dynamic-Programming.md b/docs/data-structure-algorithms/algorithm/Dynamic-Programming.md similarity index 65% rename from docs/data-structure-algorithms/Dynamic-Programming.md rename to docs/data-structure-algorithms/algorithm/Dynamic-Programming.md index ae52f00ccb..9ea856aedc 100644 --- a/docs/data-structure-algorithms/Dynamic-Programming.md +++ b/docs/data-structure-algorithms/algorithm/Dynamic-Programming.md @@ -1,6 +1,14 @@ -# 动态规划——入门、刷题都有套路可言 +--- +title: 动态规划——刷题有套路 +date: 2024-03-09 +tags: + - Algorithm +categories: Algorithm +--- -## 前言 +> 动态规划,简直就是刷题模板、套路届的典范 + +## 一、前言 为了面试,不,不,为了提高技术能力,我重拾算法有一段时间了,但是每次都把动态规划放在了后边,因为这个大名鼎鼎的名字,听着就感觉很牛逼,很难学的样子。 @@ -11,16 +19,30 @@ > 虽然动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。 > -看完之后,我说了一句脏话,然后就开始找技术博客了。 +看完之后,我说了一句脏话,然后就开始找相关文章了。 -## 写在前面 +## 二、写在前面 计算机归根结底只会做一件事:穷举。 所有的算法都是在让计算机【如何聪明地穷举】而已,动态规划也是如此。 +> A : "1+1+1+1+1+1+1+1 =?等式的值是多少" +> +> B : 计算 "8" +> +> A : 在上面等式的左边写上 "1+" 呢? "此时等式的值为多少" +> +> B : 很快得出答案 "9" +> +> A : "你怎么这么快就知道答案了" +> +> B : "只要在8的基础上加1就行了" +> +> A : "所以你不用重新计算,因为你记住了第一个等式的值为8!动态规划算法也可以说是 '记住求过的解来节省时间'" + 本文将会从以下角度来讲解动态规划: - 什么是动态规划 @@ -29,7 +51,7 @@ -## 动态规划是什么 +## 三、动态规划是什么 动态规划(dynamic programming)是运筹学的一个分支,是解决**「多阶段决策」**过程最优化的一种数学方法。 @@ -39,41 +61,43 @@ - **多阶段决策**:比如说我们有一个复杂的问题要处理,我们可以按问题的时间或从空间关系分解成几个互相联系的阶段,使每个阶段的决策问题都是一个比较容易求解的“**子问题**”,这样依次做完每个阶段的最优决策后,他们就构成了整个问题的最优决策。简单地说,就是每做一次决策就可以得到解的一部分,当所有决策做完之后,完整的解就“浮出水面”了。有一种**大事化小,小事化了**的感觉。 -- **最优子结构**:在我们拆成一个个子问题的时候,每个子问题一定都有一个最优解,既然它分解的子问题是全局最优解,那么依赖于它们解的原问题自然也是全局最优解。比如说,你的原问题是考出最高的总成绩,那么你的子问题就是要把语文考到最高,数学考到最高…… 为了每门课考到最高,你要把每门课相应的选择题分数拿到最高,填空题分数拿到最高…… 当然,最终就是你每门课都是满分,这就是最高的总成绩。 +- **最优子结构**:在我们拆成一个个子问题的时候,每个子问题一定都有一个最优解,既然它分解的子问题是全局最优解,那么依赖于它们解的原问题自然也是全局最优解。比如说,你的原问题是考出最高的总成绩,那么你的子问题就是要把语文考到最高,数学考到最高…… 为了每门课考到最高,你要把每门课相应的选择题分数拿到最高,填空题分数拿到最高…… 当然,最终就是你每门课都是满分,这就是最高的总成绩。 - **自下而上**:或者叫自底向上,对应的肯定有**自上而下**(自顶向下) - 啥叫**自顶向下**,比如我们求解递归问题,画递归树的时候,是从上向下延伸,都是从一个规模较大的原问题比如说 f(20),向下逐渐分解规模,直到 f(1) 和 f(2) 触底,然后逐层返回答案,这就叫「自顶向下」,比如我们用递归法计算斐波那契数列的时候 -  +  - 反过来,自底向上,肯定就是从最底下,最简单,问题规模最小的 f(1) 和 f(2) 开始往上推,直到推到我们想要的答案 f(20),这就是动态规划的思路,这也是为什么动态规划一般都脱离了递归,而是由循环迭代完成计算。 -  +  -从递归树中我们可以看到,自顶向下的递归算法,我们会求两次 f(18),三次 f(17),,,这就存在了大量的**「重叠子问题」**,这样暴力穷举的话效率会极其低下,为了解决重叠子问题,我们可以通过「**备忘录**」或者「**DP table**」来优化穷举过程,避免不必要的计算。 +从递归树中我们可以看到,自顶向下的递归算法,我们会求两次 f(18),三次 f(17),,,这就存在了大量的**「重复子问题」**,这样暴力穷举的话效率会极其低下,为了解决重复子问题,我们可以通过「**备忘录**」或者「**DP table**」来优化穷举过程(记忆化递归法),避免不必要的计算。 -怎样才能自下而上的求出每个子问题的最优解呢,可以肯定子问题之间是有一定联系的,即**迭代递推公式**,也叫「**状态转移方程**」,实际上就是描述问题结构的数学形式。 +怎样才能自下而上的求出每个子问题的最优解呢,可以肯定子问题之间是有一定联系的,即**迭代递推公式**,也叫「**状态转移方程**」,实际上就是描述问题结构的数学形式。(把 `f(n)` 想做一个状态 `n`,这个状态 `n` 是由状态 `n - 1` 和状态 `n - 2` 相加转移而来,这就叫状态转移,仅此而已) -> 动态规划中当前的状态往往依赖于前一阶段的状态和前一阶段的决策结果。例如我们知道了第 i 个阶段的状态Si 以及决策 Ui,那么第 i+1 阶段的状态 Si+1 也就确定了。所以解决动态规划问题的关键就是确定状态转移方程,一旦状态转移方程确定了,那么我们就可以根据方程式进行编码。 +> 动态规划中当前的状态往往依赖于前一阶段的状态和前一阶段的决策结果。例如我们知道了第 i 个阶段的状态 Si 以及决策 Ui,那么第 i+1 阶段的状态 Si+1 也就确定了。所以解决动态规划问题的关键就是确定状态转移方程,一旦状态转移方程确定了,那么我们就可以根据方程式进行编码。 > 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 > -> 动态规划在查找有很多**重叠子问题**的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。 +> 动态规划在查找有很多**重叠子问题**的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,而不会在解决同样问题时再花费时间。 > > 动态规划只能应用于有**最优子结构**的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。 -以上提到的**重叠子问题、最优子结构、状态转移方程就是动态规划三要素**。 +以上提到的**重叠(复)子问题、最优子结构、状态转移方程就是动态规划三要素**。 +> 解决动态规划问题的核心:找出子问题及其子问题与原问题的关系 -## 斐波那契数列 -PS:我们先从一个简单的斐波那契数列来进一步理解下重叠子问题与状态转移方程(斐波那契数列并不是严格意义上的动态规划,因为它没有求最值,所以也没设计到最优子结构的问题) +### 斐波那契数列 + +PS:我们先从一个简单的斐波那契数列来进一步理解下重叠子问题与状态转移方程(斐波那契数列并不是严格意义上的动态规划,因为它没有求最值,所以也没涉及到最优子结构的问题) **1、暴力递归** @@ -88,8 +112,6 @@ int fib(int N) { 这个不用多说了,我们在 **自顶向下** 那部分画出的就是它的递归树,他有大量的重复计算问题,比如 `f(18)` 被计算了两次,而且你可以看到,以 `f(18)` 为根的这个递归树体量巨大,多算一遍,会耗费巨大的时间。更何况,还不止 `f(18)` 这一个节点被重复计算,所以这个算法及其低效。 - - 这就是动态规划问题的第一个性质:**重叠子问题**。下面,我们想办法解决这个问题。 **2、带备忘录的递归解法** @@ -130,13 +152,11 @@ public int fib(int n) { 带「备忘录」的递归算法,把一棵存在巨量冗余的递归树通过「**剪枝」**,改造成了一幅不存在冗余的递归图,极大减少了子问题(即递归图中节点)的个数。 -.png) - **3、动态规划解法** 有了上一步「备忘录」的启发,**自顶向下**的递推,每次“缓存”之前的结果,那**自底向上**的推算不也可以吗?而且推算的时候,我们只需要存储之前的两个状态就行,还省了很多空间,我靠,真是个天才,这就是,**动态规划**的做法。 - + 画个图就很好理解了,我们一层一层的往上计算,得到最后的结果。 @@ -163,23 +183,29 @@ public int fib(int n) { -## 什么样的题目适合用动态规划 +## 四、什么样的题目适合用动态规划 可以使用动态规划的问题一般都有一些特点可以遵循。如题目的问法一般是三种方式: -1. 求最大值/最小值 +1. 求最大值/最小值(除了类似找出数组中最大值这种) + + 乘积最大子数组、最长回文子串、最长上升子序列等等 + +2. 求可行性(True 或 False) -2. 求可不可行 + 凑领钱、字符串交错组成问题 3. 求方案总数 + 硬币组合问题、路径规划问题 + 如果你碰到一个问题,是问你这三个问题之一的,那么有 90% 的概率是可以使用动态规划来求解。 一个问题是否能够用动态规划算法来解决,需要看这个问题是否能被分解为更小的问题(子问题)。而子问题往下细分为更小的子问题的时候往往会遇到重复的子问题,我们只处理同一个子问题一次,将它的结果保存起来,这就是动态规划最大的特点。 -接下来就要去理解动态规划的思路了,通常情况下,DP 题可从下面 4个要素去逐步剖析: +接下来就要去理解动态规划的思路了,通常情况下,DP 题可从下面 4 个要素去逐步剖析: **1. 状态是什么** @@ -191,18 +217,24 @@ public int fib(int n) { -## 套路解题 +## 五、套路解题 动态规划是用大白话说就是一个算法范例(或者理解为一个方法论,模板),**通过将其分解为子问题来解决给定的复杂问题,并存储子问题的结果,以避免再次计算相同的结果**。 我们知道了动态规划三要素:重叠子问题、最优子结构、状态转移方程。 -那要解决一个动态规划问题的大概步骤,就围绕这人这三要素展开: +那要解决一个动态规划问题的大概步骤,就围绕这三要素展开: 1. **划分阶段:**分析题目可以用动态规划解决,那就先看这个问题如何划分成各个子问题 -2. **选择状态:**网上都说有个选择状态的过程,我理解其实就是看求解的结果,我们一般用数组来存储子问题结果,所以状态我们一般定义为 $dp[i]$ + +2. **状态定义**:也有叫选择状态的,其实就是定义子问题,我理解其实就是看求解的结果,我们一般用数组来存储子问题结果,所以状态我们一般定义为 $dp[i]$,表示规模为 i 的问题的解,$dp[i-1]$ 就是规模为 i-1 的子问题的解 + 3. **确定决策并写出状态转移方程:**听名字就觉得牛逼的一步,肯定也是最难的一步,其实就是我们从 f(1)、f(2)、f(3) ... f(n-1) 一步步递推出 f(n) 的表达式,也就是说,dp[n] 一定会和 dp[n-1], dp[n-2]....存在某种关系的,这一步就是找出数组元素的关系式,比如斐波那契数列的关系式 $dp[n] = dp[n-1] + dp[n-2]$ + + > 一般来说函数的参数就是状态转移中会变化的量,也就是上面说到的「状态」;函数的返回值就是题目要求我们计算的 + 4. **找出初始值(包括边界条件):**既然状态转移方程式写好了,但是还需要一个**支点**来撬动它进行不断的计算下去,比如斐波那契数列中的 f(1)=1,f(2)=1,就是初始值 + 5. **优化**:思考有没有可以优化的点 @@ -213,7 +245,7 @@ public int fib(int n) { 按上面的套路走,最后的结果就可以套这个框架: -``` +```java # 初始化 base case dp[0][0][...] = base # 进行状态转移 @@ -227,16 +259,16 @@ for 状态1 in 状态1的所有取值: +## 六、找感觉(刷题) + 斐波那契数列上手后,我们用解题套路看下 leetcode_70,据说是道正宗的动态规划问题。 -### 一、爬楼梯(leetcode_70) +### 1、[爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/)(leetcode_70) > 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。 > -> **示例 :** -> > ``` -> 输入: 2 +>输入: 2 > 输出: 2 > 解释: 有两种方法可以爬到楼顶。 > 1. 1 阶 + 1 阶 @@ -261,9 +293,13 @@ for 状态1 in 状态1的所有取值: 爬 4 级楼梯的方式数 = 爬 3 级楼梯的方式数 + 爬 2 级楼梯的方式数 -。。。 +> 第二次做的时候,我没有用 『自底向上』,而是用『自上向下』的举例,陷入了一种错误 +> +> 我想的是 f(n) = f(n-1) + 1,从上往下的算,留出一级,肯定只能是爬 1 级这一种,所以 +> +> f(5) = f(4) + 1 ....... -一脸懵逼,这不是上一节的斐波那契数列吗????? +这不是上一节的斐波那契数列吗????? 用 $f(x)$ 表示爬到第 x 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子: @@ -284,8 +320,8 @@ $f(x) = f(x - 1) + f(x - 2)$ public int climbStairs(int n) { // 创建一个数组来保存历史数据 int[] dp = new int[n + 1]; - // 给出初始值, 爬楼梯的初始值应该是爬 1 级有1 种,2级的话有 2 种,这里2级也是个初始值 - dp[0] = 1; + // 给出初始值, 爬楼梯的初始值 + dp[0] = 0; dp[1] = 1; for(int i = 2; i <= n; i++) { //写出状态转移方程 @@ -299,30 +335,28 @@ public int climbStairs(int n) { -### 二、最大子序和(leetcode_53) +### 2、[最大子数组和](https://leetcode-cn.com/problems/maximum-subarray/)(leetcode_53) > 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 > -> 示例: -> > ``` -> 输入: [-2,1,-3,4,-1,2,1,-5,4] +>输入: [-2,1,-3,4,-1,2,1,-5,4] > 输出: 6 > 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 > ``` -> +> #### 小技巧——涨知识 拿到这类题目,避免不了的是要遍历,假设我们给定数组 [a,b,c,d,e] ,通常我们遍历子串或者子序列有如下三种遍历方式: -- 以某个元素开头的所有子序列,比如以 a 开头的子序列 [a],[a, b],[ a, b, c] ... 接着是以 b 开头的子序列 [b],[b, c],[b,c,d] ... 接着是 c 开头、d 开头... +- 以某个元素开头的所有子序列,比如以 a 开头的子序列 [a],[a, b],[ a, b, c] ... 接着是以 b 开头的子序列 [b],[b, c],[b,c,d] ... 接着是 c 开头、d 开头... - 以子序列的长度为基准,比如先遍历出子序列长度为 1 的子序列,再遍历出长度为 2 的 ... - 以某个元素结尾的所有子序列,比如以 a 结束的子序列只有 [a],以 b 结束的子序列 [a,b],[b],以 c 结束的子序列 [a,b,c],[b,c],[c],以 d 结束的 ... 想想这道题,用哪种遍历方式合适一些呢? -用哪种遍历方式,可以逐个分析嘛。第一种遍历方式通常用于暴力解法,第二中后边我们也会用到(最长回文子串),第三种由于可以产生递推关系,动态规划问题用的挺多的。 +用哪种遍历方式,可以逐个分析嘛。第一种遍历方式通常用于暴力解法,第二种后边我们也会用到(最长回文子串),第三种由于可以产生递推关系,动态规划问题用的挺多的。 #### 分析题目 @@ -336,14 +370,14 @@ public int climbStairs(int n) { 2. **初始状态**:如果数组只有 1 个元素,那 dp 数组的第一个元素也就是数组的第一个元素本身,**`dp[0] = nums[0]`**; -3. **状态转移方程**:因为我们的数组中可能有负数的情况,从头遍历的话,如有下一个元素是负数的话,和反而更小了,所以我们遍历以元素结尾的子序列,若前一个元素大于 0($nums[i-1] > 0$),则将它加到当前元素上 $nums[i] = nums[i-1]+nums[i]$。最终 $nums[i]$ 保存的是以原先数组中 $nums[i]$ 结尾的最大子序列和。最后整体遍历一边 $nums[i]$ 就能找到整个数组最大的子序列和啦。所以状态转移方程: +3. **状态转移方程**:因为我们的数组中可能有负数的情况,从头遍历的话,如有下一个元素是负数的话,和反而更小了,所以我们遍历以元素结尾的子序列,若前一个元素大于 0($nums[i-1] > 0$),则将它加到当前元素上 $nums[i] = nums[i-1]+nums[i]$。最终 $nums[i]$ 保存的是以原先数组中 $nums[i]$ 结尾的最大子序列和。最后整体遍历一遍 $nums[i]$ 就能找到整个数组最大的子序列和啦。所以状态转移方程: $dp[i]=\max \{nums[i],dp[i−1]+nums[i]\}$ 4. **输出结果**:转移方程只是保存了当前元素的最大和,我们要求的是最终的那个最大值,所以需要从 dp[i] 中找到最大值返回 ```java -public int maxSubArray3(int[] nums) { +public int maxSubArray(int[] nums) { //特判 if (nums == null || nums.length == 0) { return 0; @@ -382,34 +416,23 @@ public int maxSubArray(int[] nums) { -### 三、打家劫舍 +### 3、[ 打家劫舍](https://leetcode-cn.com/problems/house-robber/)(leetcode_198) > 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 > -> 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 -> -> 示例 1: +> 给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 > > ``` -> 输入:[1,2,3,1] +>输入:[1,2,3,1] > 输出:4 > 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 > 偷窃到的最高金额 = 1 + 3 = 4 。 > ``` -> -> 示例 2: -> -> ``` -> 输入:[2,7,9,3,1] -> 输出:12 -> 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 -> 偷窃到的最高金额 = 2 + 9 + 1 = 12 。 -> ``` -> -> -> 提示: -> -> 0 <= nums.length <= 100 +> +> +>提示: +> +>0 <= nums.length <= 100 > 0 <= nums[i] <= 400 #### 分析题目 @@ -449,7 +472,7 @@ public int rob(int[] nums) { #### 优化 -同样的优化套路,上述方法使用了数组存储结果。但是每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额。和斐波那契额数列优化同理。 +同样的优化套路,上述方法使用了数组存储结果。但是每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额。和斐波那契数列优化同理。 ```java public int rob(int[] nums) { @@ -472,7 +495,7 @@ public int rob(int[] nums) { -### 四、不同路径(leetcode_62) +### 4、[不同路径](https://leetcode-cn.com/problems/unique-paths/)(leetcode_62) > 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 > @@ -484,10 +507,8 @@ public int rob(int[] nums) { > > 例如,上图是一个7 x 3 的网格。有多少可能的路径? > -> 示例 1: -> > ``` -> 输入: m = 3, n = 2 +>输入: m = 3, n = 2 > 输出: 3 > 解释: > 从左上角开始,总共有 3 条路径可以到达右下角。 @@ -496,17 +517,14 @@ public int rob(int[] nums) { > 2. 向右 -> 向下 -> 向右 > 3. 向下 -> 向右 -> 向右 > ``` -> -> -> 示例 2: -> +> > ``` -> 输入: m = 7, n = 3 -> 输出: 28 +>输入: m = 7, n = 3 +>输出: 28 > ``` > > **提示:** -> +> > - `1 <= m, n <= 100` > - 题目数据保证答案小于等于 `2 * 10 ^ 9` @@ -516,16 +534,16 @@ public int rob(int[] nums) { 2. **初始状态**:$dp[m][n]$ 表示到坐标 (m,n) 的路径条数。由于机器人从 m=0,n=0 出发,每次只能向下或者向右移动,因此,在所有坐标为(0,m) 的位置机器人要到达的话只有一条路径(一直向下);在所有坐标为(n,0) 的位置,机器人要到达也只有一条路径(一直向右)在机器人走第 0 行,第 0 列的时候,无论怎么走,都只有 1 种走法。因此初始值是: - ``` + ```java dp[0] [0….n-1] = 1; // 机器人一直向右走,第 0 列统统为 1 dp[0…m-1] [0] = 1; // 机器人一直向下走,第 0 列统统为 1 ``` -3. **状态转移方程**:要到达任一位置 (m,n) 的总路径条数,总是等于位置 (m-1,j) 的路径条数 加上 位置(m,n-1) 的路径条数。即 $dp[m][n] = dp[m-1][n] + dp[m][n-1]$ +3. **状态转移方程**:要到达任一位置 (m,n) 的总路径条数,总是等于位置 (m-1,n) 的路径条数加上位置(m,n-1) 的路径条数。即 $dp[m][n] = dp[m-1][n] + dp[m][n-1]$ 4. **输出结果**:由于数组是从下标 0 开始算起的,所以 $dp[m - 1][n - 1]$ 才是我们要的结果 - + ```java public int uniquePaths(int m, int n) { @@ -535,17 +553,17 @@ public int uniquePaths(int m, int n) { //定义初始值 for (int i = 0; i < m; i++) { - dp[m][0] = 1; + dp[i][0] = 1; } - for (int i = 0; i < n; i++) { - dp[0][n] = 1; + for (int j = 0; j < n; j++) { + dp[0][j] = 1; } // 排除初始值的情况,都从 1 开始循环 for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { - dp[m][n] = dp[m - 1][n] + dp[m][n - 1]; + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } // 由于数组是从下标 0 开始算起的,所以dp[m - 1][n - 1] 是我们要的结果 @@ -572,28 +590,21 @@ public int uniquePaths(int m, int n) { -### 五、零钱兑换(leetcode_322) +### 5、[零钱兑换](https://leetcode-cn.com/problems/coin-change/)(leetcode_322) -> 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 -> -> 示例 1: +> 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 (你可以认为每种硬币的数量是无限的。) > > ``` -> 输入: coins = [1, 2, 5], amount = 11 +>输入: coins = [1, 2, 5], amount = 11 > 输出: 3 > 解释: 11 = 5 + 5 + 1 > ``` -> -> -> 示例 2: -> +> > ``` -> 输入: coins = [2], amount = 3 -> 输出: -1 +>输入: coins = [2], amount = 3 +>输出: -1 > ``` > -> 说明: -> 你可以认为每种硬币的数量是无限的。 #### 分析题目 @@ -601,11 +612,9 @@ public int uniquePaths(int m, int n) { 以 coins = [1, 2, 5],amount = 11 为例。我们要求组成 11 的最少硬币数,可以考虑组合中的最后一个硬币分别是1,2,5 的情况,比如: -最后一个硬币是 1 的话,最少硬币数应该为【组成 10 的最少硬币数】+ 1枚(1块硬币) - -最后一个硬币是 2 的话,最少硬币数应该为【组成 9 的最少硬币数】+ 1枚(2块硬币) - -最后一个硬币是 5 的话,最少硬币数应该为【组成 6 的最少硬币数】+ 1枚(5块硬币) +- 最后一个硬币是 1 的话,最少硬币数应该为【组成 10 的最少硬币数】+ 1枚(1块硬币) +- 最后一个硬币是 2 的话,最少硬币数应该为【组成 9 的最少硬币数】+ 1枚(2块硬币) +- 最后一个硬币是 5 的话,最少硬币数应该为【组成 6 的最少硬币数】+ 1枚(5块硬币) 在这 3 种情况中硬币数最少的那个就是结果 @@ -619,90 +628,218 @@ public int uniquePaths(int m, int n) { ```java for(int coin : coins){ - result = Math.min(result,1+dp[n-coin]) + result = Math.min(result,1+dp[amout-coin]) } ``` 4. **输出结果**: $dp[amout]$ ```java -public static int coinChange(int[] coins, int amount) { +public int coinChange(int[] coins, int amount) { //定义数组 int[] dp = new int[amount + 1]; int max = amount + 1; - // 初始化每个值为 amount+1,这样当最终求得的 dp[amount] 为 amount+1 时,说明问题无解 + // 初始化每个值为 amount+1,这样当最终求得的 dp[amount] 为 amount+1 时,说明问题无解, 或者初始化一个特殊值 Arrays.fill(dp, max); //初始值 dp[0] = 0; - // 外层 for 循环在遍历所有状态的所有取值 + // 外层 for 循环在遍历所有可能得金额,(从1到amount) //dp[i]上的值不断选择已含有硬币值当前位置的数组值 + 1,min保证每一次保存的是最小值 for (int i = 1; i < amount + 1; i++) { - //内层 for 循环在求所有选择的最小值 状态转移方程 + //内层循环所有硬币面额 for (int coin : coins) { + //如果i= coin) { //分两种情况,使用硬币coin和不使用,取最小值 dp[i] = Math.min(dp[i - coin] + 1, dp[i]); } } } - return dp[amount] > amount ? -1 : dp[amount]; + return dp[amount] == amount + 1 ? -1 : dp[amount]; } ``` +> 为啥 `dp` 数组初始化为 `amount + 1` 呢,因为凑成 `amount` 金额的硬币数最多只可能等于 `amount`(全用 1 元面值的硬币),所以初始化为 `amount + 1` 就相当于初始化为正无穷,便于后续取最小值。为啥不直接初始化为 int 型的最大值 `Integer.MAX_VALUE` 呢?因为后面有 `dp[i - coin] + 1`,这就会导致整型溢出。 +> +> 最终,dp[amout] 就是凑成总金额所需的最少硬币数,如果dp[amount] 仍是初始化的较大值,说明无法凑出,返回 -1。 +### 6、[买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/)(leetocode_121) - - -### 六、买卖股票的最佳时机(leetocode_121) - -> 买卖股票的最佳时机 > 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 -> -> 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 -> -> 注意:你不能在买入股票前卖出股票。 -> -> 示例 1: -> -> ``` +> +>如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 +> +>注意:你不能在买入股票前卖出股票。 +> +>``` > 输入: [7,1,5,3,6,4] -> 输出: 5 +>输出: 5 > 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 -> 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 -> ``` -> -> 示例 2: -> +> 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 > ``` +> +> ``` > 输入: [7,6,4,3,1] -> 输出: 0 +>输出: 0 > 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 -> ``` +>``` #### 分析题目 我们需要找出给定数组中两个数字之间的最大差值(即,最大利润)。此外,第二个数字(卖出价格)必须大于第一个数字(买入价格) +```java +public int dp(int[] prices) { + int length = prices.length; + if (length == 0) { + return 0; + } + int dp[] = new int[length]; + //保存一个最小值 + int minPrice = prices[0]; + for (int i = 1; i < length; i++) { + minPrice = Math.min(minPrice, prices[i]); + dp[i] = Math.max(dp[i - 1], prices[i] - minPrice); + } + return dp[length - 1]; +} +``` - -### 七、最长回文子串(leetcode_5) +### 7、最长回文子串(leetcode_5) > 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 > -> 示例 1: -> > ``` -> 输入: "babad" +>输入: "babad" > 输出: "bab" > 注意: "aba" 也是一个有效答案。 > ``` +#### 分析题目 + +回文的意思是正着念和倒着念一样,如:大波美人鱼人美波大 + +建立二维数组 `dp` ,找出所有的回文子串。 + +回文串两边加上两个相同字符,会形成一个新的回文串 。 + + + + + +我们用`dp[i][j]` 记录子串 `i..j` 是否为回文串 。 + + + +首先,单个字符就形成一个回文串,所以,所有 `dp[i][i] = true` 。 + + + +然后,容易得到递推关系: + +如果字符 `s[i]` 和 `s[j]` 相等,并且子串 `i+1..j-1` 是回文串的话,子串 `i..j` 也是回文串。 + +也就是,如果 `s[i] == s[j]` 且 `dp[i+1][j-1] = true` 时,`dp[i][j] = true` 。 + + + +这是本方法中主要的递推关系。 + +不过仍要注意边界情况,即 子串 `i+1..j-1` 的有效性 ,当 `i+1 <= j-1` 时,它才有效。 + +反之,如果不满足,此时 `j <= i+1` ,也就是子串 `i..j` 最多有两个字符, 如果两个字符 `s[i]` 和 `s[j]` 相等,那么是回文串。 + +```java +public String longestPalindrome_1(String s) { + int length = s.length(); + if (length < 2) { + return s; + } + boolean dp[][] = new boolean[length][length]; + for (int i = 0; i < length; i++) { + dp[i][i] = true; + } + + char[] chars = s.toCharArray(); + + //通过最大长度定位回文串位置,或者也可以用个数组记录int[] res = new int[2]; + int maxLen = 1; + int begin = 0; + for (int r = 1; r < length; r++) { + for (int l = 0; l < r; l++) { + if (chars[l] != chars[r]) { + dp[l][r] = false; + } else { + // 特例,如果 是 abaa 这种,需要最后一个和第一个也相等,但是他们距离大于等于了3,所以还需要往里判断 + if (r - l < 3) { + dp[l][r] = true; + } else { + dp[l][r] = dp[l + 1][r - 1]; + } + } + + //只要 dp[l][r] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置 + if (dp[l][r] && r - l + 1 > maxLen) { + maxLen = r - l + 1; + begin = l; + } + } + } + return s.substring(begin, begin + maxLen); +} +``` + + + +### 8、数字三角形问题 + +``` +7 +3 8 +8 1 0 +2 7 4 4 +4 5 2 6 5 +``` + +从上到下选择一条路,使得经过的数字之和最大。 + +路径上的每一步只能往左下或者右下走。 + +#### 分析题目 + +递归解法 + +可以看出每走第n行第m列时有两种后续:向下或者向右下。由于最后一行可以确定,当做边界条件,所以我们自然而然想到递归求解 + +```java +class Solution{ + + public int getMax(){ + int MAX = 101; + int[][] D = new int[MAX][MAX]; //存储数字三角形 + int n; //n表示层数 + int i = 0; int j = 0; + int maxSum = getMaxSum(D,n,i,j); + return maxSum; + } + + public int getMaxSum(int[][] D,int n,int i,int j){ + if(i == n){ + return D[i][j]; + } + int x = getMaxSum(D,n,i+1,j); + int y = getMaxSum(D,n,i+1,j+1); + return Math.max(x,y)+D[i][j]; + } +} +``` + @@ -713,14 +850,80 @@ public static int coinChange(int[] coins, int amount) { -参考: +## 番外篇 + +### 动态规划与其它算法的关系 + +#### **1. 贪心算法(Greedy Algorithm)** + +**核心思想**:每一步都做出当前状态下的**局部最优选择**(即 “贪心选择”),不考虑该选择对后续步骤的影响,最终通过局部最优的累积试图得到全局最优解。本质是 “短视的”:只看眼前,不回头。 + +**特点**: + +- 必须满足**贪心选择性质**:局部最优选择能导致全局最优解(否则贪心会失效)。 +- 子问题无依赖:每一步的选择不影响后续子问题的求解(子问题独立)。 + +**适用场景**: + +- 最优子结构明确(如霍夫曼编码) +- 贪心选择性质成立(如活动选择问题) +- 无需全局最优验证(如零钱兑换特殊场景) + +#### 2. 分治法(Divide and Conquer) + +**核心思想**: 将原问题**分解为若干个规模更小、结构相同的子问题**,递归求解子问题后,**合并子问题的解**得到原问题的解。 +本质是 “分而治之”:拆分独立子问题,逐个击破再整合。 + +**特点**: + +- **递归结构**:子问题相互独立(无重叠) +- **合并成本高**:结果合并是关键步骤 +- **并行潜力**:子问题可并发求解 + +**适用场景**: + +- 问题可自然拆分(如排序、树操作) +- 子问题规模相似(如二分搜索) +- 合并操作复杂度可控(如归并排序) + +#### **3. 动态规划(Dynamic Programming)** + +**核心思想**: 对于具有**重叠子问题**和**最优子结构**的问题,将其分解为子问题后,**存储子问题的解(记忆化)** 以避免重复计算,通过子问题的解推导出原问题的解。本质是 “精打细算的”:记录历史,避免重复劳动。 + +**特点**: + +- **最优子结构**:全局最优包含局部最优 +- **重叠子问题**:子问题反复出现(用表存储) +- **状态转移方程**:定义问题间递推关系 + +**适用场景**: + +- 子问题重叠(如斐波那契数列) +- 多阶段决策最优解(如背包问题) +- 需要回溯最优路径(如最长公共子序列) + +| **特性** | 贪心算法 | 分治法 | 动态规划 | +| -------------- | ---------------------- | -------------------- | ------------------ | +| **决策依据** | 当前局部最优 | 子问题独立解 | 历史子问题最优解 | +| **子问题关系** | 无重复计算 | 完全独立 | 高度重叠 | +| **解空间处理** | 永不回溯 | 显式分割 | 存储+重用 | +| **时间复杂度** | 通常最低 | 中等(依赖合并成本) | 通常较高 | +| **结果保证** | **不保证全局最优** | 保证正确解 | **保证全局最优** | +| **经典案例** | Dijkstra算法、活动选择 | 归并排序、快速排序 | 背包问题、最短路径 | + + + +## Reference + +- http://netedu.xauat.edu.cn/jpkc/netedu/jpkc/ycx/kcjy/kejian/pdf/05.pdf + +- https://leetcode-cn.com/circle/article/lxC3ZB/ -http://netedu.xauat.edu.cn/jpkc/netedu/jpkc/ycx/kcjy/kejian/pdf/05.pdf +- https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie -https://leetcode-cn.com/circle/article/lxC3ZB/ +- https://www.zhihu.com/question/39948290 -https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie +- https://zhuanlan.zhihu.com/p/26743197 -https://www.zhihu.com/question/39948290 +- https://writings.sh/post/algorithm-longest-palindromic-substrings -https://zhuanlan.zhihu.com/p/26743197 \ No newline at end of file diff --git a/docs/data-structure-algorithms/algorithm/Greedy.md b/docs/data-structure-algorithms/algorithm/Greedy.md new file mode 100644 index 0000000000..c08936ef44 --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/Greedy.md @@ -0,0 +1,118 @@ +--- +title: 贪心算法——刷题有套路 +date: 2024-09-09 +tags: + - Algorithm + - Greedy +categories: Algorithm +--- + + + +> 贪心算法(greedy algorithm)是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。贪心算法简洁且高效,在许多实际问题中有着广泛的应用。 +> +> 贪心算法和动态规划都常用于解决优化问题。它们之间存在一些相似之处,比如都依赖最优子结构性质,但工作原理不同。 +> +> - 动态规划会根据之前阶段的所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解。 +> - 贪心算法不会考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决。 + + + +### 贪心算法的应用场景 + +解决一个问题需要多个步骤,每一个步骤有多种选择。可以使用贪心算法解决的问题,每一步只需要解决一个子问题,只做出一种选择,就可以完成任务。 + +### 贪心算法特性 + +较于动态规划,贪心算法的使用条件更加苛刻,其主要关注问题的两个性质。 + +- **贪心选择性质**:只有当局部最优选择始终可以导致全局最优解时,贪心算法才能保证得到最优解。 +- **最优子结构**:原问题的最优解包含子问题的最优解。 + +#### 贪心算法与回溯算法、动态规划的区别 + +「解决一个问题需要多个步骤,每一个步骤有多种选择」这样的描述我们在「回溯算法」「动态规划」算法中都会看到。它们的区别如下: + +- 「回溯算法」需要记录每一个步骤、每一个选择,用于回答所有具体解的问题; +- 「动态规划」需要记录的是每一个步骤、所有选择的汇总值(最大、最小或者计数); +- 「贪心算法」由于适用的问题,每一个步骤只有一种选择,一般而言只需要记录与当前步骤相关的变量的值。 + +对于不同的求解目标和不同的问题场景,需要使用不同的算法。 + +#### 可以使用「贪心算法」的问题需要满足的条件 + +- 最优子结构:规模较大的问题的解由规模较小的子问题的解组成,区别于「动态规划」,可以使用「贪心算法」的问题「规模较大的问题的解」只由其中一个「规模较小的子问题的解」决定; +- 无后效性:后面阶段的求解不会修改前面阶段已经计算好的结果; + +- 贪心选择性质:从局部最优解可以得到全局最优解。 + +对「最优子结构」和「无后效性」的理解同「动态规划」,「贪心选择性质」是「贪心算法」最需要关注的内容。 + + + +### 贪心算法解题步骤 + +贪心问题的解决流程大体可分为以下三步。 + +1. **问题分析**:梳理与理解问题特性,包括状态定义、优化目标和约束条件等。这一步在回溯和动态规划中都有涉及。 +2. **确定贪心策略**:确定如何在每一步中做出贪心选择。这个策略能够在每一步减小问题的规模,并最终解决整个问题。 +3. **正确性证明**:通常需要证明问题具有贪心选择性质和最优子结构。这个步骤可能需要用到数学证明,例如归纳法或反证法等。 + +确定贪心策略是求解问题的核心步骤,但实施起来可能并不容易,主要有以下原因。 + +- **不同问题的贪心策略的差异较大**。对于许多问题来说,贪心策略比较浅显,我们通过一些大概的思考与尝试就能得出。而对于一些复杂问题,贪心策略可能非常隐蔽,这种情况就非常考验个人的解题经验与算法能力了。 +- **某些贪心策略具有较强的迷惑性**。当我们满怀信心设计好贪心策略,写出解题代码并提交运行,很可能发现部分测试样例无法通过。这是因为设计的贪心策略只是“部分正确”的,上文介绍的零钱兑换就是一个典型案例。 + + + +#### 「力扣」第 455 题:分发饼干(简单) + +> 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 +> +> 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 +> +> 示例 1: +> +> 输入: g = [1,2,3], s = [1,1] +> 输出: 1 +> 解释: +> 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 +> 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 +> 所以你应该输出1。 + +```java +class Solution { + public int findContentChildren(int[] g, int[] s) { + Arrays.sort(g); + Arrays.sort(s); + int numOfChildren = g.length, numOfCookies = s.length; + int count = 0; + for (int i = 0, j = 0; i < numOfChildren && j < numOfCookies; i++, j++) { + while (j < numOfCookies && g[i] > s[j]) { + j++; + } + if (j < numOfCookies) { + count++; + } + } + return count; + } +} +``` + +「贪心算法」总是做出在当前看来最好的选择就可以完成任务; +解决「贪心算法」几乎没有套路,到底如何贪心,贪什么与我们要解决的问题密切相关。因此刚开始学习「贪心算法」的时候需要学习和模仿,然后才有直觉,猜测一个问题可能需要使用「贪心算法」,进而尝试证明,学会证明。 + + + +## 贪心算法典型例题 + +贪心算法常常应用在满足贪心选择性质和最优子结构的优化问题中,以下列举了一些典型的贪心算法问题。 + +- **硬币找零问题**:在某些硬币组合下,贪心算法总是可以得到最优解。 +- **区间调度问题**:假设你有一些任务,每个任务在一段时间内进行,你的目标是完成尽可能多的任务。如果每次都选择结束时间最早的任务,那么贪心算法就可以得到最优解。 +- **分数背包问题**:给定一组物品和一个载重量,你的目标是选择一组物品,使得总重量不超过载重量,且总价值最大。如果每次都选择性价比最高(价值 / 重量)的物品,那么贪心算法在一些情况下可以得到最优解。 +- **股票买卖问题**:给定一组股票的历史价格,你可以进行多次买卖,但如果你已经持有股票,那么在卖出之前不能再买,目标是获取最大利润。 +- **霍夫曼编码**:霍夫曼编码是一种用于无损数据压缩的贪心算法。通过构建霍夫曼树,每次选择出现频率最低的两个节点合并,最后得到的霍夫曼树的带权路径长度(编码长度)最小。 +- **Dijkstra 算法**:它是一种解决给定源顶点到其余各顶点的最短路径问题的贪心算法。 + diff --git a/docs/data-structure-algorithms/algorithm/Recursion.md b/docs/data-structure-algorithms/algorithm/Recursion.md new file mode 100644 index 0000000000..f8a1992021 --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/Recursion.md @@ -0,0 +1,389 @@ +--- +title: 递归算法 +date: 2024-05-09 +tags: + - Recursion +categories: Algorithm +--- + + + + + +### 什么是递归 + +递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。 + +**简单地说,就是如果在函数中存在着调用函数本身的情况,这种现象就叫递归。** + +你以前肯定写过递归,只是有可能某些不知道这就是递归罢了。 + +以阶乘函数为例,在 factorial 函数中存在着 `factorial(n - 1)` 的调用,所以此函数是递归函数 + +```java +public long factorial(int n) { + if (n < =1) { + return 1; + } + return n * factorial(n - 1) +} +``` + +进一步剖析「递归」,先有「递」再有「归」,「递」的意思是将问题拆解成子问题来解决, 子问题再拆解成子子问题,...,直到被拆解的子问题无需再拆分成更细的子问题(即可以求解),「归」是说最小的子问题解决了,那么它的上一层子问题也就解决了,上一层的子问题解决了,上上层子问题自然也就解决了,....,直到最开始的问题解决,文字说可能有点抽象,那我们就以阶层 f(6) 为例来看下它的「递」和「归」。 + + + +求解问题 `f(6)`,由于 `f(6) = n * f(5)`, 所以 `f(6)` 需要拆解成 `f(5)` 子问题进行求解,同理 `f(5) = n * f(4) `,也需要进一步拆分,... ,直到 `f(1)`, 这是「递」,`f(1) `解决了,由于 `f(2) = 2 f(1) = 2` 也解决了,.... `f(n)` 到最后也解决了,这是「归」,所以递归的本质是能把问题拆分成具有**相同解决思路**的子问题,。。。直到最后被拆解的子问题再也不能拆分,解决了最小粒度可求解的子问题后,在「归」的过程中自然顺其自然地解决了最开始的问题。 + + + +### 递归原理 + +递归的基本思想是某个函数直接或者间接地调用自身,这样就把原问题的求解转换为许多性质相同但是规模更小的子问题。 + +我们只需要关注如何把原问题划分成符合条件的子问题,而不需要去研究这个子问题是如何被解决的。 + +递归和枚举的区别在于:枚举是横向地把问题划分,然后依次求解子问题,而递归是把问题逐级分解,是纵向的拆分。 + +```java +int func(你今年几岁) { + // 最简子问题,递归终止条件 + if (你1999年几岁) return 我0岁; + // 递归调用,缩小规模 + return func(你去年几岁) + 1; +} +``` + +任何一个有意义的递归算法总是两部分组成:**递归调用**和**递归终止条件**。 + +为了确保递归函数不会导致无限循环,它应具有以下属性: + +1. 一个简单的`基本案例(basic case)`(或一些案例) —— 能够不使用递归来产生答案的终止方案。 +2. 一组规则,也称作`递推关系(recurrence relation)`,可将所有其他情况拆分到基本案例。 + +注意,函数可能会有多个位置进行自我调用。 + +```java +public returnType recursiveFunction(parameters) { + // 1. 递归终止条件(Base Case) + if (isBaseCase(parameters)) { + return baseCaseResult; // 返回问题的基本解,避免继续递归 + } + + // 2. 递归调用(Recursive Call) + // 将当前问题分解为更小的子问题 + return recursiveFunction(modifiedParameters); + + // 或者对于分治问题,递归调用多个子问题: + // return combineResults( + // recursiveFunction(subProblem1), + // recursiveFunction(subProblem2) + // ); +} + +``` + + + +### 递归需要满足的三个条件 + +只要同时满足以下三个条件,就可以用递归来解决。 + +1. **一个问题的解可以分解为几个子问题的解** + + 何为子问题?子问题就是数据规模更小的问题。比如前面的案例,要知道“自己在哪一排”,可以分解为“前一排的人在哪一排”这样的一个子问题。 + +2. **这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样** + + 如案例所示,求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路是一模一样的。 + +3. **存在递归终止条件** + + 把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能存在无限循环,这就需要有终止条件。 + + + +### 怎样编写递归代码 + +写递归代码,可以按三步走: + +**第一要素:明确你这个函数想要干什么** + +首先,你需要明确你要解决的问题,以及这个问题是否适合用递归来解决。 + +也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。 + +例如,我定义了一个函数,算 n 的阶乘 + +```java +// 算 n 的阶乘(假设n不为0) +int factorial(int n){ + +} +``` + +**第二要素:寻找递归结束条件** + +递归基(Base Case)是递归函数结束的条件。如果没有递归基,递归函数会无限循环下去。递归基应该是问题的最小单位,在这种情况下,函数可以直接返回结果而无需进一步递归。 + +也就是说,我们需要找出**当参数为啥时,递归结束,之后直接把结果返回**,请注意,这个时候我们必须能根据这个参数的值,能够**直接**知道函数的结果是什么。 + +例如,上面那个例子,当 n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面 + +```java +// 算 n 的阶乘(假设n不为0) +int f(int n){ + if(n == 1){ + return 1; + } +} +``` + +有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗? + +当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件,所以下面这段代码也是可以的。 + +```java +// 算 n 的阶乘(假设n>=2) +int f(int n){ + if(n == 2){ + return 2; + } +} +``` + +注意我代码里面写的注释,假设 n >= 2,因为如果 n = 1时,会被漏掉,当 n <= 2时,f(n) = n,所以为了更加严谨,我们可以写成这样: + +```java +// 算 n 的阶乘(假设n不为0) +int f(int n){ + if(n <= 2){ + return n; + } +} +``` + +**第三要素:定义递归关系** + +第三要素就是,我们要**不断缩小参数的范围**,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。 + +例如,f(n) 这个范围比较大,我们可以让 `f(n) = n * f(n-1)`。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数 f(n) 不变,我们需要让 f(n-1) 乘以 n。 + +说白了,就是要找到原函数的一个等价关系式,`f(n)` 的等价关系式为 `n * f(n-1)`,即 `f(n) = n * f(n-1)`。 + +> **写递归代码的关键就是找到如何将大问题分解为小问题的规律,请求基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码**。 +> +> 当我们面对一个问题需要分解为多个子问题的时候,递归代码往往没那么好理解,比如第二个案例,人脑几乎没办法把整个“递”和“归”的过程一步一步都想清楚。 +> +> 计算机擅长做重复的事情,所以递归正符合它的胃口。而我们人脑更喜欢平铺直叙的思维方式。当我们看到递归时,我们总想把递归平铺展开,脑子里就会循环,一层一层往下调,然后再一层一层返回,试图想搞清楚计算机每一步都是怎么执行的,这样就很容易被绕进去。 +> +> 对于递归代码,这种试图想清楚整个递和归过程的做法,实际上是进入了一个思维误区。很多时候,我们理解起来比较吃力,主要原因就是自己给自己制造了这种理解障碍。那正确的思维方式应该是怎样的呢? +> +> 如果一个问题 A 可以分解为若干子问题 B、C、D,可以假设子问题 B、C、D 已经解决,在此基础上思考如何解决问题 A。而且,只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。 +> +> 换句话说就是:千万不要跳进这个函数里面企图探究更多细节,否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。 +> +> 所以,编写递归代码的关键是:**只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤**。 + + + +#### 递归代码要警惕堆栈溢出 + +递归调用的深度受限于程序的栈空间。如果递归深度太深,可能会导致栈溢出。对于深度较大的递归问题,可以考虑使用迭代方法或尾递归优化(Tail Recursion Optimization)。 + + + +#### 递归代码要警惕重复计算 + +在某些问题中(如斐波那契数列),直接递归可能导致大量重复计算,影响效率。可以使用记忆化(Memoization)或动态规划(Dynamic Programming)来优化递归,避免重复计算。 + + + +### 案例 + +#### 斐波那契数列 + +> 斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34....,即第一项 f(1) = 1,第二项 f(2) = 1.....,第 n 项目为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。 + +```java +int f(int n){ + // 1.先写递归结束条件 + if(n <= 2){ + return 1; + } + // 2.接着写等价关系式 + return f(n-1) + f(n - 2); +} +``` + + + +#### 小青蛙跳台阶 + +> 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 + +每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。 + +第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。 + +第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。 + +所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了 + +```java +int f(int n){ + // 1.先写递归结束条件 + if(n == 1){ + return 1; + } + // 2.接着写等价关系式 + ruturn f(n-1) + f(n-2); +} +``` + +大家觉得上面的代码对不对? + +答是不大对,当 n = 2 时,显然会有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,按道理是递归结束,不用继续往下调用的,但我们上面的代码逻辑中,会继续调用 f(0) = f(-1) + f(-2)。这会导致无限调用,进入**死循环**。 + +这也是我要和你们说的,关于**递归结束条件是否够严谨问题**,有很多人在使用递归的时候,由于结束条件不够严谨,导致出现死循环。也就是说,当我们在第二步找出了一个递归结束条件的时候,可以把结束条件写进代码,然后进行第三步,但是**请注意**,当我们第三步找出等价函数之后,还得再返回去第二步,根据第三步函数的调用关系,会不会出现一些漏掉的结束条件。就像上面,f(n-2)这个函数的调用,有可能出现 f(0) 的情况,导致死循环,所以我们把它补上。代码如下: + +```java +int f(int n){ + //f(0) = 0,f(1) = 1,等价于 n<=1时,f(n) = n。 + if(n <= 1){ + return n; + } + ruturn f(n-1) + f(n-2); +} +``` + +#### 反转单链表 + +> 反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1 + +链表的节点定义如下: + +```java +class Node{ + int date; + Node next; +} +``` + +这个的等价关系不像 n 是个数值那样,比较容易寻找。但是我告诉你,它的等价条件中,一定是范围不断在缩小,对于链表来说,就是链表的节点个数不断在变小 + +```java +//用递归的方法反转链表 +public Node reverseList(Node head){ + // 1.递归结束条件 + if (head == null || head.next == null) { + return head; + } + // 递归反转 子链表 + Node newList = reverseList(head.next); + // 改变 1,2节点的指向。 + // 通过 head.next获取节点2 + Node t1 = head.next; + // 让 2 的 next 指向 2 + t1.next = head; + // 1 的 next 指向 null. + head.next = null; + // 把调整之后的链表返回。 + return newList; + } +``` + + + +#### [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) + +> 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 +> +>  +> +> ``` +> 输入:head = [1,2,3,4] +> 输出:[2,1,4,3] +> ``` + +当前节点 next,指向当前节点,指针互换 + +```java + public ListNode swapPairs(ListNode head) { + // 基本情况:链表为空或只有一个节点 + if (head == null || head.next == null) { + return head; + } + + // 交换前两个节点 + ListNode first = head; + ListNode second = head.next; + + // 递归交换后续的节点 + first.next = swapPairs(second.next); + + // 交换后的第二个节点成为新的头节点 + second.next = first; + + // 返回新的头节点 + return second; + } +``` + +下边这么写也可以,少了一个局部变量,交换操作和递归调用在一行内完成。 + +```java +public ListNode swapPairs(ListNode head) { + //递归的终止条件 + if(head==null || head.next==null) { + return head; + } + //假设链表是 1->2->3->4 + //这句就先保存节点2 + ListNode tmp = head.next; + //继续递归,处理节点3->4 + //当递归结束返回后,就变成了4->3 + //于是head节点就指向了4,变成1->4->3 + head.next = swapPairs(tmp.next); + //将2节点指向1 + tmp.next = head; + return tmp; +} +``` + +当然,也可以迭代实现~ + + + +```java +public class Solution { + public ListNode swapPairs(ListNode head) { + // 创建虚拟头节点,指向链表的头节点 + ListNode dummy = new ListNode(0); + dummy.next = head; + + // 当前节点指针,初始化为虚拟头节点 + ListNode current = dummy; + + // 遍历链表 + while (current.next != null && current.next.next != null) { + // 初始化两个要交换的节点 + ListNode first = current.next; + ListNode second = current.next.next; + + // 交换这两个节点 + first.next = second.next; + second.next = first; + current.next = second; + + // 移动 current 指针到下一个需要交换的位置 + current = first; + } + + // 返回交换后的链表头 + return dummy.next; + } +} + +``` + diff --git a/docs/data-structure-algorithms/algorithm/Sort.md b/docs/data-structure-algorithms/algorithm/Sort.md new file mode 100644 index 0000000000..dbbc9842d3 --- /dev/null +++ b/docs/data-structure-algorithms/algorithm/Sort.md @@ -0,0 +1,826 @@ +--- +title: 排序 +date: 2023-10-09 +tags: + - Algorithm +categories: Algorithm +--- + + + +> 🔢 **排序算法**,从接触计算机学科就会遇到的一个问题。 + +排序算法可以分为**内部排序**和**外部排序**: +- 🧠 **内部排序**:数据记录在内存中进行排序 +- 💾 **外部排序**:因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存 + +常见的内部排序算法有:**插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序**等。 + +| 排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 | +| :----------------------: | -------------- | ----------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------- | +| 冒泡排序-Bubble Sort | $O(n²)$ | $O(n)$(当数组已经有序时,只需遍历一次) | $O(n²)$(当数组是逆序时,需要多次交换) | $O(1)$(原地排序) | 稳定 | +| 选择排序-Selection Sort | $O(n²)$ | $O(n²)$ | $O(n²)$ | $O(1)$(原地排序) | 不稳定(交换元素时可能破坏相对顺序) | +| 插入排序-Insertion Sort | $O(n²)$ | $O(n)$(当数组已经有序时) | $O(n²)$(当数组是逆序时) | $O(1)$(原地排序) | 稳定 | +| 快速排序-Quick Sort | $O(n \log n)$ | $O(n \log n)$(每次划分的子数组大小相等) | $O(n²)$(每次选取的基准值使得数组划分非常不平衡) | $O(\log n)$(对于递归栈) $O(n)$(最坏情况递归栈深度为 n) | 不稳定(交换可能改变相同元素的相对顺序) | +| 希尔排序-Shell Sort | $O(n \log² n)$ | $O(n \log n)$ | $O(n²)$(不同的增量序列有不同的最坏情况) | $O(1)$(原地排序) | 不稳定 | +| 归并排序-Merge Sort | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(n)$(需要额外的空间用于辅助数组) | 稳定 | +| 堆排序-Heap Sort | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(1)$(原地排序) | 不稳定(在调整堆时可能改变相同元素的相对顺序) | +| 计数排序-Counting Sort) | $O(n + k)$ | $O(n + k)$(k 是数组中元素的取值范围) | $O(n + k)$ | $O(n + k)$(需要额外的数组来存储计数结果) | 稳定 | +| 桶排序-Bucket Sort) | $O(n + k)$ | $O(n + k)$(k 是桶的数量,n 是元素数量) | $O(n²)$(所有元素都集中到一个桶里,退化成冒泡排序) | $O(n + k)$ | 稳定 | +| 基数排序-Radix Sort | $O(d(n + k))$ | $O(d(n + k))$(d 是位数,k 是取值范围,n 是元素数量) | $O(d(n + k))$ | $O(n + k)$ | 稳定 | + +## 📊 排序算法分类 + +十种常见排序算法可以分为两大类: + +### 🔄 非线性时间比较类排序 +通过比较来决定元素间的相对次序,由于其时间复杂度不能突破$O(nlogn)$,因此称为非线性时间比较类排序。 + +> 💡 **特点**:需要比较元素大小,时间复杂度下界为 $O(n\log n)$ + +### ⚡ 线性时间非比较类排序 +不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。 + +> 💡 **特点**:利用数据特性,可以达到线性时间复杂度 + + + +## 🫧 冒泡排序 + +冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。 + +> 🎯 **算法特点**: +> - 简单易懂,适合学习排序思想 +> - 时间复杂度:$O(n²)$ +> - 空间复杂度:$O(1)$ +> - 稳定排序 + +作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。 + +### 1. 算法步骤 + +1. 🔍 **比较相邻元素**:如果第一个比第二个大,就交换他们两个 +2. 🔄 **重复比较**:对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数 +3. 🎯 **缩小范围**:针对所有的元素重复以上的步骤,除了最后一个 +4. 🔁 **重复过程**:重复步骤1~3,直到排序完成 + +### 2. 动图演示 + + + + + +### 3. 性能分析 + +- 🚀 **什么时候最快**:当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊) +- 🐌 **什么时候最慢**:当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗) + +```java +//冒泡排序,a 表示数组, n 表示数组大小 +public void bubbleSort(int[] a) { + int n = a.length; + if (n <= 1) return; + // 外层循环遍历每一个元素 + for (int i = 0; i < n; i++) { + //提前退出冒泡循环的标志位 + boolean flag = false; + // 内层循环进行元素的比较与交换 + for (int j = 0; j < n - i - 1; j++) { + if (a[j] > a[j+1]) { + int temp = a[j]; + a[j] = a[j+1]; + a[j+1] = temp; + flag = true; //表示有数据交换 + } + } + if (!flag) break; //没有数据交换,提前退出。 + } +} +``` + +嵌套循环,应该立马就可以得出这个算法的时间复杂度为 $O(n²)$。 + +冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度是 $O(1)$,是一个原地排序算法。 + + + +## 🎯 选择排序 + +选择排序的思路是这样的:首先,找到数组中最小的元素,拎出来,将它和数组的第一个元素交换位置,第二步,在剩下的元素中继续寻找最小的元素,拎出来,和数组的第二个元素交换位置,如此循环,直到整个数组排序完成。 + +> 🎯 **算法特点**: +> - 简单直观,容易理解 +> - 时间复杂度:$O(n²)$(无论什么情况) +> - 空间复杂度:$O(1)$ +> - 不稳定排序 + +选择排序是一种简单直观的排序算法,无论什么数据进去都是 $O(n²)$ 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。 + +### 1. 算法步骤 + +1. 🔍 **找最小元素**:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 +2. 🔄 **继续寻找**:再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾 +3. 🔁 **重复过程**:重复第二步,直到所有元素均排序完毕 + +### 2. 动图演示 + + + +```java +public void selectionSort(int [] arrs) { + for (int i = 0; i < arrs.length; i++) { + //最小元素下标 + int min = i; + for (int j = i +1; j < arrs.length; j++) { + if (arrs[j] < arrs[min]) { + min = j; + } + } + //如果当前位置i的元素已经是未排序部分的最小元素,就不需要交换了 + if(min != i){ + //交换位置 + int temp = arrs[i]; + arrs[i] = arrs[min]; + arrs[min] = temp; + } + } + } +} +``` + + + +## 📝 插入排序 + +插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。 + +> 🎯 **算法特点**: +> - 像整理扑克牌一样直观 +> - 时间复杂度:$O(n²)$(最坏),$O(n)$(最好) +> - 空间复杂度:$O(1)$ +> - 稳定排序 + +它的工作原理为将待排列元素划分为「已排序」和「未排序」两部分,每次从「未排序的」元素中选择一个插入到「已排序的」元素中的正确位置。 + +插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。 + +### 1. 算法步骤 + +1. 🎯 **起始点**:从第一个元素开始(下标为 0 的元素),该元素可以认为已经被排序 +2. 🔍 **取下一个元素**:取出下一个元素,在已经排序的元素序列中**从后向前**扫描 +3. 🔄 **比较移动**:如果该元素(已排序)大于新元素,将该元素移到下一位置 +4. 🔁 **重复比较**:重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 +5. 📍 **插入位置**:将新元素插入到该位置后 +6. 🔁 **重复过程**:重复步骤2~5 + +### 2. 动图演示 + + + +```java +public void insertionSort(int[] arr) { + // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的 + for (int i = 1; i < arr.length; i++) { + int current = arr[i]; // 当前待插入元素 + int j = i - 1; // 已排序部分的末尾索引 + + // 从后向前扫描,找到插入位置 + while (j >= 0 && arr[j] > current) { + arr[j + 1] = arr[j]; // 元素后移 + j--; // 索引左移 + } + arr[j + 1] = current; // 插入到正确位置 + } +} +``` + + + +## ⚡ 快速排序 + +快速排序的核心思想是分治法,分而治之。它的实现方式是每次从序列中选出一个基准值,其他数依次和基准值做比较,比基准值大的放右边,比基准值小的放左边,然后再对左边和右边的两组数分别选出一个基准值,进行同样的比较移动,重复步骤,直到最后都变成单个元素,整个数组就成了有序的序列。 + +> 🎯 **算法特点**: +> - 分治法思想,效率高 +> - 时间复杂度:$O(n\log n)$(平均),$O(n²)$(最坏) +> - 空间复杂度:$O(\log n)$ +> - 不稳定排序 + +> 快速排序的最坏运行情况是 $O(n²)$,比如说顺序数列的快排。但它的平摊期望时间是 $O(nlogn)$,且 $O(nlogn)$ 记号中隐含的常数因子很小,比复杂度稳定等于 $O(nlogn) $的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。 +> +>  + +### 1. 算法步骤 + +1. 🎯 **选择基准值**:从数组中选择一个元素作为基准值(pivot)。常见的选择方法有选取第一个元素、最后一个元素、中间元素或随机选取一个元素 +2. 🔄 **分区(Partition)**:遍历数组,将所有小于基准值的元素放在基准值的左侧,大于基准值的元素放在右侧。基准值放置在它的正确位置上 +3. 🔁 **递归排序**:对基准值左右两边的子数组分别进行递归排序,直到每个子数组的元素个数为 0 或 1,此时数组已经有序 + +递归的最底部情形,是数列的大小是零或一,也就是数组都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。 + +### 2. 动图演示 + + + +```java +public class QuickSort { + // 对外暴露的排序方法:传入待排序数组 + public static void sort(int[] arr) { + if (arr == null || arr.length <= 1) { + return; // 数组为空或只有1个元素,无需排序 + } + // 调用递归方法:初始排序范围是整个数组(从0到最后一个元素) + quickSort(arr, 0, arr.length - 1); + } + + // 递归排序方法:排序 arr 的 [left, right] 区间 + private static void quickSort(int[] arr, int left, int right) { + // 递归终止条件:当 left >= right 时,子数组只有1个元素或为空,无需排序 + if (left >= right) { + return; + } + + // 1. 分区操作:返回基准值最终的索引位置 + // (把比基准小的放左边,比基准大的放右边,基准在中间) + int pivotIndex = partition(arr, left, right); + + quickSort(arr, left, pivotIndex - 1); + quickSort(arr, pivotIndex + 1, right); + } + + // 分区核心方法:选 arr[right] 为基准,完成分区并返回基准索引 + private static int partition(int[] arr, int left, int right) { + // 基准值:这里选当前区间的最后一个元素 + int pivot = arr[right]; + // i 指针:指向“小于基准的区域”的最后一个元素(初始时区域为空,i = left-1) + int i = left - 1; + + // j 指针:遍历当前区间的所有元素(从 left 到 right-1,跳过基准) + for (int j = left; j < right; j++) { + // 如果当前元素 arr[j] 小于等于基准,就加入“小于基准的区域” + if (arr[j] <= pivot) { + i++; // 先扩大“小于基准的区域” + swap(arr, i, j); // 交换 arr[i] 和 arr[j],把 arr[j] 放进区域 + } + } + + // 最后:把基准值放到“小于基准区域”的后面(i+1 位置) + // 此时 i+1 就是基准的最终位置(左边都<=基准,右边都>基准) + swap(arr, i + 1, right); + return i + 1; + } + + // 辅助方法:交换数组中两个位置的元素 + private static void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } + +} +``` + +**📊 时间复杂度分析** + +**最优情况:$O(n \log n)$** + +- **适用场景**:每次分区操作都能将数组**均匀划分**(基准值接近中位数) +- **推导过程**: + - 每次分区将数组分为两个近似相等的子数组 + - 递归深度为 $\log_2 n$(分治次数),每层需遍历 $n$ 个元素 + - 总比较次数满足递推公式:$T(n) = 2T(n/2) + O(n)$ + - 通过主定理或递归树展开可得时间复杂度为 $O(n \log n)$ + +**最坏情况:$O(n²)$** + +- **适用场景**:每次分区划分极不均衡(如基准值始终为最大/最小元素) +- **常见案例**:输入数组已完全有序或逆序,且基准固定选择首/尾元素 +- **推导过程**: + - 每次分区仅减少一个元素(类似冒泡排序) + - 总比较次数为等差数列求和:$(n-1) + (n-2) + \cdots + 1 = \frac{n(n-1)}{2} = O(n^2)$ + - 递归深度为 $n-1$ 层,导致时间复杂度退化为 $O(n²)$ + +**平均情况:$O(n \log n)$** + +- **适用场景**:输入数据**随机分布**,基准值随机选取 + + + +## 🔀 归并排序 + +>  + +归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 + +> 🎯 **算法特点**: +> - 分治法典型应用 +> - 时间复杂度:$O(n\log n)$(稳定) +> - 空间复杂度:$O(n)$ +> - 稳定排序 + +分治,就是分而治之,将一个大问题分解成小的子问题来解决。小的问题解决了,大问题也就解决了。 + +分治思想和递归思想很像。分治算法一般都是用递归来实现的。**分治是一种解决问题的处理思想,递归是一种编程技巧**,这两者并不冲突。 + +作为一种典型的分而治之思想的算法应用,归并排序的实现有两种方法: + +- 🔄 **自上而下的递归** +- 🔁 **自下而上的迭代**(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法) + +和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 $O(n\log n)$ 的时间复杂度。代价是需要额外的内存空间。 + +### 1. 算法步骤 + +1. 🔄 **分解**:将数组分成两半,递归地对每一半进行归并排序,直到每个子数组的大小为1(单个元素是有序的) +2. 🔀 **合并**:将两个有序子数组合并成一个有序数组 + +### 2. 动图演示 + + + + + +```java +public class MergeSort { + // 主排序函数 + public static void mergeSort(int[] arr) { + if (arr.length < 2) return; // 基本情况 + int mid = arr.length / 2; + + // 分解 + int[] left = new int[mid]; + int[] right = new int[arr.length - mid]; + + // 填充左子数组 + for (int i = 0; i < mid; i++) { + left[i] = arr[i]; + } + + // 填充右子数组 + for (int i = mid; i < arr.length; i++) { + right[i - mid] = arr[i]; + } + + // 递归排序 + mergeSort(left); + mergeSort(right); + + // 合并已排序的子数组 + merge(arr, left, right); + } + + // 合并两个有序数组 + private static void merge(int[] arr, int[] left, int[] right) { + // i、j、k 分别代表左子数组、右子数组、合并数组的指针 + int i = 0, j = 0, k = 0; + + // 合并两个有序数组,直到子数组都插入到合并数组 + while (i < left.length && j < right.length) { + //比较 left[i] 和 right[j], 左右哪边小的,就放入合并数组,指针要 +1 + if (left[i] <= right[j]) { + arr[k++] = left[i++]; + } else { + arr[k++] = right[j++]; + } + } + + // 复制剩余元素 + while (i < left.length) { + arr[k++] = left[i++]; + } + while (j < right.length) { + arr[k++] = right[j++]; + } + } + + public static void main(String[] args) { + int[] arr = {38, 27, 43, 3, 9, 82, 10}; + mergeSort(arr); + System.out.println("排序后的数组:"); + for (int num : arr) { + System.out.print(num + " "); + } + } +} + +``` + + + + + +## 🌳 堆排序 + +>  + +堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。 + +> 🎯 **算法特点**: +> - 基于堆数据结构 +> - 时间复杂度:$O(n\log n)$ +> - 空间复杂度:$O(1)$ +> - 不稳定排序 + +分为两种方法: + +1. 📈 **大顶堆**:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列 +2. 📉 **小顶堆**:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列 + +堆排序的平均时间复杂度为 $Ο(n\log n)$。 + +### 1. 算法步骤 + +1. 🏗️ **构建最大堆**: + - 首先将无序数组转换为一个**最大堆**。最大堆是一个完全二叉树,其中每个节点的值都大于或等于其子节点的值 + - 最大堆的根节点(堆顶)是整个堆中的最大元素 + +2. 🔄 **反复取出堆顶元素**: + - 将堆顶元素(最大值)与堆的最后一个元素交换,然后减少堆的大小,堆顶元素移到数组末尾 + - 调整剩余的元素使其重新成为一个最大堆 + - 重复这个过程,直到所有元素有序 + +### 2. 动图演示 + + + +```java +public class HeapSort { + // 主排序函数 + public static void heapSort(int[] arr) { + int n = arr.length; + + // 1. 构建最大堆 + for (int i = n / 2 - 1; i >= 0; i--) { + heapify(arr, n, i); + } + + // 2. 逐步将堆顶元素与末尾元素交换,并缩小堆的范围 + for (int i = n - 1; i > 0; i--) { + // 将当前堆顶(最大值)移到末尾 + swap(arr, 0, i); + + // 重新调整堆,使剩余元素保持最大堆性质 + heapify(arr, i, 0); + } + } + + // 调整堆的函数 + private static void heapify(int[] arr, int n, int i) { + int largest = i; // 设当前节点为最大值 + int left = 2 * i + 1; // 左子节点 + int right = 2 * i + 2; // 右子节点 + + // 如果左子节点比当前最大值大 + if (left < n && arr[left] > arr[largest]) { + largest = left; + } + + // 如果右子节点比当前最大值大 + if (right < n && arr[right] > arr[largest]) { + largest = right; + } + + // 如果最大值不是根节点,则交换,并递归调整 + if (largest != i) { + swap(arr, i, largest); + heapify(arr, n, largest); + } + } + + // 交换两个元素的值 + private static void swap(int[] arr, int i, int j) { + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } +} + +``` + + + +## 🔢 计数排序 + +**计数排序(Counting Sort)** 是一种基于计数的非比较排序算法,适用于对**非负整数**进行排序。计数排序通过统计每个元素出现的次数,然后利用这些信息将元素直接放到它们在有序数组中的位置,从而实现排序。 + +> 🎯 **算法特点**: +> - 非比较排序算法 +> - 时间复杂度:$O(n + k)$ +> - 空间复杂度:$O(k)$ +> - 稳定排序 +> - 适用于数据范围不大的场景 + +计数排序的时间复杂度为 `O(n + k)`,其中 `n` 是输入数据的大小,`k` 是数据范围的大小。它特别适用于数据范围不大但数据量较多的场景。 + +计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 + +### 1. 算法步骤 + +1. 🔍 **找到最大值和最小值**:找到数组中最大值和最小值,以确定计数数组的范围 +2. 🏗️ **创建计数数组**:创建一个计数数组,数组长度为 `max - min + 1`,用来记录每个元素出现的次数 +3. 📊 **统计每个元素的出现次数**:遍历原数组,将每个元素出现的次数记录在计数数组中 +4. 🔄 **累积计数**:将计数数组变为累积计数数组,使其表示元素在有序数组中的位置 +5. 📝 **回填到结果数组**:根据累积计数数组,回填到结果数组,得到最终的排序结果 + +### 2. 动图演示 + +[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/countingSort.gif) + +```java +import java.util.Arrays; + +public class CountingSort { + // 计数排序函数 + public static void countingSort(int[] arr) { + if (arr.length == 0) return; + + // 1. 找到数组中的最大值和最小值 + int max = arr[0]; + int min = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (arr[i] > max) { + max = arr[i]; + } else if (arr[i] < min) { + min = arr[i]; + } + } + + // 2. 创建计数数组 + int range = max - min + 1; + int[] count = new int[range]; + + // 3. 统计每个元素的出现次数 + for (int i = 0; i < arr.length; i++) { + count[arr[i] - min]++; + } + + // 4. 计算累积计数,确定元素的最终位置 + for (int i = 1; i < count.length; i++) { + count[i] += count[i - 1]; + } + + // 5. 创建结果数组,并根据累积计数将元素放到正确位置 + int[] output = new int[arr.length]; + for (int i = arr.length - 1; i >= 0; i--) { + output[count[arr[i] - min] - 1] = arr[i]; + count[arr[i] - min]--; + } + + // 6. 将排序好的结果复制回原数组 + for (int i = 0; i < arr.length; i++) { + arr[i] = output[i]; + } + } +} + +``` + + + +## 🪣 桶排序 + +桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。 + +桶排序(Bucket Sort)是一种基于**分配**的排序算法,尤其适用于**均匀分布**的数据。它通过将元素分布到多个桶中,分别对每个桶进行排序,最后将各个桶中的元素合并起来,得到一个有序数组。 + +> 🎯 **算法特点**: +> - 基于分配的排序算法 +> - 时间复杂度:$O(n + k)$(平均) +> - 空间复杂度:$O(n + k)$ +> - 稳定排序 +> - 适用于均匀分布的数据 + +桶排序的平均时间复杂度为 `O(n + k)`,其中 `n` 是数据的数量,`k` 是桶的数量。桶排序通常适用于小范围的、均匀分布的浮点数或整数。 + +为了使桶排序更加高效,我们需要做到这两点: + +1. 📈 **增加桶的数量**:在额外空间充足的情况下,尽量增大桶的数量 +2. 🎯 **均匀分配**:使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中 + +同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。 + +### 1. 算法步骤 + +1. 🏗️ **创建桶**:创建若干个桶,每个桶表示一个数值范围 +2. 📊 **将元素分配到桶中**:根据元素的大小,将它们分配到对应的桶中 +3. 🔄 **对每个桶内部排序**:对每个桶中的元素分别进行排序(可以使用插入排序、快速排序等) +4. 🔀 **合并所有桶中的元素**:依次将每个桶中的元素合并起来,得到最终的有序数组 + + + +```java +import java.util.ArrayList; +import java.util.Collections; + +public class BucketSort { + // 主函数:进行桶排序 + public static void bucketSort(float[] arr, int n) { + if (n <= 0) return; + + // 1. 创建 n 个桶,每个桶是一个空的 ArrayList + ArrayList[] buckets = new ArrayList[n]; + + for (int i = 0; i < n; i++) { + buckets[i] = new ArrayList(); + } + + // 2. 将数组中的元素分配到各个桶中 + for (int i = 0; i < n; i++) { + int bucketIndex = (int) arr[i] * n; // 根据值分配桶 + buckets[bucketIndex].add(arr[i]); + } + + // 3. 对每个桶中的元素进行排序 + for (int i = 0; i < n; i++) { + Collections.sort(buckets[i]); // 可以使用任意内置排序算法 + } + + // 4. 合并所有桶中的元素,形成最终的排序数组 + int index = 0; + for (int i = 0; i < n; i++) { + for (Float value : buckets[i]) { + arr[index++] = value; + } + } + } + + // 测试主函数 + public static void main(String[] args) { + float[] arr = { (float)0.42, (float)0.32, (float)0.33, (float)0.52, (float)0.37, (float)0.47, (float)0.51 }; + int n = arr.length; + bucketSort(arr, n); + + System.out.println("排序后的数组:"); + for (float value : arr) { + System.out.print(value + " "); + } + } +} + +``` + +**性能分析** + +- 🚀 **什么时候最快**:当输入的数据可以均匀的分配到每一个桶中 +- 🐌 **什么时候最慢**:当输入的数据被分配到了同一个桶中 + +> 💡 **学习提示**:思路一定要理解了,不背题哈,比如有些直接问你 +> +> 📝 **例题**:已知一组记录的排序码为(46,79,56,38,40,80, 95,24),写出对其进行快速排序的第一趟的划分结果 + + + +## 🔢 基数排序 + +**基数排序(Radix Sort)** 是一种**非比较排序算法**,用于对整数或字符串等进行排序。它的核心思想是将数据按位(如个位、十位、百位等)进行排序,从低位到高位依次进行。基数排序的时间复杂度为 `O(n * k)`,其中 `n` 是待排序元素的个数,`k` 是数字的最大位数或字符串的长度。由于它不涉及比较操作,基数排序适用于一些特定的场景,如排序长度相同的字符串或范围固定的整数。 + +> 🎯 **算法特点**: +> - 非比较排序算法 +> - 时间复杂度:$O(n \times k)$ +> - 空间复杂度:$O(n + k)$ +> - 稳定排序 +> - 适用于位数固定的数据 + +基数排序有两种实现方式: + +- 🔢 **LSD(Least Significant Digit)**:从最低位(个位)开始排序,常用的实现方式 +- 🔢 **MSD(Most Significant Digit)**:从最高位开始排序 + +基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。 + +### 1. 算法步骤(LSD 实现) + +1. 🔍 **确定最大位数**:找出待排序数据中最大数的位数 +2. 🔄 **按位排序**:从最低位到最高位,对每一位进行排序。每次排序使用**稳定的排序算法**(如计数排序或桶排序),确保相同位数的元素相对位置不变 + +### 2. 动图演示 + +[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/radixSort.gif) + +```java +import java.util.Arrays; + +public class RadixSort { + // 主函数:进行基数排序 + public static void radixSort(int[] arr) { + // 找到数组中的最大数,确定最大位数 + int max = getMax(arr); + + // 从个位开始,对每一位进行排序 + for (int exp = 1; max / exp > 0; exp *= 10) { + countingSortByDigit(arr, exp); + } + } + + // 找到数组中的最大值 + private static int getMax(int[] arr) { + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (arr[i] > max) { + max = arr[i]; + } + } + return max; + } + + // 根据当前位数进行计数排序 + private static void countingSortByDigit(int[] arr, int exp) { + int n = arr.length; + int[] output = new int[n]; // 输出数组 + int[] count = new int[10]; // 计数数组(0-9,用于处理每一位上的数字) + + // 1. 统计每个数字在当前位的出现次数 + for (int i = 0; i < n; i++) { + int digit = (arr[i] / exp) % 10; + count[digit]++; + } + + // 2. 计算累积计数 + for (int i = 1; i < 10; i++) { + count[i] += count[i - 1]; + } + + // 3. 从右到左遍历数组,按当前位将元素放入正确位置 + for (int i = n - 1; i >= 0; i--) { + int digit = (arr[i] / exp) % 10; + output[count[digit] - 1] = arr[i]; + count[digit]--; + } + + // 4. 将排序好的结果复制回原数组 + for (int i = 0; i < n; i++) { + arr[i] = output[i]; + } + } + +} + +``` + + + +## 🐚 希尔排序 + +希尔排序这个名字,来源于它的发明者希尔,也称作"缩小增量排序",是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 + +**希尔排序(Shell Sort)** 是一种**基于插入排序**的排序算法,又称为**缩小增量排序**,它通过将数组按一定间隔分成若干个子数组,分别进行插入排序,逐步缩小间隔,最终进行一次标准的插入排序。通过这种方式,希尔排序能够减少数据移动次数,使得整体排序过程更为高效。 + +> 🎯 **算法特点**: +> - 插入排序的改进版本 +> - 时间复杂度:$O(n^{1.3})$ 到 $O(n^2)$(取决于增量序列) +> - 空间复杂度:$O(1)$ +> - 不稳定排序 + +希尔排序的时间复杂度依赖于**增量序列**的选择,通常在 `O(n^1.3)` 到 `O(n^2)` 之间。 + +### 1. 算法步骤 + +1. 🎯 **确定增量序列**:首先确定一个增量序列 `gap`,通常初始的 `gap` 为数组长度的一半,然后逐步缩小 +2. 🔄 **分组排序**:将数组按 `gap` 分组,对每个分组进行插入排序。`gap` 表示当前元素与其分组中的前一个元素的间隔 +3. 📉 **缩小增量并继续排序**:每次将 `gap` 缩小一半,重复分组排序过程,直到 `gap = 1`,即对整个数组进行一次标准的插入排序 +4. ✅ **最终排序完成**:当 `gap` 变为 1 时,希尔排序相当于执行了一次插入排序,此时数组已经接近有序,因此插入排序的效率非常高 + +### 2. 动图演示 + + + +```java +import java.util.Arrays; + +public class ShellSort { + // 主函数:希尔排序 + public static void shellSort(int[] arr) { + int n = arr.length; + + // 1. 初始 gap 为数组长度的一半 + for (int gap = n / 2; gap > 0; gap /= 2) { + // 2. 对每个子数组进行插入排序 + for (int i = gap; i < n; i++) { + int temp = arr[i]; + int j = i; + + // 3. 对当前分组进行插入排序 + while (j >= gap && arr[j - gap] > temp) { + arr[j] = arr[j - gap]; + j -= gap; + } + + // 将 temp 插入到正确的位置 + arr[j] = temp; + } + } + } +} +``` + + + + + +## 📚 参考与感谢 + +- https://yuminlee2.medium.com/sorting-algorithms-summary-f17ea88a9174 + +--- + +> 🎉 **恭喜你完成了排序算法的学习!** 排序是计算机科学的基础,掌握了这些算法,你就拥有了解决各种排序问题的强大工具。记住:**选择合适的排序算法比掌握所有算法更重要**! diff --git a/docs/data-structure-algorithms/complexity.md b/docs/data-structure-algorithms/complexity.md index d28fdb242c..c347f82d35 100644 --- a/docs/data-structure-algorithms/complexity.md +++ b/docs/data-structure-algorithms/complexity.md @@ -1,173 +1,1259 @@ -# 时间复杂度 +--- +title: 算法复杂度分析:开发者的必备技能 +date: 2025-05-09 +categories: Algorithm +--- > 高级工程师title的我,最近琢磨着好好刷刷算法题更高级一些,然鹅,当我准备回忆大学和面试时候学的数据结构之时,我发现自己对这个算法复杂度的记忆只有OOOOOooo > > 文章收录在 GitHub [JavaKeeper](https://github.com/Jstarfish/JavaKeeper) ,N线互联网开发必备技能兵器谱 +## 前言 + +作为Java后端开发者,我们每天都在编写代码解决业务问题。但你是否思考过:**为什么有些系统在用户量上升时响应越来越慢?为什么某些查询在数据量大时会卡死?为什么同样功能的代码,有些占用内存巨大?** + +答案就在算法复杂度中。 + 算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。 +在实际的Java开发中,复杂度分析帮助我们: +- **预测系统性能瓶颈**:在代码上线前就能预判性能问题 +- **优化关键路径**:识别出最需要优化的代码段 +- **合理选择数据结构**:HashMap vs TreeMap,ArrayList vs LinkedList +- **面试加分项**:大厂面试必考,体现算法功底 + 那么我们应该如何去衡量不同算法之间的优劣呢? -主要还是从算法所占用的「时间」和「空间」两个维度去考量。 +## 复杂度分析的两个维度 + +主要还是从算法所占用的「时间」和「空间」两个维度去考量: -- 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。 -- 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。 +- **时间维度**:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。 +- **空间维度**:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。 因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。 -> 数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间。所以,执行效率是算法一个非常重要的考量指标。那如何来衡量你编写的算法代码的执行效率呢? -> -> 就是:时间、空间复杂度 +> 数据结构和算法本身解决的是"快"和"省"的问题,即如何让代码运行得更快,如何让代码更省存储空间。所以,执行效率是算法一个非常重要的考量指标。 ## **时间复杂度** -一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或「**时间频度**」。记为T(n)。 +一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。而且测试环境中的硬件性能和测试数据规模都会对其有很大的影响。我们也不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或「**时间频度**」。记为T(n)。 时间频度T(n)中,n 称为问题的规模,当 n 不断变化时,时间频度 T(n) 也会不断变化。但有时我们想知道它变化时呈现什么规律,为此我们引入时间复杂度的概念。算法的时间复杂度也就是算法的时间度量,记作:$T(n) = O(f(n))$。它表示随问题规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同,称作算法的**渐进时间复杂度**,简称「**时间复杂度**」。 这种表示方法我们称为「 **大O符号表示法** 」,又称为**渐进符号**,是用于描述函数渐进行为的数学符号 -常见的时间复杂度量级有: +## 如何分析算法的时间复杂度 + +### 复杂度分析的核心思路 + +**核心原则**:关注当输入规模n趋向无穷大时,算法执行时间的增长趋势,而非具体的执行时间。 + +**分析步骤**: +1. **识别基本操作**:找出算法中最频繁执行的操作 +2. **计算执行次数**:分析这个操作随输入规模n的执行次数 +3. **忽略低阶项**:只保留增长最快的项 +4. **忽略系数**:去掉常数因子 + +### 复杂度分析的实战方法 + +#### 方法1:数循环层数 +这是最直观的分析方法: + +**单层循环 → O(n)** +```java +for (int i = 0; i < n; i++) { + // 基本操作 +} +// 分析过程:循环n次,每次执行基本操作1次,总共n次 → O(n) +``` + +**双层嵌套循环 → O(n²)** +```java +for (int i = 0; i < n; i++) { // 外层:n次 + for (int j = 0; j < n; j++) { // 内层:每轮n次 + // 基本操作 + } +} +// 分析过程:外层n次 × 内层n次 = n² 次 → O(n²) +``` -- 常数阶$O(1)$ -- 线性阶$O(n)$ -- 平方阶$O(n^2)$ -- 立方阶$O(n^3)$ -- 对数阶$O(logn)$ -- 线性对数阶$O(nlogn)$ -- 指数阶$O(2^n)$ +**特殊情况:内层循环次数递减** -#### 常数阶$O(1)$ +```java +for (int i = 0; i < n; i++) { // 外层:n次 + for (int j = i; j < n; j++) { // 内层:第i轮执行(n-i)次 + // 基本操作 + } +} +// 分析过程:总次数 = n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 ≈ n²/2 +// 忽略系数:O(n²) +``` -$O(1)$,表示该算法的执行时间(或执行时占用空间)总是为一个常量,不论输入的数据集是大是小,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如: +#### 方法2:分析递归调用 +递归算法的复杂度 = 递归调用总次数 × 每次调用的复杂度 +**递归分析实例:计算斐波那契数列** ```java -int i = 1; -int j = 2; -int k = i + j; +int fibonacci(int n) { + if (n <= 1) return n; // 基本情况:O(1) + return fibonacci(n-1) + fibonacci(n-2); // 递归调用:2次 +} ``` -上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用$O(1)$来表示它的时间复杂度。 +**分析过程:** +1. **画出递归树**: + ``` + fib(n) + ├── fib(n-1) + │ ├── fib(n-2) + │ └── fib(n-3) + └── fib(n-2) + ├── fib(n-3) + └── fib(n-4) + ``` + +2. **分析递归深度**:最深到fib(0),深度约为n + +3. **分析每层节点数**:每层节点数翻倍,第k层有2^k个节点 + +4. **计算总节点数**:2^0 + 2^1 + ... + 2^n ≈ 2^n -#### 线性阶$O(n)$ +5. **得出复杂度**:O(2^n) -$O(n)$,表示一个算法的性能会随着输入数据的大小变化而线性变化,如 +**为什么这么慢?** 因为有大量重复计算!fib(n-2)既在fib(n-1)中计算,又在fib(n)中计算。 +#### 方法3:分析分治算法 +分治算法的复杂度可以用递推关系式分析。 + +**二分查找分析** ```java -for (int i = 0; i < n; i++) { - j = i; - j++; +int binarySearch(int[] arr, int target, int left, int right) { + if (left > right) return -1; // 基本情况 + + int mid = (left + right) / 2; // O(1)操作 + if (arr[mid] == target) return mid; // O(1)操作 + else if (arr[mid] < target) + return binarySearch(arr, target, mid + 1, right); // 递归一半 + else + return binarySearch(arr, target, left, mid - 1); // 递归一半 } ``` -这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用$O(n)$来表示它的时间复杂度。 +**分析过程:** +1. **建立递推关系**:T(n) = T(n/2) + O(1) + - 每次递归处理一半的数据 + - 除了递归外,其他操作都是O(1) + +2. **展开递推式**: + - T(n) = T(n/2) + 1 + - T(n/2) = T(n/4) + 1 + - T(n/4) = T(n/8) + 1 + - ... + - T(1) = 1 + +3. **求解**:总共需要log₂n层递归,每层O(1),所以T(n) = O(logn) + +### 常见复杂度等级及其识别 + +| 复杂度 | 识别特征 | 典型例子 | 数据规模感受 | +|--------|----------|----------|-------------| +| **O(1)** | 不随n变化 | 数组索引访问、HashMap查找 | 任何规模都很快 | +| **O(logn)** | 每次减半 | 二分查找、平衡树操作 | 100万数据只需20步 | +| **O(n)** | 单层循环 | 数组遍历、链表查找 | n=1000万时还可接受 | +| **O(nlogn)** | 分治+合并 | 归并排序、快速排序 | n=100万时性能良好 | +| **O(n²)** | 双层循环 | 冒泡排序、暴力匹配 | n>1000就开始慢了 | +| **O(2^n)** | 无优化递归 | 递归斐波那契 | n>30就要等很久 | + +## 复杂度分析实战练习 + +学会了分析方法,让我们通过几个经典例子来实际练习,重点关注**分析过程**而不是记忆结果。 + +### 练习1:分析冒泡排序的复杂度 -#### 平方阶$O(n^2)$ +```java +public void bubbleSort(int[] arr) { + int n = arr.length; + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < n - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + swap(arr, j, j + 1); + } + } + } +} +``` -$O(n²)$ 表示一个算法的性能将会随着输入数据的增长而呈现出二次增长。最常见的就是对输入数据进行嵌套循环。如果嵌套层级不断深入的话,算法的性能将会变为立方阶$O(n^3)$,$O(n^4)$,$O(n^k)$以此类推 +**分析过程:** +1. **识别基本操作**:比较和交换操作 `arr[j] > arr[j + 1]` +2. **分析循环次数**: + - 外层循环:i从0到n-2,共(n-1)次 + - 内层循环:第i轮时,j从0到(n-i-2),共(n-i-1)次 +3. **计算总次数**: + - 第0轮:n-1次比较 + - 第1轮:n-2次比较 + - 第2轮:n-3次比较 + - ... + - 第(n-2)轮:1次比较 + - 总计:(n-1) + (n-2) + ... + 1 = n(n-1)/2 次 +4. **简化结果**:n(n-1)/2 ≈ n²/2,忽略系数得到 **O(n²)** + +### 练习2:分析HashMap查找的复杂度 ```java -for(x=1; i<=n; x++){ - for(i=1; i<=n; i++){ - j = i; - j++; +// HashMap的get操作 +public V get(Object key) { + int hash = hash(key); // 计算hash值,O(1) + int index = hash % table.length; // 计算索引,O(1) + Node node = table[index]; // 直接访问数组,O(1) + + // 在链表/红黑树中查找 + while (node != null) { + if (node.key.equals(key)) { + return node.value; + } + node = node.next; // 遍历链表 } + return null; } ``` -#### 指数阶$O(2^n)$ +**分析过程:** +1. **理想情况**:没有hash冲突,直接命中 → **O(1)** +2. **最坏情况**:所有元素都hash到同一个位置,形成长度为n的链表 → **O(n)** +3. **平均情况**:hash函数分布均匀,每个链表长度约为1 → **O(1)** + +**为什么说HashMap是O(1)?** 指的是平均情况下的复杂度。 -$O(2^n)$,表示一个算法的性能会随着输入数据的每次增加而增大两倍,典型的方法就是裴波那契数列的递归计算实现 +### 练习3:分析递归求阶乘的复杂度 ```java -int Fibonacci(int number) -{ - if (number <= 1) return number; +public int factorial(int n) { + if (n <= 1) return 1; // 基本情况 + return n * factorial(n - 1); // 递归调用 +} +``` + +**分析过程:** +1. **递归深度**:从n递减到1,深度为n +2. **每层工作量**:除了递归调用,只有一次乘法运算,O(1) +3. **总复杂度**:n层 × 每层O(1) = **O(n)** + +**递归调用栈:** +``` +factorial(5) +├── 5 * factorial(4) + ├── 4 * factorial(3) + ├── 3 * factorial(2) + ├── 2 * factorial(1) + └── 1 +``` + +### 练习4:分析快速排序的复杂度 + +```java +public void quickSort(int[] arr, int low, int high) { + if (low < high) { + int pivot = partition(arr, low, high); // 分区,O(n) + quickSort(arr, low, pivot - 1); // 递归左半部分 + quickSort(arr, pivot + 1, high); // 递归右半部分 + } +} +``` + +**分析过程:** +1. **最好情况**:每次都能均匀分割 + - 递归深度:log₂n + - 每层工作量:O(n) + - 总复杂度:O(nlogn) + +2. **最坏情况**:每次都是最不均匀的分割(已排序数组) + - 递归深度:n + - 每层工作量:O(n) + - 总复杂度:O(n²) + +3. **平均情况**:O(nlogn) + +**关键洞察**:快速排序的性能很大程度上取决于pivot的选择。 + +### 复杂度分析的误区和技巧 + +#### 常见误区 + +1. **误区1:认为递归一定比循环慢** + ```java + // 递归版本:O(logn) + int binarySearchRecursive(int[] arr, int target, int left, int right) { + if (left > right) return -1; + int mid = (left + right) / 2; + if (arr[mid] == target) return mid; + else if (arr[mid] < target) + return binarySearchRecursive(arr, target, mid + 1, right); + else + return binarySearchRecursive(arr, target, left, mid - 1); + } + + // 循环版本:同样O(logn) + int binarySearchIterative(int[] arr, int target) { + int left = 0, right = arr.length - 1; + while (left <= right) { + int mid = (left + right) / 2; + if (arr[mid] == target) return mid; + else if (arr[mid] < target) left = mid + 1; + else right = mid - 1; + } + return -1; + } + ``` + +2. **误区2:认为代码行数多就复杂度高** + ```java + // 虽然代码很长,但复杂度是O(1) + public boolean isValidSudoku(char[][] board) { + for (int i = 0; i < 9; i++) { // 固定9次 + for (int j = 0; j < 9; j++) { // 固定9次 + // 检查逻辑... + } + } + return true; // 9×9=81次操作,是常数,所以O(1) + } + ``` + +#### 实用技巧 + +1. **看循环变量的变化规律** + ```java + // 线性增长 → O(n) + for (int i = 0; i < n; i++) + + // 指数增长 → O(logn) + for (int i = 1; i < n; i *= 2) + + // 嵌套循环 → O(n²) + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + ``` + +2. **分析递归的关键问题** + - 递归了多少次? + - 每次递归处理多大的子问题? + - 除了递归还做了什么? + +3. **利用已知的复杂度** + - 排序通常是O(nlogn) + - 哈希表查找通常是O(1) + - 数组遍历是O(n) + - 二分查找是O(logn) + +除此之外,其实还有平均情况复杂度、最好时间复杂度、最坏时间复杂度。。。一般没有特殊说明的情况下,都是指最坏时间复杂度。 + +------ + +## **空间复杂度分析方法** + +空间复杂度分析相对简单,主要关注算法运行过程中需要的**额外存储空间**。 + +### 空间复杂度的组成 +1. **算法本身**:代码指令占用的空间(通常忽略) +2. **输入数据**:输入参数占用的空间(通常忽略) +3. **额外空间**:算法运行时临时申请的空间(主要分析对象) + +### 空间复杂度分析步骤 - return Fibonacci(number - 2) + Fibonacci(number - 1); +#### 第一步:识别额外空间的来源 +- **局部变量**:函数内声明的变量 +- **数据结构**:数组、链表、栈、队列等 +- **递归调用栈**:递归函数的调用栈 + +#### 第二步:分析空间随输入规模的变化 + +**O(1) 常量空间** +```java +public void swap(int[] arr, int i, int j) { + int temp = arr[i]; // 只使用了一个额外变量 + arr[i] = arr[j]; + arr[j] = temp; } +// 无论数组多大,只用了temp一个额外空间 → O(1) ``` -#### 对数阶$O(logn)$ +**O(n) 线性空间** +```java +public int[] copyArray(int[] arr) { + int[] newArr = new int[arr.length]; // 创建了n大小的新数组 + for (int i = 0; i < arr.length; i++) { + newArr[i] = arr[i]; + } + return newArr; +} +// 创建了大小为n的数组 → O(n) +``` +**递归空间复杂度分析** ```java -int i = 1; -while(i nodeStack = new Stack<>(); + Stack sumStack = new Stack<>(); + + nodeStack.push(root); + sumStack.push(sum - root.val); + + while (!nodeStack.isEmpty()) { + TreeNode node = nodeStack.pop(); + int currSum = sumStack.pop(); + + if (node.left == null && node.right == null && currSum == 0) { + return true; + } + + if (node.left != null) { + nodeStack.push(node.left); + sumStack.push(currSum - node.left.val); + } + if (node.right != null) { + nodeStack.push(node.right); + sumStack.push(currSum - node.right.val); + } } + return false; } ``` -除此之外,其实还有平均情况复杂度、最好时间复杂度、最坏时间复杂度。。。一般没有特殊说明的情况下,都是值最坏时间复杂度。 +------ + +## 复杂度速查表 + +来源:https://liam.page/2016/06/20/big-O-cheat-sheet/ 源地址:https://www.bigocheatsheet.com/ ------ +#### 1. 线性递归 - O(n)空间 + +**LeetCode应用:链表递归** +```java +// 反转链表(递归版本) +public ListNode reverseList(ListNode head) { + if (head == null || head.next == null) { + return head; + } + + ListNode newHead = reverseList(head.next); // 递归调用 + head.next.next = head; + head.next = null; + + return newHead; +} +// 递归深度等于链表长度n,空间复杂度O(n) -## **空间复杂度** +// 计算链表长度(递归) +public int getLength(ListNode head) { + if (head == null) return 0; + return 1 + getLength(head.next); +} +// 空间复杂度:O(n),因为递归栈深度为n +``` -空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,一个算法所需的存储空间用f(n)表示。$S(n)=O(f(n))$,其中 n 为问题的规模,S(n) 表示空间复杂度。 +#### 2. 二分递归 - O(logn)空间 -一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。 +**示例:二分查找(递归版本)** -一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为 $O(1)$。当一个算法的空间复杂度与n成线性比例关系时,可表示为$0(n)$,类比时间复杂度。 +```java +public int binarySearch(int[] nums, int target, int left, int right) { + if (left > right) return -1; + + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + return binarySearch(nums, target, mid + 1, right); + } else { + return binarySearch(nums, target, left, mid - 1); + } +} +// 每次递归都将问题规模减半,递归深度为log₂n,空间复杂度O(logn) +``` -空间复杂度比较常用的有:$O(1)$、$O(n)$、$O(n²)$ +**LeetCode应用:树的遍历** +```java +// 二叉树的最大深度 +public int maxDepth(TreeNode root) { + if (root == null) return 0; + + int leftDepth = maxDepth(root.left); // 递归左子树 + int rightDepth = maxDepth(root.right); // 递归右子树 + + return Math.max(leftDepth, rightDepth) + 1; +} +// 平衡树:递归深度为O(logn),空间复杂度O(logn) +// 最坏情况(链状树):递归深度为O(n),空间复杂度O(n) +``` -#### 空间复杂度 $O(1)$ +#### 3. 多分支递归 - 注意陷阱! -如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1) -举例: +**错误理解:认为空间复杂度是O(2^n)** +```java +// 斐波那契数列(递归版本) +public int fibonacci(int n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} +``` + +**正确分析:** +- **时间复杂度**:O(2^n) - 因为有2^n个函数调用 +- **空间复杂度**:O(n) - 因为递归栈的最大深度是n + +> 关键理解:虽然有很多递归调用,但任何时刻调用栈的深度最多是n层 + +#### 4. 记忆化递归的空间复杂度 ```java -int i = 1; -int j = 2; -++i; -j++; -int m = i + j; +// 带记忆化的斐波那契 +private Map memo = new HashMap<>(); + +public int fibonacciMemo(int n) { + if (n <= 1) return n; + + if (memo.containsKey(n)) { + return memo.get(n); + } + + int result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2); + memo.put(n, result); + return result; +} +// 时间复杂度:O(n) +// 空间复杂度:O(n) = 递归栈O(n) + 缓存数组O(n) ``` -代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1) +### LeetCode中的空间复杂度优化技巧 + +#### 1. 滚动数组优化 +```java +// 原始DP:O(n)空间 +int[] dp = new int[n]; + +// 优化后:O(1)空间 +int prev1 = dp[0], prev2 = dp[1]; +``` -#### 空间复杂度 $O(n)$ +#### 2. 就地修改避免额外空间 +```java +// 数组去重(就地修改) +public int removeDuplicates(int[] nums) { + if (nums.length == 0) return 0; + + int i = 0; + for (int j = 1; j < nums.length; j++) { + if (nums[j] != nums[i]) { + i++; + nums[i] = nums[j]; // 就地修改,不需要额外空间 + } + } + return i + 1; +} // 空间复杂度:O(1) +``` +#### 3. 递归转迭代 ```java -int[] m = new int[n] -for(i=1; i<=n; ++i) -{ - j = i; - j++; +// 递归版本:O(n)空间 +public void inorderRecursive(TreeNode root) { + if (root == null) return; + inorderRecursive(root.left); + System.out.println(root.val); + inorderRecursive(root.right); +} + +// 迭代版本:O(h)空间,h为树的高度 +public void inorderIterative(TreeNode root) { + Stack stack = new Stack<>(); + TreeNode current = root; + + while (current != null || !stack.isEmpty()) { + while (current != null) { + stack.push(current); + current = current.left; + } + current = stack.pop(); + System.out.println(current.val); + current = current.right; + } } ``` -这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n) +### 常见面试问题:时间空间复杂度权衡 + +| 算法 | 时间复杂度 | 空间复杂度 | 权衡点 | +|------|------------|------------|--------| +| 快速排序 | O(nlogn) | O(logn) | 递归栈空间 | +| 归并排序 | O(nlogn) | O(n) | 需要额外数组 | +| 堆排序 | O(nlogn) | O(1) | 就地排序 | +| 计数排序 | O(n+k) | O(k) | 需要额外计数数组 | +| 哈希表查找 | O(1) | O(n) | 用空间换时间 | +| 二分查找 | O(logn) | O(1) | 要求数组有序 | ------ +## 复杂度分析在实际开发中的应用 +### 系统设计中的复杂度考量 -## 复杂度速查表 +#### 1. 数据库查询优化 +```java +// ❌ 没有索引的查询 - O(n) +SELECT * FROM users WHERE email = 'user@example.com'; -来源:https://liam.page/2016/06/20/big-O-cheat-sheet/ 源地址:https://www.bigocheatsheet.com/ +// ✅ 有索引的查询 - O(logn) +CREATE INDEX idx_email ON users(email); +SELECT * FROM users WHERE email = 'user@example.com'; +``` + +**在Java中的体现:** +```java +// JPA查询优化 +@Entity +public class User { + @Column(name = "email") + @Index(name = "idx_email") // 添加索引 + private String email; +} + +// 避免N+1查询问题 +@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :userId") +User findUserWithOrders(@Param("userId") Long userId); +``` + +#### 2. 缓存策略选择 +```java +// Redis缓存 vs 数据库查询的复杂度对比 +public class UserService { + + // ❌ 每次都查数据库 - O(logn)到O(n) + public User getUserById(Long id) { + return userRepository.findById(id); + } + + // ✅ 使用缓存 - O(1) + @Cacheable(value = "users", key = "#id") + public User getUserByIdCached(Long id) { + return userRepository.findById(id); + } +} +``` + +#### 3. 分页查询的复杂度陷阱 +```java +// ❌ 深度分页性能问题 +// LIMIT 1000000, 20 在MySQL中是O(n),需要跳过100万条记录 +public Page getUsers(int page, int size) { + return userRepository.findAll(PageRequest.of(page, size)); +} + +// ✅ 游标分页优化为O(logn) +public List getUsersAfterCursor(Long lastUserId, int limit) { + return userRepository.findByIdGreaterThanOrderById(lastUserId, + PageRequest.of(0, limit)); +} +``` + +### 微服务架构中的复杂度问题 + +#### 1. 接口聚合的复杂度 +```java +// ❌ 串行调用多个服务 - O(n) +@RestController +public class OrderController { + + public OrderDetailVO getOrderDetail(Long orderId) { + Order order = orderService.getOrder(orderId); // 100ms + User user = userService.getUser(order.getUserId()); // 100ms + Product product = productService.getProduct(order.getProductId()); // 100ms + // 总耗时:300ms + + return buildOrderDetail(order, user, product); + } +} -#### 图例 +// ✅ 并行调用优化 - O(1) +@Async +public CompletableFuture getOrderDetailAsync(Long orderId) { + CompletableFuture orderFuture = + CompletableFuture.supplyAsync(() -> orderService.getOrder(orderId)); + + CompletableFuture userFuture = orderFuture.thenCompose(order -> + CompletableFuture.supplyAsync(() -> userService.getUser(order.getUserId()))); + + CompletableFuture productFuture = orderFuture.thenCompose(order -> + CompletableFuture.supplyAsync(() -> productService.getProduct(order.getProductId()))); + + return CompletableFuture.allOf(orderFuture, userFuture, productFuture) + .thenApply(v -> buildOrderDetail(orderFuture.join(), userFuture.join(), productFuture.join())); + // 总耗时:约100ms +} +``` + +#### 2. 批量处理优化 +```java +// ❌ 循环调用接口 - O(n) +public void processUsers(List userIds) { + for (Long userId : userIds) { + User user = userService.getUser(userId); // 每次一个网络调用 + processUser(user); + } + // 1000个用户 = 1000次网络调用 +} + +// ✅ 批量调用 - O(1) +public void processUsersBatch(List userIds) { + List users = userService.getUsers(userIds); // 一次网络调用 + users.forEach(this::processUser); + // 1000个用户 = 1次网络调用 +} +``` + +### 大数据处理场景 + +#### 1. 日志分析系统 +```java +// 实时日志处理的复杂度考量 +@Component +public class LogProcessor { + + // ❌ 实时逐条处理 - 无法处理高并发 + public void processLogRealtimeSync(LogEvent event) { + // 同步处理每条日志 + analyzeLog(event); // 假设需要10ms + saveToDatabase(event); // 假设需要5ms + // 每秒最多处理:1000/(10+5) = 66条 + } + + // ✅ 批量异步处理 - 提升吞吐量 + @EventListener + @Async + public void processLogBatch(List events) { + // 批量处理,均摊网络开销 + List results = batchAnalyze(events); // 批量分析 + batchSaveToDatabase(results); // 批量保存 + // 每秒可处理:数万条 + } +} +``` + +#### 2. 报表查询优化 +```java +// 大数据量报表查询的复杂度优化 +@Service +public class ReportService { + + // ❌ 实时聚合计算 - O(n),数据量大时很慢 + public SalesReport getDailySalesReport(LocalDate date) { + List orders = orderRepository.findByCreateDate(date); + // 需要遍历所有订单进行聚合计算 + return calculateSalesReport(orders); + } + + // ✅ 预计算报表 - O(1)查询 + @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行 + public void generateDailyReports() { + LocalDate yesterday = LocalDate.now().minusDays(1); + SalesReport report = calculateSalesReport( + orderRepository.findByCreateDate(yesterday)); + reportRepository.save(report); // 预计算结果 + } + + public SalesReport getDailySalesReportCached(LocalDate date) { + return reportRepository.findByDate(date); // O(1)查询 + } +} +``` + +### Spring Boot性能优化实战 + +#### 1. 启动时间优化 +```java +// 启动时间复杂度优化 +@SpringBootApplication +public class Application { + + // ❌ 启动时加载大量数据 + @PostConstruct + public void initData() { + // 启动时从数据库加载10万条配置 - 增加启动时间 + configService.loadAllConfigs(); // O(n) + } + + // ✅ 懒加载优化 + @Bean + @Lazy + public ConfigCache configCache() { + return new ConfigCache(); // 需要时才初始化 + } +} +``` + +#### 2. 内存使用优化 +```java +// 内存复杂度优化 +@RestController +public class DataController { + + // ❌ 一次性加载所有数据到内存 - O(n)空间 + public List getAllData() { + List allData = dataRepository.findAll(); // 可能有百万条数据 + return allData.stream() + .map(this::convertToVO) + .collect(Collectors.toList()); // 内存可能溢出 + } + + // ✅ 流式处理 - O(1)空间 + public void exportAllData(HttpServletResponse response) { + try (Stream dataStream = dataRepository.findAllStream()) { + dataStream.map(this::convertToVO) + .forEach(vo -> writeToResponse(response, vo)); // 逐条处理 + } + } +} +``` + +### 消息队列的复杂度考量 + +```java +// 消息处理的复杂度优化 +@Component +public class MessageProcessor { + + // ❌ 顺序处理消息 - O(n)时间 + @RabbitListener(queues = "order.queue") + public void processOrderSync(OrderMessage message) { + // 顺序处理,一个消息处理完才处理下一个 + validateOrder(message); // 50ms + saveOrder(message); // 100ms + sendNotification(message); // 30ms + // 总计180ms/消息,吞吐量低 + } + + // ✅ 并行处理消息 - 提升吞吐量 + @RabbitListener(queues = "order.queue", concurrency = "10") + public void processOrderAsync(OrderMessage message) { + CompletableFuture.allOf( + CompletableFuture.runAsync(() -> validateOrder(message)), + CompletableFuture.runAsync(() -> saveOrder(message)), + CompletableFuture.runAsync(() -> sendNotification(message)) + ).join(); + // 并行处理,吞吐量大幅提升 + } +} +``` + +### 实际项目中的复杂度思考框架 + +#### 1. 性能需求评估 +```java +// 根据业务规模选择合适的算法复杂度 +public class BusinessScaleAnalysis { + + // 小规模系统(<1万用户) + public void smallScale() { + // O(n²)算法可以接受 + // 简单直接的实现优先 + } + + // 中等规模系统(1万-100万用户) + public void mediumScale() { + // 需要O(nlogn)或更优算法 + // 开始考虑缓存、索引优化 + } + + // 大规模系统(>100万用户) + public void largeScale() { + // 必须O(logn)或O(1)算法 + // 分布式缓存、数据库分片等 + } +} +``` + +#### 2. 技术选型的复杂度考量 +```java +// 不同技术栈的复杂度特性 +public class TechStackComplexity { + + // HashMap vs TreeMap选择 + Map userCache; + + // 如果只需要快速查找:HashMap O(1) + userCache = new HashMap<>(); + + // 如果需要有序遍历:TreeMap O(logn) + userCache = new TreeMap<>(); + + // ArrayList vs LinkedList选择 + List users; + + // 频繁随机访问:ArrayList O(1) + users = new ArrayList<>(); + + // 频繁插入删除:LinkedList O(1) + users = new LinkedList<>(); +} +``` + +## 复杂度优化的最佳实践 + +### LeetCode刷题的复杂度优化套路 + +#### 1. 暴力解法 → 哈希表优化(降时间复杂度) +```java +// 模式:O(n²) → O(n) +// 适用场景:查找、匹配类问题 + +// 示例:两数之和类问题 +// 暴力:双层循环查找 O(n²) +// 优化:哈希表存储 O(n) +Map map = new HashMap<>(); +``` + +#### 2. 递归 → 动态规划(消除重复计算) +```java +// 模式:O(2^n) → O(n) +// 适用场景:有重叠子问题的递归 + +// 递归优化三步走: +// 1. 记忆化递归(自顶向下) +Map memo = new HashMap<>(); + +// 2. 动态规划(自底向上) +int[] dp = new int[n+1]; + +// 3. 空间优化(滚动变量) +int prev1 = 0, prev2 = 1; +``` + +#### 3. 排序 + 双指针(降低循环层数) +```java +// 模式:O(n²) → O(nlogn) +// 适用场景:需要查找配对的问题 + +Arrays.sort(nums); // O(nlogn) +int left = 0, right = nums.length - 1; // O(n) +// 总体:O(nlogn),比O(n²)好 +``` + +#### 4. 二分查找(利用有序性) +```java +// 模式:O(n) → O(logn) +// 适用场景:在有序数组中查找 + +// 关键:寻找单调性 +while (left <= right) { + int mid = left + (right - left) / 2; + // 根据mid位置的性质决定搜索方向 +} +``` + +### Java集合类的复杂度选择指南 + +#### 1. List选择策略 +```java +// 根据操作频率选择 +public class ListChoiceGuide { + + // 频繁随机访问 + 较少插入删除 → ArrayList + List data = new ArrayList<>(); // get: O(1), add: O(1) + + // 频繁插入删除 + 较少随机访问 → LinkedList + List data = new LinkedList<>(); // add/remove: O(1), get: O(n) + + // 需要线程安全 → Vector 或 Collections.synchronizedList + List data = new Vector<>(); + + // 不可变列表 → Arrays.asList 或 List.of + List data = List.of("a", "b", "c"); +} +``` + +#### 2. Map选择策略 +```java +public class MapChoiceGuide { + + // 一般情况,追求最快查找 → HashMap + Map cache = new HashMap<>(); // O(1) + + // 需要有序遍历 → TreeMap + Map sortedCache = new TreeMap<>(); // O(logn) + + // 需要插入顺序 → LinkedHashMap + Map orderedCache = new LinkedHashMap<>(); // O(1) + 顺序 + + // 高并发环境 → ConcurrentHashMap + Map concurrentCache = new ConcurrentHashMap<>(); +} +``` + +#### 3. Set选择策略 +```java +public class SetChoiceGuide { + + // 一般去重需求 → HashSet + Set uniqueItems = new HashSet<>(); // O(1) + + // 需要有序 → TreeSet + Set sortedItems = new TreeSet<>(); // O(logn) + + // 需要保持插入顺序 → LinkedHashSet + Set orderedItems = new LinkedHashSet<>(); // O(1) + 顺序 +} +``` + +### 常见性能陷阱及避免方法 + +#### 1. 字符串操作陷阱 +```java +// ❌ String拼接陷阱 - O(n²) +String result = ""; +for (String str : list) { + result += str; // 每次都创建新字符串 +} + +// ✅ 使用StringBuilder - O(n) +StringBuilder sb = new StringBuilder(); +for (String str : list) { + sb.append(str); +} +String result = sb.toString(); + +// ✅ Java 8 Stream方式 - O(n) +String result = list.stream().collect(Collectors.joining()); +``` + +#### 2. 集合遍历陷阱 +```java +// ❌ 在遍历中修改集合 - 可能O(n²) +for (int i = 0; i < list.size(); i++) { + if (shouldRemove(list.get(i))) { + list.remove(i); // ArrayList.remove()是O(n)操作 + i--; // 还要调整索引 + } +} + +// ✅ 使用Iterator - O(n) +Iterator iterator = list.iterator(); +while (iterator.hasNext()) { + if (shouldRemove(iterator.next())) { + iterator.remove(); // O(1)操作 + } +} + +// ✅ 使用removeIf - O(n) +list.removeIf(this::shouldRemove); +``` + +#### 3. 数据库查询陷阱 +```java +// ❌ N+1查询问题 +public List getOrdersWithUser(List orderIds) { + List orders = orderRepository.findByIds(orderIds); // 1次查询 + return orders.stream().map(order -> { + User user = userRepository.findById(order.getUserId()); // N次查询 + return new OrderVO(order, user); + }).collect(Collectors.toList()); +} + +// ✅ 批量查询优化 +public List getOrdersWithUserOptimized(List orderIds) { + List orders = orderRepository.findByIds(orderIds); + Set userIds = orders.stream().map(Order::getUserId).collect(Collectors.toSet()); + Map userMap = userRepository.findByIds(userIds) + .stream().collect(Collectors.toMap(User::getId, user -> user)); + + return orders.stream().map(order -> { + User user = userMap.get(order.getUserId()); // O(1)查找 + return new OrderVO(order, user); + }).collect(Collectors.toList()); +} +``` + +### 复杂度分析的实用技巧 + +#### 1. 快速估算方法 +```java +// 根据数据规模快速判断可接受的复杂度 +public class ComplexityEstimation { + + public void analyzeDataScale(int n) { + if (n <= 10) { + // 任何算法都可以,包括O(n!) + System.out.println("可以使用任何算法"); + } else if (n <= 20) { + // O(2^n)可以接受,如回溯算法 + System.out.println("指数级算法可接受"); + } else if (n <= 100) { + // O(n³)勉强可以 + System.out.println("三次方算法勉强可以"); + } else if (n <= 1000) { + // O(n²)是上限 + System.out.println("平方级算法是上限"); + } else if (n <= 100000) { + // 必须O(nlogn)或更优 + System.out.println("必须线性对数级或更优"); + } else { + // 必须O(n)或O(logn) + System.out.println("必须线性级或对数级"); + } + } +} +``` + +#### 2. 复杂度分析检查清单 +```java +// 代码复杂度自检清单 +public class ComplexityCheckList { + + public void checkComplexity() { + // 1. 有几层嵌套循环? + // - 1层 → O(n) + // - 2层 → O(n²) + // - k层 → O(n^k) + + // 2. 有递归调用吗? + // - 递归深度 × 每层复杂度 = 总复杂度 + + // 3. 有二分查找吗? + // - 每次减半 → O(logn) + + // 4. 有排序操作吗? + // - 通用排序 → O(nlogn) + // - 特殊排序 → 可能O(n) + + // 5. 使用了什么数据结构? + // - HashMap → O(1) + // - TreeMap → O(logn) + // - LinkedList查找 → O(n) + } +} +``` + +#### 3. 空间复杂度优化技巧 +```java +// 常见空间优化模式 +public class SpaceOptimization { + + // 1. 滚动数组优化DP + public int optimizedDP(int n) { + // 原始:int[] dp = new int[n]; // O(n)空间 + // 优化:只保存必要的状态 + int prev = 0, curr = 1; // O(1)空间 + return curr; + } + + // 2. 就地修改避免额外空间 + public void reverseArrayInPlace(int[] arr) { + int left = 0, right = arr.length - 1; + while (left < right) { + // 就地交换,不需要额外数组 + int temp = arr[left]; + arr[left] = arr[right]; + arr[right] = temp; + left++; + right--; + } + } + + // 3. 流式处理大数据 + public void processLargeData() { + // 避免一次性加载所有数据到内存 + try (Stream lines = Files.lines(Paths.get("large-file.txt"))) { + lines.filter(line -> line.contains("target")) + .forEach(this::processLine); // 逐行处理 + } + } +} +``` + +## 复杂度速查表  @@ -197,7 +1283,80 @@ for(i=1; i<=n; ++i) -## 参考 +## 总结 + +### 核心要点回顾 -- 《大话数据结构》 -- https://zhuanlan.zhihu.com/p/50479555 \ No newline at end of file +通过本文的学习,我们掌握了算法复杂度分析的核心知识: + +#### 1. 理论基础 +- **时间复杂度**:衡量算法执行时间随输入规模增长的趋势 +- **空间复杂度**:衡量算法占用内存空间随输入规模增长的趋势 +- **大O符号**:用于描述算法复杂度的数学工具 + +#### 2. 常见复杂度等级(从优到劣) +1. **O(1)** - 常数阶:HashMap查找、数组随机访问 +2. **O(logn)** - 对数阶:二分查找、TreeMap操作 +3. **O(n)** - 线性阶:数组遍历、链表查找 +4. **O(nlogn)** - 线性对数阶:快速排序、归并排序 +5. **O(n²)** - 平方阶:冒泡排序、嵌套循环 +6. **O(2^n)** - 指数阶:递归斐波那契(需要避免) + +#### 3. LeetCode刷题复杂度优化套路 +- **暴力 → 哈希表**:O(n²) → O(n) +- **递归 → 动态规划**:O(2^n) → O(n) +- **暴力查找 → 二分查找**:O(n) → O(logn) +- **嵌套循环 → 双指针**:O(n²) → O(n) + +#### 4. Java开发实战经验 +- **集合选择**:根据操作频率选择ArrayList/LinkedList、HashMap/TreeMap +- **避免陷阱**:字符串拼接、集合遍历修改、N+1查询 +- **空间优化**:滚动数组、就地修改、流式处理 + +### 给Java后端开发者的建议 + +#### 学习路径 +1. **理论基础**:掌握本文的复杂度分析方法 +2. **刷题实战**:在LeetCode上练习复杂度分析 +3. **项目应用**:在实际项目中运用复杂度思维 +4. **持续优化**:定期review代码的性能瓶颈 + +#### 面试准备 +- **必备技能**:能快速分析代码的时间空间复杂度 +- **常考题型**:两数之和、排序算法、动态规划、树遍历 +- **优化思路**:从暴力解法开始,逐步优化到最优解 +- **实际应用**:结合项目经验谈复杂度优化案例 + +#### 职场应用 +- **代码review**:关注性能复杂度,不只是功能正确性 +- **系统设计**:提前评估算法复杂度,避免性能瓶颈 +- **技术选型**:基于复杂度分析选择合适的数据结构和算法 +- **性能调优**:使用复杂度分析定位和解决性能问题 + +### 进阶学习方向 + +如果你想在算法和数据结构方面更进一步,建议关注: + +1. **高级数据结构**:并查集、线段树、字典树 +2. **算法设计模式**:分治、贪心、回溯、动态规划 +3. **系统设计**:分布式系统中的复杂度考量 +4. **性能优化**:JVM调优、数据库优化、缓存策略 + +### 写在最后 + +复杂度分析不是纸上谈兵的理论知识,而是每个Java后端开发者都应该掌握的实用技能。它帮助我们: + +- **写出更高效的代码** +- **在面试中展现技术功底** +- **在系统设计时做出正确决策** +- **在性能调优时快速定位问题** + +希望这篇文章能帮助你建立起完整的复杂度分析知识体系。记住,**理论学习 + 刷题实战 + 项目应用** 才是掌握算法复杂度的最佳路径。 + +--- + +**下期预告**:我们将深入探讨**数组与链表**的底层实现和应用技巧,敬请期待! + +> 如果觉得文章对你有帮助,欢迎点赞分享,你的支持是我创作的动力! +> +> 更多Java技术文章请关注 GitHub [JavaKeeper](https://github.com/Jstarfish/JavaKeeper) \ No newline at end of file diff --git a/docs/data-structure-algorithms/Array.md b/docs/data-structure-algorithms/data-structure/Array.md similarity index 77% rename from docs/data-structure-algorithms/Array.md rename to docs/data-structure-algorithms/data-structure/Array.md index edd6c1f097..81505f83ea 100644 --- a/docs/data-structure-algorithms/Array.md +++ b/docs/data-structure-algorithms/data-structure/Array.md @@ -168,43 +168,22 @@ ## 刷题 -### 两数之和(1) + + +### [1. 两数之和](https://leetcode-cn.com/problems/two-sum/) > 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 > > 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 > -> -> -> 示例: -> -> 给定 nums = [2, 7, 11, 15], target = 9 -> -> 因为 nums[0] + nums[1] = 2 + 7 = 9 -> 所以返回 [0, 1] +> ``` +>输入:nums = [2,7,11,15], target = 9 +> 输出:[0,1] +>解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 +> ``` -### 买卖股票的最佳时机(121) - -> 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 -> -> 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 -> -> 注意:你不能在买入股票前卖出股票。 -> -> 示例 1: -> -> 输入: [7,1,5,3,6,4] -> 输出: 5 -> 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 -> 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 -> 示例 2: -> -> 输入: [7,6,4,3,1] -> 输出: 0 -> 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 - ### 寻找两个正序数组的中位数(4) @@ -231,6 +210,99 @@ > 则中位数是 (2 + 3)/2 = 2.5 > +二分查找 + +给定两个有序数组,要求找到两个有序数组的中位数,最直观的思路有以下两种: + +- 使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。 + +- 不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 00 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。 + + + + + +### [215. 数组中的第K个最大元素](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/) + +> 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 +> +> 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 +> +> +> +> 示例 1: +> +> 输入: [3,2,1,5,6,4] 和 k = 2 +> 输出: 5 +> +> 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 +> 输出: 4 + + + +### [11. 盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water/) + +> 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 +> +> 说明:你不能倾斜容器。 +> +> +> +> 示例 1: +> +>  +> +> 输入:[1,8,6,2,5,4,8,3,7] +> 输出:49 +> 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +> +> 输入:height = [1,1] +> 输出:1 +> +> 输入:height = [4,3,2,1,4] +> 输出:16 +> +> 输入:height = [1,2,1] +> 输出:2 + +双指针,,从两头开始内卷,先卷了挫的那头 + + + +### [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/) + +> 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 +> +> 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 +> +> 此外,你可以假设该网格的四条边均被水包围。 +> +> +> +> 示例 : +> +> ``` +> 输入:grid = [ +> ["1","1","1","1","0"], +> ["1","1","0","1","0"], +> ["1","1","0","0","0"], +> ["0","0","0","0","0"] +> ] +> 输出:1 +> ``` +> +> ``` +> 输入:grid = [ +> ["1","1","0","0","0"], +> ["1","1","0","0","0"], +> ["0","0","1","0","0"], +> ["0","0","0","1","1"] +> ] +> 输出:3 +> ``` + + + ### 移动零(283) @@ -252,7 +324,31 @@ -### 三数之和(15) +### 买卖股票的最佳时机(121) + +> 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 +> +> 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 +> +> 注意:你不能在买入股票前卖出股票。 +> +> 示例 1: +> +> 输入: [7,1,5,3,6,4] +> 输出: 5 +> 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 +> 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 +> 示例 2: +> +> 输入: [7,6,4,3,1] +> 输出: 0 +> 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + + + + + +### [15. 三数之和](https://leetcode-cn.com/problems/3sum/) > 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 > diff --git a/docs/data-structure-algorithms/data-structure/Binary-Tree.md b/docs/data-structure-algorithms/data-structure/Binary-Tree.md new file mode 100755 index 0000000000..4f22ac687b --- /dev/null +++ b/docs/data-structure-algorithms/data-structure/Binary-Tree.md @@ -0,0 +1,619 @@ +--- +title: 程序员心里得有点树——重学数据结构之二叉树 +date: 2022-06-09 +tags: + - data-structure + - binary-tree +categories: data-structure +--- + +## 前言 + +> 重学二叉树 + +## 树 + +树是一种数据结构,它是由n(n>=0)个有限节点组成一个具有层次关系的集合,存储的是具有“一对多”关系的数据元素的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的 + +- 除根节点之外的节点被划分为非空集,其中每个节点被称为子树 +- 树的节点父子关系,就是姐妹(兄弟)关系 +- 在通用树中,一个节点可以具有任意数量的子节点,但它只能有一个父节点 +- 下图就是一棵树,节点 `A` 为根节点,而其他节点可以看作是 `A` 的子节点 + + + + + +### [基本术语](https://www.bootwiki.com/datastructure/data-structure-tree.html) + +- **根节点** :树中最顶端的节点,根没有父节点(树中至少有一个节点——根) +- **子树**: 如果根节点不为空,则树 `B`,`C` 和 `D` 称为根节点的子树(树中各子树是互不相交的集合) +- **父节点(Parent)**:如果节点拥有子节点,则该节点为子节点的父节点 +- **叶节点**:没有子节点的节点,是树的末端节点 +- **边(Edge)**:两个节点中间的链接 +- **路径**: 连续边的序列称为路径。 在上图所示的树中,节点`E`的路径为`A→B→E` +- **祖先节点**: 节点的祖先是从根到该节点的路径上的任何前节点。根节点没有祖先节点。 在上图所示的树中,节点`F`的祖先是`B`和`A` +- **度**: 节点的度数等于子节点数。 在上图所示的树中,节点`B`的度数为`2`。叶子节点的度数总是`0`,而在完整的二叉树中,每个节点的度数等于`2` +- **高度(Height)**:[根]节点到叶子节点的最常路径(边数) +- 深度(Depth):根节点到这个节点所经历的边的个数 +- **级别编号**: 为树的每个节点分配一个级别编号,使得每个节点都存在于高于其父级的一个级别。树的根节点始终是级别`0`。 +- **层级(Level)**:根为 Level 0 层,根的子节点为 Level 1 层,以此类推 +- 有序树、无序树:如果将树中的各个子树看成是从左到右是有次序的,则称该树是有序树;若不考虑子树的顺序称为无序树 +- 森林:m(m>=0)棵互不交互的树的集合。对树中每个结点而言,其子树的集合即为森林 + + + +### 基本操作 + +1. 构造空树(初始化) + +2. 销毁树(将树置为空树) + +3. 求双亲函数 + +4. 求孩子节点函数 + +5. 插入子树 + +6. 遍历操作 + + ...... + + +> 其他都好理解,主要回顾下几种遍历操作,也是面试常客 + +#### 遍历 + +遍历的含义就是把树的所有节点(Node)按照**某种顺序**访问一遍。包括**前序**,**中序**,**后续**,**广度优先**(队列),**深度优先**(栈)5 种遍历方法 + +| 遍历方法 | 顺序 | 示意图 | 顺序 | 应用 | +| -------- | ------------------------ | ------------------------------------------------------------ | -------- | ------------------------------------------------------------ | +| 前序 | **根 ➜ 左 ➜ 右** |  | 12457836 | 想在节点上直接执行操作(或输出结果)使用先序 | +| 中序 | **左 ➜ 根 ➜ 右** |  | 42758136 | 在**二分搜索树**中,中序遍历的顺序符合从小到大(或从大到小)顺序的 要输出排序好的结果使用中序 | +| 后序 | **左 ➜ 右 ➜ 根** |  | 47852631 | 后续遍历的特点是在执行操作时,肯定**已经遍历过该节点的左右子节点** 适用于进行破坏性操作 比如删除所有节点,比如判断树中是否存在相同子树 | +| 广度优先 | **层序,横向访问** |  | 12345678 | 当**树的高度非常高**(非常瘦) 使用广度优先剑节省空间 | +| 深度优先 | **纵向,探底到叶子节点** |  | 12457836 | 当**每个节点的子节点非常多**(非常胖),使用深度优先遍历节省空间 (访问顺序和入栈顺序相关,相当于先序遍历) | + +> 之所以叫前序、中序、后序遍历,是因为根节点在前、中、后 + +> 数据结构中有很多树的结构,其中包括二叉树、二叉搜索树、2-3树、红黑树等等。本文中对数据结构中常见的几种树的概念和用途进行了汇总,不求严格精准,但求简单易懂。 + +## 二叉树 + +二叉树是每个节点最多有两个子树的树结构 + +- 二叉树中不存在度大于 2 的结点 +- 左子树和右子树是有顺序的,次序不能任意颠倒 + +根据二叉树的定义和特点,可以将二叉树分为五种不同的形态,如下图所示 + + + +### 二叉树的性质 + +- 在非空二叉树中,二叉树的第 i 层最多有 2^(i-1) 个结点(i>=1); +- 深度为 k 的二叉树最多有 2^k – 1 个结点,最少有 k 个结点(k>=1); +- 对于任意一棵非空二叉树如果其叶结点数为 n0,而度为 2 的非叶结点总数为 n2,则 n0=n2+1; +- 具有 n (n>=0) 个结点的完全二叉树的深度为 log2(n) +1; +- 任意一棵二叉树,其节点个数等于分支个数加 1,即 n=B+1 + +### 两个特别的二叉树 + +- 满二叉树:如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树,也叫严格二叉树 + - 二叉树中第 i 层的节点数为 2^(n-1) 个。 + - 深度为 k 的满二叉树必有 2^(k-1) 个节点 ,叶子数为 2^(k-1) + - 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层 + - 具有 n 个节点的满二叉树的深度为 log2(n+1) +- **完全二叉树**:若设二叉树的深度为 h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。 + + + +**满二叉树一定是一颗棵完全二叉树,但完全二叉树不一定是满二叉树。** + +- 其实还有一种更特殊的二叉树:**斜树**,顾名思义,就是斜着长的,分为左斜树和右斜树。(线性表结构可以理解为是树的一种极其特殊的表现形式) + +### 常见的存储方法 + +二叉树的存储结构有两种,分别为顺序存储和链式存储。 + +#### 二叉树的顺序存储结构 + +二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。 + +完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可。 + + + +普通二叉树转完全二叉树,只需给二叉树额外添加一些节点,将其"拼凑"成完全二叉树即可。 + + + +#### 二叉树的链式存储结构 + +并不是每个二叉树都是完全二叉树,普通二叉树使用顺序表存储或多或少会存在空间浪费的现象,所以就有了链式存储结构。 + +二叉树的链式存储结构就是用链表来表示一棵二叉树,即用链来指示着元素的逻辑关系。通常有下面两种形式。 + +##### 二叉链表存储 + +链表中每个结点由三个域组成,除了数据域外,还有两个指针域,分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。 + +其中,data 域存放某结点的数据信息;lchild 与 rchild 分别存放指向左孩子和右孩子的指针,当左孩子或右孩子不存在时,相应指针域值为空(用符号 ∧ 或 NULL 表示)。 + + + +##### 三叉链表存储 + +为了方便找到父节点,可以在上述结点结构中增加一个指针域,指向结点的父结点。利用此结点结构得到的二叉树存储结构称为三叉链表。 + + + +### 二叉树的基本操作 + +二叉树的遍历方式主要有:先序遍历、中序遍历、后序遍历、层次遍历。 + +关于应用部分,选择遍历方法的基本的原则:**更快的访问到你想访问的节点**。先序会先访问根节点,后序会先访问叶子节点 + +> coding 部分,下一篇结合 leetcode 常见的二叉树算法题,再一并说下二叉树的建立、递归等操作 + +------ + +## 二叉查找树 + +**二叉查找树定义**:又称为二叉排序树(Binary Sort Tree)或二叉搜索树。二叉排序树要么是一棵空树,要么是具有如下性质的二叉树: + +1. 若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值 +2. 若它的右子树不为空,则右子树上所有结点的值均大于它的根结点的值 +3. 左、右子树也分别为二叉排序树 +4. 没有键值相等的节点 + + + +### 性质 + +二叉查找树是一个递归的数据结构。对二叉查找树进行中序遍历,即可得到有序的数列。 + +### 时间复杂度 + +它和二分查找一样,插入和查找的时间复杂度均为 $O(log2n)$,但是在最坏的情况下仍然会有 $O(n)$ 的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是平衡查找树设计的初衷。 + +**二叉查找树的高度决定了二叉查找树的查找效率。** + +### 基本操作 + +在实行基本操作之前,我们需要先定义一下基本数据类型: + +```java +class TreeNode>{ + private E data; + private TreeNode left; + private TreeNode right; + TreeNode(E theData){ + data = theData; + left = null; + right = null; + } +} +public class BinarySearchTree>{ + private TreeNode root = null; +} +``` + +#### 1. 树的遍历: + +假设我们需要遍历树中所有节点,这里有许多递归方法可以实现: + +**1.中序遍历:当到达某个节点时,先访问左子节点,再输出该节点,最后访问右子节点。** + +```java +/* 中序遍历 */ +void inOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:左子树 -> 根节点 -> 右子树 + inOrder(root.left); + list.add(root.val); + inOrder(root.right); +} +``` + +**2. 前序遍历:当到达某个节点时,先输出该节点,再访问左子节点,最后访问右子节点。** + +```java +/* 前序遍历 */ +void preOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:根节点 -> 左子树 -> 右子树 + list.add(root.val); + preOrder(root.left); + preOrder(root.right); +} +``` + +**3. 后序遍历:当到达某个节点时,先访问左子节点,再访问右子节点,最后输出该节点。** + +```java +/* 后序遍历 */ +void postOrder(TreeNode root) { + if (root == null) + return; + // 访问优先级:左子树 -> 右子树 -> 根节点 + postOrder(root.left); + postOrder(root.right); + list.add(root.val); +} +``` + +前序、中序和后序遍历都属于深度优先遍历(depth-first traversal),也称深度优先搜索(depth-first search, DFS),它体现了一种“先走到尽头,再回溯继续”的遍历方式。 + +**深度优先遍历就像是绕着整棵二叉树的外围“走”一圈**,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。 + + + +**4. 层序遍历:**层序遍历(level-order traversal)从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。 + +层序遍历本质上属于广度优先遍历(breadth-first traversal),也称广度优先搜索(breadth-first search, BFS),它体现了一种“一圈一圈向外扩展”的逐层遍历方式。 + +```java +/* 层序遍历 */ +List levelOrder(TreeNode root) { + // 初始化队列,加入根节点 + Queue queue = new LinkedList<>(); + queue.add(root); + // 初始化一个列表,用于保存遍历序列 + List list = new ArrayList<>(); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); // 队列出队 + list.add(node.val); // 保存节点值 + if (node.left != null) + queue.offer(node.left); // 左子节点入队 + if (node.right != null) + queue.offer(node.right); // 右子节点入队 + } + return list; +} +``` + + + +#### 2. 树的搜索: + +树的搜索和树的遍历差不多,就是在遍历的时候只搜索不输出就可以了(类比有序数组的搜索) + + + +```java +public boolean searchNode(TreeNode node){ + TreeNode currentNode = root; + while(true){ + if(currentNode == null){ + return false; + } + if(currentNode.getData().compareTo(node.getData()) == 0){ + return true; + }else if(currentNode.getData().compareTo(node.getData()) < 0){ + currentNode = currentNode.getLeft(); + }else{ + currentNode = currentNode.getRight(); + } + } +} +``` + +#### 3. 节点插入: + +步骤: + +1. 递归地去查找该二叉树,找到应该插入的节点 +2. 若当前的二叉查找树为空,则插入的元素为根节点 +3. 若插入的元素值小于根节点值,则将元素插入到左子树中 +4. 若插入的元素值不小于根节点值,则将元素插入到右子树中 + + + +```java +public void insertNode(TreeNode node){ + TreeNode currentNode = root; + if(currentNode == null){ + root = node; + return; + }else{ + while(true){ + if(node.getData().compareTo(currentNode.getData()) < 0){ + if(currentNode.getLeft() == null){ + break; + }else{ + currentNode = currentNode.getLeft(); + } + }else if(node.getData().compareTo(currentNode.getData()) > 0){ + + if(currentNode.getRight() == null){ + break; + }else{ + currentNode = currentNode.getRight(); + } + } + } + } + if(node.getData().compareTo(currentNode.getData()) < 0){ + currentNode.setLeft(node); + }else if(node.getData().compareTo(currentNode.getData()) > 0){ + currentNode.setRight(node); + } +} +``` + +#### 4. 节点删除: + +首先需要搜索该节点,然后可以分为以下四种情况进行讨论: + +- 如果找不到该节点,那么什么都不用做 + +- 如果被移除的元素在叶节点(no children):那么直接移除该节点,并且将父节点原本指向该位置改为 null (如果是根节点,那就不用修改父节点指向位置) + +- 如果删除的元素只有一个儿子(one child):那么也很简单,直接删除该节点,并且将父节点原本指向的位置改为该儿子 (如果是根节点,那么该儿子成为新的根节点) + + 例如:要在树中删除元素 20 + + + +- 如果删除的元素有两个儿子,那么可以取左子树中最大元素或者右子树中最小元素进行替换,然后将最大元素最小元素原位置置空 + + 例如:要在树中删除元素 15 + + + +- 有序数组转为二叉查找树 + + + +- 将二叉树转为有序数组 + + + +------ + +## 平衡二叉树 + +二叉搜索树虽然在插入和删除时的效率都有所提升,但是如果原序列有序时,比如 {3,4,5,6,7},这个时候构造二叉树搜索树就变成了斜树,二叉树退化成单链表,搜索效率降低到 $O(n)$,查找数字 7 的话,需要找 5 次。这又说明了**二叉查找树的高度决定了二叉查找树的查找效率**。 + + + +为了解决这一问题,两位科学家大爷,G. M. Adelson-Velsky 和 E. M. Landis 又发明了平衡二叉树,从他两名字中提取出了 AVL,所以平衡二叉树又叫 **AVL 树**。 + +二叉搜索树的查找效率取决于树的高度,所以保持树的高度最小,就可保证树的查找效率,如下保持左右平衡,像不像天平? + + + +**定义**: + +平衡二叉查找树,简称平衡二叉树,指的是左子树上的所有节点的值都比根节点的值小,而右子树上的所有节点的值都比根节点的值大,且左子树与右子树的高度差最大为1。因此,平衡二叉树满足所有二叉排序(搜索)树的性质: + +- 可以是空树 +- 假如不是空树,**任何一个结点的左子树与右子树都是平衡二叉树**,并且高度之差的绝对值不超过 1 + +**平衡因子**: + +某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子(BF,Balance Factor),平衡二叉树中不存在平衡因子大于 1 的节点。在一棵平衡二叉树中,节点的平衡因子只能取 0 、1 或者 -1 ,分别对应着左右子树等高,左子树比较高,右子树比较高。 + +在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(logn)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。**这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。** + +平衡二叉树(AVL树)在符合二叉查找树的条件下,还满足任何节点的两个子树的高度最大差为1。下面的两张图片,左边是AVL树,它的任何节点的两个子树的高度差<=1;右边的不是AVL树,其根节点的左子树高度为3,而右子树高度为1; [](https://camo.githubusercontent.com/8e50f7d9828f1e438edcb97b04cb051542d3ad7fa588dd3220aeeb14eed3010d/68747470733a2f2f696d672d626c6f672e6373646e2e6e65742f3230313630323032323033353534363633) + +如果在AVL树中进行插入或删除节点,可能导致AVL树失去平衡,这种失去平衡的二叉树可以概括为四种姿态:LL(左左)、RR(右右)、LR(左右)、RL(右左)。它们的示意图如下: [](https://camo.githubusercontent.com/b67a94873e0ca508363d711117242d0f7f295086fd764f2097f400f189c5ccdb/68747470733a2f2f696d672d626c6f672e6373646e2e6e65742f3230313630323032323033363438313438) + +这四种失去平衡的姿态都有各自的定义: LL:LeftLeft,也称“左左”。插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。 + +RR:RightRight,也称“右右”。插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。 + +LR:LeftRight,也称“左右”。插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。 + +RL:RightLeft,也称“右左”。插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。 + +AVL树失去平衡之后,可以通过旋转使其恢复平衡。下面分别介绍四种失去平衡的情况下对应的旋转方法。 + +LL的旋转。LL失去平衡的情况下,可以通过一次旋转让AVL树恢复平衡。步骤如下: + +1. 将根节点的左孩子作为新根节点。 +2. 将新根节点的右孩子作为原根节点的左孩子。 +3. 将原根节点作为新根节点的右孩子。 + +LL旋转示意图如下: [](https://camo.githubusercontent.com/6850de62dc5ddb0126755ce28bdd117752132cf9749fa8d527068bc5d9af46de/68747470733a2f2f696d672d626c6f672e6373646e2e6e65742f3230313630323032323034313133393934) + +RR的旋转:RR失去平衡的情况下,旋转方法与LL旋转对称,步骤如下: + +1. 将根节点的右孩子作为新根节点。 +2. 将新根节点的左孩子作为原根节点的右孩子。 +3. 将原根节点作为新根节点的左孩子。 + +RR旋转示意图如下: [](https://camo.githubusercontent.com/910e0d2207881d121608d1efb0b1121732bb8915f5a6a38f8803f1b162d6a0fd/68747470733a2f2f696d672d626c6f672e6373646e2e6e65742f3230313630323032323034323037393633) + +LR的旋转:LR失去平衡的情况下,需要进行两次旋转,步骤如下: + +1. 围绕根节点的左孩子进行RR旋转。 +2. 围绕根节点进行LL旋转。 + +LR的旋转示意图如下: [](https://camo.githubusercontent.com/ef203ace5934366c04ddc854bd7f7a57670f549f8c0b12f28ab5096993259887/68747470733a2f2f696d672d626c6f672e6373646e2e6e65742f3230313630323032323034323537333639) + +RL的旋转:RL失去平衡的情况下也需要进行两次旋转,旋转方法与LR旋转对称,步骤如下: + +1. 围绕根节点的右孩子进行LL旋转。 +2. 围绕根节点进行RR旋转。 + +RL的旋转示意图如下: [](https://camo.githubusercontent.com/36a25db63c2608975c3c40ea85e63bfca51e6601f236721489f07be41bce3684/68747470733a2f2f696d672d626c6f672e6373646e2e6e65742f3230313630323032323034333331303733) + +】 + +### AVL树插入时的失衡与调整 + +平衡二叉树大部分操作和二叉查找树类似,主要不同在于插入删除的时候平衡二叉树的平衡可能被改变,当平衡因子的绝对值大于1的时候,我们就需要对其进行旋转来保持平衡。 + +### AVL树的四种插入节点方式 + +假设一颗 AVL 树的某个节点为 A,有四种操作会使 A 的左右子树高度差大于 1,从而破坏了原有 AVL 树的平衡性: + +1. 在 A 的左儿子的左子树进行一次插入 +2. 对 A 的左儿子的右子树进行一次插入 +3. 对 A 的右儿子的左子树进行一次插入 +4. 对 A 的右儿子的右子树进行一次插入 + + + +情形 1 和情形 4 是关于 A 的镜像对称,情形 2 和情形 3 也是关于 A 的镜像对称,因此理论上看只有两种情况,但编程的角度看还是四种情形。 + +第一种情况是插入发生在“外边”的情形(左左或右右),该情况可以通过一次单旋转完成调整;第二种情况是插入发生在“内部”的情形(左右或右左),这种情况比较复杂,需要通过双旋转来调整。 + +单旋转又有左旋和右旋之分 + +#### 左旋 + +情景 1 对 A 的左二子的左子树插入节点,只需要一次右旋即可。 + +https://www.zhihu.com/search?type=content&q=平衡二叉树 + +https://www.cxyxiaowu.com/1696.html + +https://www.cnblogs.com/zhangbaochong/p/5164994.html + +### AVL树的四种删除节点方式 + +AVL 树和二叉查找树的删除操作情况一致,都分为四种情况: + +1. 删除叶子节点 +2. 删除的节点只有左子树 +3. 删除的节点只有右子树 +4. 删除的节点既有左子树又有右子树 + +只不过 AVL 树在删除节点后需要重新检查平衡性并修正,同时,删除操作与插入操作后的平衡修正区别在于,插入操作后只需要对插入栈中的弹出的第一个非平衡节点进行修正,而删除操作需要修正栈中的所有非平衡节点。 + +删除操作的大致步骤如下: + +- 以前三种情况为基础尝试删除节点,并将访问节点入栈。 +- 如果尝试删除成功,则依次检查栈顶节点的平衡状态,遇到非平衡节点,即进行旋转平衡,直到栈空。 +- 如果尝试删除失败,证明是第四种情况。这时先找到被删除节点的右子树最小节点并删除它,将访问节点继续入栈。 +- 再依次检查栈顶节点的平衡状态和修正直到栈空。 + +对于删除操作造成的非平衡状态的修正,可以这样理解:对左或者右子树的删除操作相当于对右或者左子树的插入操作,然后再对应上插入的四种情况选择相应的旋转就好了。 + + + +## 红黑树 + +顾名思义,红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑树还需要满足这样几个要求: + +- 根节点是黑色的; +- 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据; +- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的; +- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点; + +下面是一个具体的红黑树的图例: + + + +这些约束确保了红黑树的关键特性: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。 + +要知道为什么这些性质确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。 + +**红黑树的自平衡操作:** + +因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量(O(logn))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为O(logn) 次。 + +**我们首先以二叉查找树的方法增加节点并标记它为红色。如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的(违背性质5)。**但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。下面要进行什么操作取决于其他临近节点的颜色。同人类的家族树中一样,我们将使用术语叔父节点来指一个节点的父节点的兄弟节点。注意: + +- 性质1和性质3总是保持着。 +- 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。 +- 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。 + + **插入操作:** + + 假设,将要插入的节点标为**N**,N的父节点标为**P**,N的祖父节点标为**G**,N的叔父节点标为**U**。在图中展示的任何颜色要么是由它所处情形这些所作的假定,要么是假定所暗含的。 + + **情形1:** 该树为空树,直接插入根结点的位置,违反性质1,把节点颜色由红改为黑即可。 + + **情形2:** 插入节点N的父节点P为黑色,不违反任何性质,无需做任何修改。在这种情形下,树仍是有效的。性质5也未受到威胁,尽管新节点N有两个黑色叶子子节点;但由于新节点N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质。 + + 注: 情形1很简单,情形2中P为黑色,一切安然无事,但P为红就不一样了,下边是P为红的各种情况,也是真正难懂的地方。 + + **情形3:** 如果父节点P和叔父节点U二者都是红色,(此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里右图仅显示N做为P左子的情形)则我们可以将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质4)。现在我们的新节点N有了一个黑色的父节点P。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行上述情形的整个过程(把G当成是新加入的节点进行各种情形的检查)。比如,G为根节点,那我们就直接将G变为黑色(情形1);如果G不是根节点,而它的父节点为黑色,那符合所有的性质,直接插入即可(情形2);如果G不是根节点,而它的父节点为红色,则递归上述过程(情形3)。 + +[](https://camo.githubusercontent.com/4d3f371b7365cc94d8c9a881f1d750b8d0b72a22b17a4eba9648714370531e8c/68747470733a2f2f7069633030322e636e626c6f67732e636f6d2f696d616765732f323031312f3333303731302f323031313132303131363432353235312e706e67) + + **情形4:** 父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。在这种情形下,我们进行针对祖父节点G的一次右旋转; 在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是红色就违反了性质4,所以G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。 + +[](https://camo.githubusercontent.com/6589a9c75d8e686270a6f2e981c1dd7ea274ce0c68064861e205ee259dc0a164/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f362f36362f5265642d626c61636b5f747265655f696e736572745f636173655f352e706e67) + + **情形5:** 父节点P是红色而叔父节点U是黑色或缺少,并且新节点N是其父节点P的右子节点而父节点P又是其父节点的左子节点。在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色; 接着,我们按**情形4**处理以前的父节点P以解决仍然失效的性质4。注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。 + +[](https://camo.githubusercontent.com/b3607bd07118241f8f16f2cc07603423bb3bfcae73c203434d0633a4bd3cdd60/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f352f35362f5265642d626c61636b5f747265655f696e736572745f636173655f342e706e67) + + **注: 插入实际上是原地算法,因为上述所有调用都使用了尾部递归。** + + **删除操作:** + + **如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题**。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们找到要么在它的左子树中的最大元素、要么在它的右子树中的最小元素,并把它的值转移到要删除的节点中。我们接着删除我们从中复制出值的那个节点,它必定有少于两个非叶子的儿子。因为只是复制了一个值,不违反任何性质,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。 + + **我们只需要讨论删除只有一个儿子的节点**(如果它两个儿子都为空,即均为叶子,我们任意将其中一个看作它的儿子)。如果我们删除一个红色节点(此时该节点的儿子将都为叶子节点),它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它,并不会破坏性质3和性质4。通过被删除节点的所有路径只是少了一个红色节点,这样可以继续保证性质5。另一种简单情况是在被删除节点是黑色而它的儿子是红色的时候。如果只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏性质5,但是如果我们重绘它的儿子为黑色,则曾经通过它的所有路径将通过它的黑色儿子,这样可以继续保持性质5。 + + **需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时候**,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为**N**(在新的位置上),称呼它的兄弟(它父亲的另一个儿子)为**S**。在下面的示意图中,我们还是使用**P**称呼N的父亲,**SL**称呼S的左儿子,**SR**称呼S的右儿子。 + + 如果N和它初始的父亲是黑色,则删除它的父亲导致通过N的路径都比不通过它的路径少了一个黑色节点。因为这违反了性质5,树需要被重新平衡。有几种情形需要考虑: + + **情形1:** N是新的根。在这种情形下,我们就做完了。我们从所有路径去除了一个黑色节点,而新根是黑色的,所以性质都保持着。 + + **注意**: 在情形2、5和6下,我们假定N是它父亲的左儿子。如果它是右儿子,则在这些情形下的左和右应当对调。 + + **情形2:** S是红色。在这种情形下我们在N的父亲上做左旋转,把红色兄弟转换成N的祖父,我们接着对调N的父亲和祖父的颜色。完成这两个操作后,尽管所有路径上黑色节点的数目没有改变,但现在N有了一个黑色的兄弟和一个红色的父亲(它的新兄弟是黑色因为它是红色S的一个儿子),所以我们可以接下去按**情形4**、**情形5**或**情形6**来处理。 + +[](https://camo.githubusercontent.com/e91996abee214a19db6edb37d13288a5bcb97b3decd3c51e96a55b139c965da7/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f332f33392f5265642d626c61636b5f747265655f64656c6574655f636173655f322e706e67) + + **情形3:** N的父亲、S和S的儿子都是黑色的。在这种情形下,我们简单的重绘S为红色。结果是通过S的所有路径,它们就是以前*不*通过N的那些路径,都少了一个黑色节点。因为删除N的初始的父亲使通过N的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过P的所有路径现在比不通过P的路径少了一个黑色节点,所以仍然违反性质5。要修正这个问题,我们要从**情形1**开始,在P上做重新平衡处理。 + +[](https://camo.githubusercontent.com/11d59ddec9bba2b6f6fd119e7c3a3504ab8a14a99fbf48c7f75c5016f0bd8fd7/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f632f63372f5265642d626c61636b5f747265655f64656c6574655f636173655f332e706e67) + + **情形4:** S和S的儿子都是黑色,但是N的父亲是红色。在这种情形下,我们简单的交换N的兄弟和父亲的颜色。这不影响不通过N的路径的黑色节点的数目,但是它在通过N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点。 + +[](https://camo.githubusercontent.com/6b648caabf6e7caf7b156f24482634f5e77bbd1c565a77b0be67f0af6eeaa350/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f642f64372f5265642d626c61636b5f747265655f64656c6574655f636173655f342e706e67) + + **情形5:** S是黑色,S的左儿子是红色,S的右儿子是黑色,而N是它父亲的左儿子。在这种情形下我们在S上做右旋转,这样S的左儿子成为S的父亲和N的新兄弟。我们接着交换S和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在N有了一个黑色兄弟,他的右儿子是红色的,所以我们进入了**情形6**。N和它的父亲都不受这个变换的影响。 + +[](https://camo.githubusercontent.com/ddae9cf5c9133ed4f91d7146251d39f9837dfe9dd2342eb2c8973fea3fe0694e/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f332f33302f5265642d626c61636b5f747265655f64656c6574655f636173655f352e706e67) + + **情形6:** S是黑色,S的右儿子是红色,而N是它父亲的左儿子。在这种情形下我们在N的父亲上做左旋转,这样S成为N的父亲(P)和S的右儿子的父亲。我们接着交换N的父亲和S的颜色,并使S的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以性质3没有被违反。但是,N现在增加了一个黑色祖先: 要么N的父亲变成黑色,要么它是黑色而S被增加为一个黑色祖父。所以,通过N的路径都增加了一个黑色节点。 + + 此时,如果一个路径不通过N,则有两种可能性: + +- 它通过N的新兄弟。那么它以前和现在都必定通过S和N的父亲,而它们只是交换了颜色。所以路径保持了同样数目的黑色节点。 +- 它通过N的新叔父,S的右儿子。那么它以前通过S、S的父亲和S的右儿子,但是现在只通过S,它被假定为它以前的父亲的颜色,和S的右儿子,它被从红色改变为黑色。合成效果是这个路径通过了同样数目的黑色节点。 + + 在任何情况下,在这些路径上的黑色节点数目都没有改变。所以我们恢复了性质4。在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色。 + +[](https://camo.githubusercontent.com/c9e3fda5572806f8c15531bcb0dd91fee28010f68bfdf9a1a7c7869964d81cd3/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f332f33312f5265642d626c61636b5f747265655f64656c6574655f636173655f362e706e67) + +## 哈夫曼树 + +> “不假,最近有没有 Java 学习资源?” +> +> “有的,我压缩后发到微信群里哈”(加入 JavaKeeper 交流群各种学习资料哈) + +我们都有过对文件进行压缩、解压的操作,压缩而不出错是怎么做到的呢?压缩文本的时候其实是对文本进行了一次重新编码,减少了不必要的空间,而哈夫曼编码算是一种最基本的压缩编码方式。 + +发明这种压缩编码方式的数学家叫哈夫曼,所以就把他在编码中用到的特殊的二叉树称之为哈弗曼树。 + +## leetcode + +## 参考与感谢 + +https://www.cnblogs.com/maybe2030/p/4732377.html + +[www.bootwiki.com](http://www.bootwiki.com/) + +https://www.jianshu.com/p/45661b029292 + +https://charlesliuyx.github.io/2018/10/22/[直观算法]树的基本操作/ + +https://www.jianshu.com/p/45661b029292 + +https://www.cnblogs.com/gaochundong/p/binary_search_tree.html + +https://blog.csdn.net/yin767833376/article/details/81511377 \ No newline at end of file diff --git a/docs/data-structure-algorithms/data-structure/HashTable.md b/docs/data-structure-algorithms/data-structure/HashTable.md new file mode 100755 index 0000000000..edb7eb82ab --- /dev/null +++ b/docs/data-structure-algorithms/data-structure/HashTable.md @@ -0,0 +1,168 @@ +--- +title: 深入解析哈希表:从散列冲突到算法应用全景解读 +date: 2022-06-09 +tags: + - data-structure + - HashTable +categories: data-structure +--- + + + +## 一、散列冲突的本质与解决方案 + +哈希表作为数据结构的核心组件,其灵魂在于通过哈希函数实现 $(1)$ 时间复杂度的数据存取。但正如硬币的两面,哈希算法在带来高效存取的同时,也面临着不可避免的**散列冲突**问题——不同的输入值经过哈希运算后映射到同一存储位置的现象。 + +### 1.1 开放寻址法:空间换时间的博弈 + +**典型代表**:Java 的 ThreadLocalMap + +开放寻址法采用"线性探测+数组存储"的经典组合,当冲突发生时,通过系统性的探测策略(线性探测/二次探测/双重哈希)在数组中寻找下一个可用槽位。这种方案具有三大显著优势: + +- **缓存友好性**:数据连续存储在数组中,有效利用CPU缓存行预取机制 +- **序列化简单**:无需处理链表指针等复杂内存结构 +- **空间紧凑**:内存分配完全可控,无动态内存开销 + +但硬币的另一面是: + +- **删除复杂度**:需引入墓碑标记(TOMBSTONE)处理逻辑 +- **负载因子限制**:建议阈值0.7以下,否则探测次数指数级增长 +- **内存浪费**:动态扩容时旧数组需要保留至数据迁移完成 + +```Java +// 线性探测的典型实现片段 +int index = hash(key); +while (table[index] != null) { + if (table[index].key.equals(key)) break; + index = (index + 1) % capacity; +} +``` + +### 1.2 链表法:时间与空间的动态平衡 + +**典型代表**:Java的LinkedHashMap + +链表法采用"数组+链表/树"的复合结构,每个桶位维护一个动态数据结构。其核心优势体现在: + +- **高负载容忍**:允许负载因子突破1.0(Java HashMap默认0.75) +- **内存利用率**:按需创建节点,避免空槽浪费 +- **结构灵活性**:可升级为红黑树(Java8+)应对哈希碰撞攻击 + +但需要注意: + +- **指针开销**:每个节点多消耗4-8字节指针空间 +- **缓存不友好**:节点内存地址离散影响访问局部性 +- **小对象劣势**:当存储值小于指针大小时内存利用率降低 + +```Java +// 树化转换阈值定义(Java HashMap) +static final int TREEIFY_THRESHOLD = 8; +static final int UNTREEIFY_THRESHOLD = 6; +``` + + + +## 二、哈希算法:从理论到工程实践 + +哈希算法作为数字世界的"指纹生成器",必须满足四大黄金准则: + +1. **不可逆性**:哈希值到原文的逆向推导在计算上不可行 +2. **雪崩效应**:微小的输入变化导致输出剧变 +3. **低碰撞率**:不同输入的哈希相同概率趋近于零 +4. **高效计算**:处理海量数据时仍保持线性时间复杂度 + +### 2.1 七大核心应用场景解析 + +#### 场景1:安全加密(SHA-256示例) + +```Java +MessageDigest md = MessageDigest.getInstance("SHA-256"); +byte[] hashBytes = md.digest("secret".getBytes()); +``` + +#### 场景2:内容寻址存储(IPFS协议) + +通过三级哈希验证确保内容唯一性: + +1. 内容分块哈希 +2. 分块组合哈希 +3. 最终Merkle根哈希 + +#### 场景3:P2P传输校验(BitTorrent协议) + +种子文件包含分片哈希树,下载时逐层校验: + +``` + 分片1(SHA1) → 分片2(SHA1) → ... → 分片N(SHA1) + ↘ ↙ ↘ ↙ + 中间哈希节点 根哈希 +``` + +#### 场景4:高性能散列函数(MurmurHash3) + +针对不同场景的哈希优化: + +- 内存型:CityHash +- 加密型:SipHash +- 流式处理:XXHash + +#### 场景5:会话保持负载均衡 + +```Python +def get_server(client_ip): + hash_val = hashlib.md5(client_ip).hexdigest() + return servers[hash_val % len(servers)] +``` + +#### 场景6:大数据分片处理 + +```SQL +-- 按用户ID哈希分库 +CREATE TABLE user_0 ( + id BIGINT PRIMARY KEY, + ... +) PARTITION BY HASH(id) PARTITIONS 4; +``` + +#### 场景7:一致性哈希分布式存储 + +构建虚拟节点环解决数据倾斜问题: + +``` + NodeA → 1000虚拟节点 +NodeB → 1000虚拟节点 +NodeC → 1000虚拟节点 +``` + + + +## 三、工程实践中的进阶技巧 + +### 3.1 动态扩容策略 + +- 渐进式扩容:避免一次性rehash导致的STW停顿 +- 容量质数选择:降低哈希聚集现象(如Java HashMap使用2^n优化模运算) + +### 3.2 哈希攻击防御 + +- 盐值加密:password_hash(pass,PASSWORDBCRYPT,[′salt′=>*p**a**ss*,*P**A**SS**W**OR**D**B**CR**Y**PT*,[′*s**a**l**t*′=>salt]) +- 密钥哈希:HMAC-SHA256(secretKey, message) + +### 3.3 性能优化指标 + +| 指标 | 开放寻址法 | 链表法 | +| ------------ | ---------- | ------ | +| 平均查询时间 | O(1/(1-α)) | O(α) | +| 内存利用率 | 60-70% | 80-90% | +| 最大负载因子 | 0.7 | 1.0+ | +| 并发修改支持 | 困难 | 较容易 | + + + +## 四、未来演进方向 + +- **量子安全哈希**:抗量子计算的Lattice-based哈希算法 +- **同态哈希**:支持密文域计算的哈希方案 +- **AI驱动哈希**:基于神经网络的自适应哈希函数 + +哈希表及其相关算法作为计算机科学的基石,在从单机系统到云原生架构的演进历程中持续发挥着关键作用。理解其核心原理并掌握工程化实践技巧,将帮助开发者在高并发、分布式场景下构建出更健壮、更高效的系统。 diff --git a/docs/data-structure-algorithms/data-structure/Linked-List.md b/docs/data-structure-algorithms/data-structure/Linked-List.md new file mode 100644 index 0000000000..ba18ccf934 --- /dev/null +++ b/docs/data-structure-algorithms/data-structure/Linked-List.md @@ -0,0 +1,336 @@ +--- +title: 链表 +date: 2022-06-08 +tags: + - LikedList +categories: data-structure +--- + +# 链表 + +与数组相似,链表也是一种`线性`数据结构。 + +链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个域是指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为**结点**(node)。 + + + +## 单链表 + + + +一种最简单的结点结构如上图所示,它是构成单链表的基本结点结构。在结点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的结点。 + +单链表中的每个结点不仅包含值,还包含链接到下一个结点的`引用字段`。通过这种方式,单链表将所有结点按顺序组织起来。 + + + +链表的第一个结点和最后一个结点,分别称为链表的**首结点**和**尾结点**。尾结点的特征是其 next 引用为空(null)。链表中每个结点的 next 引用都相当于一个指针,指向另一个结点,借助这些 next 引用,我们可以从链表的首结点移动到尾结点。如此定义的结点就称为**单链表**(single linked list)。 + +上图蓝色箭头显示单个链接列表中的结点是如何组合在一起的。 + +在单链表中通常使用 head 引用来指向链表的首结点,由 head 引用可以完成对整个链表中所有节点的访问。有时也可以根据需要使用指向尾结点的 tail 引用来方便某些操作的实现。 + +在单链表结构中还需要注意的一点是,由于每个结点的数据域都是一个 Object 类的对象,因此,每个数据元素并非真正如图中那样,而是在结点中的数据域通过一个 Object 类的对象引用来指向数据元素的。 + +与数组类似,单链表中的结点也具有一个线性次序,即如果结点 P 的 next 引用指向结点 S,则 P 就是 S 的**直接前驱**,S 是 P 的**直接后续**。单链表的一个重要特性就是只能通过前驱结点找到后续结点,而无法从后续结点找到前驱结点。 + +接着我们来看下单链表的 CRUD: + +> [707. 设计链表](https://leetcode.cn/problems/design-linked-list/) 搞定一题 + +以下是单链表中结点的典型定义,`值 + 链接到下一个元素的指针`: + +```java +// Definition for singly-linked list. +public class SinglyListNode { + int val; + SinglyListNode next; + SinglyListNode(int x) { val = x; } +} +``` + +有了结点,还需要一个“链” 把所有结点串起来 + +```java +class MyLinkedList { + private SinglyListNode head; + /** Initialize your data structure here. */ + public MyLinkedList() { + head = null; + } +} +``` + + + +### 查找 + +与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 $O(N)$ 时间,其中 N 是链表的长度。 + +使用 Java 语言实现整个过程的关键语句是: + +```java +/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ +public int get(int index) { + // if index is invalid + if (index < 0 || index >= size) return -1; + + ListNode curr = head; + // index steps needed + // to move from sentinel node to wanted index + for(int i = 0; i < index + 1; ++i) { + curr = curr.next; + } + return curr.val; +} +``` + + + +### 添加 + +单链表中数据元素的插入,是通过在链表中插入数据元素所属的结点来完成的。对于链表的不同位置,插入的过程会有细微的差别。 + + + +除了单链表的首结点由于没有直接前驱结点,所以可以直接在首结点之前插入一个新的结点之外,在单链表中的其他任何位置插入一个新结点时,都只能是在已知某个特定结点引用的基础上在其后面插入一个新结点。并且在已知单链表中某个结点引用的基础上,完成结点的插入操作需要的时间是 $O(1)$。 + +> 思考:如果是带头结点的单链表进行插入操作,是什么样子呢? + +```java +//最外层有个链表长度,便于我们头插和尾插操作 +int size; + +public void addAtHead(int val) { + addAtIndex(0, val); +} + +//尾插就是从最后一个 +public void addAtTail(int val) { + addAtIndex(size, val); +} + +public void addAtIndex(int index, int val) { + + if (index > size) return; + + if (index < 0) index = 0; + + ++size; + // find predecessor of the node to be added + ListNode pred = head; + for(int i = 0; i < index; ++i) { + pred = pred.next; + } + + // node to be added + ListNode toAdd = new ListNode(val); + // insertion itself + toAdd.next = pred.next; + pred.next = toAdd; +} +``` + + + +### 删除 + +类似的,在单链表中数据元素的删除也是通过结点的删除来完成的。在链表的不同位置删除结点,其操作过程也会有一些差别。 + + + +在单链表中删除一个结点时,除首结点外都必须知道该结点的直接前驱结点的引用。并且在已知单链表中某个结点引用的基础上,完成其后续结点的删除操作需要的时间是 $O(1)$。 + +> 在使用单链表实现线性表的时候,为了使程序更加简洁,我们通常在单链表的最前面添加一个**哑元结点**,也称为头结点。在头结点中不存储任何实质的数据对象,其 next 域指向线性表中 0 号元素所在的结点,头结点的引入可以使线性表运算中的一些边界条件更容易处理。 +> +> 对于任何基于序号的插入、删除,以及任何基于数据元素所在结点的前面或后面的插入、删除,在带头结点的单链表中均可转化为在某个特定结点之后完成结点的插入、删除,而不用考虑插入、删除是在链表的首部、中间、还是尾部等不同情况。 + + + + + +```java + public void deleteAtIndex(int index) { + // if the index is invalid, do nothing + if (index < 0 || index >= size) return; + + size--; + // find predecessor of the node to be deleted + ListNode pred = head; + for(int i = 0; i < index; ++i) { + pred = pred.next; + } + + // delete pred.next + pred.next = pred.next.next; + } +``` + + + +## 双向链表 + +单链表的一个优点是结构简单,但是它也有一个缺点,即在单链表中只能通过一个结点的引用访问其后续结点,而无法直接访问其前驱结点,要在单链表中找到某个结点的前驱结点,必须从链表的首结点出发依次向后寻找,但是需要 $Ο(n)$ 时间。 + +所以我们在单链表结点结构中新增加一个域,该域用于指向结点的直接前驱结点。 + + + +双向链表是通过上述定义的结点使用 pre 以及 next 域依次串联在一起而形成的。一个双向链表的结构如下图所示。 + + + +接着我们来看下双向链表的 CRUD: + +以下是双链表中结点的典型定义: + +```java +public class ListNode { + int val; + ListNode next; + ListNode prev; + ListNode(int x) { val = x; } +} + +class MyLinkedList { + int size; + // sentinel nodes as pseudo-head and pseudo-tail + ListNode head, tail; + public MyLinkedList() { + size = 0; + head = new ListNode(0); + tail = new ListNode(0); + head.next = tail; + tail.prev = head; + } +``` + +### 查找 + +在双向链表中进行查找与在单链表中类似,只不过在双向链表中查找操作可以从链表的首结点开始,也可以从尾结点开始,但是需要的时间和在单链表中一样。 + +```java + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + public int get(int index) { + if (index < 0 || index >= size) return -1; + + ListNode curr = head; + if (index + 1 < size - index) + for(int i = 0; i < index + 1; ++i) { + curr = curr.next; + } + else { + curr = tail; + for(int i = 0; i < size - index; ++i) { + curr = curr.prev; + } + } + + return curr.val; + } +``` + + + +### 添加 + +单链表的插入操作,除了首结点之外必须在某个已知结点后面进行,而在双向链表中插入操作在一个已知的结点之前或之后都可以进行,如下表示在结点 11 之前 插入 9。 + + + +使用 Java 语言实现整个过程的关键语句是 + +```java + + public void addAtHead(int val) { + ListNode pred = head, succ = head.next; + + ++size; + ListNode toAdd = new ListNode(val); + toAdd.prev = pred; + toAdd.next = succ; + pred.next = toAdd; + succ.prev = toAdd; + } + + /** Append a node of value val to the last element of the linked list. */ + public void addAtTail(int val) { + ListNode succ = tail, pred = tail.prev; + + ++size; + ListNode toAdd = new ListNode(val); + toAdd.prev = pred; + toAdd.next = succ; + pred.next = toAdd; + succ.prev = toAdd; + } + + public void addAtIndex(int index, int val) { + if (index > size) return; + + if (index < 0) index = 0; + + ListNode pred, succ; + if (index < size - index) { + pred = head; + for(int i = 0; i < index; ++i) pred = pred.next; + succ = pred.next; + } + else { + succ = tail; + for (int i = 0; i < size - index; ++i) succ = succ.prev; + pred = succ.prev; + } + + // insertion itself + ++size; + ListNode toAdd = new ListNode(val); + toAdd.prev = pred; + toAdd.next = succ; + pred.next = toAdd; + succ.prev = toAdd; + } +``` + +在结点 p 之后插入一个新结点的操作与上述操作对称,这里不再赘述。 + +插入操作除了上述情况,还可以在双向链表的首结点之前、双向链表的尾结点之后进行,此时插入操作与上述插入操作相比更为简单。 + +### 删除 + +单链表的删除操作,除了首结点之外必须在知道待删结点的前驱结点的基础上才能进行,而在双向链表中在已知某个结点引用的前提下,可以完成该结点自身的删除。如下表示删除 16 的过程。 + + + +```java + /** Delete the index-th node in the linked list, if the index is valid. */ + public void deleteAtIndex(int index) { + if (index < 0 || index >= size) return; + + ListNode pred, succ; + if (index < size - index) { + pred = head; + for(int i = 0; i < index; ++i) pred = pred.next; + succ = pred.next.next; + } + else { + succ = tail; + for (int i = 0; i < size - index - 1; ++i) succ = succ.prev; + pred = succ.prev.prev; + } + + // delete pred.next + --size; + pred.next = succ; + succ.prev = pred; + } +} +``` + +对线性表的操作,无非就是排序、加法、减法、反转,说的好像很简单,我们去下一章刷题吧。 + + + +## 参考与感谢 + +- https://aleej.com/2019/09/16/数据结构与算法之美学习笔记 \ No newline at end of file diff --git a/docs/data-structure-algorithms/Queue.md b/docs/data-structure-algorithms/data-structure/Queue.md similarity index 90% rename from docs/data-structure-algorithms/Queue.md rename to docs/data-structure-algorithms/data-structure/Queue.md index 30bce5fd26..2c91ca275f 100644 --- a/docs/data-structure-algorithms/Queue.md +++ b/docs/data-structure-algorithms/data-structure/Queue.md @@ -1,25 +1,26 @@ -# 队列 +--- +title: Queue +date: 2023-05-03 +tags: + - Stack +categories: data-structure +--- -## 一、前言 + + +> 队列(queue)是一种采用先进先出(FIFO)策略的抽象数据结构,它的想法来自于生活中排队的策略。顾客在付款结账的时候,按照到来的先后顺序排队结账,先来的顾客先结账,后来的顾客后结账。 -队列(queue)是一种采用先进先出(FIFO)策略的抽象数据结构,它的想法来自于生活中排队的策略。顾客在付款结账的时候,按照到来的先后顺序排队结账,先来的顾客先结账,后来的顾客后结账。 +## 一、前言 队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。 - + 1. 队列是一个有序列表,可以用数组或是链表来实现。 2. 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出 - - -在 FIFO 数据结构中,将`首先处理添加到队列中的第一个元素`。 - -如上图所示,队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在`队列的末尾`。 删除(delete)操作也被称为出队(dequeue)。 你只能移除`第一个元素`。 - - ## 二、基本属性 @@ -85,13 +86,11 @@ public interface MyQueue { ### 3.1 基于数组实现的队列 -- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中 capacity是该队列的最大容量。 -- 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标, **front 会随着数据输出而改变,而 rear 则是随着数据输入而改变**,如图所示: - - +- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中 capacity 是该队列的最大容量。 +- 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标, **front 会随着数据输出而改变,而 rear 则是随着数据输入而改变**、 - 当我们将数据存入队列时的处理需要有两个步骤: - 1. 将尾指针往后移:`rear+1` , 当 `front == rear`队列为空 + 1. 将尾指针往后移:`rear+1` , 当 `front == rear` 队列为空 2. 若尾指针 rear 小于队列的最大下标 `capacity-1`,则将数据存入 rear 所指的数组元素中,否则无法存入数据,即队列满了。 ```java @@ -149,15 +148,17 @@ public class MyArrayQueue implements MyQueue { } ``` +### 3.2 基于链表实现的队列 +链表实现的队列没有固定的大小限制,可以动态地添加更多的元素。这种方式更加灵活,有效避免了空间浪费,但其操作可能比数组实现稍微复杂一些,因为需要处理节点之间的链接。 -**缺点** -上面的实现很简单,但在某些情况下效率很低。 -假设我们分配一个最大长度为 5 的数组。当队列满时,我们想要循环利用的空间的话,在执行取出队首元素的时候,我们就必须将数组中其他所有元素都向前移动一个位置,时间复杂度就变成了 $O(n)$ +## 四、队列的变体 +上面的实现很简单,但在某些情况下效率很低。 +假设我们分配一个最大长度为 5 的数组。当队列满时,我们想要循环利用的空间的话,在执行取出队首元素的时候,我们就必须将数组中其他所有元素都向前移动一个位置,时间复杂度就变成了 $O(n)$  @@ -170,7 +171,7 @@ public class MyArrayQueue implements MyQueue { 为了提高运算的效率,我们用另一种方式来表达数组中各单元的位置关系,将数组看做是一个环形的。当 rear 到达数组的最大下标时,重新指回数组下标为`0`的位置,这样就避免了数据迁移的低效率问题。 - + 用循环数组实现的队列称为循环队列,我们将循环队列中从对首到队尾的元素按逆时针方向存放在循环数组中的一段连续的单元中。当新元素入队时,将队尾指针 rear 按逆时针方向移动一位即可,出队操作也很简单,只要将对首指针 front 逆时针方向移动一位即可。 @@ -435,9 +436,13 @@ public class MyPriorityQueue { -## 队列的应用 +## 五、队列的应用 +队列在计算机科学的许多领域都有应用,包括: +- **操作系统**:在多任务处理和调度中,队列用来管理进程执行的顺序。 +- **网络**:在数据包的传输中,队列帮助管理数据包的发送顺序和处理。 +- **算法**:在广度优先搜索(BFS)等算法中,队列用于存储待处理的节点。 diff --git a/docs/data-structure-algorithms/Skip-List.md b/docs/data-structure-algorithms/data-structure/Skip-List.md similarity index 94% rename from docs/data-structure-algorithms/Skip-List.md rename to docs/data-structure-algorithms/data-structure/Skip-List.md index 830d158f81..ad767655f8 100644 --- a/docs/data-structure-algorithms/Skip-List.md +++ b/docs/data-structure-algorithms/data-structure/Skip-List.md @@ -1,13 +1,19 @@ -# 跳表 +--- +title: 跳表 +date: 2023-05-09 +tags: + - Skip List +categories: data-structure +--- - + > Redis 是怎么想的:用跳表来实现有序集合? > 干过服务端开发的应该都知道 Redis 的 ZSet 使用跳表实现的(当然还有压缩列表、哈希表),我就不从 1990 年的那个美国大佬 William Pugh 发表的那篇论文开始了,直接开跳 - + 文章拢共两部分 @@ -20,7 +26,7 @@ ### 跳表的简历 - + 跳表,英文名:Skip List @@ -38,13 +44,13 @@ 前提:跳表处理的是有序的链表,所以我们先看个不能再普通了的有序列表(一般是双向链表) - + 如果我们想查找某个数,只能遍历链表逐个比对,时间复杂度 $O(n)$,插入和删除操作都一样。 为了提高查找效率,我们对链表做个”索引“ - + 像这样,我们每隔一个节点取一个数据作为索引节点(或者增加一个指针),比如我们要找 31 直接在索引链表就找到了(遍历 3 次),如果找 16 的话,在遍历到 31的时候,发现大于目标节点,就跳到下一层,接着遍历~ (蓝线表示搜索路径) @@ -52,7 +58,7 @@ > > 数据量多的话,我们也可以多建几层索引,如下 4 层索引,效果就比较明显了 - + 每加一层索引,我们搜索的时间复杂度就降为原来的 $O(n/2)$ @@ -82,7 +88,7 @@ > > 不信,你照着下图比划比划,看看同一层能画出 3 条线不~~ > - >  + >  5. 既然知道了每一层最多遍历两个节点,那跳表查询数据的时间复杂度就是 $O(2*log(n))$,常数 2 忽略不计,就是 $O(logn)$ 了。 @@ -104,7 +110,7 @@ 其实插入数据和查找一样,先找到元素要插入的位置,时间复杂度也是 $O(logn)$,但有个问题就是如果一直往原始列表里加数据,不更新我们的索引层,极端情况下就会出现两个索引节点中间数据非常多,相当于退化成了单链表,查找效率直接变成 $O(n)$ - + @@ -122,7 +128,7 @@ 比如我们要插入新节点 X,那要不要为 X 向上建索引呢,就是抛硬币决定的,正面的话建索引,否则就不建了,就是这么随意(比如一个节点随机出的层数是 3,那就把它链入到第1 层到第 3 层链表中,也就是我们除了原链表的之外再往上 2 层索引都加上)。 - + 其实是因为我们不能预测跳表的添加和删除操作,很难用一种有效的算法保证索引部分始终均匀。学过概率论的我们都知道抛硬币虽然不能让索引位置绝对均匀,当数量足够多的时候最起码可以保证大体上相对均匀。 @@ -298,7 +304,7 @@ public Node search(int target) { > > - 当数据多的时候,ZSet 是由一个 dict + 一个 skiplist 来实现的。简单来讲,dict 用来查询数据到分数的对应关系,而 skiplist 用来根据分数查询数据(可能是范围查找)。 > ->  +>  > > Redis 的跳跃表做了些修改 > @@ -351,7 +357,7 @@ Redis 的 zset 是一个复合结构,一方面它需要一个 hash 结构来 Redis 中的有序集合是通过压缩列表、哈希表和跳表的组合来实现的,当数据较少时,ZSet 是由一个 ziplist 来实现的。当数据多的时候,ZSet 是由一个dict + 一个 skiplist 来实现的 - + diff --git a/docs/data-structure-algorithms/data-structure/Stack.md b/docs/data-structure-algorithms/data-structure/Stack.md new file mode 100644 index 0000000000..4e8996f3a0 --- /dev/null +++ b/docs/data-structure-algorithms/data-structure/Stack.md @@ -0,0 +1,546 @@ +--- +title: Stack +date: 2023-05-09 +tags: + - Stack +categories: data-structure +--- + + + +> 栈(stack)又名堆栈,它是**一种运算受限的线性表**。 限定仅在表尾进行插入和删除操作的线性表。 + + + +## 一、概述 + +### 定义 + +注意:本文所说的栈是数据结构中的栈,而不是内存模型中栈。 + +栈(stack)是限定仅在表尾一端进行插入或删除操作的**特殊线性表**。又称为堆栈。 + +对于栈来说, 允许进行插入或删除操作的一端称为栈顶(top),而另一端称为栈底(bottom)。不含元素栈称为空栈,向栈中插入一个新元素称为入栈或压栈, 从栈中删除一个元素称为出栈或退栈。 + +假设有一个栈S=(a1, a2, …, an),a1先进栈, an最后进栈。称 a1 为栈底元素,an 为栈顶元素。出栈时只允许在栈顶进行,所以 an 先出栈,a1最后出栈。因此又称栈为后进先出(Last In First Out,LIFO)的线性表。 + +栈(stack),是一种线性存储结构,它有以下几个特点: + +- 栈中数据是按照"后进先出(LIFO, Last In First Out)"方式进出栈的。 +- 向栈中添加/删除数据时,只能从栈顶进行操作。 + + + + + +### 基本操作 + +栈的基本操作除了进栈 `push()`,出栈 `pop()` 之外,还有判空 `isEmpty()`、取栈顶元素 `peek()` 等操作。 + +抽象成接口如下: + +```java +public interface MyStack { + + /** + * 返回堆栈的大小 + */ + public int getSize(); + + /** + * 判断堆栈是否为空 + */ + public boolean isEmpty(); + + /** + * 入栈 + */ + public void push(Object e); + + /** + * 出栈,并删除 + */ + public Object pop(); + + /** + * 返回栈顶元素 + */ + public Object peek(); +} +``` + + + +和线性表类似,栈也有两种存储结构:顺序存储和链式存储。 + +实际上,栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作**顺序栈**,用链表实现的栈,我们叫作**链式栈**。 + +## 二、栈的顺序存储与实现 + +顺序栈是使用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放栈中的数据元素。由于栈是一种特殊的线性表,因此在线性表的顺序存储结构的基础上,选择线性表的一端作为栈顶即可。那么根据数组操作的特性,选择数组下标大的一端,即线性表顺序存储的表尾来作为栈顶,此时入栈、出栈操作可以 $O(1)$ 时间完成。 + +由于栈的操作都是在栈顶完成,因此在顺序栈的实现中需要附设一个指针 top 来动态地指示栈顶元素在数组中的位置。通常 top 可以用栈顶元素所在的数组下标来表示,`top=-1` 时表示空栈。 + +栈在使用过程中所需的最大空间难以估计,所以,一般构造栈的时候不应设定最大容量。一种合理的做法和线性表类似,先为栈分配一个基本容量,然后在实际的使用过程中,当栈的空间不够用时再倍增存储空间。 + +```java +public class MyArrayStack implements MyStack { + + private final int capacity = 2; //默认容量 + private Object[] arrs; //数据元素数组 + private int top; //栈顶指针 + + MyArrayStack(){ + top = -1; + arrs = new Object[capacity]; + } + + public int getSize() { + return top + 1; + } + + public boolean isEmpty() { + return top < 0; + } + + public void push(Object e) { + if(getSize() >= arrs.length){ + expandSapce(); //扩容 + } + arrs[++top]=e; + } + + private void expandSapce() { + Object[] a = new Object[arrs.length * 2]; + for (int i = 0; i < arrs.length; i++) { + a[i] = arrs[i]; + } + arrs = a; + } + + public Object pop() { + if(getSize()<1){ + throw new RuntimeException("栈为空"); + } + Object obj = arrs[top]; + arrs[top--] = null; + return obj; + } + + public Object peek() { + if(getSize()<1){ + throw new RuntimeException("栈为空"); + } + return arrs[top]; + } +} +``` + +以上基于数据实现的栈代码并不难理解。由于有 top 指针的存在,所以`size()`、`isEmpty()`方法均可在 $O(1) $ 时间内完成。`push()`、`pop()`和`peek()`方法,除了需要`ensureCapacity()`外,都执行常数基本操作,因此它们的运行时间也是 $O(1)$ + + + +## 三、栈的链式存储与实现 + +栈的链式存储即采用链表实现栈。当采用单链表存储线性表后,根据单链表的操作特性选择单链表的头部作为栈顶,此时,入栈和出栈等操作可以在 $O(1)$ 时间内完成。 + +由于栈的操作只在线性表的一端进行,在这里使用带头结点的单链表或不带头结点的单链表都可以。使用带头结点的单链表时,结点的插入和删除都在头结点之后进行;使用不带头结点的单链表时,结点的插入和删除都在链表的首结点上进行。 + +下面以不带头结点的单链表为例实现栈,如下示意图所示: + + + +在上图中,top 为栈顶结点的引用,始终指向当前栈顶元素所在的结点。若 top 为null,则表示空栈。入栈操作是在 top 所指结点之前插入新的结点,使新结点的 next 域指向 top,top 前移即可;出栈则直接让 top 后移即可。 + +```java +public class MyLinkedStack implements MyStack { + + class Node { + private Object element; + private Node next; + + public Node() { + this(null, null); + } + + public Node(Object ele, Node next) { + this.element = ele; + this.next = next; + } + + public Node getNext() { + return next; + } + + public void setNext(Node next) { + this.next = next; + } + + public Object getData() { + return element; + } + + public void setData(Object obj) { + element = obj; + } + } + + private Node top; + private int size; + + public MyLinkedStack() { + top = null; + size = 0; + } + + public int getSize() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public void push(Object e) { + Node node = new Node(e, top); + top = node; + size++; + } + + public Object pop() { + if (size < 1) { + throw new RuntimeException("堆栈为空"); + } + Object obj = top.getData(); + top = top.getNext(); + size--; + return obj; + } + + public Object peek() { + if (size < 1) { + throw new RuntimeException("堆栈为空"); + } + return top.getData(); + } +} +``` + +上述 `MyLinkedStack` 类中有两个成员变量,其中 `top` 表示首结点,也就是栈顶元素所在的结点;`size` 指示栈的大小,即栈中数据元素的个数。不难理解,所有的操作均可以在 $O(1)$ 时间内完成。 + + + +## 四、JDK 中的栈实现 Stack + +Java 工具包中的 Stack 是继承于 Vector(矢量队列)的,由于 Vector 是通过数组实现的,这就意味着,Stack 也是通过数组实现的,而非链表。当然,我们也可以将 LinkedList 当作栈来使用。 + +### Stack的继承关系 + +```java +java.lang.Object + java.util.AbstractCollection + java.util.AbstractList + java.util.Vector + java.util.Stack + +public class Stack extends Vector {} +``` + + + + + +## 五、栈应用 + +栈有一个很重要的应用,在程序设计语言里实现了递归。 + +### [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) + +>给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断字符串是否有效。 +> +>有效字符串需满足: +> +>1. 左括号必须用相同类型的右括号闭合。 +>2. 左括号必须以正确的顺序闭合。 +> +>注意空字符串可被认为是有效字符串。 +> +>``` +>输入: "{[]}" +>输出: true +>输入: "([)]" +>输出: false +>``` + +**思路** + +左括号进栈,右括号匹配出栈 + +- 栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号时,把右括号入栈,遇到右括号时将对应栈顶元素与其对比并出栈,相同的话说明匹配,继续遍历,遍历完所有括号后 `stack` 仍然为空,说明是有效的。 + +```java + public boolean isValid(String s) { + if(s.isEmpty()) { + return true; + } + Stack stack=new Stack(); + //字符串转为字符串数组 遍历 + for(char c:s.toCharArray()){ + if(c=='(') { + stack.push(')'); + } else if(c=='{') { + stack.push('}'); + } else if(c=='[') { + stack.push(']'); + } else if(stack.empty()||c!=stack.pop()) { + return false; + } + } + return stack.empty(); + } +``` + + + +### [739. 每日温度](https://leetcode.cn/problems/daily-temperatures/) + +>给定一个整数数组 `temperatures` ,表示每天的温度,返回一个数组 `answer` ,其中 `answer[i]` 是指对于第 `i`天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 `0` 来代替。 +> +>``` +>输入: temperatures = [73,74,75,71,69,72,76,73] +>输出: [1,1,4,2,1,1,0,0] +>``` + +**思路**: + +维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。 + +正向遍历温度列表。 + +1. 栈为空,先入栈 +2. 如果栈内有元素,用栈顶元素对应的温度和当前温度比较,temperatures[i] > temperatures[pre] 的话,就把栈顶元素移除,把 pre 对应的天数设置为 i-pre,重复操作区比较,知道栈为空或者栈顶元素对应的温度小于当前问题,把当前温度索引入栈 + +```java + public int[] dailyTemperatures_stack(int[] temperatures) { + int length = temperatures.length; + int[] result = new int[length]; + Stack stack = new Stack<>(); + for (int i = 0; i < length; i++) { + while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { + int pre = stack.pop(); + result[pre] = i - pre; + } + stack.add(i); + } + return result; + } +``` + + + +### [150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) + +> 给你一个字符串数组 `tokens` ,表示一个根据 [逆波兰表示法](https://baike.baidu.com/item/逆波兰式/128437) 表示的算术表达式。 +> +> 请你计算该表达式。返回一个表示表达式值的整数。 +> +> **注意:** +> +> - 有效的算符为 `'+'`、`'-'`、`'*'` 和 `'/'` 。 +> - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。 +> - 两个整数之间的除法总是 **向零截断** 。 +> - 表达式中不含除零运算。 +> - 输入是一个根据逆波兰表示法表示的算术表达式。 +> - 答案及所有中间计算结果可以用 **32 位** 整数表示。 +> +> ``` +> 输入:tokens = ["4","13","5","/","+"] +> 输出:6 +> 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 +> ``` + +> **逆波兰表达式:** +> +> 逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。 +> +> - 平常使用的算式则是一种中缀表达式,如 `( 1 + 2 ) * ( 3 + 4 )` 。 +> - 该算式的逆波兰表达式写法为 `( ( 1 2 + ) ( 3 4 + ) * )` 。 +> +> 逆波兰表达式主要有以下两个优点: +> +> - 去掉括号后表达式无歧义,上式即便写成 `1 2 + 3 4 + * `也可以依据次序计算出正确结果。 +> - 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中 + +**思路**:逆波兰表达式严格遵循「从左到右」的运算。 + +计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作: + +- 如果遇到操作数,则将操作数入栈; + +- 如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。 + +整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。 + +```java + public int evalRPN(String[] tokens) { + List charts = Arrays.asList("+", "-", "*", "/"); + Stack stack = new Stack<>(); + for (String s : tokens) { + if (!charts.contains(s)) { + stack.push(Integer.parseInt(s)); + } else { + int num2 = stack.pop(); + int num1 = stack.pop(); + switch (s) { + case "+": + stack.push(num1 + num2); + break; + case "-": + stack.push(num1 - num2); + break; + case "*": + stack.push(num1 * num2); + break; + case "/": + stack.push(num1 / num2); + break; + } + } + } + return stack.peek(); + } +} +``` + + + +### [155. 最小栈](https://leetcode.cn/problems/min-stack/) + +> 设计一个支持 `push` ,`pop` ,`top` 操作,并能在常数时间内检索到最小元素的栈。 +> +> 实现 `MinStack` 类: +> +> - `MinStack()` 初始化堆栈对象。 +> - `void push(int val)` 将元素val推入堆栈。 +> - `void pop()` 删除堆栈顶部的元素。 +> - `int top()` 获取堆栈顶部的元素。 +> - `int getMin()` 获取堆栈中的最小元素。 + +**思路**: 添加一个辅助栈,这个栈同时保存的是每个数字 `x` 进栈的时候的**值 与 插入该值后的栈内最小值**。 + +```java +import java.util.Stack; + +public class MinStack { + + // 数据栈 + private Deque data; + // 辅助栈 + private Deque helper; + + public MinStack() { + data = new LinkedList(); + helper = new LinkedList(); + helper.push(Integer.MAX_VALUE); + } + + public void push(int x) { + data.push(x); + helper.push(Math.min(helper.peek(), x)); + } + + public void pop() { + data.pop(); + helper.pop(); + } + + public int top() { + return data.peek(); + } + + public int getMin() { + return helper.peek(); + } + +} +``` + + + +### [227. 基本计算器 II](https://leetcode.cn/problems/basic-calculator-ii/) + +> 给你一个字符串表达式 `s` ,请你实现一个基本计算器来计算并返回它的值。 +> +> 整数除法仅保留整数部分。 +> +> 你可以假设给定的表达式总是有效的。所有中间结果将在 `[-231, 231 - 1]` 的范围内。 +> +> **注意:**不允许使用任何将字符串作为数学表达式计算的内置函数,比如 `eval()` 。 +> +> ``` +> 输入:s = "3+2*2" +> 输出:7 +> ``` + +**思路**:和逆波兰表达式有点像 + +- 加号:将数字压入栈; +- 减号:将数字的相反数压入栈; +- 乘除号:计算数字与栈顶元素,并将栈顶元素替换为计算结果。 + +> 这个得先知道怎么把字符串形式的正整数转成 int +> +> ```java +> String s = "458"; +> +> int n = 0; +> for (int i = 0; i < s.length(); i++) { +> char c = s.charAt(i); +> n = 10 * n + (c - '0'); +> } +> // n 现在就等于 458 +> ``` +> +> 这个还是很简单的吧,老套路了。但是即便这么简单,依然有坑:**`(c - '0')` 的这个括号不能省略,否则可能造成整型溢出**。 +> +> 因为变量 `c` 是一个 ASCII 码,如果不加括号就会先加后减,想象一下 `s` 如果接近 INT_MAX,就会溢出。所以用括号保证先减后加才行。 + +```java + public static int calculate(String s) { + Stack stack = new Stack<>(); + int length = s.length(); + int num = 0; + char operator = '+'; + for (int i = 0; i < length; i++) { + if (Character.isDigit(s.charAt(i))) { + // 转为 int + num = num * 10 + (s.charAt(i) - '0'); + } + // 计算符 (排除空格) + if (!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ' || i == length - 1) { + // switch (s.charAt(i)){ 这里是要和操作符比对,考虑第一次 + switch (operator) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + stack.push(stack.pop() * num); + break; + default: + stack.push(stack.pop() / num); + } + operator = s.charAt(i); + num = 0; + } + } + int result = 0; + while (!stack.isEmpty()) { + result += stack.pop(); + } + return result; + } +``` + diff --git a/docs/data-structure-algorithms/sort.md b/docs/data-structure-algorithms/sort.md deleted file mode 100644 index ac0daccaa3..0000000000 --- a/docs/data-structure-algorithms/sort.md +++ /dev/null @@ -1,458 +0,0 @@ -# 排序 - -排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:**插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序**等。用一张图概括: - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/sort.png) - -**关于时间复杂度**: - -1. 平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。 -2. 线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序; -3. O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序 -4. 线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。 - -**关于稳定性**: - -稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。 - -不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。 - -**名词解释**: - -**n**:数据规模 - -**k**:“桶”的个数 - -**In-place**:占用常数内存,不占用额外内存 - -**Out-place**:占用额外内存 - -**稳定性**:排序后 2 个相等键值的顺序和排序之前它们的顺序相同 - - - -十种常见排序算法可以分为两大类: - -**非线性时间比较类排序**:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。 - -**线性时间非比较类排序**:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。 - - - -## 冒泡排序 - -冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 - -作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。 - -### 1. 算法步骤 - -1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 -2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 -3. 针对所有的元素重复以上的步骤,除了最后一个。 -4. 重复步骤1~3,直到排序完成。 - -### 2. 动图演示 - - - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/bubbleSort.gif) - -### 3. 什么时候最快 - -当输入的数据已经是正序时(都已经是正序了,我还要你冒泡排序有何用啊)。 - -### 4. 什么时候最慢 - -当输入的数据是反序时(写一个 for 循环反序输出数据不就行了,干嘛要用你冒泡排序呢,我是闲的吗)。 - -```java -public class BubbleSort { - - public static void main(String[] args) { - int[] arrs = {1, 3, 4, 2, 6, 5}; - - for (int i = 0; i < arrs.length; i++) { - for (int j = 0; j < arrs.length - 1 - i; j++) { - if (arrs[j] > arrs[j + 1]) { - int tmp = arrs[j]; - arrs[j] = arrs[j + 1]; - arrs[j + 1] = tmp; - } - } - } - - for (int arr : arrs) { - System.out.print(arr + " "); - } - } -} -``` - -嵌套循环,应该立马就可以得出这个算法的时间复杂度为 O(n²)。 - - - -## 选择排序 - -选择排序的思路是这样的:首先,找到数组中最小的元素,拎出来,将它和数组的第一个元素交换位置,第二步,在剩下的元素中继续寻找最小的元素,拎出来,和数组的第二个元素交换位置,如此循环,直到整个数组排序完成。 - -选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。 - -### 1. 算法步骤 - -1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 -2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 -3. 重复第二步,直到所有元素均排序完毕。 - -### 2. 动图演示 - - - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/selectionSort.gif) - -```java -public class SelectionSort { - - public static void main(String[] args) { - int[] arrs = {5, 2, 4, 6, 1, 3}; - - for (int i = 0; i < arrs.length; i++) { - //最小元素下标 - int min = i; - for (int j = i +1; j < arrs.length; j++) { - if (arrs[j] < arrs[min]) { - min = j; - } - } - //交换位置 - int temp = arrs[i]; - arrs[i] = arrs[min]; - arrs[min] = temp; - } - for (int arr : arrs) { - System.out.println(arr); - } - } -} -``` - - - -## 插入排序 - -插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 - -插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。 - -### 1. 算法步骤 - -1. 从第一个元素开始,该元素可以认为已经被排序; -2. 取出下一个元素,在已经排序的元素序列中从后向前扫描; -3. 如果该元素(已排序)大于新元素,将该元素移到下一位置; -4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置; -5. 将新元素插入到该位置后; -6. 重复步骤2~5。 - -### 2. 动图演示 - - - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/insertionSort.gif) - -```java -public static void main(String[] args) { - int[] arr = {5, 2, 4, 6, 1, 3}; - // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的 - for (int i = 1; i < arr.length; i++) { - - // 记录要插入的数据 - int tmp = arr[i]; - - // 从已经排序的序列最右边的开始比较,找到比其小的数 - int j = i; - while (j > 0 && tmp < arr[j - 1]) { - arr[j] = arr[j - 1]; - j--; - } - - // 存在比其小的数,插入 - if (j != i) { - arr[j] = tmp; - } - } - - for (int i : arr) { - System.out.println(i); - } -} -} -``` - - - -## 快速排序 - -这篇很好:https://www.cxyxiaowu.com/5262.html - -快速排序的核心思想也是分治法,分而治之。它的实现方式是每次从序列中选出一个基准值,其他数依次和基准值做比较,比基准值大的放右边,比基准值小的放左边,然后再对左边和右边的两组数分别选出一个基准值,进行同样的比较移动,重复步骤,直到最后都变成单个元素,整个数组就成了有序的序列。 - -> 快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。 - -### 1. 算法步骤 - -1. 从数列中挑出一个元素,称为 “基准”(pivot); -2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; -3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; - -递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。 - -### 2. 动图演示 - - - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/quickSort.gif) - -### 单边扫描 - -快速排序的关键之处在于切分,切分的同时要进行比较和移动,这里介绍一种叫做单边扫描的做法。 - -我们随意抽取一个数作为基准值,同时设定一个标记 mark 代表左边序列最右侧的下标位置,当然初始为 0 ,接下来遍历数组,如果元素大于基准值,无操作,继续遍历,如果元素小于基准值,则把 mark + 1 ,再将 mark 所在位置的元素和遍历到的元素交换位置,mark 这个位置存储的是比基准值小的数据,当遍历结束后,将基准值与 mark 所在元素交换位置即可。 - -```java -public static void sort(int[] arr) { - sort(arr, 0, arr.length - 1); -} - -private static void sort(int[] arr, int startIndex, int endIndex) { - if (endIndex <= startIndex) { - return; - } - //切分 - int pivotIndex = partitionV2(arr, startIndex, endIndex); - sort(arr, startIndex, pivotIndex-1); - sort(arr, pivotIndex+1, endIndex); -} - -private static int partition(int[] arr, int startIndex, int endIndex) { - int pivot = arr[startIndex];//取基准值 - int mark = startIndex;//Mark初始化为起始下标 - - for(int i=startIndex+1; i<=endIndex; i++){ - if(arr[i]= right) { - break; - } - - //交换左右数据 - int temp = arr[left]; - arr[left] = arr[right]; - arr[right] = temp; - } - - //将基准值插入序列 - int temp = arr[startIndex]; - arr[startIndex] = arr[right]; - arr[right] = temp; - return right; -} -``` - - - - - -## 希尔排序 - -希尔排序这个名字,来源于它的发明者希尔,也称作“缩小增量排序”,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 - -希尔排序是基于插入排序的以下两点性质而提出改进方法的: - -- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率; -- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位; - -希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。 - -### 1. 算法步骤 - -1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1; -2. 按增量序列个数 k,对序列进行 k 趟排序; -3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。 - -### 2. 动图演示 - - - - - -## 归并排序 - -归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 - -作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法: - -- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法); -- 自下而上的迭代; - -在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为: - -> However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle. -> -> 然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。 - -说实话,我不太理解这句话。意思是 JavaScript 编译器内存太小,递归太深容易造成内存溢出吗?还望有大神能够指教。 - -和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。 - -### 2. 算法步骤 - -1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; -2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置; -3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; -4. 重复步骤 3 直到某一指针达到序列尾; -5. 将另一序列剩下的所有元素直接复制到合并序列尾。 - -### 3. 动图演示 - - - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/mergeSort.gif) - - - - - - - -## 堆排序 - -堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法: - -1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列; -2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列; - -堆排序的平均时间复杂度为 Ο(nlogn)。 - -### 1. 算法步骤 - -1. 将待排序序列构建成一个堆 H[0……n-1],根据(升序降序需求)选择大顶堆或小顶堆; -2. 把堆首(最大值)和堆尾互换; -3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置; -4. 重复步骤 2,直到堆的尺寸为 1。 - -### 2. 动图演示 - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/heapSort.gif) - - - - - -## 计数排序 - -计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。 - -### 1. 动图演示 - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/countingSort.gif) - - - - - -## 桶排序 - -桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点: - -1. 在额外空间充足的情况下,尽量增大桶的数量 -2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中 - -同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。 - -### 1. 什么时候最快 - -当输入的数据可以均匀的分配到每一个桶中。 - -### 2. 什么时候最慢 - -当输入的数据被分配到了同一个桶中。 - - - -## 基数排序 - -基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。 - -### 1. 基数排序 vs 计数排序 vs 桶排序 - -基数排序有两种方法: - -这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异案例看大家发的: - -- 基数排序:根据键值的每位数字来分配桶; -- 计数排序:每个桶只存储单一键值; -- 桶排序:每个桶存储一定范围的数值; - -### 2. LSD 基数排序动图演示 - -[](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/res/radixSort.gif) - diff --git a/docs/data-structure-algorithms/soultion/.DS_Store b/docs/data-structure-algorithms/soultion/.DS_Store new file mode 100644 index 0000000000..57bc0bb425 Binary files /dev/null and b/docs/data-structure-algorithms/soultion/.DS_Store differ diff --git a/docs/data-structure-algorithms/soultion/Array-Solution.md b/docs/data-structure-algorithms/soultion/Array-Solution.md new file mode 100755 index 0000000000..1462dee476 --- /dev/null +++ b/docs/data-structure-algorithms/soultion/Array-Solution.md @@ -0,0 +1,1441 @@ +--- +title: 数组-热题 +date: 2025-01-08 +tags: + - Array + - algorithms +categories: leetcode +--- + + + +> **导读**:数组是最基础的数据结构,也是面试中最高频的考点。数组题目看似简单,但往往需要巧妙的算法技巧才能高效解决。掌握双指针、滑动窗口、前缀和等核心技巧是解决数组问题的关键。 +> +> **关键词**:双指针、滑动窗口、前缀和、哈希表、排序 + + +### 📋 分类索引 + +1. **🔥 双指针技巧类**:[三数之和](#_15-三数之和)、[盛最多水的容器](#_11-盛最多水的容器)、[移动零](#_283-移动零)、[颜色分类](#_75-颜色分类)、[接雨水](#_42-接雨水) +2. **🔍 哈希表优化类**:[两数之和](#_1-两数之和)、[字母异位词分组](#_49-字母异位词分组)、[最长连续序列](#_128-最长连续序列)、[存在重复元素](#_217-存在重复元素) +3. **🪟 滑动窗口类**:[无重复字符的最长子串](#_3-无重复字符的最长子串)、最小覆盖子串 +4. **📊 前缀和技巧类**:[和为K的子数组](#_560-和为-k-的子数组)、[最大子数组和](#_53-最大子数组和)、[除自身以外数组的乘积](#_238-除自身以外数组的乘积) +5. **🔄 排序与搜索类**:[合并区间](#_56-合并区间)、[数组中的第K个最大元素](#_215-数组中的第k个最大元素)、[寻找重复数](#_287-寻找重复数)、[下一个排列](#_31-下一个排列)、[搜索旋转排序数组](#_33-搜索旋转排序数组) +6. **⚡ 原地算法类**:[合并两个有序数组](#_88-合并两个有序数组)、[旋转数组](#_189-旋转数组) +7. **🧮 数学技巧类**:[只出现一次的数字](#_136-只出现一次的数字)、[多数元素](#_169-多数元素)、[找到所有数组中消失的数字](#_448-找到所有数组中消失的数字)、[缺失的第一个正数](#_41-缺失的第一个正数) +8. **🎯 综合应用类**:[买卖股票的最佳时机](#_121-买卖股票的最佳时机)、[乘积最大子数组](#_152-乘积最大子数组)、[子集](#_78-子集)、[跳跃游戏](#_55-跳跃游戏)、[跳跃游戏 II](#_45-跳跃游戏-ii)、[最长公共前缀](#_14-最长公共前缀)、[螺旋矩阵](#_54-螺旋矩阵) + +### 🎯 核心考点概览 + +- **双指针技巧**:快慢指针、左右指针、滑动窗口 +- **哈希表优化**:空间换时间,$O(1)$查找 +- **前缀和技巧**:区间求和,子数组问题 +- **排序与搜索**:二分查找、快速选择 +- **原地算法**:$O(1)$空间复杂度优化 + +### 📝 解题万能模板 + +#### 基础模板 + +```java +// 双指针模板 +int left = 0, right = nums.length - 1; +while (left < right) { + if (condition) { + left++; + } else { + right--; + } +} + +// 滑动窗口模板 +int left = 0, right = 0; +while (right < nums.length) { + // 扩展窗口 + window.add(nums[right]); + right++; + + while (window needs shrink) { + // 收缩窗口 + window.remove(nums[left]); + left++; + } +} + +// 前缀和模板 +int[] prefixSum = new int[nums.length + 1]; +for (int i = 0; i < nums.length; i++) { + prefixSum[i + 1] = prefixSum[i] + nums[i]; +} +``` + +#### 边界检查清单 + +- ✅ 空数组:`if (nums == null || nums.length == 0) return ...` +- ✅ 单元素:`if (nums.length == 1) return ...` +- ✅ 数组越界:确保索引在有效范围内 +- ✅ 整数溢出:使用long或检查边界 + +#### 💡 记忆口诀(朗朗上口版) + +- **双指针**:"左右夹逼找目标,快慢追踪解环题" +- **滑动窗口**:"右扩左缩维护窗,条件满足就更新" +- **前缀和**:"累积求和建数组,区间相减得答案" +- **哈希表**:"空间换时间来优化,常数查找是关键" +- **二分查找**:"有序数组用二分,对数时间最高效" +- **原地算法**:"双指针巧妙来交换,空间复杂度为一" + +**最重要的一个技巧就是,你得行动,写起来** + + + +## 一、双指针技巧类(核心中的核心)🔥 + +**💡 核心思想** + +- **左右指针**:从两端向中间逼近,解决对撞类问题 +- **快慢指针**:不同速度遍历,解决环形、去重问题 +- **滑动窗口**:双指针维护窗口,解决子数组问题 + +**🎯 必掌握模板** + +```java +// 左右指针模板 +int left = 0, right = nums.length - 1; +while (left < right) { + if (sum < target) { + left++; + } else if (sum > target) { + right--; + } else { + // 找到答案 + return ...; + } +} +``` + +### [15. 三数之和](https://leetcode-cn.com/problems/3sum/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:双指针经典** + +> 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 + +**💡 核心思路**:排序后双指针 + +```java +public List> threeSum(int[] nums) { + //存放结果list + List> result = new ArrayList<>(); + int length = nums.length; + //特例判断 + if (length < 3) { + return result; + } + Arrays.sort(nums); + for (int i = 0; i < length; i++) { + //排序后的第一个数字就大于0,就说明没有符合要求的结果 + if (nums[i] > 0) break; + + //去重,当起始的值等于前一个元素,那么得到的结果将会和前一次相同,不能是 nums[i] == nums[i +1 ],会造成遗漏 + if (i > 0 && nums[i] == nums[i - 1]) continue; + //左右指针 + int l = i + 1; + int r = length - 1; + while (l < r) { + int sum = nums[i] + nums[l] + nums[r]; + if (sum == 0) { + result.add(Arrays.asList(nums[i], nums[l], nums[r])); + //去重(相同数字的话就移动指针) + //在将左指针和右指针移动的时候,先对左右指针的值,进行判断,以防[0,0,0]这样的造成数组越界 + //不要用成 if 判断,只跳过 1 条,还会有重复的 + while (l< r && nums[l] == nums[l + 1]) l++; + while (l< r && nums[r] == nums[r - 1]) r--; + //移动指针 + l++; + r--; + } else if (sum < 0) l++; + else if (sum > 0) r--; + } + } + return result; +} +``` + +### [11. 盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:双指针应用** + +> 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 + +**💡 核心思路**:双指针 + +水量 = 两个指针指向的数字中较小值∗指针之间的距离 + +```java +public int maxArea(int[] height){ + int l = 0; + int r = height.length - 1; + int ans = 0; + while(l < r){ + int area = Math.min(height[l], height[r]) * (r - l); + ans = Math.max(ans,area); + if(height[l] < height[r]){ + l ++; + }else { + r --; + } + } + return ans; +} +``` + +### [283. 移动零](https://leetcode-cn.com/problems/move-zeroes/) + +**🎯 考察频率:高 | 难度:简单 | 重要性:双指针基础** + +> 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 + +**💡 核心思路**:双指针法,当快指针指向非零元素时,将其与慢指针指向的元素交换,然后慢指针向前移动一位。 + +```java +public void moveZero(int[] nums) { + int j = 0; //慢指针 + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + nums[j++] = nums[i]; // 直接将非零元素放到j指针位置,并移动j指针 + } + } + // 将j指针之后的所有元素置为零 + for (int i = j; i < nums.length; i++) { + nums[i] = 0; + } +} +``` + +### [75. 颜色分类](https://leetcode.cn/problems/sort-colors/) + +**🎯 考察频率:中等 | 难度:中等 | 重要性:三指针技巧** + +> 给定一个包含红色(0)、白色(1)、蓝色(2)的数组 `nums`,要求你对它们进行排序,使得相同颜色相邻,并按照红、白、蓝的顺序排列。 + +**💡 核心思路**:双指针(荷兰国旗问题)。用 3 个指针遍历数组。 + +这题被称为 **荷兰国旗问题 (Dutch National Flag Problem)**。 + 用 **三个指针** 解决: + +- `low`:下一个 0 应该放置的位置 +- `high`:下一个 2 应该放置的位置 +- `i`:当前遍历位置 + +规则: + +1. 如果 `nums[i] == 0` → 和 `nums[low]` 交换,`low++,i++` +2. 如果 `nums[i] == 1` → 正常情况,`i++` +3. 如果 `nums[i] == 2` → 和 `nums[high]` 交换,`high--`,**但 i 不++**,因为换过来的数还要检查 + +这样一次遍历就能完成排序。 + +```java +public void sortColors(int[] nums) { + int left = 0, right = nums.length - 1, curr = 0; + while (curr <= right) { + if (nums[curr] == 0) { + swap(nums, curr++, left++); + } else if (nums[curr] == 2) { + swap(nums, curr, right--); + } else { + curr++; + } + } +} + +private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; +} +``` + +### [42. 接雨水](https://leetcode.cn/problems/trapping-rain-water/) + +**🎯 考察频率:极高 | 难度:困难 | 重要性:双指针+贪心** + +> 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能够接多少雨水。 + +**💡 核心思路**:双指针法 + +```java +public int trap(int[] height) { + int left = 0, right = height.length - 1; + int leftMax = 0, rightMax = 0; + int result = 0; + + while (left < right) { + if (height[left] < height[right]) { + if (height[left] >= leftMax) { + leftMax = height[left]; + } else { + result += leftMax - height[left]; + } + left++; + } else { + if (height[right] >= rightMax) { + rightMax = height[right]; + } else { + result += rightMax - height[right]; + } + right--; + } + } + return result; +} +``` + + + +--- + +## 二、哈希表优化类(空间换时间)🔍 + +**💡 核心思想** + +- **O(1)查找**:利用哈希表常数时间查找特性 +- **空间换时间**:用额外空间降低时间复杂度 +- **去重与计数**:处理重复元素和频次统计 + +**🎯 必掌握模板** + +```java +// 哈希表查找模板 +Map map = new HashMap<>(); +for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) { + // 找到答案 + return new int[]{map.get(target - nums[i]), i}; + } + map.put(nums[i], i); +} +``` + +### [1. 两数之和](https://leetcode-cn.com/problems/two-sum/) + +**🎯 考察频率:极高 | 难度:简单 | 重要性:哈希表经典** + +> 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 +> +> 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 +> +> ```text +> 输入:nums = [2,7,11,15], target = 9 +> 输出:[0,1] +> 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 +> ``` + +**思路**:哈希表 + +```java +public static int[] twoSum(int[] nums,int target){ + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + int temp = target - nums[i]; + if(map.containsKey(temp)){ + return new int[]{map.get(temp),i}; + } + map.put(nums[i],i); + } + return new int[]{-1,-1}; +} +``` + +**⏱️ 复杂度分析**: + +- **时间复杂度**:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。 +- **空间复杂度**:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。 + +### [49. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:分组统计经典** + +> 给你一个字符串数组,请你将 **字母异位词** 组合在一起。可以按任意顺序返回结果列表。 +> +> **字母异位词** 是由重新排列源单词的所有字母得到的一个新单词。 +> +> ``` +> 输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] +> 输出: [["bat"],["nat","tan"],["ate","eat","tea"]] +> ``` + +**思路**:哈希表。键是字符串中的字符排序后的字符串,值是具有相同键的原始字符串列表 + +```java +public List> groupAnagrams(String[] strs) { + Map> map = new HashMap<>(); + + for (String str : strs) { + // 将字符串转换为字符数组并排序 + char[] chars = str.toCharArray(); + Arrays.sort(chars); + + // 将排序后的字符数组转换回字符串,作为哈希表的键 + String key = new String(chars); + + // 如果键不存在于哈希表中,则创建新的列表并添加到哈希表中 + if (!map.containsKey(key)) { + map.put(key, new ArrayList<>()); + } + + // 将原始字符串添加到对应的列表中 + map.get(key).add(str); + } + + // 返回哈希表中所有值的列表 + return new ArrayList<>(map.values()); + } +``` + +- 时间复杂度:$O(nklogk)$,其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要遍历 n 个字符串,对于每个字符串,需要 O(klogk) 的时间进行排序以及 O(1) 的时间更新哈希表,因此总时间复杂度是 O(nklogk)。 + +- 空间复杂度:$O(nk)$,其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要用哈希表存储全部字符串 + +### [217. 存在重复元素](https://leetcode-cn.com/problems/contains-duplicate/) + +**🎯 考察频率:简单 | 难度:简单 | 重要性:哈希表基础** + +> 给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回 `true` 。如果数组中每个元素都不相同,则返回 `false` 。 + +**💡 核心思路**:哈希表,和两数之和的思路一样 | 或者排序 + +```java +public boolean containsDuplicate(int[] nums){ + Map map = new HashMap<>(); + for(int i=0;i 给定一个未排序的整数数组 `nums` ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 +> +> 请你设计并实现时间复杂度为 `O(n)` 的算法解决此问题。 +> +> ``` +> 输入:nums = [100,4,200,1,3,2] +> 输出:4 +> 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。 +> ``` + +**思路**:哈希表,**每个数都判断一次这个数是不是连续序列的开头那个数** + +```java +public int longestConsecutive(int[] nums) { + Set numSet = new HashSet<>(); + int longestStreak = 0; + + // 将数组中的所有数字添加到哈希表中 + for (int num : nums) { + numSet.add(num); + } + + // 遍历哈希表中的每个数字 + for (int num : numSet) { + // 如果 num-1 不在哈希表中,说明 num 是一个连续序列的起点 + if (!numSet.contains(num - 1)) { + int currentNum = num; + int currentStreak = 1; + + // 检查 num+1, num+2, ... 是否在哈希表中,并计算连续序列的长度 + while (numSet.contains(currentNum + 1)) { + currentNum++; + currentStreak++; + } + + // 更新最长连续序列的长度 + longestStreak = Math.max(longestStreak, currentStreak); + } + } + + return longestStreak; +} +``` + +### [3. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:滑动窗口经典** + +> 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。 + +**💡 核心思路**:滑动窗口 + 哈希表 + +```java +public int lengthOfLongestSubstring(String s) { + Set window = new HashSet<>(); + int left = 0, maxLen = 0; + + for (int right = 0; right < s.length(); right++) { + char c = s.charAt(right); + while (window.contains(c)) { + window.remove(s.charAt(left)); + left++; + } + window.add(c); + maxLen = Math.max(maxLen, right - left + 1); + } + return maxLen; +} +``` + +**⏱️ 复杂度分析**: +- **时间复杂度**:O(n) - 每个字符最多被访问两次(一次by right,一次by left) +- **空间复杂度**:O(min(m,n)) - m是字符集大小,n是字符串长度 + + +--- + +## 三、前缀和技巧类(区间计算利器)📊 + +**💡 核心思想** + +- **累积求和**:预处理前缀和数组,快速计算区间和 +- **差值技巧**:利用前缀和差值解决子数组问题 +- **哈希优化**:结合哈希表优化前缀和查找 + +**🎯 必掌握模板** + +```java +// 前缀和模板 +int[] prefixSum = new int[nums.length + 1]; +for (int i = 0; i < nums.length; i++) { + prefixSum[i + 1] = prefixSum[i] + nums[i]; +} +// 区间[i,j]的和 = prefixSum[j+1] - prefixSum[i] + +// 前缀和+哈希表模板 +Map map = new HashMap<>(); +map.put(0, 1); // 前缀和为0出现1次 +int prefixSum = 0; +for (int num : nums) { + prefixSum += num; + if (map.containsKey(prefixSum - k)) { + count += map.get(prefixSum - k); + } + map.put(prefixSum, map.getOrDefault(prefixSum, 0) + 1); +} +``` + +### [560. 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:前缀和+哈希表经典** + +> 给你一个整数数组 `nums` 和一个整数 `k` ,请你统计并返回 *该数组中和为 `k` 的子数组的个数* 。 +> +> 子数组是数组中元素的连续非空序列。 +> +> ``` +> 输入:nums = [1,2,3], k = 3 +> 输出:2 +> ``` + +思路:**前缀和 + 哈希表** + +1. **遍历数组**,逐步计算前缀和(表示从数组起始位置到当前位置的所有元素之和) `prefixSum`。 +2. 检查哈希表中是否存在 `prefixSum - k`: + - 若存在,累加其出现次数到结果 `count`。 +3. **更新哈希表**:将当前前缀和存入哈希表,若已存在则次数加 1。 + +```java +public int subarraySum(int[] nums, int k) { + int count = 0; + int prefixSum = 0; + Map sumMap = new HashMap<>(); + sumMap.put(0, 1); // 初始化前缀和为0的计数为1 + + for (int num : nums) { + prefixSum += num; + // 检查是否存在前缀和为 prefixSum - k 的键 + if (sumMap.containsKey(prefixSum - k)) { + count += sumMap.get(prefixSum - k); + } + // 更新当前前缀和的次数 + sumMap.put(prefixSum, sumMap.getOrDefault(prefixSum, 0) + 1); + } + return count; +} +``` + +- **时间复杂度**:O(n),每个元素遍历一次。 +- **空间复杂度**:O(n),哈希表最多存储 n 个不同的前缀和。 + +### [238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:前缀乘积** + +> 给你一个整数数组 nums,返回数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 + +**💡 核心思路**:左右乘积数组 + +```java +public int[] productExceptSelf(int[] nums) { + int n = nums.length; + int[] answer = new int[n]; + + // 计算左侧所有元素的乘积 + answer[0] = 1; + for (int i = 1; i < n; i++) { + answer[i] = nums[i - 1] * answer[i - 1]; + } + + // 计算右侧所有元素的乘积并更新答案 + int rightProduct = 1; + for (int i = n - 1; i >= 0; i--) { + answer[i] = answer[i] * rightProduct; + rightProduct *= nums[i]; + } + + return answer; +} +``` + + +--- + + + +## 四、排序与搜索类(有序数据处理)🔄 + +**💡 核心思想** + +- **预排序**:先排序再处理,降低问题复杂度 +- **二分查找**:在有序数组中快速定位 +- **快速选择**:基于快排的选择算法 +- **区间处理**:排序后处理重叠区间问题 + +**🎯 必掌握模板** + +```java +// 排序+双指针模板 +Arrays.sort(nums); +int left = 0, right = nums.length - 1; +while (left < right) { + // 处理逻辑 +} + +// 二分查找模板 +int left = 0, right = nums.length - 1; +while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) return mid; + else if (nums[mid] < target) left = mid + 1; + else right = mid - 1; +} +``` + +### [31. 下一个排列](https://leetcode.cn/problems/next-permutation/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:字典序算法** + +> 整数数组的一个 **排列** 就是将其所有成员以序列或线性顺序排列。 +> +> 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 +> 整数数组的 **下一个排列** 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 **下一个排列** 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。 +> +> - 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。 +> - 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。 +> - 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。 +> +> 给你一个整数数组 nums ,找出 nums 的下一个排列。 +> +> **题干的意思就是**:找出这个数组排序出的所有数中,刚好比当前数大的那个数 +> +> 必须 **原地** 修改,只允许使用额外常数空间。 +> +> ``` +> 输入:nums = [1,2,3] +> 输出:[1,3,2] +> ``` +> +> ``` +> 输入:nums = [3,2,1] +> 输出:[1,2,3] +> ``` + +**💡 核心思路**:两遍扫描算法 + +1. **从右往左找第一个逆序对**:找到 nums[i] < nums[i+1] 的位置 i +2. **从右往左找第一个大于nums[i]的数**:找到位置 j,满足 nums[j] > nums[i] +3. **交换 nums[i] 和 nums[j]** +4. **反转 i+1 到末尾的部分**:使其变为最小的字典序 + +```java +public void nextPermutation(int[] nums) { + // 步骤1:从右向左找到第一个比右边元素小的元素 + int i = nums.length - 2; + while (i >= 0 && nums[i] >= nums[i + 1]) { + i--; + } + + // 步骤2:如果找到这样的元素,再从右向左找第一个比它大的元素并交换 + if (i >= 0) { + int j = nums.length - 1; + while (nums[j] <= nums[i]) { + j--; + } + swap(nums, i, j); + } + + // 步骤3:反转i后面的元素,使其成为最小排列 + reverse(nums, i + 1); +} + +// 交换数组中两个位置的元素 +private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; +} + +// 反转数组中从start位置到末尾的元素 +private void reverse(int[] nums, int start) { + int left = start; + int right = nums.length - 1; + while (left < right) { + swap(nums, left, right); + left++; + right--; + } +} +``` + +**⏱️ 复杂度分析**: +- **时间复杂度**:O(n) - 最多遍历数组3次 +- **空间复杂度**:O(1) - 只使用了常数级别的额外空间 + + + +### [56. 合并区间](https://leetcode.cn/problems/merge-intervals/) + +> 以数组 `intervals` 表示若干个区间的集合,其中单个区间为 `intervals[i] = [starti, endi]` 。请你合并所有重叠的区间,并返回 *一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间* 。 +> +> ``` +> 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] +> 输出:[[1,6],[8,10],[15,18]] +> 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. +> ``` + +思路: + +1. **排序** 将区间按起始位置升序排列,使可能重叠的区间相邻,便于合并操作 + +  + +2. **合并逻辑** + + **遍历判断**:依次遍历排序后的区间,比较当前区间与结果列表中的最后一个区间是否重叠: + + - 重叠条件:当前区间起始 ≤ 结果列表中最后一个区间的结束。 + - 合并操作:更新结果列表中最后一个区间的结束值为两者的最大值。 + - **非重叠条件**:直接将当前区间加入结果列表 + +```java +public int[][] merge(int[][] intervals) { + // 边界条件:空数组直接返回空 + if (intervals.length == 0) return new int[0][]; + + // 按区间起始点升序排序 + Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); + + // 结果列表 + List merged = new ArrayList<>(); + merged.add(intervals[0]); // 初始化第一个区间 + + for (int i = 1; i < intervals.length; i++) { + int[] last = merged.get(merged.size() - 1); + int[] current = intervals[i]; + + if (current[0] <= last[1]) { // 重叠,合并区间 + last[1] = Math.max(last[1], current[1]); // 更新结束值为较大值 + } else { // 不重叠,直接添加 + merged.add(current); + } + } + + return merged.toArray(new int[merged.size()][]); // 转换为二维数组 +} +``` + +### [215. 数组中的第K个最大元素](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:快速选择算法** + +> 给定整数数组 `nums` 和整数 `k`,请返回数组中第 `k` 个最大的元素。 + +**💡 核心思路**:快速选择算法(基于快排的分治思想) + +```java +class Solution { + Random random = new Random(); + + public int findKthLargest(int[] nums, int k) { + return quickSelect(nums, 0, nums.length - 1, nums.length - k); + } + + public int quickSelect(int[] a, int l, int r, int index) { + int q = randomPartition(a, l, r); + if (q == index) { + return a[q]; + } else { + return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index); + } + } + + public int randomPartition(int[] a, int l, int r) { + int i = random.nextInt(r - l + 1) + l; + swap(a, i, r); + return partition(a, l, r); + } + + public int partition(int[] a, int l, int r) { + int x = a[r], i = l - 1; + for (int j = l; j < r; ++j) { + if (a[j] <= x) { + swap(a, ++i, j); + } + } + swap(a, i + 1, r); + return i + 1; + } + + public void swap(int[] a, int i, int j) { + int temp = a[i]; + a[i] = a[j]; + a[j] = temp; + } +} +``` + +### [287. 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:二分查找应用** + +> 给定一个包含 `n + 1` 个整数的数组 `nums` ,其数字都在 `[1, n]` 范围内,可知至少存在一个重复的整数。 + +**💡 核心思路**:二分查找(基于抽屉原理) + +- 利用二分查找的思路,统计数组中小于等于中间值的数的个数 +- 如果个数大于中间值,说明重复数在左半部分,否则在右半部分 + +```java +public int findDuplicate(int[] nums) { + int left = 1; + int right = nums.length - 1; + while (left < right) { + int mid = left + (right - left) / 2; + int count = 0; + for (int num : nums) { + if (num <= mid) { + count++; + } + } + if (count > mid) { + right = mid; + } else { + left = mid + 1; + } + } + return left; +} +``` + +### [33. 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:二分查找变体** + +> 整数数组 nums 按升序排列,数组中的值互不相同。在传递给函数之前,nums 在预先未知的某个下标 k 上进行了旋转。 + +**💡 核心思路**:二分查找,判断哪一边是有序的 + +```java +public int search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + + while (left <= right) { + int mid = left + (right - left) / 2; + + if (nums[mid] == target) { + return mid; + } + + // 判断左半部分是否有序 + if (nums[left] <= nums[mid]) { + if (nums[left] <= target && target < nums[mid]) { + right = mid - 1; + } else { + left = mid + 1; + } + } else { // 右半部分有序 + if (nums[mid] < target && target <= nums[right]) { + left = mid + 1; + } else { + right = mid - 1; + } + } + } + + return -1; +} +``` + +--- + +## 五、数学技巧类(巧妙算法优化)🧮 + +**💡 核心思想** + +- **位运算技巧**:利用异或、与运算等特性 +- **数学性质**:利用数组特殊性质简化问题 +- **原地标记**:不使用额外空间的标记方法 + +**🎯 必掌握模板** + +```java +// 异或运算模板(找单独元素) +int result = 0; +for (int num : nums) { + result ^= num; // 相同元素异或为0,单独元素保留 +} + +// 原地标记模板(数组作为哈希表) +for (int i = 0; i < nums.length; i++) { + int index = Math.abs(nums[i]) - 1; + if (nums[index] > 0) { + nums[index] = -nums[index]; // 标记已访问 + } +} +``` + +### [136. 只出现一次的数字](https://leetcode.cn/problems/single-number/) + +**🎯 考察频率:高 | 难度:简单 | 重要性:异或运算经典** + +> 给你一个非空整数数组 `nums` ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 + +**💡 核心思路**:异或运算 + +- 初始化结果:result = 0, 任何数和 0 异或都是它本身 +- 由于异或运算的性质,出现两次的数字会被互相抵消,最终 result 就是只出现一次的数字 + +```java +public int singleNumber(int[] nums) { + int result = 0; + for (int num : nums) { + result ^= num; + } + return result; +} +``` + +### [169. 多数元素](https://leetcode.cn/problems/majority-element/) + +**🎯 考察频率:高 | 难度:简单 | 重要性:Boyer-Moore算法** + +> 给定一个大小为 `n` 的数组 `nums` ,返回其中的多数元素。多数元素是指在数组中出现次数大于 `⌊ n/2 ⌋` 的元素。 + +**💡 核心思路**:排序后取中间元素 + +```java +class Solution { + public int majorityElement(int[] nums) { + Arrays.sort(nums); + return nums[nums.length / 2]; + } +} +``` + +### [448. 找到所有数组中消失的数字](https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/) + +**🎯 考察频率:中等 | 难度:简单 | 重要性:原地标记** + +> 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字。 + +**💡 核心思路**:原地标记法 + +```java +public static List findNumbers(int[] nums){ + List list = new ArrayList<>(); + int[] x = new int[nums.length + 1]; + // 新建一个数组 x,用来做哈希表,占位标记。 + // 注意:长度是 nums.length + 1,因为我们希望下标范围 0..n, + // 这样 1..n 的数字就能直接映射到数组下标。 + + // 1. 遍历 nums,把出现的数字对应位置标记 + for (int i = 0; i < nums.length; i++) { + x[nums[i]]++; // 出现一次就加一 + } + + // 2. 再遍历 x,找到没出现过的数字 + for (int i = 1; i < x.length; i++) { // 注意从 1 开始,因为 0 不是合法数字 + if(x[i] == 0){ + list.add(i); // 没出现的数字加入结果集 + } + } + + return list; +} +``` + +### [41. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) + +**🎯 考察频率:高 | 难度:困难 | 重要性:原地哈希** + +> 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 + +**💡 核心思路**:原地哈希,将数组本身作为哈希表 + +```java +public int firstMissingPositive(int[] nums) { + int n = nums.length; + + // 将不在范围[1,n]的数字置为n+1 + for (int i = 0; i < n; i++) { + if (nums[i] <= 0 || nums[i] > n) { + nums[i] = n + 1; + } + } + + // 用数组下标标记数字是否出现 + for (int i = 0; i < n; i++) { + int num = Math.abs(nums[i]); + if (num <= n) { + nums[num - 1] = -Math.abs(nums[num - 1]); + } + } + + // 找第一个正数的位置 + for (int i = 0; i < n; i++) { + if (nums[i] > 0) { + return i + 1; + } + } + + return n + 1; +} +``` + +--- + +## 六、原地算法类(空间优化大师)⚡ + +**💡 核心思想** + +- **双指针交换**:使用双指针原地重排元素 +- **环形替换**:循环移动元素到正确位置 +- **分区思想**:将数组分为不同区域处理 + +### [88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/) + +**🎯 考察频率:高 | 难度:简单 | 重要性:原地合并** + +> 给你两个按非递减顺序排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 +> +> 注意: +> +> - `nums1` 的长度为 `m + n`,其中前 `m` 个元素表示有效部分,后 `n` 个元素为空(用来存放 `nums2` 的元素)。 +> - `nums2` 的长度为 `n`。 +> +> ``` +> 输入: +> nums1 = [1,2,3,0,0,0], m = 3 +> nums2 = [2,5,6], n = 3 +> +> 输出: +> [1,2,2,3,5,6] +> ``` + +**💡 核心思路**:直接合并后排序 或 从后往前双指针合并 + +```java +public void merge(int[] nums1, int m, int[] nums2, int n) { + for (int i = 0; i != n; ++i) { + nums1[m + i] = nums2[i]; + } + Arrays.sort(nums1); +} +``` + +### [189. 旋转数组](https://leetcode.cn/problems/rotate-array/) + +**🎯 考察频率:中等 | 难度:中等 | 重要性:原地算法** + +> 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 + +**💡 核心思路**:三次反转 + +```java +public void rotate(int[] nums, int k) { + k %= nums.length; + reverse(nums, 0, nums.length - 1); + reverse(nums, 0, k - 1); + reverse(nums, k, nums.length - 1); +} + +private void reverse(int[] nums, int start, int end) { + while (start < end) { + int temp = nums[start]; + nums[start] = nums[end]; + nums[end] = temp; + start++; + end--; + } +} +``` + +--- + +## 七、综合应用类(面试高频)🎯 + +**💡 核心思想** + +- **多技巧结合**:综合运用多种数组处理技巧 +- **实际应用场景**:贴近实际开发中的问题 + +### [53. 最大子数组和](https://leetcode-cn.com/problems/maximum-subarray/) + +**🎯 考察频率:极高 | 难度:简单 | 重要性:动态规划经典** + +> 给你一个整数数组 `nums` ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 +> +> **子数组** 是数组中的一个连续部分。 +> +> ``` +> 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] +> 输出:6 +> 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 +> ``` + +**思路**:「连续」子数组,题目要求的是返回结果,用 [动态规划、分治] + +```java +public static int maxSubArray(int[] nums) { + //特判 + if (nums == null || nums.length == 0) { + return 0; + } + //初始化 + int length = nums.length; + int[] dp = new int[length]; + // 初始值,只有一个元素的时候最大和即它本身 + dp[0] = nums[0]; + int ans = nums[0]; + // 状态转移 + for (int i = 1; i < length; i++) { + // 取当前元素的值 和 当前元素的值加上一次结果的值 中最大数 + dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]); + // 和最大数对比 取大 + ans = Math.max(ans, dp[i]); + } + return ans; +} + +//优化版 +public int maxSubArray(int[] nums) { + int pre = 0, maxAns = nums[0]; + for (int x : nums) { + pre = Math.max(pre + x, x); + maxAns = Math.max(maxAns, pre); + } + return maxAns; +} +``` + +### [121. 买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) + +**🎯 考察频率:极高 | 难度:简单 | 重要性:动态规划入门** + +> 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。 + +**💡 核心思路**:一次遍历,记录最低价格和最大利润 + +```java +public int maxProfit(int[] prices) { + int minPrice = Integer.MAX_VALUE; + int maxProfit = 0; + + for (int price : prices) { + if (price < minPrice) { + minPrice = price; + } else if (price - minPrice > maxProfit) { + maxProfit = price - minPrice; + } + } + + return maxProfit; +} +``` + +### [152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:动态规划变体** + +> 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组,并返回该子数组所对应的乘积。 + +**💡 核心思路**:动态规划,同时维护最大值和最小值 + +```java +public int maxProduct(int[] nums) { + int maxSoFar = nums[0]; + int minSoFar = nums[0]; + int result = maxSoFar; + + for (int i = 1; i < nums.length; i++) { + int curr = nums[i]; + int tempMaxSoFar = Math.max(curr, Math.max(maxSoFar * curr, minSoFar * curr)); + minSoFar = Math.min(curr, Math.min(maxSoFar * curr, minSoFar * curr)); + + maxSoFar = tempMaxSoFar; + result = Math.max(maxSoFar, result); + } + + return result; +} +``` + +### [78. 子集](https://leetcode.cn/problems/subsets/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:回溯算法** + +> 给你一个整数数组 nums ,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。 + +**💡 核心思路**:回溯算法 + +```java +public List> subsets(int[] nums) { + List> result = new ArrayList<>(); + backtrack(result, new ArrayList<>(), nums, 0); + return result; +} + +private void backtrack(List> result, List tempList, int[] nums, int start) { + result.add(new ArrayList<>(tempList)); + for (int i = start; i < nums.length; i++) { + tempList.add(nums[i]); + backtrack(result, tempList, nums, i + 1); + tempList.remove(tempList.size() - 1); + } +} +``` + +### [55. 跳跃游戏](https://leetcode.cn/problems/jump-game/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:贪心算法** + +> 给定一个非负整数数组 nums ,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。 + +**💡 核心思路**:贪心算法,维护能到达的最远位置 + +```java +public boolean canJump(int[] nums) { + int maxReach = 0; + for (int i = 0; i < nums.length; i++) { + if (i > maxReach) { + return false; + } + maxReach = Math.max(maxReach, i + nums[i]); + } + return true; +} +``` + +### [45. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) + +**🎯 考察频率:中等 | 难度:中等 | 重要性:贪心优化** + +> 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。 + +**💡 核心思路**:贪心算法,在能跳到的范围内选择能跳得最远的 + +```java +public int jump(int[] nums) { + int jumps = 0; + int currentEnd = 0; + int farthest = 0; + + for (int i = 0; i < nums.length - 1; i++) { + farthest = Math.max(farthest, i + nums[i]); + + if (i == currentEnd) { + jumps++; + currentEnd = farthest; + } + } + + return jumps; +} +``` + + +### [14. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) + +**🎯 考察频率:中等 | 难度:简单 | 重要性:字符串处理** + +> 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 `""`。 + +**💡 核心思路**:逐个字符比较 + +```java +public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + + String prefix = strs[0]; + for (int i = 1; i < strs.length; i++) { + int j = 0; + while (j < prefix.length() && j < strs[i].length() && prefix.charAt(j) == strs[i].charAt(j)) { + j++; + } + prefix = prefix.substring(0, j); + if (prefix.isEmpty()) { + return ""; + } + } + return prefix; +} +``` + +### [54. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:矩阵遍历** + +> 给你一个 m 行 n 列的矩阵 matrix ,请按照顺时针螺旋顺序,返回矩阵中的所有元素。 + +**💡 核心思路**:模拟螺旋过程,维护边界 + +```java +public List spiralOrder(int[][] matrix) { + List result = new ArrayList<>(); + if (matrix.length == 0) return result; + + int top = 0, bottom = matrix.length - 1; + int left = 0, right = matrix[0].length - 1; + + while (top <= bottom && left <= right) { + // 从左到右 + for (int j = left; j <= right; j++) { + result.add(matrix[top][j]); + } + top++; + + // 从上到下 + for (int i = top; i <= bottom; i++) { + result.add(matrix[i][right]); + } + right--; + + if (top <= bottom) { + // 从右到左 + for (int j = right; j >= left; j--) { + result.add(matrix[bottom][j]); + } + bottom--; + } + + if (left <= right) { + // 从下到上 + for (int i = bottom; i >= top; i--) { + result.add(matrix[i][left]); + } + left++; + } + } + + return result; +} +``` + +--- + +## 🚀 面试前15分钟速记表 + +### 核心模板(必背) + +```java +// 1. 双指针模板 +int left = 0, right = nums.length - 1; +while (left < right) { + // 处理逻辑 +} + +// 2. 滑动窗口模板 +int left = 0; +for (int right = 0; right < nums.length; right++) { + // 扩展窗口 + while (窗口需要收缩) { + // 收缩窗口 + left++; + } +} + +// 3. 前缀和模板 +int[] prefixSum = new int[nums.length + 1]; +for (int i = 0; i < nums.length; i++) { + prefixSum[i + 1] = prefixSum[i] + nums[i]; +} +``` + +### 题型速查表 + +| 题型分类 | 核心技巧 | 高频题目 | 记忆口诀 | 难度 | +|----------|----------|----------|----------|------| +| **双指针类** | 左右指针/快慢指针 | 两数之和、三数之和、盛水容器、移动零、颜色分类 | 左右夹逼找目标,快慢追踪解环题 | ⭐⭐⭐ | +| **哈希表类** | 空间换时间 | 字母异位词分组、最长连续序列、存在重复元素 | 空间换时间来优化,常数查找是关键 | ⭐⭐⭐ | +| **前缀和类** | 累积求和 | 和为K的子数组、最大子数组和、除自身以外数组的乘积 | 累积求和建数组,区间相减得答案 | ⭐⭐⭐⭐ | +| **排序搜索** | 预排序/二分 | 合并区间、第K大元素、寻找重复数、搜索旋转数组 | 有序数组用二分,对数时间最高效 | ⭐⭐⭐⭐ | +| **原地算法** | 双指针交换 | 移动零、颜色分类、合并有序数组、旋转数组 | 双指针巧妙来交换,空间复杂度为一 | ⭐⭐⭐⭐ | +| **数学技巧** | 位运算/性质 | 只出现一次的数字、多数元素、消失的数字 | 数学性质巧运用,位运算显神通 | ⭐⭐⭐ | +| **综合应用** | 多技巧结合 | 买卖股票、跳跃游戏、子集、乘积最大子数组 | 多技巧组合显神通,复杂问题巧拆解 | ⭐⭐⭐⭐⭐ | + +### 核心题目优先级(面试前重点复习) + +1. **两数之和** - 哈希表基础,必须熟练掌握 +2. **三数之和** - 双指针经典,排序+双指针 +3. **最大子数组和** - 动态规划入门,Kadane算法 +4. **合并区间** - 排序应用,区间处理 +5. **盛最多水的容器** - 双指针对撞,贪心思想 +6. **颜色分类** - 三指针技巧,荷兰国旗问题 +7. **搜索旋转排序数组** - 二分查找变体 +8. **除自身以外数组的乘积** - 前缀乘积,空间优化 + +### 常见陷阱提醒 + +- ⚠️ **数组越界**:`i < nums.length`,确保索引在有效范围内 +- ⚠️ **整数溢出**:使用long或检查边界,特别是乘积运算 +- ⚠️ **空数组处理**:`if (nums == null || nums.length == 0)` +- ⚠️ **双指针边界**:确保`left < right`,避免死循环 +- ⚠️ **滑动窗口**:正确维护窗口边界,注意收缩条件 +- ⚠️ **前缀和**:注意数组长度为n+1,处理边界情况 + +### 时间复杂度总结 + +- **遍历类**:O(n) - 一次遍历 +- **双指针**:O(n) - 同时移动或对撞指针 +- **排序类**:O(n log n) - 基于比较的排序 +- **哈希表**:O(n) - 平均情况下常数时间查找 +- **二分查找**:O(log n) - 每次缩小一半搜索空间 + +### 面试答题套路 + +1. **理解题意**:确认输入输出,边界情况,时间空间复杂度要求 +2. **选择方法**:根据题型选择对应技巧(双指针、哈希表、前缀和等) +3. **画图分析**:手动模拟小规模测试用例,验证思路 +4. **编码实现**:套用对应模板,注意边界条件处理 +5. **优化分析**:考虑时间空间复杂度优化可能性 +6. **测试验证**:空数组、单元素、正常情况、边界情况 + +**最重要的一个技巧就是,你得行动,写起来** diff --git a/docs/data-structure-algorithms/soultion/Binary-Tree-Solution.md b/docs/data-structure-algorithms/soultion/Binary-Tree-Solution.md new file mode 100755 index 0000000000..76d93578a9 --- /dev/null +++ b/docs/data-structure-algorithms/soultion/Binary-Tree-Solution.md @@ -0,0 +1,2214 @@ +--- +title: 二叉树通关秘籍:LeetCode 热题 100 全解析 +date: 2025-01-08 +tags: + - Binary Tree +categories: leetcode +--- + + + +> 二叉树作为数据结构中的常青树,在算法面试中出现的频率居高不下。 +> +> 有人说刷题要先刷二叉树,培养思维。目前还没培养出来,感觉分类刷都差不多。 +> +> 我把二叉树相关题目进行了下分类。 +> +> 当然,在做二叉树题目时候,第一想到的应该是用 **递归** 来解决。 + +### 📋 分类索引 + +1. **🌳 遍历基础类**:[二叉树的前序遍历](#_144-二叉树的前序遍历)、[二叉树的中序遍历](#_94-二叉树的中序遍历)、[二叉树的后序遍历](#_145-二叉树的后序遍历)、[二叉树的层序遍历](#_102-二叉树的层序遍历)、[锯齿形层序遍历](#_103-二叉树的锯齿形层序遍历)、[完全二叉树的节点个数](#_222-完全二叉树的节点个数)、[在每个树行中找最大值](#_515-在每个树行中找最大值)、[二叉树最大宽度](#_662-二叉树最大宽度) + +2. **🔍 查找路径类**:[二叉树的最大深度](#_104-二叉树的最大深度)、[二叉树的最小深度](#_111-二叉树的最小深度)、[路径总和](#_112-路径总和)、[路径总和II](#_113-路径总和-ii)、[二叉树的右视图](#_199-二叉树的右视图)、[路径总和III](#_437-路径总和-iii)、[二叉树的最近公共祖先](#_236-二叉树的最近公共祖先)、[二叉树中的最大路径和](#_124-二叉树中的最大路径和) + +3. **🏗️ 构造变换类**:[从前序与中序遍历序列构造二叉树](#_105-从前序与中序遍历序列构造二叉树)、[翻转二叉树](#_226-翻转二叉树)、[对称二叉树](#_101-对称二叉树)、[相同的树](#_100-相同的树) + +4. **⚖️ 平衡验证类**:[平衡二叉树](#_110-平衡二叉树)、[验证二叉搜索树](#_98-验证二叉搜索树)、[二叉搜索树中第K小的元素](#_230-二叉搜索树中第K小的元素) + +5. **🎯 搜索树操作类**:[二叉搜索树的最近公共祖先](#_235-二叉搜索树的最近公共祖先)、[删除二叉搜索树中的节点](#_450-删除二叉搜索树中的节点)、[将有序数组转换为二叉搜索树](#_108-将有序数组转换为二叉搜索树) + +6. **📊 树形DP类**:[打家劫舍III](#_337-打家劫舍-iii)、[二叉树的直径](#_543-二叉树的直径)、[另一棵树的子树](#_572-另一棵树的子树) + +7. **🔄 序列化类**:[二叉树的序列化与反序列化](#_297-二叉树的序列化与反序列化) + +8. **🚀 进阶技巧类**:[二叉树展开为链表](#_114-二叉树展开为链表)、[填充每个节点的下一个右侧节点指针](#_116-填充每个节点的下一个右侧节点指针)、[二叉树的完全性检验](#_958-二叉树的完全性检验)、[填充每个节点的下一个右侧节点指针II](#_117-填充每个节点的下一个右侧节点指针-ii) + +## 🎯 核心考点概览 + +- **遍历技巧**:前序、中序、后序、层序遍历 +- **递归思维**:分治思想、递归终止条件 +- **路径问题**:根到叶子路径、任意路径求和 +- **树的构造**:根据遍历序列重建二叉树 +- **树的变换**:翻转、对称、平衡判断 +- **搜索树性质**:BST的查找、插入、删除 +- **动态规划**:树形DP的经典应用 + +## 💡 解题万能模板 + +### 🔄 递归遍历模板 + +```java +// 递归遍历通用模板 +public void traverse(TreeNode root) { + if (root == null) return; + + // 前序位置 - 在递归之前 + doSomething(root.val); + + traverse(root.left); + + // 中序位置 - 在左右递归之间 + doSomething(root.val); + + traverse(root.right); + + // 后序位置 - 在递归之后 + doSomething(root.val); +} +``` + +### 🔍 层序遍历模板 + +```java +// BFS层序遍历模板 +public void levelOrder(TreeNode root) { + if (root == null) return; + + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + // 处理当前节点 + doSomething(node.val); + + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + } +} +``` + +### 🎯 分治思想模板 + +```java +// 分治思想模板 +public ResultType divide(TreeNode root) { + if (root == null) return nullResult; + + // 分:递归处理左右子树 + ResultType leftResult = divide(root.left); + ResultType rightResult = divide(root.right); + + // 治:合并左右结果 + ResultType result = merge(leftResult, rightResult, root); + + return result; +} +``` + +## 🛡️ 边界检查清单 + +- ✅ 空树处理:`root == null` +- ✅ 单节点树:`root.left == null && root.right == null` +- ✅ 递归终止:明确递归出口条件 +- ✅ 返回值检查:确保每个递归分支都有返回值 + +## 💡 记忆口诀 + +- **遍历顺序**:"前序根左右,中序左根右,后序左右根,层序逐层走" +- **递归思维**:"递归三要素,终止、递推、返回值" +- **路径问题**:"路径用回溯,求和用递归" +- **树的构造**:"前中后序定根节点,中序划分左右树" +- **搜索树**:"左小右大有序性,中序遍历是关键" +- **平衡判断**:"高度差不超过一,递归检查每个节点" +- **最值问题**:"深度优先找路径,广度优先找最近" + +--- + +## 🌳 一、遍历基础类(核心中的核心) + +### 💡 核心思想 + +- **递归遍历**:利用函数调用栈实现深度优先 +- **迭代遍历**:手动维护栈模拟递归过程 +- **层序遍历**:队列实现广度优先搜索 + +### [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) + +**🎯 考察频率:极高 | 难度:简单 | 重要性:遍历基础** + +> 前序遍历:根→左→右,`[1,null,2,3]` → `[1,2,3]` + +**💡 核心思路**:递归 + 迭代两种实现 + +- 递归:root → left → right 的顺序访问 +- 迭代:使用栈模拟递归过程 + +**🔑 记忆技巧**: + +- **口诀**:"前序根左右,栈中右先入" +- **形象记忆**:像读书一样,先看标题(根)再看内容(左右) + +```java +// 递归解法 +public List preorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + preorder(root, result); + return result; +} + +private void preorder(TreeNode root, List result) { + if (root == null) return; + + result.add(root.val); // 根 + preorder(root.left, result); // 左 + preorder(root.right, result);// 右 +} +``` + +**🔧 迭代解法**: + +```java +public List preorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + if (root == null) return result; + + Stack stack = new Stack<>(); + stack.push(root); + + while (!stack.isEmpty()) { + TreeNode node = stack.pop(); + result.add(node.val); + + // 先入右子树,再入左子树(栈是后进先出) + if (node.right != null) stack.push(node.right); + if (node.left != null) stack.push(node.left); + } + + return result; +} +``` + +**⏱️ 复杂度分析**: + +- **时间复杂度**:O(n) - 每个节点访问一次 +- **空间复杂度**:O(h) - 递归栈深度,h为树高 + +### [94. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) + +**🎯 考察频率:极高 | 难度:简单 | 重要性:BST基础** + +> 中序遍历:左→根→右,`[1,null,2,3]` → `[1,3,2]` + +**💡 核心思路**:左→根→右的访问顺序 + +- 对于BST,中序遍历得到有序序列 +- 迭代实现需要先处理完所有左子树 + +**🔑 记忆技巧**: + +- **口诀**:"中序左根右,BST变有序" +- **形象记忆**:像中国人读书从左到右的顺序 + +```java +// 递归解法 +public List inorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + inorder(root, result); + return result; +} + +private void inorder(TreeNode root, List result) { + if (root == null) return; + + inorder(root.left, result); // 左 + result.add(root.val); // 根 + inorder(root.right, result); // 右 +} +``` + +**🔧 迭代解法**: + +```java +public List inorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + Stack stack = new Stack<>(); + TreeNode current = root; + + while (current != null || !stack.isEmpty()) { + // 一直向左走到底 + while (current != null) { + stack.push(current); + current = current.left; + } + + // 处理栈顶节点 + current = stack.pop(); + result.add(current.val); + + // 转向右子树 + current = current.right; + } + + return result; +} +``` + +**⏱️ 复杂度分析**: + +- **时间复杂度**:O(n) - 每个节点访问一次 +- **空间复杂度**:O(h) - 递归栈或显式栈的深度 + +### [145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) + +**🎯 考察频率:高 | 难度:简单 | 重要性:分治思想基础** + +> 后序遍历:左→右→根,`[1,null,2,3]` → `[3,2,1]` + +**💡 核心思路**:左→右→根的访问顺序 + +- 后序遍历常用于树的删除、计算等操作 +- 迭代实现相对复杂,需要记录访问状态 + +**🔑 记忆技巧**: + +- **口诀**:"后序左右根,删除用后序" +- **形象记忆**:像拆房子,先拆左右房间,最后拆主体 + +```java +// 递归解法 +public List postorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + postorder(root, result); + return result; +} + +private void postorder(TreeNode root, List result) { + if (root == null) return; + + postorder(root.left, result); // 左 + postorder(root.right, result); // 右 + result.add(root.val); // 根 +} +``` + +**🔧 迭代解法(双栈法)**: + +```java +public List postorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + if (root == null) return result; + + Stack stack1 = new Stack<>(); + Stack stack2 = new Stack<>(); + + stack1.push(root); + + while (!stack1.isEmpty()) { + TreeNode node = stack1.pop(); + stack2.push(node); + + if (node.left != null) stack1.push(node.left); + if (node.right != null) stack1.push(node.right); + } + + while (!stack2.isEmpty()) { + result.add(stack2.pop().val); + } + + return result; +} +``` + +**⏱️ 复杂度分析**: + +- **时间复杂度**:O(n) - 每个节点访问一次 +- **空间复杂度**:O(h) - 递归栈深度 + +### [102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) + +**🎯 考察频率:极高 | 难度:中等 | 重要性:BFS经典** + +> 层序遍历:按层从左到右,`[3,9,20,null,null,15,7]` → `[[3],[9,20],[15,7]]` + +**💡 核心思路**:队列实现BFS + + + +- 使用队列保存每层的节点 +- 记录每层的节点数量,分层处理 + +**🔑 记忆技巧**: + +- **口诀**:"层序用队列,按层逐个走" +- **形象记忆**:像楼房一样,一层一层地访问 + +```java +public List> levelOrder(TreeNode root) { + List> result = new ArrayList<>(); + if (root == null) return result; + + Queue queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + List level = new ArrayList<>(); + + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + level.add(node.val); + + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + + result.add(level); + } + + return result; +} +``` + +**⏱️ 复杂度分析**: + +- **时间复杂度**:O(n) - 每个节点访问一次 +- **空间复杂度**:O(w) - w为树的最大宽度 + +### [103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) + +**🎯 考察频率:高 | 难度:中等 | 重要性:层序变种** + +> 锯齿形遍历:奇数层从左到右,偶数层从右到左 + +**💡 核心思路**:层序遍历 + 奇偶层判断 + +- 基于普通层序遍历 +- 偶数层需要反转结果 + +**🔑 记忆技巧**: + +- **口诀**:"锯齿形遍历,奇正偶反转" + +```java +public List> zigzagLevelOrder(TreeNode root) { + List
stylereport_styletype
_view
stylereport_materialtype
stylereport_material
stylereport_material_
clickposition_view