├── pnpm-workspace.yaml
├── .npmrc
├── docs
├── images
│ ├── icon.png
│ ├── odd-even.png
│ ├── water-cup.jpg
│ ├── random-list.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── island-eye-color.jpg
│ ├── random-list-step1.png
│ ├── random-list-step2.png
│ ├── random-list-step3.png
│ └── github-doocs-yanglbme-collabocats.png
├── public
│ ├── favicon-16x16.png
│ └── favicon-32x32.png
├── .vitepress
│ ├── theme
│ │ ├── index.js
│ │ └── Layout.vue
│ └── config.mts
├── index.md
├── the-beauty-of-programming.md
├── getting-started.md
├── algorithm-stories.md
├── clean-code.md
├── effective-coding.md
└── effective-java.md
├── Main.java
├── package.json
├── .gitignore
├── .github
└── workflows
│ ├── prettier.yml
│ ├── compress.yml
│ └── deploy.yml
├── README.md
├── LICENSE
└── pnpm-lock.yaml
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - '.'
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 | strict-peer-dependencies=false
3 |
--------------------------------------------------------------------------------
/docs/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/icon.png
--------------------------------------------------------------------------------
/docs/images/odd-even.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/odd-even.png
--------------------------------------------------------------------------------
/docs/images/water-cup.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/water-cup.jpg
--------------------------------------------------------------------------------
/docs/images/random-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/random-list.png
--------------------------------------------------------------------------------
/docs/images/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/public/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/public/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/images/island-eye-color.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/island-eye-color.jpg
--------------------------------------------------------------------------------
/docs/images/random-list-step1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/random-list-step1.png
--------------------------------------------------------------------------------
/docs/images/random-list-step2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/random-list-step2.png
--------------------------------------------------------------------------------
/docs/images/random-list-step3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/random-list-step3.png
--------------------------------------------------------------------------------
/docs/images/github-doocs-yanglbme-collabocats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doocs/coding-interview/HEAD/docs/images/github-doocs-yanglbme-collabocats.png
--------------------------------------------------------------------------------
/Main.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author yanglbme
3 | */
4 | public class Main {
5 | public static void main(String[] args) {
6 | System.out.println("互联网公司 IT 技术面试题集");
7 | }
8 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "vitepress": "^2.0.0-alpha.12"
4 | },
5 | "scripts": {
6 | "docs:dev": "vitepress dev docs",
7 | "docs:build": "vitepress build docs",
8 | "docs:preview": "vitepress preview docs"
9 | },
10 | "dependencies": {
11 | "vitepress-plugin-comment-with-giscus": "^1.1.15"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | node_modules
26 |
27 |
28 | docs/.vitepress/dist
29 | docs/.vitepress/cache
--------------------------------------------------------------------------------
/.github/workflows/prettier.yml:
--------------------------------------------------------------------------------
1 | name: Prettier
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | prettier:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v2
16 | with:
17 | ref: ${{ github.head_ref }}
18 |
19 | - name: Prettify code
20 | uses: creyD/prettier_action@v3.3
21 | with:
22 | prettier_options: --write **/*.{md}
23 | commit_message: "style: prettify code"
24 | env:
25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | import DefaultTheme from "vitepress/theme";
2 | import giscusTalk from "vitepress-plugin-comment-with-giscus";
3 | import { useData, useRoute } from "vitepress";
4 | import { toRefs } from "vue";
5 | import Layout from "./Layout.vue";
6 |
7 | export default {
8 | extends: DefaultTheme,
9 | Layout: Layout,
10 | enhanceApp(ctx) {
11 | DefaultTheme.enhanceApp(ctx);
12 | },
13 | setup() {
14 | const { frontmatter } = toRefs(useData());
15 | const route = useRoute();
16 |
17 | giscusTalk(
18 | {
19 | repo: "doocs/coding-interview",
20 | repoId: "MDEwOlJlcG9zaXRvcnkxNTQ5MTMxODI=",
21 | mapping: "pathname",
22 | category: "Announcements",
23 | categoryId: "IC_kwDOCTvJns4CpHBe",
24 | inputPosition: "top",
25 | lang: "zh-CN",
26 | homePageShowComment: true,
27 | loading: "lazying",
28 | lightTheme: "light",
29 | darkTheme: "transparent_dark",
30 | },
31 | {
32 | frontmatter,
33 | route,
34 | },
35 | true
36 | );
37 | },
38 | };
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "coding-interview"
7 | text: "互联网公司 IT 技术面试题集"
8 | tagline: Doocs 技术社区出品
9 | actions:
10 | - theme: brand
11 | text: 开始阅读
12 | link: /getting-started
13 | - theme: alt
14 | text: GitHub
15 | link: https://github.com/doocs/coding-interview
16 |
17 | features:
18 | - icon: 📚
19 | title: 剑指 Offer
20 | details: 经典面试题解析合集,涵盖算法、数据结构与解题思路。
21 | link: /coding-interview
22 |
23 | - icon: 💡
24 | title: 编程之美
25 | details: 微软出品,充满趣味与思考的编程挑战题解。
26 | link: /the-beauty-of-programming
27 |
28 | - icon: 📝
29 | title: 代码整洁之道
30 | details: 如何写出可读、可维护的高质量代码,编码风格提升宝典。
31 | link: /clean-code
32 |
33 | - icon: 🎓
34 | title: 阿里巴巴 Java 开发手册
35 | details: 阿里内部实践沉淀的 Java 编程规范手册。
36 | link: /effective-coding
37 |
38 | - icon: 🛏️
39 | title: 枕边算法书
40 | details: 轻松入门算法的最佳伴侣,睡前读物般的温柔讲解。
41 | link: /algorithm-stories
42 |
43 | - icon: ☕
44 | title: Effective Java
45 | details: Java 编程进阶指南,覆盖最佳实践和设计模式。
46 | link: /effective-java
47 | ---
48 |
--------------------------------------------------------------------------------
/.github/workflows/compress.yml:
--------------------------------------------------------------------------------
1 | name: Compress
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | paths:
7 | - "**.jpg"
8 | - "**.jpeg"
9 | - "**.png"
10 | - "**.webp"
11 | workflow_dispatch:
12 |
13 | jobs:
14 | compress:
15 | runs-on: ubuntu-latest
16 | if: github.repository == 'doocs/coding-interview'
17 | steps:
18 | - name: Checkout Branch
19 | uses: actions/checkout@v2
20 |
21 | - name: Compress Images
22 | id: calibre
23 | uses: calibreapp/image-actions@main
24 | with:
25 | githubToken: ${{ secrets.ACTION_TOKEN }}
26 | compressOnly: true
27 |
28 | - name: Commit Files
29 | if: |
30 | steps.calibre.outputs.markdown != ''
31 | run: |
32 | git config --local user.email "szuyanglb@outlook.com"
33 | git config --local user.name "yanglbme"
34 | git commit -m "chore: auto compress images" -a
35 |
36 | - name: Push Changes
37 | if: |
38 | steps.calibre.outputs.markdown != ''
39 | uses: ad-m/github-push-action@master
40 | with:
41 | github_token: ${{ secrets.ACTION_TOKEN }}
42 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 | 
20 | |
21 |
22 | 
26 | |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and deploy
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | with:
14 | fetch-depth: 0
15 |
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: 22
20 |
21 | - uses: pnpm/action-setup@v4
22 | with:
23 | version: 9
24 |
25 | - name: Get pnpm store directory
26 | shell: bash
27 | run: |
28 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
29 |
30 | - uses: actions/cache@v4
31 | name: Setup pnpm cache
32 | with:
33 | path: ${{ env.STORE_PATH }}
34 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
35 | restore-keys: |
36 | ${{ runner.os }}-pnpm-store-
37 |
38 | - name: Install dependencies
39 | run: pnpm install --frozen-lockfile
40 |
41 | - name: Build with VitePress
42 | run: pnpm run docs:build
43 |
44 | - name: Generate CNAME
45 | run: echo "interview.doocs.org" > docs/.vitepress/dist/CNAME
46 |
47 | - name: Upload artifact
48 | uses: actions/upload-pages-artifact@v3
49 | with:
50 | path: docs/.vitepress/dist
51 |
52 | deploy:
53 | needs: build
54 | runs-on: ubuntu-latest
55 | permissions:
56 | pages: write
57 | id-token: write
58 | environment:
59 | name: github_pages
60 | url: ${{ steps.deployment.outputs.page_url }}
61 | steps:
62 | - name: Deploy to GitHub Pages
63 | id: deployment
64 | uses: actions/deploy-pages@v4
65 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitepress'
2 |
3 | // https://vitepress.dev/reference/site-config
4 | export default defineConfig({
5 | title: "coding-interview",
6 | description: "互联网公司 IT 技术面试题集",
7 | themeConfig: {
8 | // https://vitepress.dev/reference/default-theme-config
9 | nav: [
10 | { text: '首页', link: '/' },
11 | { text: '题解', link: '/coding-interview' },
12 | { text: '编程之美', link: '/the-beauty-of-programming' },
13 | { text: '代码整洁之道', link: '/clean-code' },
14 | { text: '阿里巴巴 Java 开发手册', link: '/effective-coding' },
15 | { text: '枕边算法书', link: '/algorithm-stories' },
16 | { text: 'Effective Java', link: '/effective-java' }
17 | ],
18 | search: {
19 | provider: 'local'
20 | },
21 | logo: '/favicon-32x32.png',
22 | footer: {
23 | message: 'Released under the CC-BY-SA-4.0 license.',
24 | copyright: `版权所有 © 2018-${new Date().getFullYear()} Doocs`
25 | },
26 | docFooter: {
27 | prev: '上一篇',
28 | next: '下一篇'
29 | },
30 | editLink: {
31 | pattern: 'https://github.com/doocs/coding-interview/edit/main/docs/:path',
32 | text: '在 GitHub 编辑'
33 | },
34 | sidebar: [
35 | {
36 | text: '📚 题解',
37 | items: [
38 | { text: '剑指 Offer', link: '/coding-interview' },
39 | { text: '编程之美', link: '/the-beauty-of-programming' }
40 | ]
41 | },
42 | {
43 | text: '📝 代码整洁',
44 | items: [
45 | { text: '代码整洁之道', link: '/clean-code' },
46 | { text: '阿里巴巴 Java 开发手册', link: '/effective-coding' }
47 | ]
48 | },
49 | {
50 | text: '📖 其他书籍',
51 | items: [
52 | { text: '枕边算法书', link: '/algorithm-stories' },
53 | { text: 'Effective Java', link: '/effective-java' }
54 | ]
55 | }
56 | ],
57 |
58 | socialLinks: [
59 | { icon: 'github', link: 'https://github.com/doocs/coding-interview' }
60 | ]
61 | },
62 | head: [
63 | ['link', { rel: 'icon', type: 'image/png', href: '/favicon-32x32.png' }],
64 | ],
65 | cleanUrls: true,
66 | sitemap: {
67 | hostname: 'https://interview.doocs.org'
68 | }
69 | })
70 |
--------------------------------------------------------------------------------
/docs/the-beauty-of-programming.md:
--------------------------------------------------------------------------------
1 | # 《编程之美》
2 |
3 | ## 1.2 中国象棋将帅问题
4 |
5 | ### 题目描述
6 |
7 | 中国象棋里,“将”、“帅”不能照面。现假设棋盘上只有“将” `A`和“帅” `B`二子。
8 |
9 | A、B 二子被限制在己方 `3*3` 的格子里运动。请写处一个程序,输出 A、B 所有合法位置。要求在代码中**只能使用一个变量**。
10 |
11 | ### 解法
12 |
13 | 程序的大致框架如下:
14 |
15 | ```
16 | 遍历 A 的位置
17 | 遍历 B 的 位置
18 | 判断 A、B 的位置组合是否满足要求,若满足,则输出。
19 | ```
20 |
21 | 本题的难点在于如何只用一个变量来实现。
22 |
23 | 对于本题,每个子只需要 9 个数字就可以表达它的全部位置。
24 |
25 | ```
26 | 1 - 2 - 3
27 | | | |
28 | 4 - 5 - 6
29 | | | |
30 | 7 - 8 - 9
31 | ```
32 |
33 | #### 解法一
34 |
35 | 一个 8 位的 byte 类型能够表达 `2^8=256` 个值,所以可以用前 4 个 bit 表示 A 的位置,用后面的 4 bit 表示 B 的位置。
36 |
37 | ```java
38 | public class Solution {
39 |
40 | private final int GRIDW = 3;
41 |
42 | public void printAll() {
43 | byte b = 1;
44 | for (b = lSet(b, 1); lGet(b) <= GRIDW * GRIDW; b = lSet(b, lGet(b) + 1)) {
45 | for (b = rSet(b, 1); rGet(b) <= GRIDW * GRIDW; b = rSet(b, rGet(b) + 1)) {
46 | if (lGet(b) % GRIDW != rGet(b) % GRIDW) {
47 | System.out.println("A=" + lGet(b) + ", B=" + rGet(b));
48 | }
49 | }
50 | }
51 | }
52 |
53 | public byte lSet(byte b, int x) {
54 | return (byte) ((b & 0xf) | (x << 4));
55 | }
56 |
57 | public byte lGet(byte b) {
58 | return (byte) ((b >>> 4) & 0xf);
59 | }
60 |
61 | public byte rSet(byte b, int x) {
62 | return (byte) ((b & 0xf0) | x);
63 | }
64 |
65 | public byte rGet(byte b) {
66 | return (byte) (b & 0xf);
67 | }
68 | }
69 | ```
70 |
71 | #### 解法二
72 |
73 | ```java
74 | public class Solution {
75 | public void printAll() {
76 | byte b = 81;
77 | while (b > 0) {
78 | if (b % 9 % 3 != b / 9 % 3) {
79 | System.out.println("A=" + (b % 9 + 1) + ", B=" + (b / 9 + 1));
80 | }
81 | --b;
82 | }
83 | }
84 | }
85 |
86 | ```
87 |
88 | #### 解法三
89 |
90 | 这是 C 语言的实现的另一个解法。
91 |
92 | ```c
93 | #include
94 |
95 | struct {
96 | unsigned char a;
97 | unsigned char b;
98 | } i;
99 |
100 | int main() {
101 | for (i.a = 1; i.a <= 9; i.a++) {
102 | for (i.b = 1; i.b <= 9; i.b++) {
103 | if (i.a % 3 != i.b % 3) {
104 | printf("A = %d, B = %d\n", i.a, i.b);
105 | }
106 | }
107 | }
108 | }
109 | ```
110 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # 互联网公司 IT 技术面试题集
2 |
3 | [](https://github.com/doocs/coding-interview/blob/main/LICENSE)
4 | [](https://github.com/doocs/coding-interview/stargazers)
5 | [](https://github.com/doocs/coding-interview/issues)
6 | [](https://github.com/doocs/coding-interview/network/members)
7 | [](http://makeapullrequest.com)
8 |
9 | ## 项目介绍
10 |
11 | 本仓库用于记录各大互联网公司 IT 技术面试高频题以及经典书籍读书笔记,包括《剑指 Offer》、《编程之美》、《代码整洁之道》等,抽空更新中。
12 |
13 | ## 站点
14 |
15 | https://interview.doocs.org
16 |
17 | ## 书籍笔记
18 |
19 | ### [《剑指 Offer》](/coding-interview.md)
20 |
21 | 这本书选取的[题目](/coding-interview.md)都是被各大公司面试官反复采用的编程题,极具实战意义。当然,如果一开始觉得这些题目比较难,也是很正常的,因为大公司的面试本身就不简单。我们逐步掌握书中总结的解题方法之后,再去面试大公司,将会轻松很多。
22 |
23 | 推荐三个在线刷《剑指 Offer》的平台,个人比较倾向于 [LeetCode 中国](https://leetcode.cn/problemset/lcof/) 。
24 |
25 | - [LeetCode 中国](https://leetcode.cn/problemset/lcof/):近期上线,支持多种编程语言,墙裂推荐。
26 | - [AcWing](https://www.acwing.com/problem/):支持 Java11。团队成员来自北大清华,剑指 Offer 第二版题目都有。
27 | - [NowCoder](https://www.nowcoder.com/ta/coding-interviews):这个应该大多数人都知道,但是没有剑指 Offer 第二版新增的题目。
28 |
29 | ### [《代码整洁之道》](/clean-code.md)
30 |
31 | 这本书名为 _Clean Code_,乃是 Object Mentor(鲍勃大叔开办的技术咨询和培训公司)一干大牛在编程方面的经验累积。写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的“整洁感”。
32 |
33 | 作者 Robert C. Martin 在书中阐述了代码各个方面如何做到整洁的经验与最佳实践。我们若能长期遵照这些经验编写代码,所谓“代码感”也就自然而然滋生出来。
34 |
35 | ### [《阿里巴巴 Java 开发手册》](/effective-coding.md)
36 |
37 | 别人都说我们是搬砖的码农,但我们知道自己是追求个性的艺术家。也许我们不会过多在意自己的外表和穿着,但在我们不羁的外表下,骨子里追求着代码的美、系统的美、设计的美,代码规范其实就是一个对程序美的定义。
38 |
39 | ### [《枕边算法书》](/algorithm-stories.md)
40 |
41 | 这本书,我是把它当作一本故事书来读的,里面的部分知识点还挺有意思。
42 |
43 | ### [《Effective Java》](/effective-java.md)
44 |
45 | 这本书共包含了 78 个条目,每个条目讨论一条规则。它适用于任何具有实际 Java 工作经验的工程师,对于高级工程师,也能够提供一些发人深思的东西,是所有 Java 工程师必读书籍之一。
46 |
47 | ---
48 |
49 | ## Doocs 社区优质项目
50 |
51 | Doocs 技术社区,致力于打造一个内容完整、持续成长的互联网开发者学习生态圈!以下是 Doocs 旗下的一些优秀项目,欢迎各位开发者朋友持续保持关注。
52 |
53 | | # | 项目 | 描述 | 热度 |
54 | | --- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
55 | | 1 | [advanced-java](https://github.com/doocs/advanced-java) | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 | 
 |
56 | | 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。 | 
 |
57 | | 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 | 
 |
58 | | 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 | 
 |
59 | | 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 | 
 |
60 | | 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 | 
 |
61 | | 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 | 
 |
62 |
63 | ## 贡献者
64 |
65 | 感谢以下所有朋友对 [Doocs 技术社区](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 互联网公司 IT 技术面试题集
2 |
3 | [](https://github.com/doocs/coding-interview/blob/main/LICENSE)
4 | [](https://github.com/doocs/coding-interview/stargazers)
5 | [](https://github.com/doocs/coding-interview/issues)
6 | [](https://github.com/doocs/coding-interview/network/members)
7 | [](http://makeapullrequest.com)
8 |
9 | ## 项目介绍
10 |
11 | 本仓库用于记录各大互联网公司 IT 技术面试高频题以及经典书籍读书笔记,包括《剑指 Offer》、《编程之美》、《代码整洁之道》等,抽空更新中。
12 |
13 | ## 站点
14 |
15 | https://interview.doocs.org
16 |
17 | ## 书籍笔记
18 |
19 | ### [《剑指 Offer》](/docs/coding-interview.md)
20 |
21 | 这本书选取的[题目](/docs/coding-interview.md)都是被各大公司面试官反复采用的编程题,极具实战意义。当然,如果一开始觉得这些题目比较难,也是很正常的,因为大公司的面试本身就不简单。我们逐步掌握书中总结的解题方法之后,再去面试大公司,将会轻松很多。
22 |
23 | 推荐三个在线刷《剑指 Offer》的平台,个人比较倾向于 [LeetCode 中国](https://leetcode.cn/problemset/lcof/) 。
24 |
25 | - [LeetCode 中国](https://leetcode.cn/problemset/lcof/):近期上线,支持多种编程语言,墙裂推荐。
26 | - [AcWing](https://www.acwing.com/problem/):支持 Java11。团队成员来自北大清华,剑指 Offer 第二版题目都有。
27 | - [NowCoder](https://www.nowcoder.com/ta/coding-interviews):这个应该大多数人都知道,但是没有剑指 Offer 第二版新增的题目。
28 |
29 | ### [《代码整洁之道》](/docs/clean-code.md)
30 |
31 | 这本书名为 _Clean Code_,乃是 Object Mentor(鲍勃大叔开办的技术咨询和培训公司)一干大牛在编程方面的经验累积。写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的“整洁感”。
32 |
33 | 作者 Robert C. Martin 在书中阐述了代码各个方面如何做到整洁的经验与最佳实践。我们若能长期遵照这些经验编写代码,所谓“代码感”也就自然而然滋生出来。
34 |
35 | ### [《阿里巴巴 Java 开发手册》](/docs/effective-coding.md)
36 |
37 | 别人都说我们是搬砖的码农,但我们知道自己是追求个性的艺术家。也许我们不会过多在意自己的外表和穿着,但在我们不羁的外表下,骨子里追求着代码的美、系统的美、设计的美,代码规范其实就是一个对程序美的定义。
38 |
39 | ### [《枕边算法书》](/docs/algorithm-stories.md)
40 |
41 | 这本书,我是把它当作一本故事书来读的,里面的部分知识点还挺有意思。
42 |
43 | ### [《Effective Java》](/docs/effective-java.md)
44 |
45 | 这本书共包含了 78 个条目,每个条目讨论一条规则。它适用于任何具有实际 Java 工作经验的工程师,对于高级工程师,也能够提供一些发人深思的东西,是所有 Java 工程师必读书籍之一。
46 |
47 | ---
48 |
49 | ## Doocs 社区优质项目
50 |
51 | Doocs 技术社区,致力于打造一个内容完整、持续成长的互联网开发者学习生态圈!以下是 Doocs 旗下的一些优秀项目,欢迎各位开发者朋友持续保持关注。
52 |
53 | | # | 项目 | 描述 | 热度 |
54 | | --- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
55 | | 1 | [advanced-java](https://github.com/doocs/advanced-java) | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 | 
 |
56 | | 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。 | 
 |
57 | | 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 | 
 |
58 | | 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 | 
 |
59 | | 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 | 
 |
60 | | 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 | 
 |
61 | | 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 | 
 |
62 |
63 | ## 贡献者
64 |
65 | 感谢以下所有朋友对 [Doocs 技术社区](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | ## 公众号
74 |
75 | [Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
76 |
77 |
78 |
79 |
80 | 
81 | |
82 |
83 | 
84 | |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/algorithm-stories.md:
--------------------------------------------------------------------------------
1 | # 《枕边算法书》
2 |
3 | > 这里仅挑一些有意思的故事或知识点做个记录。
4 |
5 | - [“红色眼睛与褐色眼睛”谜题](#红色眼睛与褐色眼睛谜题)
6 | - [找出剩下的一个数](#找出剩下的一个数)
7 | - [说出 2199 年 7 月 2 日是星期几](#说出2199年7月2日是星期几)
8 | - [梅森素数](#梅森素数)
9 | - [杯中的水是否超过一半](#杯中的水是否超过一半)
10 |
11 | ## “红色眼睛与褐色眼睛”谜题
12 |
13 | 从前,有个小岛上只住着和尚。有些和尚的眼睛是红色的,而另一些则是褐色的。红色眼睛的和尚受到诅咒,如果得知自己的眼睛是红色的,那么当晚 12 点必须自行了断,无一例外。
14 |
15 | 和尚间有一条不成文的规定,就是彼此不能提起对方眼睛的颜色。小岛上没有一面镜子,也没有任何可以反射自己容貌的物体。因此,没有任何一个和尚能够得知自己眼睛的颜色。出于这些原因,每个和尚都过着幸福的日子。
16 |
17 | 有一天,岛上突然来了一位游客,她完全处于状况外。于是,她对和尚们说:“你们当中至少有一位的眼睛是红色的”。
18 |
19 | 
20 |
21 | 这名无心的游客当天就离开了小岛,而和尚们却因第一次听到有关眼睛颜色的话题而惴惴不安。当晚,小岛上开始出现了可怕的事情......
22 |
23 | 究竟是什么事呢?
24 |
25 | 这道题不简单却非常有意思,而一旦知道答案,又会觉得并不太难。这并非是那种荒谬的问题,要想解开需要一些逻辑推理,所以不要试图一下子解开。先花 2 分钟时间独立思考一下吧。
26 |
27 | ```
28 | if ((思考时间 > 2 分钟) || (已经知道答案了吗)) {
29 | 跳转至下一段
30 | } else {
31 | 返回上一段,并至少思考 2 分钟
32 | }
33 | ```
34 |
35 | 下面开始查看正确答案。
36 |
37 | 游客说,“至少有一个人”的眼睛是红色的。假如这岛上**没有任何一个和尚的眼睛是红色的**,那么这会导致最糟糕的结果。你想一想,对于和尚们来说,除了自己以外,看到的其它和尚的眼睛都是褐色的。因此,每个和尚都会认为自己的眼睛是红色的,可想而知,所有和尚当晚都会自杀。
38 |
39 | 如果**只有一名和尚的眼睛是红色的**,会出现什么结果呢?很简单,这名和尚知道其它和尚眼睛都是褐色的,那么就会判断出自己眼睛的颜色,进而选择自杀。游客的无心之言就这样夺走了一条生命。
40 |
41 | 考虑稍微复杂点的情况。假如**有两个红眼和尚**,那么他们各自都知道有一个红眼和尚,都以为说的是对方。这两个和尚心想:“那个红眼的家伙今晚就要自杀喽。”当晚,各自都安心入睡了。第二天,这两个和尚相互碰面,并看到对方没有自杀时,心理备受打击。他们都会意识到,红眼和尚有两个而非一个,而另一个正是自己。除此之外的任何情况都不可能让对方在第一个晚上不自杀而安然入睡。因此,受到极大打击的这两个红眼和尚在第二天晚上**都会悲惨死去**。
42 |
43 | 再考虑更复杂的情况。如果有 3 个红眼和尚,又会是怎样呢?平时,这 3 位会看到两个红眼和尚,所以听到游客的话后,都不会选择自杀。第一晚过后,他们又会想,另外两个和尚在第二天晚上都会自杀(就是前面探讨的“有两个红眼和尚”的情形)。到了第三天早上,看到本以为会自杀的另两个和尚并没有自杀时,根本没想到自己也是红眼和尚的这 3 人会同时受到极大的打击。因为,两个红眼和尚第二天晚上也没有自杀,这表明还有一个红眼和尚,而这第三个红眼和尚正是自己。
44 |
45 | 这种逻辑会反复循环。因此,该题的答案是“若小岛上共有 n 个红眼和尚,那么第 n 个晚上这些和尚会同时自杀”。例如,小岛上共有 5 个红眼和尚,那么第 5 个晚上,这 5 个红眼和尚会同时自杀。
46 |
47 | 这道题其实可以利用递归的方法。假设红眼和尚人数 N 为 10,那么我们可以适用 N 为 9 的逻辑。同理,N 为 8 或 7 时,都适用 `N-1` 时的逻辑。将 `N=1`,即 “只有一个红眼和尚” 视为终止条件,即可得出最终结果。这种过程与计算机算法中函数的递归调用过程完全相同。
48 |
49 | ## 找出剩下的一个数
50 |
51 | 有一个能保存 99 个数值的数组 `item[0], item[1],...item[98]`。从拥有 `1~100` 元素的集合 {1,2,3,...,100} 中,随机抽取 99 个元素保存到数组。集合中共有 100 个元素,而数组只能保存 99 个数值,所以集合中会剩下一个元素。编写程序,找出最后剩下的数。
52 |
53 | 还是先花 2 分钟想一想吧。
54 |
55 | 好了,这个问题其实非常简单,但没能正确理解题意的读者可能认为很难。答案如下代码所示。
56 |
57 | ```java
58 | int res = 5050;
59 | for (int i = 0; i < 99; ++i) {
60 | res -= item[i];
61 | }
62 | System.out.println("最后剩下的数是:" + res);
63 | ```
64 |
65 | 如果将集合的 100 个数值累加,会得到 5050。依次从 5050 减去数组中的 99 个数值,最后的数就是没能保存到数组的那个剩余数值。也许很多读者想到了与此相近的算法。即使没有得到正确答案也不用失望,因为真正应该感到失望的人是那些没能找到答案后轻易选择放弃、想要直接查看正确答案的人。
66 |
67 | ## 说出 2199 年 7 月 2 日是星期几
68 |
69 | 先公布答案吧,2199 年 7 月 2 日是星期二。其实可以靠运气蒙一下,准确率是 1/7。要想真正求出正确答案,过程并不简单。也许有些读者会自己设计精妙算法求出正确答案,但我还是想通过约翰•康威教授的“末日”算法进行说明。
70 |
71 | 末日算法虽然不是“游戏”,但在聚会中能够引起初次见面的异性的好奇。因此,为不少“花花公子”踏入数学殿堂做出了很大贡献。例如,“美丽的女士,请告诉我您的生日,让我猜猜是星期几。” “请您随便说一个年份,我会猜出当年的情人节是星期几”。虽然听起来比较肉麻,不过这样就能一下子吸引对方的注意。
72 |
73 | 康威教授的末日算法执行环境就是我们今天使用的“**公历**”环境。
74 |
75 | 首先,先理清楚**什么是闰年**。闰年是年份能被 4 整除但不能被 100 整除,或者能被 400 整除的年份。闰年 2 月有 29 天,而平年 2 月是 28 天。
76 |
77 | ```java
78 | // 判断是否是闰年
79 | boolean isLeapYear(int year) {
80 | return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
81 | }
82 | ```
83 |
84 | 康威末日算法的运行原理非常简单。为了判断不同日期的星期,算法中首先**设立一个必要的“基准”**。然后,**根据星期以 7 为循环的原则和对闰年的考虑**,计算日期对应的星期。
85 |
86 | 平年时,将 2.28 日设置为“末日”;到了闰年,将 2.29 日设置为“末日”。只要知道了特殊年份(e.g. 1900 年) “末日”的星期,那么根据康威算法即可判断其它日期的星期。
87 |
88 | 我们都知道,星期以 7 为循环,所以与“末日”以 7 的倍数为间隔的日期就和“末日”具有相同的星期。利用这个原理,先记住每个月中总是与“末日”星期相同的一个日期,即可快速算出结果。
89 |
90 | 每个月与“末日”具有相同星期的一天分别是:
91 |
92 | ```
93 | 4.4、6.6、8.8、10.10、12.12、9.5、5.9、7.11、11.7、3.7
94 | ```
95 |
96 | 只需要记住 4、6、8、10、12 这几个月与日的数字相同,然后是 9.5、5.9、7.11、11.7,这几个是对称的,还有一个是 3.7。是不是很容易记住?
97 |
98 | 好了,那么我们**只要知道当年的“末日”是星期几,就可以推算出当年的任何一天是星期几了**。
99 |
100 | 举个例子吧。2003 年的“末日”是星期五,我们推算一下那一年的圣诞节的星期。由于 2003 年“末日”是星期五,所以 12 月 12 日也是星期五(我们上面记住了每个月与“末日”具有相同星期的一天),那么 `12+7*2=26`,12 月 26 日也是星期五,所以 12 月 25 日是星期四。
101 |
102 | 那么问题来了,**怎么才能知道某一年的“末日”是星期几呢**?
103 |
104 | 这种情况下,需要记住“末日”的星期每跨 1 年就会加 1,若遇到闰年就会加 2。
105 |
106 | 例如,1900 年的“末日”是星期三,那么 1901 年的“末日”是星期四(+1),1902 年的“末日”是星期五(+1),1903 年的“末日”是星期六(+1),而 1904 年(闰年)的“末日”是“星期一”(+2)。
107 |
108 | 就是说,我们记住了 1900 年“末日”是星期三,就可以推算出其它年份的“末日”是星期几了。
109 |
110 | 这样一个个推算还是很麻烦,可能一不小心就推错了。为此,康威教授贴心地给我们提供了如下形式的列表。
111 |
112 | ```
113 | 6, 11.5, 17, 23, 28, 34, 39.5, 45, 51, 56, 62, 67.5, 73, 79, 84, 90, 95.5
114 | ```
115 |
116 | 就是说,1900 年“末日”是星期三,那么 1906,1917,1923... “末日”也是星期三, 11.5 表示 1911 年的“末日”是星期二(-1),而 1912 年的“末日”是星期四(+1)。记住这个列表,我们就能够算出所有 20 世纪年份的“末日基准”了。
117 |
118 | 如果一个美丽的姑娘说“我的生日是 1992.9.13” 时,我们可以马上说出当天的星期。既然康威列表有 90 这个数字,表示 1990 年的“末日”也是星期三,那么 1901 年(平年)“末日”是星期四(+1),1902 年(闰年)“末日”是星期六(+2),所以 9.5/9.12 也是星期六,1992.9.13 就是星期日。
119 |
120 | 不过,**年份跨越世纪时,康威列表就会失去作用**。
121 |
122 | 题目中问的是 2199.7.2 的星期,如果不能得知 2199 年“末日”是星期几,那么这道题很难求解。对于不同世纪的年份,没有什么特别的方法能够猜出“末日”的星期。只能将被 100 整除的年份表示为日历形式时,从中得到一些规律而已。
123 |
124 | | 日 | 一 | 二 | 三 | 四 | 五 | 六 |
125 | | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
126 | | 1599 | | 1600 | 1601 | 1602 | | |
127 | | 1700 | 1701 | 1702 | 1703 | | 1704 | 1705 |
128 | | | 1796 | 1797 | 1798 | 1799 | 1800 | 1801 |
129 | | 1897 | 1898 | 1899 | 1900 | 1901 | 1902 | 1903 |
130 | | 1999 | | | 2000 | 2001 | 2002 | 2003 |
131 | | 2100 | 2101 | 2102 | 2103 | | 2104 | 2105 |
132 | | | 2196 | 2197 | 2198 | 2199 | 2200 | 2201 |
133 |
134 | 这道题看似简单,但其实不仅需要了解“末日”算法,还需要深入了解上述模式。上面的日历中,2199 年的“末日”是星期四,所以 2199.7.11/2199.7.4 也是星期四,所以 2199.7.2 是星期二。
135 |
136 | 感受到康威教授末日算法的精妙之处了吧。
137 |
138 | ## 梅森素数
139 |
140 | 马林•梅森是法国哲学家、修道士。16 世纪,数论领域存在着一个错误的假设,而一直被认为是事实。根据这个假设,对所有素数 p,2p-1 也是素数。将素数 2,5,7 带入,结果均为负数。
141 |
142 | 从直观角度看,对素数 p,总有 2p-1 也是素数的假设成立。不过,仅仅通过几个结果就想判断命题真伪,这在数学中是最“无知”的行为。这种代入几个变量进行的测试往往以程序能够正常运行的“晴天”作为前提条件,如果遇到“雨天”,这种只经过松散测试的程序会发生很多意想不到的问题。算法的内部逻辑应该紧凑,不给 Bug 任何可乘之机。
143 |
144 | 后来,人们最终证明,p 为素数时,2p-1 的结果不一定是素数。虽然如此,有些人还是好奇,p 是什么样的素数时,2p-1 结果将为素数。为了解答这种好奇,梅森在 1644 年发表的论文里提出了如下主张:
145 |
146 | > “若 p 为 2、3、5、7、13、17、19、31、67、127、257 之一,那么 2p-1 的结果是素数。”
147 |
148 | 梅森一直希望将存在的所有素数都表示为 2p-1 这种短小而精简的公式形式。若真能找到那样一个公式,将是美丽得让人窒息的、绝妙的数学发现。不过,梅森的梦想没能实现。
149 |
150 | 随着时间的流逝,后世数学家们通过计算得出,应当删除梅森假设中的 67 和 257,而可以添加 61、89、107。就这样,从前简洁而“有理”的命题 “若 p 是素数,则 2p-1 也是素数” 已消失不见,而留下的 “p 为某值时,结果为素数,否则不是素数”等杂乱的 if-else 语句正让算法变得越来越杂乱不堪。
151 |
152 | 实际编程中,如果越来越复杂的 `if-else` 语句影响程序简洁性,那么到了某一时刻,程序员就会考虑“重构”,对于算法也是一样。后来,人们将精简的新算法献给一生都在祈祷和学习的修道士梅森:
153 |
154 | > “如果 p 为素数时 2p-1 也是素数,那么此素数为梅森素数。”
155 |
156 | ## 杯中的水是否超过一半
157 |
158 | 空房间中有个圆柱形水杯,杯口和杯底直径相同,里面有半杯左右的水。找出方法,判断杯中水超过一半还是不到一半。空荡荡的房间中没有任何可使用的器具或工具。
159 |
160 | 答案本身非常简单,不过能够真正求解的人却寥寥无几。想问题的时候,请不要考虑房间或水的温度,以及化学反应等“不讲理”的方法。另外,不允许喝杯子里的水。
161 |
162 | 
163 |
164 | 即使读完题没能马上想起答案,但看到插图后能够立刻明白,也可以说很有编程的感觉。将杯子倾斜,使水面刚好到达杯口时,查看杯底的水就能得出答案了。
165 |
166 | 算法的编写与之大体相同。各位因为找不到突破口而郁闷时,甚至会怀疑给出的问题究竟有没有解。然而找到突破口后,再回首会发现,原来解决之道竟如此简单。
167 |
--------------------------------------------------------------------------------
/docs/clean-code.md:
--------------------------------------------------------------------------------
1 | # 《代码整洁之道》
2 |
3 | ## 代码猴子与童子军军规
4 |
5 | 1. 我们就像一群代码猴子,上蹿下跳,自以为领略了编程的真谛。可惜,当我们抓着几个酸桃子,得意洋洋坐到树枝上,却对自己造成的混乱熟视无睹。那堆“可以运行”的乱麻程序,就在我们的眼皮底下慢慢腐坏。
6 |
7 | ## 第一章 整洁代码
8 |
9 | 1. 勒布朗法则:稍后等于永不(Later equals never)。
10 | 1. 制造混乱无助于赶上期限。混乱只会立刻拖慢你,叫你错过期限。赶上期限的唯一方法——做的快的唯一方法——就是始终尽可能保持代码整洁。
11 | 1. Javadoc 中的 `@author` 字段告诉我们自己是什么人。我们是作者,作者都有读者。实际上,作者有责任与读者做良好沟通。下次你写代码的时候,记得自己是作者,要为评判你工作的读者写代码。
12 |
13 | ## 第三章 函数
14 |
15 | 1. 函数的第一规则是要短小。第二条规则是还要更短小...经过漫长的试错,经验告诉我,函数就该小。
16 | 1. 函数应该做一件事。做好这件事。只做这一件事。判断函数是否不止做了一件事,有一个方法,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。
17 | 1. 要确保函数只做一件事,函数中的语句都要在同一抽象层级上。函数中混杂不同的抽象层级,往往让人迷惑。读者可能无法判断某个表达式是基础概念还是细节。更恶劣的是,就像破损的窗户,一旦细节与基础概念混杂,更多的细节就会在函数中纠结起来。
18 | 1. 写出短小的 `switch` 语句往往很难。写出只做一件事的 `switch` 语句也很难。我们总无法避开 `switch` 语句,不过还是能够确保 `switch` 都埋藏在较低的抽象层级,而且永远不重复。
19 | 1. 最理想的参数数量是零,其次是一,再次是二...从测试的角度看,参数甚至更叫人为难。想想看,要编写能确保参数的各种组合运行正常的测试用例,是多么困难的事。如果没有参数,就是小菜一碟。
20 | 1. 函数承诺只做一件事,但还是会做其它被藏起来的事。有时,它会对自己类中的变量做出未能预期的改动,导致古怪的时序性耦合及顺序依赖。
21 |
22 | ```java
23 | public class UserValidator {
24 | private Cryptographer cryptographer;
25 |
26 | public boolean checkPassword(String userName, String password) {
27 | User user = UserGateway.findByName(userName);
28 | if (user != null) {
29 | String codePhrase = user.getPhraseEncodeByPassword();
30 | String phrase = cryptographer.decrypt(codePhrase, password);
31 | if ("Valid Password".equals(phrase)) {
32 | Session.initialize();
33 | return true;
34 | }
35 | }
36 | return false;
37 | }
38 | }
39 | ```
40 |
41 | 副作用就在于对 `Session.initialize()` 的调用。`checkPassword` 函数是用来检查密码的。该名称未暗示它会初始化该次会话。当某个误信了函数名的调用者想要检查用户有效性时,就得冒着抹除现有会话数据的风险。这一副作用造成了一次时序性耦合。也就是说,`checkPassword` 只能在特定时刻调用。
42 |
43 | ## 第四章 注释
44 |
45 | 1. 注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。注释总是一种失败。我们总无法找到不用注释就能表达自我的方法,所以总要有注释,这并不值得庆贺。
46 | 2. 如果你发现自己需要写注释,再想想看是否有办法翻盘,用代码来表达。
47 | 3. 有时,有理由用 `// TODO` 形式在源代码中放置要做的工作列表。`TODO` 是一种程序员认为应该做,但由于某些原因目前还没做的工作。
48 | 4. 没有什么比被良好描述的公共 API 更有用和令人满意的了。如果你在编写公共 API,就该为它编写良好的 Javadoc。
49 | 5. 删掉无用而多余的 Javadoc 吧,这些注释只是一味将代码搞得含糊不明,完全没有文档上的价值。
50 | 6. 所谓每个函数都要有 Javadoc 或每个变量都要有注释的规矩全然是愚蠢可笑的。这类注释徒然让代码变得散乱,满口胡言,令人迷惑不解。
51 | 7. 20 世纪 60 年代,曾经有那么一段时间,注释掉的代码可能有用。但我们已经拥有优良的源代码控制系统如此之久,这些系统可以为我们记住不要的代码。我们无需再用注释来标记,删掉即可,它们丢不了,我担保。
52 |
53 | ## 第五章 格式
54 |
55 | 1. 代码格式很重要,必须严肃对待。代码格式关乎沟通,而沟通是专业开发者的头等大事。
56 | 1. 你今天编写的功能,极有可能在下一版本中被修改,但代码的可读性却会对以后可能发生的修改行为产生深远影响。原始代码修改之后很久,其代码风格和可读性仍会影响到可维护性和扩展性。即便代码不复存在,你的风格和律条仍会存活下来。
57 | 1. 若某个函数调用了另外一个,就应该把它们放在一起,而且调用者应该尽可能放在被调用者上面。
58 |
59 | ## 第六章 对象和数据结构
60 |
61 | 1. 最为精炼的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象,或 `DTO`(Data Transfer Objects)。DTO 是非常有用的结构,尤其是在于数据库通信、或解析套接字传输的消息之类的场景中。
62 |
63 | ## 第七章 使用异常而非返回码
64 |
65 | 1. 在很久以前,许多语言都不支持异常。这些语言处理和汇报错误的手段都有限。你要么设置一个错误标识,要么返回给调用者检查的错误码。这类手段的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即可检查错误。不幸的是,这个步骤很容易被遗忘。最好是抛出一个异常,这样其逻辑不会被错误处理搞乱。
66 | 1. 使用不可控异常。可控异常 `checked exception` 的代价是违反开闭原则。如果你在方法中抛出可控异常,而 catch 语句在三个层级之上,你就得在 catch 语句和抛出异常处之间的每个方法签名中声明该异常。这意味着对软件中低层级的修改,都将涉及较高层级的签名。最终得到的就是一个从软件最底端贯穿到最高端的修改链。
67 | 1. 别返回 null 值。我不想去计算曾经见过多少每行代码都在检查 null 值的应用程序。Java 中有 `Colletions.emptyList()` 方法,该方法返回一个预定义不可变列表,这样编码,就能尽量避免 `NullPointerException` 的出现,代码也就更整洁了。
68 | 1. 别传递 null 值。在大多数编程语言中,没有良好的方法能对付由调用者意外传入 null 值。事已如此,恰当的做法就是禁止传入 null 值。
69 |
70 | ## 第八章 边界
71 |
72 | 1. 第三方代码帮助我们在更少时间内发布更丰富的功能。在利用第三方程序包时,该从何处入手呢?我们没有测试第三方代码的职责,但为要使用的第三方代码编写测试,可能最符合我们的利益。
73 | 1. 学习第三方代码很难,整合第三方代码也很难,同时做这两件事难上加难。不要在生产代码中试验新东西,而是编写测试来遍览和理解第三方代码,这叫“学习性测试”。
74 |
75 | ## 第九章 单元测试
76 |
77 | 1. TDD 三定律:
78 | - **定律一** 在编写不能通过的单元测试前,不可编写生产代码。
79 | - **定律二** 只可编写刚好无法通过的单元测试,不能编译也算不通过。
80 | - **定律三** 只可编写刚好足以通过当前失败测试的生产代码。
81 | 1. TDD 三定律其实说的是,先写失败的 Case,写完之后才开始写功能 Code,只要 Code 通过了 Case,就不要再写功能代码了。也就是说,写完一个测试,就要写对应的生产代码。
82 | 1. 测试代码和生产代码一样重要。它可不是二等公民。它需要被思考、被设计和被照料。它该像生产代码一般保持整洁。
83 | 1. 如果测试不能保持整洁,你就会失去它们。没有了测试,你就会失去保证生产代码可扩展的一切要素。有了测试,你就不担心对代码的修改!没有测试,每次修改都可能带来缺陷。
84 | 1. 覆盖了生产代码的自动化单元测试程序组能尽可能地保持设计和架构的整洁。测试带来了一切好处,因为测试使改动变得可能。
85 | 1. 整洁的测试有三个要素:可读性、可读性、可读性。测试应该明确、简洁,还有足够的表达力。在测试中,要以尽量少的文字表达大量的内容。
86 | 1. F.I.R.S.T 规则:
87 | - **Fast**(快速) 测试应该能快速运行。测试运行缓慢,你就不会想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正。
88 | - **Independent**(独立) 测试应该相互独立。某个测试不应为下一个测试设定条件。你应该可以单独运行每个测试,及以任何顺序运行测试。
89 | - **Repeatable**(可重复) 测试应当可在任何环境中重复通过。
90 | - **Self-Validating** (自足验证) 测试应该有布尔值输出。
91 | - **Timely**(及时) 测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。
92 |
93 | ## 第十章 类
94 |
95 | 1. 面向对象的其中一个设计原则是“开放——闭合原则”,即类应当对扩展开放,对修改封闭。我们希望将系统打造成在添加或修改特性时尽可能少惹麻烦的架子。在理想系统中,我们通过扩展系统而不是修改现有代码来添加新特性。
96 | 1. 类的另一条设计原则是“依赖倒置原则”(Dependency Inversion Principle, DIP),DIP 认为类应该依赖于抽象而不是依赖于具体细节。
97 |
98 | ## 第十一章 系统
99 |
100 | 1. 有一种强大的机制可以实现分离构造与使用,那就是依赖注入(Dependency Injection, DI),它是控制反转(Inversion of Control, IoC)在依赖管理中的一种应用手段。控制反转将第二权责从对象中拿出来,转移到另一个专注于此的对象中,从而遵循了**单一权责原则**。在依赖管理情境中,对象不应负责实体化对自身的依赖,而应当将这份权责移交给其它“有权力”的机制,从而实现控制的反转。
101 | 1. “一开始就做对系统”纯属神话。反之,我们应该只去实现今天的用户故事,然后重构,明天再扩展系统、实现新的用户故事。这就是迭代和增量敏捷的精髓所在。
102 |
103 | ## 第十二章 迭进
104 |
105 | 1. 简单设计的四条规则,按重要程度排序:
106 | - 运行所有测试;
107 | - 不可重复;
108 | - 表达了程序员的意图;
109 | - 尽可能减少类和方法的数量。
110 | 1. 全面测试并持续通过所有测试的系统,就是可测试的系统。看似浅显,但却重要。不可测试的系统同样不可验证。不可验证的系统,绝不应该部署。
111 | 1. 重复是拥有良好设计系统的大敌,它代表着额外的工作、额外的风险和额外且不必要的复杂度。要想创建整洁的系统,需要有消除重复的意愿。
112 | 1. 软件项目的主要成本在于长期维护。代码应当清晰地表达其作者的意图。作者把代码写得越清晰,其他人花在理解代码上的时间也就越少,从而减少缺陷,缩减维护成本。
113 | 1. 为了保持类和函数短小,我们可能会造出太多的细小类和方法。所以这条规则也主张函数和类的数量要少。我们的目标是在保持函数和类短小的同时,保持整个系统短小精悍。不过更重要的是测试、消除重复和表达力。
114 |
115 | ## 第十三章 并发编程
116 |
117 | 1. 并发是一种解耦策略。它帮助我们把**做什么**(目的)和**何时做**(时机)分解开。解耦目的与时机能明显地改进应用程序的吞吐量和结构。
118 | 1. 并发有时能改进性能,但只在多个线程或处理器之间能分享大量等待时间的时候管用,事情没那么简单。
119 | 1. 并发算法的设计有可能与单线程系统的设计极不相同。目的与时机的解耦往往对系统结构产生巨大影响。
120 | 1. 并发编程中的一些基础定义:
121 | - **限定资源**:并发环境中有着固定尺寸或数量的资源。
122 | - **互斥**:每一时刻仅有一个线程能访问共享数据或共享资源。
123 | - **线程饥饿**:一个或一组线程在很长时间内或永久被禁止。例如,总是让执行得快的线程先运行,加入执行得快得线程没完没了,则执行时间长的线程就会“饥饿”。
124 | - **死锁**:两个或多个线程互相等待执行结束。每个线程都拥有其它线程需要的资源,得不到其它线程拥有的资源,就无法终止。
125 | - **活锁**:执行次序一致的线程,每个都想要起步,但发现其它线程已经“在路上”。由于竞步的原因,线程会持续尝试起步,但在很长时间内却无法如愿,甚至永远无法启动。
126 |
127 | ## 第十四章 逐步改进
128 |
129 | 1. 代码能工作还不够,能工作的代码经常会严重崩溃。满足于仅仅让代码工作的程序员不够专业。他们会害怕没时间改进代码的结构和设计,我不敢苟同。没什么比糟糕的代码给开发项目带来更深远和长期的损害了。
130 | 1. 进度可以重订,需求可以重新定义,团队动态可以修正。糟糕的代码只会一直腐败发酵,无情地拖着团队的后腿。
131 | 1. 保持代码持续整洁和简单,永不让腐坏有机会开始。
132 |
133 | ## 第十五章 JUnit 框架
134 |
135 | 1. 成员变量的前缀可以删除。在现今的运行环境中,这类范围性编码纯属多余。
136 | 1. 条件判断应当封装起来,从而更清晰地表达代码的意图。可以拆解处一个方法,解释这个条件判断。
137 |
138 | ```java
139 | public String compact(String message) {
140 | if (expected == null || actual == null || areStringsEqual()) {
141 | return Assert.format(message, expected, actual);
142 | }
143 | }
144 |
145 | // 拆解后...
146 | public String compact(String message) {
147 | if (shouldNotCompact()) {
148 | return Assert.format(message, expected, actual);
149 | }
150 | }
151 |
152 | private boolean shouldNotCompact() {
153 | return expected == null || actual == null || areStringsEqual();
154 | }
155 | ```
156 |
157 | ## 第十七章 味道与启发
158 |
159 | 1. 让注释传达本该更好地在源代码控制系统、问题追踪系统或任何其它记录系统中保存的信息,是不恰当的。
160 | 1. 除函数签名之外什么也没说的 Javadoc,也是多余的。
161 | 1. 看到注释掉的代码,就删除它!别担心,源代码控制系统还会记得它。
162 | 1. 每次看到重复代码,都代表遗漏了抽象。将重复代码叠放进类似的抽象,增加了你的设计语言的词汇量。其它程序员可以用到你创建的抽象设施。编码变得越来越快,错误越来越少,因为你提升了抽象层级。
163 | 1. 死代码就是不执行的代码,可以在检查不会发生的条件的 if 语句中找到,可以在从不抛出异常的 try/catch 块中找到,可以在从不调用的小工具方法中找到,也可以在不会发生 switch/case 条件中找到。如果你找到死代码,就体面地埋葬它,将它从系统中删除掉。
164 | 1. 特性依恋是 Martin Fowler 提出的代码味道之一。类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其它类中的变量和函数。我们要消除特性依恋。
165 | 1. 用多态替代 if/else 或 switch/case。对于给定的选择类型,不应有多于一个 switch 语句。在那个 switch 语句中的多个 case,必须创建多态对象,取代系统中其它类似 switch 语句。
166 | 1. 用命名常量替代魔术数。
167 | 1. 现在 enum 已经加入 java 语言了,放心用吧!别再用那个 `public static final int` 老花招。那样做 int 的意义就丧失了,而用 enum 则不然,因为它们隶属于有名称的枚举。
168 |
--------------------------------------------------------------------------------
/docs/effective-coding.md:
--------------------------------------------------------------------------------
1 | # 《Effective Coding——阿里巴巴 Java 开发手册》
2 |
3 | ## 第一章 编程规约
4 |
5 | ### 命名风格
6 |
7 | 1. 包名统一采用**单数**形式,但是类名如果有复数含义,则类名可以使用复数形式。e.g. `com.alibaba.ai.util.MessageUtils`
8 |
9 | ### 常量定义
10 |
11 | 1. 如果一个变量值仅在一个范围内变化,则用 enum 类型来定义。
12 |
13 | ### 代码格式
14 |
15 | 1. `if/for/while/switch/do` 等保留字与括号之间都**必须加空格**。
16 | 1. 采用 4 个空格缩进,禁止使用 Tab 控制符。
17 | 1. 注释的双斜线与注释内容之间**有且仅有一个空格**。e.g. `// 这是示例注释`
18 | 1. 单行字符数不超过 120 个,超出则需要换行,换行遵循:
19 | - 第二行相对第一行**缩进 4 个空格**,从第三行开始,不再缩进。
20 | - 运算符与下文一起换行。
21 | - 方法调用的点符号与下文一起换行。
22 | - 方法调用的点符号与下文一起换行时,在逗号后进行。
23 | - 在括号前不要换行。
24 |
25 | ```java
26 | // 正例
27 | StringBuffer sb = new StringBuffer();
28 | sb.append("zi").append("xin")...
29 | .append("huang")...
30 | .append("huang")...
31 | .append("huang");
32 |
33 | // 反例
34 | StringBuffer sb = new StringBuffer();
35 | sb.append("ge").append("cheng")...append
36 | ("no line break here");
37 |
38 | // 方法参数超过 120 个字符时,不要在逗号前换行
39 | method(args1, args2, args3, ...
40 | , argsX);
41 | ```
42 |
43 | 5. IDE 的 text file encoding 设置为 UTF-8;IDE 文件的换行符使用 UNIX 格式,不要使用 Windows 格式。
44 | 1. **没有必要**增加若干空格来使某一行的字符与上一行对应位置的字符对齐。
45 |
46 | ### OOP 规约
47 |
48 | 1. 对外部正在调用的接口,不允许修改方法签名,以避免对接口调用方产生影响。若接口过时,必须加 `@Deprecated` 注解,并清晰地说明采用的新接口或者新服务是什么。
49 |
50 | ### 集合处理
51 |
52 | 1. **所有**相同类型的包装类对象之间值的比较,全部使用 equals 方法。
53 | 1. 构造方法里面**禁止**加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
54 | 1. 慎用 Object 的 clone 方法来拷贝对象。
**说明**:对象的 clone 方法**默认是浅拷贝**,若想实现深拷贝,需要重写 clone 方法。
55 | 1. 关于 hashCode 和 equals 的处理,遵循如下规则:
56 | - 只要重写 equals,就必须重写 hashCode;
57 | - 因为 Set 存储的是不重复对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
58 | - 如果自定义对象作为 Map 的键,那么必须重写这两个方法。
59 | - **说明**:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地将 String 对象作为 key 来使用。
60 | 1. ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 `ClassCastException` 异常。
**说明**:subList 是 ArrayList 的一个视图,对于 subList 子列表的所有操作最终会反映到原列表上。
61 | 1. 在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生 `ConcurrentModificationException`。
62 |
63 | ```java
64 | List list = new ArrayList<>();
65 | int count = 5;
66 | for (int i = 0; i < count; ++i) {
67 | list.add(i + 1);
68 | }
69 |
70 | // 子列表
71 | List subList = list.subList(0, list.size() - 1);
72 |
73 | // 对原集合元素个数修改
74 | list.add(11);
75 |
76 | // 导致子列表异常
77 | // Exception in thread "main" java.util.ConcurrentModificationException
78 | System.out.println(subList);
79 | ```
80 |
81 | 7. 在使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,否则会抛出 `UnsupportedOperationException` 异常。
**说明**:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。体现的是适配器模式,只是转换接口,后台的数据仍是数组。
82 |
83 | ```java
84 | String[] str = new String[] {"you", "wu"};
85 | List list = Arrays.asList(str);
86 |
87 | // list.add("bingo") 运行时异常
88 |
89 | str[0] = "bingo";
90 | // list.get(0) 也会随着修改。
91 | ```
92 |
93 | 8. 在集合初始化时,指定集合初始值大小。若 HashMap 需要放置 1024 个元素,由于没有设置初始大小(默认 16),随着元素不断增加,容量被迫扩大 7 次,resize 需要重建 hash 表,这严重影响性能。
94 | 1. 使用 entrySet 遍历 Map 类集合 K/V,而不是 keySet 方式遍历。如果时 JDK8,使用 Map.foreach() 方法。
95 | 1. **高度注意** Map 类集合 K/V 能不能存储 null 值的情况。由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值的,而事实上,存储 null 值时会抛出 NPE 异常。
96 |
97 | | 集合类 | Key | Value | Supper | 说明 |
98 | | ----------------- | ----------------- | ----------------- | ----------- | ---------- |
99 | | Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
100 | | ConcurrentHashMap | **不允许**为 null | **不允许**为 null | AbstractMap | 锁分段技术 |
101 | | TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
102 | | HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |
103 |
104 | 11. 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。
105 |
106 | ### 并发处理
107 |
108 | 1. 在创建线程或线程池时,请指定有意义的线程名称,方便出错时回溯。
109 |
110 | ```java
111 | public class TimeTaskThread extends Thread {
112 | public TimeTaskThread() {
113 | super.setName("TimeTaskThread");
114 | // ...
115 | }
116 | }
117 | ```
118 |
119 | 2. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
**说明**:使用线程池的好处是减少在创建和销毁线程上所消耗的时间及系统资源,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大流量同类线程而导致消耗完内存或者“过度切换”的问题。
120 | 1. 在对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则**可能会造成死锁**。
**说明**:如果线程一需要对表 A/B/C 依次加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A/B/C,否则可能出现死锁。
121 | 1. volatile 解决多线程内存不可见问题。对于一写多读,可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
122 |
123 | ### 控制语句
124 |
125 | 1. 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,**都必须**包含一个 default 语句并且放在最后,即使它什么代码都没有。
126 | 1. 在高并发场景中,**避免使用**“等于”判断作为中断或退出的条件。
**说明**:如果并发控制没有处理好,容易产生等值判断被击穿的情况,应使用大于或小于的区间判断条件来代替。
127 | 1. 不要在条件判断中执行其它复杂的语句,可将复杂逻辑判断的结果赋值给一个**有意义的布尔变量名**,以提高可读性。
128 |
129 | ### 注释规约
130 |
131 | 1. 特殊注释标记。TODO 实际上是一个 Javadoc 的标签,虽然目前的 Javadoc 还没有实现,但已经被广泛使用,且**只能应用于类、接口和方法上**。在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正。
132 |
133 | ### 其他
134 |
135 | 1. 注意 Math.random() 这个方法返回的是 double 类型,取值范围 x ∈ [0, 1),如果想获得整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。
136 |
137 | ## 第二章 异常日志
138 |
139 | ### 异常处理
140 |
141 | 1. catch 时请分清稳定代码和不稳定代码。稳定代码指的是无论如何都不会出错的代码。对于非稳定代码的 catch,尽可能在进行异常类型的区分后,再做对应的异常处理。
142 | 1. 不要在 finally 块中使用 return。
**说明**:当 finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
143 | 1. 定义时区分 unchecked/checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如 DAOException/ServiceException 等。
144 |
145 | ### 日志规约
146 |
147 | 1. 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API。使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
148 |
149 | ```java
150 | import org.slf4j.Logger;
151 | import org.slf4j.LoggerFactory;
152 |
153 | private static final Logger logger = LoggerFactory.getLogger(Abc.class);
154 | ```
155 |
156 | 2. 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并及时删除这些观察日志。
157 |
158 | ## 第三章 单元测试
159 |
160 | 1. 单元测试是可重复执行的,不能受到外界环境的影响。
161 | 1. 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。
162 | 1. 单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。
163 |
164 | ## 第四章 安全规约
165 |
166 | 1. 针对发帖、评论、发送即时消息等用户生成内容的场景,必须实现防刷、文本内容违禁词过滤等风控策略。
167 |
168 | ## 第五章 MySQL 数据库
169 |
170 | ### 建表规约
171 |
172 | 1. 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型为 `unsigned tinyint`。
**说明**:任何字段如果为非负数,则必须是 unsigned。
173 | 1. 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。e.g. 商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,**避免关联查询**。冗余字段遵循:
174 | - 不是频繁修改的字段;
175 | - 不是 varchar 超长字段,更不能是 text 字段。
176 |
177 | ### 索引规约
178 |
179 | 1. 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
180 | 1. 页面搜索严禁左模糊或者全模糊,如果需要请通过搜索引擎来解决。
**说明**:索引文件具有 B-Tree 的**最左前缀匹配特性**,如果左边的值未确定,那么无法使用此索引。
181 | 1. 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
182 | - **正例**:where a=? and b=? order by c; 索引: a_b_c。
183 | - **反例**:索引中有范围查找,那么索引有序性无法利用,如 WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
184 | 1. 利用延迟关联或者子查询优化超多分页场景。
**说明**:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 的行,返回 N 行。当 offset 特别大的时候,效率会非常的低下,要么控制返回的总页数,要么对超过阈值的页数进行 SQL 改写。
185 | 1. 建组合索引的时候,区分度最高的在最左边。
186 | 1. SQL 性能优化的目标,至少要达到 range 级别,要求是 ref 级别,最好是 consts。
187 |
188 | ### SQL 语句
189 |
190 | 1. 不要使用 count(列名) 或 count(常量) 来替代 count(\*),count(\*) 是 SQL92 定义的标准统计行数的语句,跟数据库无关,跟 NULL 和非 NULL 无关。
**说明**:count(\*) 会统计值为 NULL 的行,而 count(列名) 不会统计此列为 NULL 值的行。
191 | 1. `count(distinct column)` 计算该列除 NULL 外的不重复行数。注意,`count(distinct column1,column2)` 如果其中一列全为 NULL,那么即使另一列用不同的值,也返回为 0。
192 | 1. 当某一列的值全为 NULL 时,`count(column)` 的返回结果为 0,但 `sum(column)` 的返回结果为 NULL,因此使用 sum() 时需注意 NPE 问题。
可以使用如下方式来避免 sum 的 NPE 问题。
193 |
194 | ```sql
195 | SELECT IF(ISNULL(SUM(g), 0, SUM(g))) FROM table;
196 | ```
197 |
198 | 4. 使用 `ISNULL()` 来判断是否为 NULL 值。
**说明**:NULL 与任何值的直接比较都为 NULL。
199 | 1. 不得使用外键与级联,一切外键概念必须在应用层解决。
**说明**:以学生和成绩的关系为例,学生表的 student_id 是主键,成绩表的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为**级联更新**。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
200 | 1. **禁止使用存储过程**。存储过程难以调试和扩展,更没有移植性。
201 | 1. `in` 操作能避免则避免。若实在避免不了,需要仔细评估 in 后面的集合元素数量,控制在 1000 个之内。
202 |
203 | ### ORM 映射
204 |
205 | 1. POJO 类的布尔属性不能加 is,而数据库字段必须加 is\_,要求在 resultMap 中进行字段与属性的映射。
206 | 1. `sql.xml` 配置参数使用:`#{}, #param#`,不要使用 \${},此种方式容易出现 SQL 注入。
207 | 1. `@Transactional` 事务不要滥用。事务会影响数据库的 QPS。另外,使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
208 |
209 | ## 第六章 工程结构
210 |
211 | ### 应用分层
212 |
213 | 1. 在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行 catch,因此使用 `catch(Exception e)` 方式,并 throw new `DAOException(e)`,不需要打印日志,因为日志在 Manager/Service 层,一定需要捕获并写到日志文件中去。如果同台服务器再写日志,会浪费性能和存储。
214 |
215 | ### 二方库依赖
216 |
217 | 1. 定义 GAV 遵从以下规则:
218 | - GroupID 格式:com.{公司/BU}.业务线.\[子业务线\],最多 4 级。e.g. `com.taobao.jstorm`
219 | - ArtifactID 格式:产品线名-模块名。语义不重复不遗漏。e.g. `dubbo-client、fastjson-api、jstorm-tool`
220 | - Version 格式:主版本号.次版本号.修订号。
221 | 1. 线上应用不要依赖 SNAPSHOT 版本。
**说明**:不依赖 SNAPSHOT 版本是保证应用发布的幂等性。另外,也可以加快编译时的打包构建。
222 |
223 | ### 服务器
224 |
225 | 1. 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
**说明**:操作系统默认 240s 后才会关闭 time_wait 状态的连接。在高并发访问下,服务器端会因为处于 time_wait 的连接数过多,而无法建立新的连接,所以需要在服务器上调小此等待值。
226 | 1. 给 JVM 设置 `-XX:+HeapDumpOnOutOfMemoryError` 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。
**说明**:OOM 的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错非常有价值。
227 | 1. 在线上生产环境,JVM 的 Xms 和 Xms 设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。
228 |
229 | ## 第七章 设计规约
230 |
231 | 1. 谨慎使用继承的方式进行扩展,优先使用**聚合或组合**的方式来实现。
**说明**:若一定要继承,则必须符合里氏代换原则。此原则要求在父类能够出现的地方子类一定能够出现。
232 | 1. 在系统设计时,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。
233 | 1. 注意对扩展开放,对修改闭合。
234 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Attribution-ShareAlike 4.0 International
2 |
3 | =======================================================================
4 |
5 | Creative Commons Corporation ("Creative Commons") is not a law firm and
6 | does not provide legal services or legal advice. Distribution of
7 | Creative Commons public licenses does not create a lawyer-client or
8 | other relationship. Creative Commons makes its licenses and related
9 | information available on an "as-is" basis. Creative Commons gives no
10 | warranties regarding its licenses, any material licensed under their
11 | terms and conditions, or any related information. Creative Commons
12 | disclaims all liability for damages resulting from their use to the
13 | fullest extent possible.
14 |
15 | Using Creative Commons Public Licenses
16 |
17 | Creative Commons public licenses provide a standard set of terms and
18 | conditions that creators and other rights holders may use to share
19 | original works of authorship and other material subject to copyright
20 | and certain other rights specified in the public license below. The
21 | following considerations are for informational purposes only, are not
22 | exhaustive, and do not form part of our licenses.
23 |
24 | Considerations for licensors: Our public licenses are
25 | intended for use by those authorized to give the public
26 | permission to use material in ways otherwise restricted by
27 | copyright and certain other rights. Our licenses are
28 | irrevocable. Licensors should read and understand the terms
29 | and conditions of the license they choose before applying it.
30 | Licensors should also secure all rights necessary before
31 | applying our licenses so that the public can reuse the
32 | material as expected. Licensors should clearly mark any
33 | material not subject to the license. This includes other CC-
34 | licensed material, or material used under an exception or
35 | limitation to copyright. More considerations for licensors:
36 | wiki.creativecommons.org/Considerations_for_licensors
37 |
38 | Considerations for the public: By using one of our public
39 | licenses, a licensor grants the public permission to use the
40 | licensed material under specified terms and conditions. If
41 | the licensor's permission is not necessary for any reason--for
42 | example, because of any applicable exception or limitation to
43 | copyright--then that use is not regulated by the license. Our
44 | licenses grant only permissions under copyright and certain
45 | other rights that a licensor has authority to grant. Use of
46 | the licensed material may still be restricted for other
47 | reasons, including because others have copyright or other
48 | rights in the material. A licensor may make special requests,
49 | such as asking that all changes be marked or described.
50 | Although not required by our licenses, you are encouraged to
51 | respect those requests where reasonable. More_considerations
52 | for the public:
53 | wiki.creativecommons.org/Considerations_for_licensees
54 |
55 | =======================================================================
56 |
57 | Creative Commons Attribution-ShareAlike 4.0 International Public
58 | License
59 |
60 | By exercising the Licensed Rights (defined below), You accept and agree
61 | to be bound by the terms and conditions of this Creative Commons
62 | Attribution-ShareAlike 4.0 International Public License ("Public
63 | License"). To the extent this Public License may be interpreted as a
64 | contract, You are granted the Licensed Rights in consideration of Your
65 | acceptance of these terms and conditions, and the Licensor grants You
66 | such rights in consideration of benefits the Licensor receives from
67 | making the Licensed Material available under these terms and
68 | conditions.
69 |
70 |
71 | Section 1 -- Definitions.
72 |
73 | a. Adapted Material means material subject to Copyright and Similar
74 | Rights that is derived from or based upon the Licensed Material
75 | and in which the Licensed Material is translated, altered,
76 | arranged, transformed, or otherwise modified in a manner requiring
77 | permission under the Copyright and Similar Rights held by the
78 | Licensor. For purposes of this Public License, where the Licensed
79 | Material is a musical work, performance, or sound recording,
80 | Adapted Material is always produced where the Licensed Material is
81 | synched in timed relation with a moving image.
82 |
83 | b. Adapter's License means the license You apply to Your Copyright
84 | and Similar Rights in Your contributions to Adapted Material in
85 | accordance with the terms and conditions of this Public License.
86 |
87 | c. BY-SA Compatible License means a license listed at
88 | creativecommons.org/compatiblelicenses, approved by Creative
89 | Commons as essentially the equivalent of this Public License.
90 |
91 | d. Copyright and Similar Rights means copyright and/or similar rights
92 | closely related to copyright including, without limitation,
93 | performance, broadcast, sound recording, and Sui Generis Database
94 | Rights, without regard to how the rights are labeled or
95 | categorized. For purposes of this Public License, the rights
96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
97 | Rights.
98 |
99 | e. Effective Technological Measures means those measures that, in the
100 | absence of proper authority, may not be circumvented under laws
101 | fulfilling obligations under Article 11 of the WIPO Copyright
102 | Treaty adopted on December 20, 1996, and/or similar international
103 | agreements.
104 |
105 | f. Exceptions and Limitations means fair use, fair dealing, and/or
106 | any other exception or limitation to Copyright and Similar Rights
107 | that applies to Your use of the Licensed Material.
108 |
109 | g. License Elements means the license attributes listed in the name
110 | of a Creative Commons Public License. The License Elements of this
111 | Public License are Attribution and ShareAlike.
112 |
113 | h. Licensed Material means the artistic or literary work, database,
114 | or other material to which the Licensor applied this Public
115 | License.
116 |
117 | i. Licensed Rights means the rights granted to You subject to the
118 | terms and conditions of this Public License, which are limited to
119 | all Copyright and Similar Rights that apply to Your use of the
120 | Licensed Material and that the Licensor has authority to license.
121 |
122 | j. Licensor means the individual(s) or entity(ies) granting rights
123 | under this Public License.
124 |
125 | k. Share means to provide material to the public by any means or
126 | process that requires permission under the Licensed Rights, such
127 | as reproduction, public display, public performance, distribution,
128 | dissemination, communication, or importation, and to make material
129 | available to the public including in ways that members of the
130 | public may access the material from a place and at a time
131 | individually chosen by them.
132 |
133 | l. Sui Generis Database Rights means rights other than copyright
134 | resulting from Directive 96/9/EC of the European Parliament and of
135 | the Council of 11 March 1996 on the legal protection of databases,
136 | as amended and/or succeeded, as well as other essentially
137 | equivalent rights anywhere in the world.
138 |
139 | m. You means the individual or entity exercising the Licensed Rights
140 | under this Public License. Your has a corresponding meaning.
141 |
142 |
143 | Section 2 -- Scope.
144 |
145 | a. License grant.
146 |
147 | 1. Subject to the terms and conditions of this Public License,
148 | the Licensor hereby grants You a worldwide, royalty-free,
149 | non-sublicensable, non-exclusive, irrevocable license to
150 | exercise the Licensed Rights in the Licensed Material to:
151 |
152 | a. reproduce and Share the Licensed Material, in whole or
153 | in part; and
154 |
155 | b. produce, reproduce, and Share Adapted Material.
156 |
157 | 2. Exceptions and Limitations. For the avoidance of doubt, where
158 | Exceptions and Limitations apply to Your use, this Public
159 | License does not apply, and You do not need to comply with
160 | its terms and conditions.
161 |
162 | 3. Term. The term of this Public License is specified in Section
163 | 6(a).
164 |
165 | 4. Media and formats; technical modifications allowed. The
166 | Licensor authorizes You to exercise the Licensed Rights in
167 | all media and formats whether now known or hereafter created,
168 | and to make technical modifications necessary to do so. The
169 | Licensor waives and/or agrees not to assert any right or
170 | authority to forbid You from making technical modifications
171 | necessary to exercise the Licensed Rights, including
172 | technical modifications necessary to circumvent Effective
173 | Technological Measures. For purposes of this Public License,
174 | simply making modifications authorized by this Section 2(a)
175 | (4) never produces Adapted Material.
176 |
177 | 5. Downstream recipients.
178 |
179 | a. Offer from the Licensor -- Licensed Material. Every
180 | recipient of the Licensed Material automatically
181 | receives an offer from the Licensor to exercise the
182 | Licensed Rights under the terms and conditions of this
183 | Public License.
184 |
185 | b. Additional offer from the Licensor -- Adapted Material.
186 | Every recipient of Adapted Material from You
187 | automatically receives an offer from the Licensor to
188 | exercise the Licensed Rights in the Adapted Material
189 | under the conditions of the Adapter's License You apply.
190 |
191 | c. No downstream restrictions. You may not offer or impose
192 | any additional or different terms or conditions on, or
193 | apply any Effective Technological Measures to, the
194 | Licensed Material if doing so restricts exercise of the
195 | Licensed Rights by any recipient of the Licensed
196 | Material.
197 |
198 | 6. No endorsement. Nothing in this Public License constitutes or
199 | may be construed as permission to assert or imply that You
200 | are, or that Your use of the Licensed Material is, connected
201 | with, or sponsored, endorsed, or granted official status by,
202 | the Licensor or others designated to receive attribution as
203 | provided in Section 3(a)(1)(A)(i).
204 |
205 | b. Other rights.
206 |
207 | 1. Moral rights, such as the right of integrity, are not
208 | licensed under this Public License, nor are publicity,
209 | privacy, and/or other similar personality rights; however, to
210 | the extent possible, the Licensor waives and/or agrees not to
211 | assert any such rights held by the Licensor to the limited
212 | extent necessary to allow You to exercise the Licensed
213 | Rights, but not otherwise.
214 |
215 | 2. Patent and trademark rights are not licensed under this
216 | Public License.
217 |
218 | 3. To the extent possible, the Licensor waives any right to
219 | collect royalties from You for the exercise of the Licensed
220 | Rights, whether directly or through a collecting society
221 | under any voluntary or waivable statutory or compulsory
222 | licensing scheme. In all other cases the Licensor expressly
223 | reserves any right to collect such royalties.
224 |
225 |
226 | Section 3 -- License Conditions.
227 |
228 | Your exercise of the Licensed Rights is expressly made subject to the
229 | following conditions.
230 |
231 | a. Attribution.
232 |
233 | 1. If You Share the Licensed Material (including in modified
234 | form), You must:
235 |
236 | a. retain the following if it is supplied by the Licensor
237 | with the Licensed Material:
238 |
239 | i. identification of the creator(s) of the Licensed
240 | Material and any others designated to receive
241 | attribution, in any reasonable manner requested by
242 | the Licensor (including by pseudonym if
243 | designated);
244 |
245 | ii. a copyright notice;
246 |
247 | iii. a notice that refers to this Public License;
248 |
249 | iv. a notice that refers to the disclaimer of
250 | warranties;
251 |
252 | v. a URI or hyperlink to the Licensed Material to the
253 | extent reasonably practicable;
254 |
255 | b. indicate if You modified the Licensed Material and
256 | retain an indication of any previous modifications; and
257 |
258 | c. indicate the Licensed Material is licensed under this
259 | Public License, and include the text of, or the URI or
260 | hyperlink to, this Public License.
261 |
262 | 2. You may satisfy the conditions in Section 3(a)(1) in any
263 | reasonable manner based on the medium, means, and context in
264 | which You Share the Licensed Material. For example, it may be
265 | reasonable to satisfy the conditions by providing a URI or
266 | hyperlink to a resource that includes the required
267 | information.
268 |
269 | 3. If requested by the Licensor, You must remove any of the
270 | information required by Section 3(a)(1)(A) to the extent
271 | reasonably practicable.
272 |
273 | b. ShareAlike.
274 |
275 | In addition to the conditions in Section 3(a), if You Share
276 | Adapted Material You produce, the following conditions also apply.
277 |
278 | 1. The Adapter's License You apply must be a Creative Commons
279 | license with the same License Elements, this version or
280 | later, or a BY-SA Compatible License.
281 |
282 | 2. You must include the text of, or the URI or hyperlink to, the
283 | Adapter's License You apply. You may satisfy this condition
284 | in any reasonable manner based on the medium, means, and
285 | context in which You Share Adapted Material.
286 |
287 | 3. You may not offer or impose any additional or different terms
288 | or conditions on, or apply any Effective Technological
289 | Measures to, Adapted Material that restrict exercise of the
290 | rights granted under the Adapter's License You apply.
291 |
292 |
293 | Section 4 -- Sui Generis Database Rights.
294 |
295 | Where the Licensed Rights include Sui Generis Database Rights that
296 | apply to Your use of the Licensed Material:
297 |
298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
299 | to extract, reuse, reproduce, and Share all or a substantial
300 | portion of the contents of the database;
301 |
302 | b. if You include all or a substantial portion of the database
303 | contents in a database in which You have Sui Generis Database
304 | Rights, then the database in which You have Sui Generis Database
305 | Rights (but not its individual contents) is Adapted Material,
306 |
307 | including for purposes of Section 3(b); and
308 | c. You must comply with the conditions in Section 3(a) if You Share
309 | all or a substantial portion of the contents of the database.
310 |
311 | For the avoidance of doubt, this Section 4 supplements and does not
312 | replace Your obligations under this Public License where the Licensed
313 | Rights include other Copyright and Similar Rights.
314 |
315 |
316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
317 |
318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
328 |
329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
338 |
339 | c. The disclaimer of warranties and limitation of liability provided
340 | above shall be interpreted in a manner that, to the extent
341 | possible, most closely approximates an absolute disclaimer and
342 | waiver of all liability.
343 |
344 |
345 | Section 6 -- Term and Termination.
346 |
347 | a. This Public License applies for the term of the Copyright and
348 | Similar Rights licensed here. However, if You fail to comply with
349 | this Public License, then Your rights under this Public License
350 | terminate automatically.
351 |
352 | b. Where Your right to use the Licensed Material has terminated under
353 | Section 6(a), it reinstates:
354 |
355 | 1. automatically as of the date the violation is cured, provided
356 | it is cured within 30 days of Your discovery of the
357 | violation; or
358 |
359 | 2. upon express reinstatement by the Licensor.
360 |
361 | For the avoidance of doubt, this Section 6(b) does not affect any
362 | right the Licensor may have to seek remedies for Your violations
363 | of this Public License.
364 |
365 | c. For the avoidance of doubt, the Licensor may also offer the
366 | Licensed Material under separate terms or conditions or stop
367 | distributing the Licensed Material at any time; however, doing so
368 | will not terminate this Public License.
369 |
370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
371 | License.
372 |
373 |
374 | Section 7 -- Other Terms and Conditions.
375 |
376 | a. The Licensor shall not be bound by any additional or different
377 | terms or conditions communicated by You unless expressly agreed.
378 |
379 | b. Any arrangements, understandings, or agreements regarding the
380 | Licensed Material not stated herein are separate from and
381 | independent of the terms and conditions of this Public License.
382 |
383 |
384 | Section 8 -- Interpretation.
385 |
386 | a. For the avoidance of doubt, this Public License does not, and
387 | shall not be interpreted to, reduce, limit, restrict, or impose
388 | conditions on any use of the Licensed Material that could lawfully
389 | be made without permission under this Public License.
390 |
391 | b. To the extent possible, if any provision of this Public License is
392 | deemed unenforceable, it shall be automatically reformed to the
393 | minimum extent necessary to make it enforceable. If the provision
394 | cannot be reformed, it shall be severed from this Public License
395 | without affecting the enforceability of the remaining terms and
396 | conditions.
397 |
398 | c. No term or condition of this Public License will be waived and no
399 | failure to comply consented to unless expressly agreed to by the
400 | Licensor.
401 |
402 | d. Nothing in this Public License constitutes or may be interpreted
403 | as a limitation upon, or waiver of, any privileges and immunities
404 | that apply to the Licensor or You, including from the legal
405 | processes of any jurisdiction or authority.
406 |
407 |
408 | =======================================================================
409 |
410 | Creative Commons is not a party to its public
411 | licenses. Notwithstanding, Creative Commons may elect to apply one of
412 | its public licenses to material it publishes and in those instances
413 | will be considered the “Licensor.” The text of the Creative Commons
414 | public licenses is dedicated to the public domain under the CC0 Public
415 | Domain Dedication. Except for the limited purpose of indicating that
416 | material is shared under a Creative Commons public license or as
417 | otherwise permitted by the Creative Commons policies published at
418 | creativecommons.org/policies, Creative Commons does not authorize the
419 | use of the trademark "Creative Commons" or any other trademark or logo
420 | of Creative Commons without its prior written consent including,
421 | without limitation, in connection with any unauthorized modifications
422 | to any of its public licenses or any other arrangements,
423 | understandings, or agreements concerning use of licensed material. For
424 | the avoidance of doubt, this paragraph does not form part of the
425 | public licenses.
426 |
427 | Creative Commons may be contacted at creativecommons.org.
428 |
--------------------------------------------------------------------------------
/docs/effective-java.md:
--------------------------------------------------------------------------------
1 | # 《Effective Java》
2 |
3 | ## 第二章 创建和销毁对象
4 |
5 | ### 第 1 条:考虑用静态工厂方法代替构造器
6 |
7 | 静态工厂方法相比构造器,优势有以下几个:
8 |
9 | 1. 静态工厂方法有名称,能更确切地描述正被返回的对象,更易于阅读。构造器方法名称都是固定的,只能通过改变参数列表来构造不同对象。
10 | 1. 不必在每次调用时都创建一个对象,可以先将对象缓存起来,需要时直接返回,避免创建不必要的重复对象。比较时可以直接用 `==` 操作符。
11 | 1. 可以返回原返回类型的任何子类对象,更加灵活。适用于基于接口的框架。
12 | 1. 在创建参数化实例时,代码更加简洁。
13 |
14 | 不需要接连两次提供类型参数:
15 |
16 | ```java
17 | Map> m = new HashMap>();
18 | ```
19 |
20 | 只需要提供一个静态工厂方法:
21 |
22 | ```java
23 | public static Hash newInstance() {
24 | return new HashMap;
25 | }
26 |
27 | Map> m = HashMap.newInstance();
28 | ```
29 |
30 | 但是,静态工厂方法也有一些缺点:
31 |
32 | 1. 类如果只包含私有构造器,那么就不能被子例化(继承)。但这样也许也会因祸得福,因为它鼓励使用复合,而不是继承;
33 | 1. 静态工厂方法与其它静态方法没什么区别,无法像构造器一样在 API 文档中明确标识出来。但是,静态工厂方法有一些惯用名称,如 `valueOf`, `of`, `getInstance`, `newInstance`......
34 |
35 | ### 第 2 条:遇到多个构造器参数时要考虑用构建器
36 |
37 | 考虑用一个类表示包含食品外面显示的营养成分标签。这些标签有几个域是必需的,还有超过 20 个可选域。大多数产品在某几个可选域中都会有非零的值。
38 |
39 | 对于这样的类,应该用哪种构造器或者静态方法来编写呢?
40 |
41 | 1. 重叠构造器模式
42 |
43 | 第一种方式是**重复构造器模式**。先提供一个只有必要参数的构造器,再提供一个有一个可选参数的构造器,接着是两个可选参数的构造器,依次类推,最后一个构造器包含所有可选参数。
44 |
45 | ```java
46 | public class NutritionFacts {
47 | private final int servingSize;
48 | private final int servings;
49 | private final int calories;
50 | private final int fat;
51 | private final int sodium;
52 | private final int carbohydrate;
53 |
54 | public NutritionFacts(int servingSize, int servings) {
55 | this(servingSize, servings, 0);
56 | }
57 |
58 | public NutritionFacts(int servingSize, int servings, int calories) {
59 | this(servingSize, servings, calories, 0);
60 | }
61 |
62 | public NutritionFacts(int servingSize, int servings, int calories, int fat) {
63 | this(servingSize, servings, calories, fat, 0);
64 | }
65 |
66 | public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
67 | this(servingSize, servings, calories, fat, sodium, 0);
68 | }
69 |
70 | public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
71 | this.servingSize = servingSize;
72 | this.servings = servings;
73 | this.calories = calories;
74 | this.fat = fat;
75 | this.sodium = sodium;
76 | this.carbohydrate = carbohydrate;
77 | }
78 | }
79 | ```
80 |
81 | 想要创建实例的时候,就利用参数列表最短的构造器,但该列表中包含了要设置的所有参数:
82 |
83 | ```java
84 | NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
85 | ```
86 |
87 | 这个构造器通常需要许多你本不想设置的参数,但还是不得不为它们传递值。随着参数数目的增多,很快就会失去了控制。客户端代码也会很难编写,可读性也不好。
88 |
89 | 2. JavaBean 模式
90 |
91 | 第二种模式是 **JavaBean** 模式,在这种模式下,创建一个无参构造器来创建对象,然后调用 setter 方法设置每个必要的参数。这种模式,弥补了重叠构造器模式的不足,代码读起来也很容易,很多读者应该都很熟悉了。
92 |
93 | ```java
94 | NutritionFacts cocaCola = new NutritionFacts();
95 | cocaCola.setServingSize(200);
96 | cocaCola.setServings(8);
97 | cocaCola.setCalories(100);
98 | cocaCola.setSodium(35);
99 | cocaCola.setCarbohydrate(27);
100 | ```
101 |
102 | 遗憾的是,JavaBean 模式自身有很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中 JavaBean 可能处于不一致的状态。若试图使用处于不一致状态的对象,将会导致失败,调试起来也十分困难。程序员需要付出额外的努力来确保它的线程安全。
103 |
104 | 3. Builder 模式
105 |
106 | 有第三种替代方法,既能保证像重叠构造器模式那样的安全性,也能保证像 JavaBean 模式一样,有很好的可读性,就是 **Builder** 模式。
107 |
108 | ```java
109 | public class NutritionFacts {
110 | private final int servingSize;
111 | private final int servings;
112 | private final int calories;
113 | private final int fat;
114 | private final int sodium;
115 | private final int carbohydrate;
116 |
117 | public static class Builder {
118 | // Required params
119 | private final int servingSize;
120 | private final int servings;
121 |
122 | // Optional params
123 | private int calories = 0;
124 | private int fat = 0;
125 | private int sodium = 0;
126 | private int carbohydrate = 0;
127 |
128 | public Builder(int servingSize, int servings) {
129 | this.servingSize = servingSize;
130 | this.servings = servings;
131 | }
132 |
133 | public Builder calories(int val) {
134 | this.calories = val;
135 | return this;
136 | }
137 | public Builder fat(int val) {
138 | this.fat = val;
139 | return this;
140 | }
141 | public Builder sodium(int val) {
142 | this.sodium = val;
143 | return this;
144 | }
145 | public Builder carbohydrate(int val) {
146 | this.carbohydrate = val;
147 | return this;
148 | }
149 | public NutritionFacts build() {
150 | return new NutritionFacts(this);
151 | }
152 | }
153 |
154 | // 私有构造器
155 | private NutritionFacts(Builder builder) {
156 | servingSize = builder.servingSize;
157 | servings = builder.servings;
158 | calories = builder.calories;
159 | fat = builder.fat;
160 | sodium = builder.sodium;
161 | carbohydrate = builder.carbohydrate;
162 | }
163 | }
164 | ```
165 |
166 | 客户端代码就很容易编写了,更为重要的是,易于阅读。
167 |
168 | ```java
169 | NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
170 | .calories(100)
171 | .sodium(35)
172 | .carbohydrate(27)
173 | .build();
174 | ```
175 |
176 | Builder 模式也有它自身的不足。为了创建对象,必须先创建它的构建器。虽然创建构建器的开销在实践种可能不那么明显,但是在某些十分注重性能的情况下,可能就成了问题了。Builder 模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如 4 个或者更多个参数。
177 |
178 | 简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder 模式就是种不错的选择,特别是当大多数参数都是可选的时候。它较传统的重叠构造器模式相比,更易于阅读;而较 JavaBean 模式,更加安全。
179 |
180 | ### 第 3 条:用私有构造器或者枚举类型强化 Singleton 属性
181 |
182 | 1. 公有静态成员
183 |
184 | ```java
185 | public class Singleton {
186 | public static final Singleton INSTANCE = new Singleton();
187 | private Singleton() {}
188 | }
189 | ```
190 |
191 | 2. 静态工厂方法
192 |
193 | ```java
194 | public class Singleton {
195 | private static final Singleton INSTANCE = new Singleton();
196 | private Singleton() {}
197 |
198 | public static Singleton getInstance() {
199 | return INSTANCE;
200 | }
201 | }
202 | ```
203 |
204 | 这两种方法都能保证 Singleton 的全局唯一性。但是,享有特权的客户端可以借助 `AccessibleObject.setAccessible` 方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
205 |
206 | 为了使利用这其中一种方法实现的 Singleton 类变成可序列化的,仅仅在声明中加上 `implements Serializable` 是不够的。为了维护并保证 Singleton,必须声明所有实例域都是瞬时(transient)的,并提供一个 `readResolve` 方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。
207 |
208 | ```java
209 | priavte Object readResolve() {
210 | return INSTANCE;
211 | }
212 | ```
213 |
214 | 3. 单元素枚举
215 |
216 | Java 1.5 版本开始,实现 Singleton 有了第三种方法。只需编写一个包含单个元素的枚举类型:
217 |
218 | ```java
219 | public enum Singleton {
220 | INSTANCE;
221 |
222 | public void otherMethods() {...}
223 | }
224 | ```
225 |
226 | 这种方法更加简洁,无偿提供了序列化机制,绝对防止多次实例化,是实现 Singleton 的最佳方式。
227 |
228 | ### 第 4 条:通过私有构造器强化不可实例化的能力
229 |
230 | 我们在项目开发过程中,有时候肯定会遇到一些工具类,我们不希望它们被实例化,因为它们的方法可能都被 `static` 来修饰,所以实例对它们没有任何的意义;然而我们在编码的过程中,可能往往对一些工具类的编写都没有注意,没有去写构造方法,这时候,在缺少显式的构造器的情况下,编译器会提供一个共有的、无参的缺省构造器(`default constructor`)。对于用户而言,这个构造器和其它的构造器没有任何区别。所以在一些已发行的 API 里面我们经常会看到一些被无意识实例化的类。
231 |
232 | **企图通过讲类做成抽象类来强制该类不可被实例化,这是行不通的。** 因为抽象类可以被子类化,子类也可以被实例化。同时定义为抽象类,还会误导用户,以为这种类是专门为了继承而设计的。那怎样才可以确保类不被实例化呢,因为只有当类不包含显示的构造器时,编译器才会生成缺省的构造器,所以我们只要给这个类构建私有的构造器,它就不会被实例化了:
233 |
234 | ```java
235 | // Noninstantiable utility class
236 | public class UtilityClass {
237 | // Suppress default constructor for noninstantiability
238 | private UtilityClass() {
239 | throw new AssertionError();
240 | }
241 |
242 | ... // Remainder omitted
243 | }
244 | ```
245 |
246 | 如上,由于显示的构造器是私有的,则不会在类的外部被实例化。 AssertionError 不是必须的,但是这样写,可以避免在类的内部调用构造器。它保证该类在任何情况下都不会被实例化。
247 |
248 | **注意** 这种用法也有副作用,它使得一个类不能被子类化。因为所有的构造器都必须显式或隐式地调用超类(superclass)构造器,在上面这种情况下,子类就没有可访问的超类构造器去调用了。
249 |
250 | ### 第 5 条:避免创建不必要的对象
251 |
252 | 一般来说,最好不要在每次需要的时候都创建一个功能相同的新对象而是重用对象。重用方式既快速,又流行。如果对象是不可变的(immutable),它就始终可以被重用。
253 | 下面举一个极端的反面例子,考虑下面的语句:
254 |
255 | ```java
256 |
257 | String s = new String("stringette"); // Don't do this!
258 |
259 | ```
260 |
261 | 上面的语句每次被执行的时候都会创建一个新的 String 实例,但是这些创建对象的动作全都是不必要的。传递给 String 构造器的参数("stringette")本身就是一个 String 实例,功能方面等同于构造器创建的所有对象。想一想 ,如果该用法在一个循环中,或者是在一个被频繁调用的方法中,就会创建出成千上万个不必要的 String 实例。
262 |
263 | 改进后的版本如下所示:
264 |
265 | ```java
266 |
267 | String s = "stringette";
268 |
269 | ```
270 |
271 | 上面版本只用了一个 String 实例,而不是每次执行的时候都创建一个新的 String 实例。并且,它还可以保证,对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用[JLS,3.10.5]。
272 |
273 | 对于同时提供了静态工厂方法(见第 1 条)和构造器的不可变类,通常应该使用静态工厂方法而不是构造器,这样可以避免创建不必要的对象。构造器在每次被调用的时候都会创建一个新的对象,而静态工厂方法重来不要求这样做,实际上也不会这么做。
274 |
275 | 除了重用不可变对象之外,也可以重用那些已知不会被修改的可变对象。下面通过我们熟悉的可变 Date 对象来实现一个比较微妙、也比较具体的反面例子,由于 Date 对象一旦计算出来之后就不再改变。
276 |
277 | ```java
278 |
279 | public class Person {
280 |
281 | private final Date birthDate;
282 |
283 | public Person(Date birthDate) {
284 | this.birthDate = birthDate;
285 | }
286 |
287 | // Other fields, methods, and constructor omitted
288 | // Don't do this!
289 | public boolean isBabyBoomer() {
290 | // Unnecessary allocation of expensive object
291 | Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
292 | gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
293 | Date boomStart = gmtCal.getTime();
294 | gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
295 | Date boomEnd = gmtCal.getTime();
296 |
297 | return birthDate.compareTo(boomStart) >= 0 &&
298 | birthDate.compareTo(boomEnd) < 0;
299 | }
300 |
301 | }
302 |
303 | ```
304 |
305 | 上面的类建立了一个模型:其中有一个人,并有一个 isBabyBoomer 方法,用来检验这个人是否为一个 “baby boomber(生育高峰期出生的小孩)” ,相当于就是检测这个人是否出生于 1946 年至 1964 年之间。
306 |
307 | 通过上述代码可以发现,isBabyBoomer 方法每次都调用的时候,都会创建一个新的 Calendar、一个 TimeZone 和两个 Date 实例,这其实是不必要的。下面我们通过一个改进的版本,用一个静态的初始化器(`initializer`),避免了这种效率低下的情况:
308 |
309 | ```java
310 |
311 | public class Person {
312 | private final Date birthDate;
313 |
314 | public Person(Date birthDate) {
315 | this.birthDate = birthDate;
316 | }
317 | // Other fields, methods, and constructor omitted
318 |
319 | // The starting and ending dates of the baby boom
320 | private static final Date BOOM_START;
321 | private static final Date BOOM_END;
322 |
323 | static {
324 | Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
325 | gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
326 | BOOM_START = gmtCal.getTime();
327 | gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
328 | BOOM_END = gmtCal.getTime();
329 | }
330 |
331 | public boolean isBabyBoomer() {
332 | return birthDate.compareTo(BOOM_START) >= 0 &&
333 | birthDate.compareTo(BOOM_END) < 0;
334 | }
335 | }
336 |
337 | ```
338 |
339 | 改进后的 Person 类只在初始化的时候创建 Calendar、 TimeZone 和 Date 实例一次,而不是在每次调用 isBabyBoomer 的时候都会创建这些实例。如果 isBabyBoomer 方法被频繁地调用,改进后的方法将会显著的提高性能。就比如我们要检查 1000 万人是否出生在 1946 年和 1964 年之间,经过测试,原来的版本需要 32000ms,而改进后的只需要 130ms,大约快了 250 倍。但是这种优化带来的效果不总是那么明显,因为 Calendar 实例的创建代价特别昂贵。但是改进后的版本在数据量大的情况下就会有明显的性能提升,并且代码更加的清晰,因为 BOOM_START 和 BOOM_END 很明显应该被作为常量来对待。
340 |
341 | 在本条目前面的例子中,所讨论到的对象显然都是能够被重用的,因为它们被初始化后就不会再改变。其它有些情形则并不总是那么明显了。考虑适配器(`adapter`)的情形,有时也叫做视图(`view`)。适配器是指这样一个对象:它把功能委托给一个后备对象(`backing object`),从而为后备对象提供一个可以替代的接口。由于适配器除了后备对象之外,没有其它的状态信息,所以针对某个给定对象的特定适配器而言,它不需要创建多个适配器实例。
342 |
343 | 例如,Map 接口的 keySet 方法返回该 Map 对象的 Set 视图,其中包含该 Map 中所有的键(`key`)。表面看起来,好像每次调用 keySet 都应该创建一个新的 Set 实例,但是,对于一个给定的 Map 对象,实际上每次调用 keySet 方法都会返回同样的 Set 实例。虽然被返回的 Set 实例一般是可改变的,但是所有返回的对象在功能上是等同的:当其中一个返回对象发生变化的时候,所有其它的返回对象也要发生变化,因为它们是有同一个 Map 实例支撑的。虽然创建 keySet 视图对象的多个实例并无害处,却也是没有必要的。
344 |
345 | 在 Java 1.5 发行版本中,有一种创建多余对象的新方法,称为自动装箱(`autoboxing`),它允许程序员将基本类型和装箱基本类型(`Boxed Primitive Type`)混用,按需要自动装箱和拆箱。自动装箱使得基本类型和装箱基本类型的差别变得很模糊,但是并没有完全消除。它们在语义上有着微妙的差别,在性能上也有着比较明显的差别(见第 49 条)。考虑下面的程序,它计算所有 int 正值的总和。为此,程序必须使用 long 类型,因为 int 不够大,无法容纳所有 int 正值的总和:
346 |
347 | ```java
348 |
349 | // Hideously slow program! Can you spot the object creation?
350 | public static void mian(String[] args) {
351 | Long sum = 0L;
352 | for (long i = 0; i < Integer.MAX_VALUE; i++) {
353 | sum += i;
354 | }
355 | System.out.println(sum);
356 | }
357 |
358 | ```
359 |
360 | 这段程序程序算出的答案是正确的,但是比实际情况要更慢一些,只因为打错一个字符。变量 sum 被声明成 Long 而不是 long,意味着程序构造了大约 2^31 个多余的 Long 实例(大约每次往 Long sum 中增加 long 时构造一个实例)。将 sum 的声明从 Long 改成 long,运行时间从 43 秒减少到 6.8 秒。结论很明显:**要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。**
361 |
362 | 当然,我们也不要错误地认为本条目所介绍的内容暗示着“创建对象的代价非常昂贵,我们应该尽可能地避免创建对象”。相反,由于小对象的构造器只做很少量的显式工作,所以,小对象的创建和回收动作是非常廉价的,特别是在现代的 JVM 实际上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。
363 |
364 | 反之,通过维护自己的对象池(`object pool`)来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价是非常昂贵的,因此重用这些对象是非常有意义。而且,数据库的许可可能限制你只能使用一定数量的连接。但是,一般而言,维护自己的对象池必定会把代码弄得很乱,同时增加内存占用(`footprint`),并且还会损害性能。所以我们要慎用对象池。
365 |
366 | 与本条目对应的是第 39 条中有关的“保护性拷贝(`defensive copying`)”的内容。本条目提及“当你应该重用现有对象的时候,请不要创建新的对象”,而第 39 条则说“当你应该创建新的对象的时候,请不要重用现有的对象”。注意,在提倡使用保护性拷贝的时候,是因为重用对象而付出的代价要远远大于因创建对象而付出的代价。必要时如果没能实施保护性拷贝,会导致潜在的错误和安全漏洞;而不必要的创建对象则只会影响程序的风格和性能。
367 |
368 | **总结来说,就是应该按情况具体分析,该创建对象还是重用对象;通过分析,我们应该知道没有保护的重用对象,需要特别注意,不然可能会导致错误和安全漏洞。**
369 |
370 | ### 第 6 条:消除过期的对象引用
371 |
372 | 当你从手工管理内存的语言(比如 C 或 C++)转换到具有垃圾回收功能的语言(比如 Java 或 Go)的时候,程序员的工作会变得更加的容易,因为当你用完了对象之后,它们会被自动回收。当你由 C 或 C++ 语言转换到 Java 编程语言第一次经历对象回收功能的时候,会觉得有点不可思议。这很容易给你留下不需要自己考虑内存管理的印象,其实不然。
373 |
374 | 考虑下面这个简单的栈实现的例子:
375 |
376 | ```java
377 |
378 | // Can you spot the "memory leak"
379 | public class Stack {
380 | private Object[] elements;
381 | private int size = 0;
382 | private static final int DEFAULT_INITIAL_CAPACITY = 16;
383 |
384 | public Stack() {
385 | elements = new Object[DEFAULT_INITIAL_CAPACITY];
386 | }
387 |
388 | public void push(Object e) {
389 | ensureCapacity();
390 | elements[size++] = e;
391 | }
392 |
393 | public Object pop() {
394 | if (size == 0)
395 | throw new EmptyStackException();
396 | return elements[--size];
397 | }
398 |
399 | /**
400 | * Ensure space for at least one more element, roughly
401 | * doubling the capacity each time the array needs to grow.
402 | */
403 | private void ensureCapacity() {
404 | if (elements.length == size) {
405 | elements = Arrays.copyOf(elements, 2 * size + 1);
406 | }
407 | }
408 |
409 | }
410 |
411 | ```
412 |
413 | 这段程序(它的泛型版本请见第 26 条)中并没有很明显的错误。但是这个程序中隐藏着一个问题。不严格地讲,这段程序有一个“内存泄漏”,随着垃圾回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。在极端的情况下,这种内存泄漏会导致磁盘交换(`Disk Paging`),甚至导致程序失败(`OutOfMemoryError`错误),但是这种失败情形相对比较少见。
414 |
415 | 那么,程序中在哪里发生了泄漏呢?如果一个栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为,栈内部维护着对这些对象的过期引用(`Obsolete refence`)。所谓的过期引用,是指永远也不会再被解除的引用。在本例中,凡是在 elements 数组的“活动部分(`active portion`)”之外的任何引用都是过期的。活动部分是指 elements 中小标小于 size 的那些元素。
416 |
417 | 在支持垃圾回收的语言中,内存泄漏是非常隐蔽的(称这类内存泄漏为“无意识的对象保持(`unintentional object retention`)“更为恰当)。如果一个对象引用被无意识的保留起来了,那么,垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其它对象。即使只有少量的几个对象被无意识的保留下来,也会有许许多多的对象排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。
418 |
419 | 这类问题的修复方法很简单:一旦对象引用已经过期,只要清空这些引用即可。对于上述的 Stack 类而言,只要一个单元被弹出栈,指向它的引用就已经过期了。pop 方法的改进版如下:
420 |
421 | ```java
422 |
423 | public Object pop() {
424 | if (size == 0)
425 | throw new EmptyStackException();
426 | elements[size] = null; // Eliminate obsolete reference
427 | return elements[--size];
428 | }
429 |
430 | ```
431 |
432 | 清空过期引用的另一个好处是,如果它们以后又被错误的解除引用,程序就会立即抛出 NullPointerException 异常,而不是悄悄地错误运行下去。尽快的检测出程序中的错误往往是有益的。
433 |
434 | 当程序员第一次被类似这样的问题困扰的时候,他们往往会过分小心:对于每一个对象引用,一旦程序不再用到它,就把它清空。其实这样做即没必要,也不是我们所期望的,因为这样做会把我们的程序弄的很乱。**清空对象引用应该是一种例外,而不是一种规范行为** 。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。如果你是在最紧凑(最小)的作用域范围定义每一个变量(见第 45 条),这种情形就会自然而然的发生。
435 |
436 | 那么,何时应该清空引用呢?Stack 类的哪方面特性使它易于遭受内存泄漏的影响呢?简而言之,问题在于,Stack 类自己管理内存(`manage its own memory`)。存储池(`storage pool`)包含了 elements 数组(对象应用单元,而不是对象本身)的元素。数组活动区域(同前面的定义)中的元素是已分配的(`allocated`),而数组其余部分的元素则是自由的(`free`)。但是垃圾回收器并不知道这一点;对于垃圾回收器而言,elements 数组中所有的对象引用都同等有效。只有程序员知道数组的非活动部分是不重要的。程序员可以把这个情况告知垃圾回收器,做法很简单:一旦数组元素变成了非活动部分的一部分,程序员就手工清空这些数组元素。
437 |
438 | 一般而言,**只要类是自己管理内存,程序员就应该警惕内存泄漏问题**。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
439 |
440 | **内存泄漏的另一个常见来源是缓存**。一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使用它不再有用很长一段时间内仍然留在缓存中。对于这个问题,有几种可能的解决方案。如果你正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那就可以用 WeakHashMap 代表缓存;当缓存中的项过期之后,它们就会自动被删除。记住只有当所要的缓存项的生命周期是由该键的外部引用而不是由键的值决定时候,WeakHashMap 才有用处。
441 |
442 | 更为常见的情形则是,“缓存项的生命周期是否有意义”并不是很容易确定,随着时间的推移,其中的项的价值变得越来越没有价值。在这种情况下,缓存应该时不时的清空掉无用的项。清除工作可以由一个后台线程(可能是 Timer 或者 ScheduledThreadPoolExecutor)来完成,或者也可以给缓存添加新数据的时候顺便进行清理。LinkedHashMap 利用其 removeEldestEntry 方法可以很容易地实现后一种方案。对于更加复杂的缓存,必须直接使用 java.lang.ref。
443 |
444 | **内存泄漏的第三个常见来源是监听器和其它回调**。如果你实现了一个 API,客户端在这个 API 中注册回调,却没有显示地取消注册,那么除非你采取某些动作,否则它们就会聚集。确保回调立即被当作垃圾回收的最佳方法是只保存它们的软引用(`weak reference`),例如,只将它们保存成 WeakHashMap 中的键。
445 |
446 | 由于内存泄漏通常不会表现成明显的失败,所以它们可以在一个系统中存在很多年。往往只有通过代码检查,或者借助于 Heap 剖析工具(`Heap Profiler`)才可以发现内存泄漏问题。所以,如果我们能在内存泄漏发生之前就知道如何预测和分析此类问题,并预防和阻止它们发生,那是最好不过了。
447 |
448 | ### 第 7 条:避免使用终结方法
449 |
450 | **终结方法(`finalizer`)通常是不可预测的,也是很危险的,一般情况下是不必要的**。使用终结方法会有很多缺点:会导致行为不稳定、降低性能,以及可移植性问题。当然,终结方法也有其可用之处,该条目后面会进行介绍;但是根据经验,还是应该避免使用终结方法。
451 |
452 | C++ 程序员被告知“不要把终结方法当作是 C++ 中析构器(`destructors`)的对应物”。在 C++ 中,析构器是回收一个对象所占用资源的常规方法,是构造器所必须的对应物。在 Java 中,当一个对象变得不可到达时,垃圾回收器会回收与该对象相关联的存储空间,并不需要程序员做专门的工作。C++ 中的析构器也可以被用来回收其他的内存资源。而在 Java 中,一般用 try-finally 块来完成类似的工作。
453 |
454 | 终结方法的缺点在于不能保证会被及时地执行[JLS,12.6]。从一个对象变得不可到达开始,到它的终结方法被执行,所花费的这段时间是任意长的。这意味着,注重时间(`time-critical`)的任务是不应该由终结方法来完成。例如,用终结方法来关闭已经打开的文件,这是严重错误,因为打开文件的描述符是一种有限的资源。由于 JVM 会延时执行终结方法,所以大量的文件会保留在打开状态,当一个程序再不能打开文件的时候,它可能会运行失败。
455 |
456 | 及时地执行终结方法正是垃圾回收算法的一个主要功能,这种算法在不同的 JVM 实现中会大相径庭。如果程序依赖于终结方法被执行的时间点,那么这个程序在不同的 JVM 运行的表现可能会截然不同。一个程序可能在你测试的 JVM 平台上运行的非常好,但是在你最重要顾客的 JVM 平台上却根本无法运行,这是完成可能的。
457 |
458 | 延迟终结过程并不只是一个理论问题。在很少见的情况下,为类提供终结方法,可能会随意地延迟其实例的回收过程。一位同事最近在调试一个长期运行的 GUI 应用程序的时候,该应用程序莫名其妙地出现 OutOfMemoryError 错误而死掉。分析表明,该应用程序死掉的时候,其终结方法队列中有数千个图像对象正在被等待终结和回收。遗憾的是,终结方法线程的优先级比该程序的其它线程的要低很多,所以,图形对象的终结速度达不到进入队列的速度。Java 语言规范并不保证哪个线程将会执行终结方法,所以,除了不使用终结方法以外,并没有很轻便的办法能够避免这样的问题。
459 |
460 | Java 语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。当一个程序终止的时候,某些已经无法访问的对象上的终结方法却根本没有被执行,这是完全有可能的。结论是:**不应该依赖终结方法来更新重要的持久状态**。例如,依赖终结方法来解释共享资源(比如数据库)上的永久锁,很容易让整个分布式系统垮掉。
461 |
462 | 不要被 System.gc 和 System.runFinalization 这两个方法所诱惑,它们确实增加了终结方法被执行的机会,但是它们并不保证终结方法一定会被执行。唯一声称保证终结方法被执行的方法是 System.runFinalizersOnExit,以及它臭名昭著的孪生兄弟 RunTime.runFinalizersOnExit。这两个方法都有致命的缺陷,已经被废弃了[ThreadStop]。
463 |
464 | 当你并不确定是否应该避免使用终结方法的时候,这里还有一种值得考虑的情形:如果未被捕获的异常在终结方法中被抛出来,那么这种异常可以被忽略,并且该对象的终结过程也会终止[JLS,12.6]。未被捕获的异常会使对象处于破坏的状态(a corrupt state),如果另一个线程企图使用这种被破坏的对象,则可能发生任何不确定的行为。在正常情况下,未被捕获的异常会使线程终止,并打印出栈轨迹(`Stack Trace`),但是,如果发生在终结方法之中,则不会如此,甚至连警告都不会打印出来。
465 |
466 | 还有一点:**使用终结方法有一个非常严重的(`Severe`)性能损失**。在我的机器上,创建和销毁一个简单对象的时间大约是 5.6ns,增加一个终结方法使时间增加到了 2400ns。换句话说,用终结方法创建或销毁对象大约慢了 430 倍。
467 |
468 | 那么,如果类中封装的资源(例如文件或者线程)确实需要终止,应该怎么做才能不编写终结方法呢?只需**提供一个显示的终止方法**,并要求该类的客户端在每个实例不再有用的时候调用这个方法。值得提及的一个细节是,该实例必须记录下自己是否已经被终结了:显示的终止方法必须在一个私有域中记录下“该对象已经不再有效”。如果这些方法是在对象已经被终止之后调用,其它的方法就必须检查这个域,并抛出 IllegalStateException 异常。
469 |
470 | 显示终止方法的典型例子是 InputStream、OutputStream 和 java.sql.Connection 上的 close 方法。另一个例子是 java.utils.Timer 上的 cancel 方法,它执行必要的状态改变,使得与 Timer 实例相关联的该线程温和地终止自己。java.awt 中的例子还包括 Graphics.dispose 和 Window.dispose。这些方法通常由于性能不好而不被人们关注。一个相关的方法是 Image.flush,它会释放所有与 Image 实例相关的资源,但是该实例仍然处于可用的状态,如果有必要的话,会重新分配资源。
471 |
472 | **显示的终止方法通常与 try-finally 结构联合起来使用,以确保及时终止**。在 finally 子句内部调用显示的终止方法,可以确保即使在使用对象的时候有异常抛出,该终止方法也会执行:
473 |
474 | ```java
475 |
476 | // try-finally block guarantees execution of termination method
477 | Foo foo = new Foo(...);
478 | try {
479 | // Do what must be done with foo
480 | ...
481 | } finally {
482 | foo.terminate(); // Explicitt termination method
483 | }
484 |
485 | ```
486 |
487 | 那么终结方法有什么好处呢?它们有两种合法用途。第一种用途是,当对象的所有者忘记调用前面段落建议的显示终止方法时,终结方法可以充当“安全网(`safety net`)”。虽然这样做并不是保证终结方法会被及时的调用,但是在客户端无法通过调用显式的终止方法来正常结束操作的情况下(希望这种情况尽可能少地发生),迟一点释放关键资源总比永远不释放要好。但是如果终结方法发现资源还未被终止,则应该在日志中记录一条警告,因为这表示客户端代码中的一个 Bug,应该得到修复。如果你正考虑编写这样的安全网终结方法,就要认真考虑清楚,这种额外的保护是否值得你付出这份额外的代价。
488 |
489 | 显示终止方法模式的示例中所示的四个类(`FileInputStream`、`FileOutputStream`、`Timer` 和 `Connection`),都具有终结方法,当它们的终止方法未能被调用的情况下,这些终止方法充当了安全网。
490 |
491 | 终结方法的第二种合理用途与对象的本地对等体(`native peer`)有关。本地对等体是一个本地对象(`native object`),普通方法通过本地方法(`native method`)委托给一个本地对象。因为本地对等体不是一个普通对象,所以垃圾回收器不会知道它,当它的 Java 对等体回收的时候,它不会被回收。在本地对等体并不拥有关键资源的前提下,终结方法正是执行这项任务最合适的工具。如果本地对等体拥有被及时终止的资源,那么该类就应该拥有一个显式的终止方法,如前所述。终止方法应该完成完成所有必要的工作以便释放关键的资源。终止方法可以是本地方法,或则它也可以调用本地方法。
492 |
493 | 值得注意的最重要的一点是,“终结方法链(`finalizer chaining`)”并不会被自动执行。如果类(不是 Object)有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工调用超类的终结方法。你应该在一个 try 块中终结子类,并在相应的 finally 块中调用超类的终结方法。这样做可以保证:即使子类的终结过程抛出异常,超类的终结方法也会得到执行。反之亦然。代码示例如下。注意这个示例使用了 Override 注解(`@Override`),这是 Java 1.5 发行版本将它增加到 Java 平台中的。你现在可以不管 Override 注解,或者到第 36 条查阅一下它们是什么意思:
494 |
495 | ```java
496 |
497 | // Manual finalizer chaining
498 | @Override
499 | protected void finalize() throws Throwable {
500 | try {
501 | // Finalize subclass state
502 | } finally {
503 | super.finalize();
504 | }
505 | }
506 |
507 | ```
508 |
509 | 如果子类实现者覆盖了超类的终结方法,但是忘了手工调用超类的终结方法(或者有意选择不调用超类的终结方法),那么超类的终结方法将会永远也不会被调用到。要防范这样粗心大意或者恶意的子类是有可能的,代价就是为每个被终结的对象创建了一个附加的对象。不是把终结方法放在要求终结处理的类中,而是把终结方法在一个匿名的类(见第 22 条)中,该匿名类的唯一用途就是终结它的外围实例(`enclosing instance`)。该匿名类的单个实例被称为**终结方法守卫者(`finalizer guardian`)**,外围类的每个实例都会创建这样一个守卫者。外围实例在它的私有实例域中保存这一个对其终结方法守卫者的唯一引用,因此终结方法守卫者与外围实例可以同时启动终结过程。当守卫者被终结的时候,它执行外围实例所期望的终结行为,就好像它的终结方法是外围对象上的一个方法一样:
510 |
511 | ```java
512 |
513 | // Finalizer Guardian idiom
514 | public class Foo {
515 | // Sole purpose of this object is to finalize outer Foo object
516 | private final object finalizerGuardian = new Object() {
517 | @Override
518 | protect void finalize() throws Throwable {
519 | ... // Finalize outer Foo object
520 | }
521 | };
522 |
523 | ... // Remainder omitted
524 | }
525 |
526 | ```
527 |
528 | 注意,共有类 Foo 并没有终结方法(除了它从 Object 中继承了一个无关紧要的之外),所以子类的终结方法是否调用 super.finalize() 并不重要。对于每一个带有终结方法的非 final 共有类,都应该考虑使用这种方法。
529 |
530 | 总之,除非是作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法。在很少见的情况下,既然使用了终结方法,就要记住调用 super.finalize。如果用终结方法作为安全网,要记得记录终结方法的非法用法。最后,如果需要把终结方法与共有的非 final 类关联起来,请考虑使用终结方法守卫者,以确保即使子类的终结方法未能调用 super.finalize,该终结方法也会被执行。
531 |
532 | ## 第三章 对于所有对象都通用的方法
533 |
534 | ### 第 8 条:覆盖 equals 时请遵守通用约定
535 |
536 | 覆盖 equals 方法看起来很简单,但是有很多覆盖方式会导致错误,并且后果非常严重。最容易避免这类问题的方法就是不覆盖 equals 方法,在这种情况下,类的每个实例都只能与它自身相等。如果满足了一下任何一个条件,这就正是所期望的结果:
537 |
538 | - **类的每个实例本质上都是唯一的**。对于代表活动实体而不是值(`value`)的类来说确实如此,例如 Thread。Object 提供的 equals 实现对这些类来说正是正确的行为。
539 |
540 | - **不关心类是否提供了“逻辑相等(`logical equality`)”的测试功能**。例如,java.util.Random 覆盖了 equals,以检查两个 Random 实例产生相同的随机数序列,但是设计者并不认为客户需要或者期望这样的功能。在这样的情况下,从 Object 继承得到的 equals 实现已经足够了。
541 |
542 | - **超类已经覆盖了 equals,从超类继承过来的行为对于子类也是合适的**。例如,大多数 Set 实现都从 AbstractSet 继承 equals 实现,List 实现从 AbstractList 继承 equals 实现,Map 实现从 AbstractMap 继承实现。
543 |
544 | - **类是私有的或是包级私有的,可以确定它的 equals 方法永远不会被调用**。在这种情况下,无疑是应该覆盖 equals 方法的,以防它被意外调用:
545 |
546 | ```java
547 |
548 | @Override
549 | public boolean equals(Object o) {
550 | throw new AssertionError(); // Method is never called
551 | }
552 |
553 | ```
554 |
555 | 那么什么时候应该覆盖 Object.equals 呢?如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖 equals 以实现期望的行为,这是我们就需要覆盖 equals 方法。这通常属于“值类(`value class`)”的情形。值类仅仅是一个表示值的类,例如 Integer 或者 Date。
556 |
557 | 有一种“值类”不需要覆盖 equals 方法,即用实例受控(见第 1 条)确保“每个值至多只存在一个对象”的类。枚举类型(见第 30 条)就属于这种类。对于这样的类而言,逻辑相同与对象等同是一回事,因此 Object 上的 equals 方法等同于逻辑意义上的 equals 方法。
558 |
559 | 在覆盖 equals 方法的时候,你必须遵守它的通用约定。下面的内容来自于 Object 的规范[JavaSE6]:
560 | equals 方法实现了等价关系(`equivalence relation`):
561 |
562 | - **自反性(`reflexive`)**。对于任何非 null 的引用值 x,x.equals(x) 必须返回 true。
563 |
564 | - **对称性(`symmetric`)**。对于任何非 null 的引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 必须返回 true。
565 |
566 | - **传递性(`transitive`)**。对于任何非 null 的引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 也返回 true,那么 x.equals(z) 也必须返回 true。
567 |
568 | - **一致性(`consistent`)**。对于任何非 null 的引用值 x 和 y,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y) 就会一致地返回 true,或者一致地返回 false。
569 |
570 | - **非空性**。对于任何非 null 的引用值 x,x.equlas(null) 必须返回 false。
571 |
572 | 现在我们按照顺序逐一查看以下 5 个要求:
573 |
574 | **自反性(`reflexive`)** --- 第一个要求仅仅说明对象必须等于其本身。很难想象会无意识地违反这一条。假如违背了这一条,然后把该类的实例添加到集合(`collection`)中,该集合的 contains 方法将会果断地告诉你,该集合不包含你刚刚添加的实例。
575 |
576 | **对称性(`symmetry`)** --- 第二个要求是说,任何对象对于“它们是否相等”的问题都必须保存一致。与第一个要求不同,若无意中违反这一条,这种情形倒是不难想象。例如,考虑下面的类,它实现了一个区分大小写的字符串。字符串由 toString 保存,但在比较操作中被忽略。
577 |
578 | ```java
579 |
580 | // Broken - violates symmetry
581 | public final class CaseInsensitiveString {
582 | private final String s;
583 |
584 | public CaseInsensitiveString(String s) {
585 | if (s == null) {
586 | throw new NullPointerException();
587 | }
588 | this.s = s;
589 | }
590 |
591 | @Override
592 | public boolean equals(Object o) {
593 | if (o instanceof CaseInsensitiveString) {
594 | return s.equalsIgnoreCase(((CaseInsensitiveString)o).s)
595 | }
596 |
597 | // One-way interoperability
598 | if (o instanceof String) {
599 | return s.equalsIgnoreCase((String)o);
600 | }
601 |
602 | ...// Remainder ommited
603 | }
604 | }
605 |
606 | ```
607 |
608 | 在这个类中,equals 方法的意图非常好,它企图与普通的字符串(String)对象进行互操作。假设我们有一个不区分大小写的字符串和一个普通的字符串:
609 |
610 | ```java
611 |
612 | CaseInsensitiveString cis = new CaseInsensitiveString("TommyYang");
613 | String s = "tommyyang";
614 |
615 | ```
616 |
617 | 如我们所想,cis.equals(s) 返回 true。问题在于 CaseInsensitiveString 类中的 equals 方法知道普通的字符(String)对象,而 String 类中的 equals 方法却不知道不区分大小写的字符串。因此,s.equals(cis)返回 false,显然这违反了对称性。假设你把不区分大小写的字符串对象放到一个集合中:
618 |
619 | ```java
620 |
621 | List cisList = new ArrayList<>();
622 | cisList.add(cis);
623 |
624 | cisList.contains(s);
625 |
626 | ```
627 |
628 | 此时 cisList.contains(s) 会返回什么样的结果?没人知道。在 Sun 的当前实现中,它返回 false,但是这只是这个特定实现得出的结果而已。在其它的实现中,它可能返回 true,或抛出运行时(`Runtime`)异常。**一旦违反了 euqals 约定,当其它对象面对你的对象时,你完成不知道这些对象的行为会怎么样**。
629 |
630 | 为了解决这个问题,你只需要将其与 String 对象互操作的代码移除就可以了。
631 |
632 | ```java
633 |
634 | @Override
635 | public boolean equals(Object o) {
636 | return (o instanceof CaseInsensitiveString)
637 | && s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
638 | }
639 |
640 | ```
641 |
642 | **传递性(`transitive`)** --- euqals 约定的第三个要求是,如果一个对象等于第二个对象,第二个对象等于第三个对象,那个第一个对象一定等于第三个对象。同样地,无意识地违反这条规定的情形也不难想象。考虑子类的情形,它将一个新的值组件(`value component`)添加到超类中。换句话说,子类增加的信息会影响到 equals 的比较结果。我们首先以一个简单的不可变的二维整形 Point 类作为开始:
643 |
644 | ```java
645 |
646 | public class Point {
647 | private final int x;
648 | private final int y;
649 |
650 | public Point(int x, int y) {
651 | this.x = x;
652 | this.y = y;
653 | }
654 |
655 | @Override
656 | public boolean equals(Object o) {
657 | if (!(o instanceof Point)) {
658 | return false;
659 | }
660 |
661 | Point p = (Point)o;
662 | return p.x == this.x && p.y == this.y;
663 | }
664 |
665 | ...// Remainder ommited
666 | }
667 |
668 | ```
669 |
670 | 假设你想要扩展这个类,为一个点添加颜色信息:
671 |
672 | ```java
673 |
674 | public class ColorPoint extends Point {
675 | private final Color color;
676 |
677 | public ColorPoint(int x, int y, Color color) {
678 | super(x, y);
679 | this.color = color;
680 | }
681 |
682 | ...// Remainder ommited
683 | }
684 |
685 | ```
686 |
687 | equals 方法会怎么样呢?如果完全不提供 equals 方法,而是直接从 Point 类继承过来,在 equals 方法做比较的时候颜色信息就会被忽略掉。虽然这么做不会违反 equals 约定,但是很明显这是无法接受的。那么我们应该怎么重写 equals 方法呢?
688 |
689 | ```java
690 |
691 | // Broken - violates symmetry
692 | @Override
693 | public boolean equals(Object o) {
694 | if (!(o instanceof ColorPoint)) {
695 | return false;
696 | }
697 |
698 | return super.equals(o) && ((ColorPoint)o).color == this.color;
699 | }
700 |
701 | ```
702 |
703 | 这个方法的问题在于,你在比较普通点和有色点,以及相反的情形时,可能会得到不同的结果。前一种比较忽略了颜色信息,而后一种比较则总是返回 false,因为参数的类型不正确。为了直观地说明问题所在,我们创建一个普通点和一个有色点:
704 |
705 | ```java
706 |
707 | Point p = new Point(1, 2);
708 | ColorPoint cp = new ColorPoint(1, 2, Color.Red);
709 |
710 | ```
711 |
712 | 然而,p.equals(cp) 返回 true, cp.equals(p) 返回 false。你可以做这样的尝试来修正这个问题,让 ColorPoint.equals 在进行“混合比较”的时候忽略颜色信息:
713 |
714 | ```java
715 |
716 | // Broken - violates transitivity
717 | @Override
718 | public boolean equals(Object o) {
719 | if (!(o instanceof Point)) {
720 | return false;
721 | }
722 |
723 | // if o is a normal Point, do a color-blind comparison
724 | if (!o instanceof ColorPoint) {
725 | return o.equals(this);
726 | }
727 |
728 | // o is a ColorPoint, do a full comparison
729 | return super.equals(o) && ((ColorPoint)o).color == this.color;
730 | }
731 |
732 | ```
733 |
734 | 这种做法确实提供了对称性,却忽略了传递性:
735 |
736 | ```java
737 |
738 | ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
739 | Point p2 = new Point(1, 2);
740 | ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
741 |
742 | ```
743 |
744 | 此时 p1.equals(p2) 和 p2.equals(p3) 都返回 true,但 p1.equals(p3) 返回 false,很显然违反了传递性。前两者的比较不考虑颜色信息(“色盲”),而第三者的比较则考虑了颜色信息。
745 |
746 | 那么,怎么解决上述问题呢?事实上,这是面向对象语言中关于等价关系的一个基本问题。我们**无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留 equals 约定**,除非愿意放弃面向对象的抽象带来的优势。
747 |
748 | 也许你了解到,在 equals 方法中用 getClass 测试代替 instanceof 测试,可以扩展可实例化的类和增加新的值组件,同时保留 equals 约定:
749 |
750 | ```java
751 |
752 | // Broken - violates Liskov substitution principle
753 | @Override
754 | public boolean equals(Object o) {
755 | if (o == null || o.getClass() != this.getClass()) {
756 | return false;
757 | }
758 |
759 | Point p = (Point)o;
760 | return p.x == this.x && p.y == this.y;
761 | }
762 |
763 | ```
764 |
765 | 这段程序只有当对象具有相同实现时,才能是对象等同。虽然这样也不算太糟糕,但是结果确实无法接受的。
766 |
767 | 假设我们编写一个方法,已检测某个整值点是否处在单位圆中。下面是可以采用的一种方法:
768 |
769 | ```java
770 |
771 | // Initialize UnitCircle to contain all Points on the unit circle
772 | private static final Set unitCircle;
773 | static {
774 | unitCircle = new HashSet<>();
775 | unitCircle.add(new Point(1, 0));
776 | unitCircle.add(new Point(0, 1));
777 | unitCircle.add(new Point(-1, 0));
778 | unitCircle.add(new Point(0, -1));
779 | }
780 |
781 | public static boolean onUnitCircle(Point p) {
782 | return unitCircle.contains(p);
783 | }
784 |
785 | ```
786 |
787 | 虽然这种这可能不是实现这种功能的最快方式,不过它的效果很好。但是,假设你通过某种不添加值组件的方式扩展了 Point,例如让它的构造器记录创建了多少个实例:
788 |
789 | ```java
790 |
791 | public class CounterPoint extends Point {
792 | private static final AtomicInteger counter = new AtomicInteger();
793 |
794 | public CounterPoint(int x, int y) {
795 | super(x, y);
796 | counter.incrementAndGet();
797 | }
798 |
799 | public int numberCreated() {
800 | return counter.get();
801 | }
802 | }
803 |
804 | ```
805 |
806 | **里氏替换原则(`Liskov substitution principle`)**认为,一个类型的任何重要属性也将适用于它的子类型,因此为该类型编写的任何方法,在它的子类型上,也应该运行的很好[Liskov87]。但是假设我们将 CounterPointer 实例传递给了 onUnitCircle 方法。如果 Point 类使用了基于 getClass 的 equals 方法,无论 CounterPoint 实例的 x 和 y 的值是多少,onUnitCircle 方法都会返回 false。这时候基于 instanceof 的 equals 方法就会运行的很好。
807 |
808 | 虽然没有一种令人满意的方法可以既扩展不可实例化的类,又增加值组件,但是还是有一种不错的权宜之计(workaround)。根据第 16 条的建议:复合优先于继承。我们不再让 ColorPoint 继承 Point,而是在 ColorPoint 中加入一个私有的 Point 域,以及一个共有的视图(view)方法(见第 5 条),此方法返回一个与该有色点处在相同位置的普通 Point 对象:
809 |
810 | ```java
811 |
812 | // Add a value component without violating the equals contract
813 | public class ColorPoint {
814 | private final Point point;
815 | private final Color color;
816 |
817 | public ColorPoint(int x, int y, Color color) {
818 | if (color == null) {
819 | throw new NullPointerException();
820 | }
821 |
822 | this.point = new Point(x, y);
823 | this.color = color;
824 | }
825 |
826 | // return the point-view of this color point.
827 | public Point asPoint() {
828 | return this.point;
829 | }
830 |
831 | @Override
832 | public boolean equals(Object o){
833 | if (!(o instanceof ColorPoint)){
834 | return false;
835 | }
836 |
837 | ColorPoint cp = (ColorPoint)o;
838 |
839 | return cp.point.equals(this.point) && cp.color.equals(this.color);
840 | }
841 |
842 | }
843 |
844 | ```
845 |
846 | 需要我们记住的是:**复合优先于继承**。
847 |
848 | **一致性(`consistency`)** --- equals 约定的第四个条件是,如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或两个都)被修改了。换句话说,可变对象在不同的时候可以与不同的对象相等,而不可变对象则不会这样。当你在写一个类的时候,应该仔细考虑它是否应该不可变的(见第 15 条)。如果类是不可变的,就必须保证 equals 方法满足这样的限制条件:相等的对象永远相等,不相等的对象永远不相等。
849 |
850 | 无论是否是不可变的,**都不要使 equals 方法依赖于不可靠的资源**。如果违反了这条禁令,要想满足一致性的要求就十分困难了。例如,java.net.URL 的 equals 方法依赖于对 URL 中主机 IP 地址的比较。将一个主机名转变成 IP 地址可能需要访问网络,随着时间的推移,不确保会产生相同的结果。这样会导致 URL 的 equals 方法违反 equals 约定,在实践中有可能引发一些问题。(遗憾的是,因为兼容性的要求,这一行为无法被改变。)除了极少数的例外情况,equals 方法都应该对驻留在内存中的对象执行确定性的计算。
851 |
852 | **非空性(`Non-nullity`)** ---最后一个要求是所有的对象都必须不等于 null。为了满足 equals 方法的这个要求,有人会使用通过一个显示的 null 测试来防止这种情况:
853 |
854 | ```java
855 |
856 | @Override
857 | public boolean equals(Object o) {
858 | if (o == null) {
859 | return false;
860 | }
861 | ...
862 | }
863 |
864 | ```
865 |
866 | 这项测试是不需要的。为了测试其参数的等同性,equals 方法必须把参数转换成适当的类型,以便可以调用它的访问方法(`accessor`),或者访问它的域。在转换之前,equals 方法必须使用 instanceof 操作符,检查其参数是否为正确的类型:
867 |
868 | ```java
869 |
870 | @Override
871 | public boolean equals(Object o) {
872 | if (!(o instanceof MyType)) {
873 | return false;
874 | }
875 | MyType mt = MyType(o);
876 | ...
877 | }
878 |
879 | ```
880 |
881 | 如果漏掉了这一步的类型检查,并且传递给 equals 方法的参数又是错误的类型,那么 equals 方法将会抛出 ClassCastException 异常,这就违反了 equals 的约定。由于 instanceof 的特性[JLS, 15.20.2],第一个操作数为 null,不管第二个操作数是不是 null,都会返回 false。所以这样就不需要我们再自己手动地判断是否为空。
882 |
883 | 结合所有这些方法,得出了以下实现高质量 equals 方法的诀窍:
884 |
885 | 1. **使用 == 操作符检查“参数是否为这个对象的引用”**。如果是,则返回 true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。
886 |
887 | 2. **使用 instanceof 操作符“参数是否为正确的类型”**。
888 |
889 | 3. **把参数转换成正确的类型**。因为转换之前进行过 instanceof 测试,所以确保会成功。
890 |
891 | 4. **对于该类中的每个“关键”域,检查参数中的域与该对象中对应的域相匹配**。
892 |
893 | 5. **当你编写完成了 equals 方法之后,应该问自己三个问题:它是否是对称的、传递的、一致的?**。
894 |
895 | - **覆盖 equals 时总是要覆盖 hashCode(见第 9 条)**。
896 | - **不要企图让 equals 方法过于智能**。
897 | - **不要将 equals 声明中的 Object 对象替换为其他的类型**。
898 |
899 | ### 第 9 条:覆盖 equals 总要覆盖 hashCode
900 |
901 | 一个很常见的错误根源在于没有覆盖 hashCode 方法。在每个覆盖 equals 方法的类中,也必须覆盖 hashCode 方法。如果不这样做的话,就会违反 Object.hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合类包括 HashMap、HashSet 和 HashTable。
902 |
903 | 下面是约定的内容,摘自 Object 规范[JavaSE6]:
904 |
905 | - 在应用程序执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode 方法都必须始终如意的返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
906 |
907 | - 如果两个对象根据 equals(Object) 方法比较是相等的,那么调用两个对象中任意一个对象的 hashCode 方法都必须产生同样的整数结果。
908 |
909 | - 如果两个对象根据 equals(Object) 方法比较是不相等的,那么调用两个对象中任意一个对象的 hashCode 方法,则不一定要产生不同的整数结果。但是作为程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hashTable)的性能。
910 |
911 | **因没有覆盖 hashCode 而违反的关键约定是第二条:相等的对象必须具有相等的散列码(hash code)**。根据类的 equals 方法,两个截然不同的实例在逻辑上是有可能相等的,但是,根据 Object 类的 hashCode 方法,它们仅仅是两个没有共同之处的对象。因此,对象的 hashCode 方法返回两个看起来是随机的整数,而不是根据第二个约定所要求的那样,返回两个相等的整数。
912 |
913 | 上 demo,看下面的 PhoneNumber 类,它的 equals 方法是根据第 8 条中给出的“诀窍”构造出来的:
914 |
915 | ```java
916 |
917 | public final class PhoneNumber {
918 | private final short areaCode;
919 | private final short prefix;
920 | private final short lineNumber;
921 |
922 | public PhoneNumber(short areaCode, short prefix, short lineNumber) {
923 | this.areaCode = areaCode;
924 | this.prefix = prefix;
925 | this.lineNumber = lineNumber;
926 | }
927 |
928 | @Override
929 | public boolean equals(Object o) {
930 | if (o == this) {
931 | return true;
932 | }
933 |
934 | if (! (o instanceof PhoneNumber)) {
935 | return false;
936 | }
937 |
938 | PhoneNumber pn = (PhoneNumber)o;
939 |
940 | return pn.areaCode = this.areaCode && pn.prefix = this.prefix
941 | && pn.lineNumber = this.lineNumber;
942 | }
943 |
944 | // Broken -- no hashCode method
945 |
946 | ... // Remainder omitted
947 | }
948 |
949 | ```
950 |
951 | 假设你企图将这个类与 HashMap 一起使用:
952 |
953 | ```java
954 |
955 | Map map = new HashMap();
956 | map.put(new PhoneNumber(21, 210, 20000), "tommy");
957 |
958 | ```
959 |
960 | 这时候,你期望的是 map.get(new PhoneNumber(21, 210, 20000)) 会返回 "tommy",但它实际上返回的是 null。注意,
961 | 这里涉及两个实例:第一个被用于插入到 HashMap 中,第二个实例和第一个相等,被作为用于获取数据的 key。由于 PhoneNumber
962 | 没有覆盖 hashCode 方法,从而导致两个相等的实例具有不相等的散列码,违反了 hashCode 的约定。因此,put 方法把 PhoneNumber
963 | 对象存放到一个散列桶(hash bucket)中,get 方法却在另一个散列桶中查找这个 PhoneNumber 对象。即使这两个实例正好被放到
964 | 同一个散列桶里,get 方法也必定会返回 null,因为 HashMap 有一项优化,可以将与每个项相关联的散列码缓存起来,如果散列码不
965 | 匹配,也不必检验对象的等同性。
966 |
967 | 下面说说如何设计一个好的散列函数,好的散列函数通常倾向于“为不相等的对象产生不同的散列码”。这正是 hashCode 约定中第三条
968 | 的含义。理想情况下,散列函数应该把集合中不相等的实例均匀的分布到所有可能的散列值上。要想完全达到这种理想的情形是非常困难的。
969 | 幸运的是,相对接近这种理想情形并不太困难。下面给出一种简单的解决办法:
970 |
971 | 1. 把某个非零的常数值,比如说 17,保存在一个名为 res 的 int 类型变量中。
972 |
973 | 2. 对于对象中每个关键域 f (指 equals 方法中涉及的每个域),完成以下步骤:
974 |
975 | a. 为该域计算 int 类型的散列码 c:
976 |
977 | i. 如果该域是 boolean 类型,则计算(f ? 1 : 0)。
978 |
979 | ii. 如果该域是 byte、char、short 或者 int 类型,则计算 (int)f。
980 |
981 | iii. 如果该域是 long 类型,则计算 (int)(f ^ (f >>> 32))。
982 |
983 | iv. 如果该域是 float 类型,则计算 Float.floatToIntBits(f)。
984 |
985 | v. 如果该域是 double 类型,则计算 Double.floatToLongBits(f),然后按步骤 2.a.iii,
986 | 为得到的 long 类型值计算散列值。
987 |
988 | vi. 如果该域是一个对象引用,如果该类的 equals 方法通过递归地调用 equals 的方式来比较这个域,则同样为这个
989 | 域递归地调用 hashCode。如果需要更复杂的比较,则为这个域计算一个“范式(canonical representation)”,
990 | 然后针对这个范式调用 hashCode。如果这个域的值为 null,则返回 0(或者其它某个常数,但通常是0)。
991 |
992 | vii. 如果该域是一个数组,则要把每个元素当作单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算
993 | 一个散列码,然后根据步骤 2.b 中做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用发行版
994 | 本 1.5 中增加的其中一个 Arrays.hashCode 方法。
995 |
996 | b. 按照以下公式,把步骤 2.a 中计算得到的散列码 c 合并到 res 中:
997 |
998 | res = res * 31 + c;
999 |
1000 | 3. 返回 res。
1001 |
1002 | 4. 写完 hashCode 方法之后,问问自己“相等的实例是否都具有相同的散列码”。要编写单元测试来验证你的推断。如果相等的实例有着
1003 | 不相同的散列码,则要找出原因,并修正错误。
1004 |
1005 | 在散列码的计算过程中,可以把冗余域(`redundant field`)排除在外。换句话说,如果一个域的值可以根据参与计算的其它域值计算
1006 | 出来,则可以把这样的域排除在外。必须排除 equals 比较计算中没有用到的任何域,否则很有可能违反 hashCode 约定的第二条。
1007 |
1008 | 上述步骤 1 中用到了一个非零的初始值,因此步骤 2.a 中计算的散列值为 0 的那些初始域,会影响到散列值。如果步骤 1 中的初始值
1009 | 为 0,则整个散列值将不受这些初始域的影响,因为这些初始域会增加冲突的可能性。值 17 则是任选的。
1010 |
1011 | 步骤 2.b 中的乘法部分使得散列值依赖于域的顺序,如果一个类包含多个相似的域,这样的乘法运算就会产生一个更好的散列函数。例如,
1012 | 如果 String 散列函数省略了这个乘法部分,那么只是字母顺序不同的所有字符串都会有相同的散列码。之所以选择 31,是因为它是一个
1013 | 奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失。因为与 2 相乘等价于移位运算,使用素数的好处并不明显,但是习惯上都是
1014 | 使用素数来计算散列结果。31 有个很好的特性,即用移位和减法来替代乘法,可以得到更好的性能:31 \* i == (i << 5) - 1。现代
1015 | JVM 可以自动完成这种优化。
1016 |
1017 | 用上述方法,我们重写 hashCode 方法:
1018 |
1019 | ```java
1020 |
1021 | @Override
1022 | public int hashCode() {
1023 | int res = 17;
1024 | res = 31 * res + areaCode;
1025 | res = 31 * res + prefix;
1026 | res = 31 * res + lineNumber;
1027 | return res;
1028 | }
1029 |
1030 | ```
1031 |
1032 | 实际上,对于 PhoneNumber 类的 hashCode 实现而言,上面这个方法是非常合理的,相当于 JDK 中的实现。它的做法非常简单,也相当
1033 | 快捷,恰当地把不相等的电话号码分散到不同的散列桶中。
1034 |
1035 | 如果一个类是不可变的,并且计算散列码的开销的也比较大,就应该考虑把散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。
1036 | 如果你觉得这种类型的大多数对象会被用做散列键(hash keys),就应该在创建实例的时候计算散列码。否则,可以选择 “延迟初始化(`lazily initialize`)” 散列码,一直到 hashCode 第一次被调用的时候才初始化(见 71 条)。如果是 PhoneNumber 会被
1037 | 经常用来作为 hash key 的话,那么应该这样实现:
1038 |
1039 | ```java
1040 |
1041 | // Lazily initialized, cached hashCode
1042 | private volatile int hashCode;
1043 |
1044 | @Override
1045 | public int hashCode() {
1046 | int res = hashCode;
1047 | if (res == 0) {
1048 | int res = 17;
1049 | res = 31 * res + areaCode;
1050 | res = 31 * res + prefix;
1051 | res = 31 * res + lineNumber;
1052 | }
1053 |
1054 | return res;
1055 | }
1056 |
1057 | ```
1058 |
1059 | **不要试图从散列码计算中排除掉一个对象的关键部分来提高性能**。虽然这样得到的散列函数运行起来可能会更快,但是它的效果不见得会更好,可能会导致散列表慢到根本无法使用。
1060 |
1061 | Java 平台类库中的许多类,比如 String、 Integer 和 Date,都可以把它们的 hashCode 方法返回的确切值规定为该实例值的一个函数。一般来说,这并不是一个好主意,因为这样做严格地限制了在将来的版本中改进散列函数的能力。如果没有规定散列函数的细节,那么当你
1062 | 发现了它的内部缺陷时,就可以在后面的发行版本中修正它,确信没有任何客户端依赖于散列函数返回的确切值。
1063 |
1064 | ### 第 10 条:始终覆盖 toString
1065 |
1066 | 虽然 java.lang.Object 提供了 toString 方法的一个实现,但它返回的字符串通常并不是类的用户所期望看到的。它包含类的名称,以及一个 “@” 符号,接着是散列码的无符号十六进制表示法,例如 “PhoneNumber@193b2d”。toString 的通用约定指出,被返回的字符串应该是一个“简洁的,但信息丰富,并易于阅读的表达形式”[JavaSE 6]。尽管有人认为“PhoneNumber@193b2d”,但是与“(021)589-5588”比起来,它还算不上是信息丰富的。toString 的约定进一步指出,“建议所有的子类都覆盖这个方法”。这是一个非常好的建议,真的!因为这样我们可以按照自己想要的形式去打印一个对象,比如 `System.out.println(phoneNumber)`,默认就会打印出 phoneNumber.toString()。这也就是建议所有子类都重写 toString 的原因。
1067 |
1068 | 虽然遵守 toString 的约定并不像遵守 equals 和 hashCode(见[第 8 条](第 8 条:覆盖 equals 时请遵守通用约定) 和 [第 9 条](第 9 条:覆盖 equals 总要覆盖 hashCode))的约定那么重要,但是,提供好的 toString 可以使类用起来更加的舒适。当对象被传递给 println、printf、字符串连接操作符(+)以及 assert 或者被调试器打印出来时,toString 方法会被自动调用。
1069 |
1070 | 如果为 PhoneNumber 提供了好的 toString 方法,那么,要产生有用的诊断信息会非常简单:
1071 |
1072 | ```java
1073 |
1074 | System.out.println("failed to connect: " + phoneNumber)
1075 |
1076 | ```
1077 |
1078 | 不管是否覆盖了 toString 方法,程序员都将以这种方式来产生诊断信息,但是,如果没有覆盖 toString 方法,产生的消息将难以理解。提供好的 toString 方法,不仅有益于这个类的实例,同样也有益于那些包含这些实例引用的引用的对象,特别是集合对象。打印 Map 时,有下面两条信息:“Tommy = (021)589-5588” 和 “Tommy = PhoneNumber@193b2d”,你更愿意看哪一个?
1079 |
1080 | 在实际应用中,toString 方法应该返回对象中包含的所有值得关注的信息,譬如上面的电话号码的例子一样。如果对象太大,或者对象包含的状态信息难以用字符串来表达,这样做就有点不切实际。
1081 |
1082 | 在实现 toString 的时候,必须要做出一个重要的决定:是否在文档中指定返回值的格式。对于值类(value class),比如电话号码类、矩阵类,也建议这么做。指定格式的好处是,它可以被用做一种标准的、明确的、适合人阅读的对象表示法。这种表示法可以用于输入和输出,以及用在永久适合人类阅读的数据对象中,例如 XML 文档。如果你指定了格式,那么你最好提供一个相匹配的静态工厂或者构造器,以便于程序员可以很容易地在对象和它的字符串表示法之间来回转换。JDK 类库中的许多值类都采用了这种做法,包括 BigInteger、 BigDecimal 和绝大多数的基本类型包装类(boxed primitive class)。
1083 |
1084 | 指定 toString 返回值的格式也有不足之处:如果这个类已经被广泛使用,一旦指定格式,就必须始终如一地坚持这种格式。程序员将会编写出相应的代码来解析这种字符串表示法、产生字符串表示法,以及把字符串表示法嵌入到持久的数据中。如果将来的发行版本中改变了这种表示法,就破坏他们的代码和数据,他们当然会抱怨。如果不指定格式,就可以保留灵活性,便于在将来的发行版本中增加信息,或者改进格式。
1085 |
1086 | **无论你是否决定指定格式,都应该在文档中明确地表明你的意图**。如下,[第 9 条](第 9 条:覆盖 equals 总要覆盖 hashCode)中 PhoneNumber 的 toString 方法:
1087 |
1088 | ```java
1089 |
1090 | /**
1091 | * return (XXX)-YYY-ZZZZ,where XXX is the area code, YYY is the prefix,
1092 | * and ZZZZ is the line number.
1093 | */
1094 | @Override
1095 | public String toString() {
1096 | return String.format("(%03d)-%03d-%04d", this.areaCode, this.prefix, this.lineNumber);
1097 | }
1098 |
1099 | ```
1100 |
1101 | 如果你决定不指定格式,那么在文档注释部分也应该有如下所示的指示信息:
1102 |
1103 | ```java
1104 |
1105 | /**
1106 | * return a brief description of this potion.
1107 | */
1108 | @Override
1109 | public String toString() {
1110 | ...
1111 | }
1112 |
1113 | ```
1114 |
1115 | 对于那些依赖于格式的细节进行编程或者产生永久数据的程序员,在读到这段注释之后,一旦格式被改变,则只能自己承担后果。
1116 |
1117 | 无论是否指定格式,**都为 toString 返回值中包含的所有信息,提供一种编程式的访问途径**。
1118 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | vitepress-plugin-comment-with-giscus:
12 | specifier: ^1.1.15
13 | version: 1.1.15(vue@3.5.22)
14 | devDependencies:
15 | vitepress:
16 | specifier: ^2.0.0-alpha.12
17 | version: 2.0.0-alpha.12(postcss@8.5.6)
18 |
19 | packages:
20 |
21 | '@babel/helper-string-parser@7.27.1':
22 | resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
23 | engines: {node: '>=6.9.0'}
24 |
25 | '@babel/helper-validator-identifier@7.27.1':
26 | resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
27 | engines: {node: '>=6.9.0'}
28 |
29 | '@babel/parser@7.28.4':
30 | resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
31 | engines: {node: '>=6.0.0'}
32 | hasBin: true
33 |
34 | '@babel/types@7.28.4':
35 | resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
36 | engines: {node: '>=6.9.0'}
37 |
38 | '@docsearch/css@4.2.0':
39 | resolution: {integrity: sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g==}
40 |
41 | '@docsearch/js@4.2.0':
42 | resolution: {integrity: sha512-KBHVPO29QiGUFJYeAqxW0oXtGf/aghNmRrIRPT4/28JAefqoCkNn/ZM/jeQ7fHjl0KNM6C+KlLVYjwyz6lNZnA==}
43 |
44 | '@esbuild/aix-ppc64@0.25.11':
45 | resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
46 | engines: {node: '>=18'}
47 | cpu: [ppc64]
48 | os: [aix]
49 |
50 | '@esbuild/android-arm64@0.25.11':
51 | resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==}
52 | engines: {node: '>=18'}
53 | cpu: [arm64]
54 | os: [android]
55 |
56 | '@esbuild/android-arm@0.25.11':
57 | resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==}
58 | engines: {node: '>=18'}
59 | cpu: [arm]
60 | os: [android]
61 |
62 | '@esbuild/android-x64@0.25.11':
63 | resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==}
64 | engines: {node: '>=18'}
65 | cpu: [x64]
66 | os: [android]
67 |
68 | '@esbuild/darwin-arm64@0.25.11':
69 | resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==}
70 | engines: {node: '>=18'}
71 | cpu: [arm64]
72 | os: [darwin]
73 |
74 | '@esbuild/darwin-x64@0.25.11':
75 | resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==}
76 | engines: {node: '>=18'}
77 | cpu: [x64]
78 | os: [darwin]
79 |
80 | '@esbuild/freebsd-arm64@0.25.11':
81 | resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==}
82 | engines: {node: '>=18'}
83 | cpu: [arm64]
84 | os: [freebsd]
85 |
86 | '@esbuild/freebsd-x64@0.25.11':
87 | resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==}
88 | engines: {node: '>=18'}
89 | cpu: [x64]
90 | os: [freebsd]
91 |
92 | '@esbuild/linux-arm64@0.25.11':
93 | resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==}
94 | engines: {node: '>=18'}
95 | cpu: [arm64]
96 | os: [linux]
97 |
98 | '@esbuild/linux-arm@0.25.11':
99 | resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==}
100 | engines: {node: '>=18'}
101 | cpu: [arm]
102 | os: [linux]
103 |
104 | '@esbuild/linux-ia32@0.25.11':
105 | resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==}
106 | engines: {node: '>=18'}
107 | cpu: [ia32]
108 | os: [linux]
109 |
110 | '@esbuild/linux-loong64@0.25.11':
111 | resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==}
112 | engines: {node: '>=18'}
113 | cpu: [loong64]
114 | os: [linux]
115 |
116 | '@esbuild/linux-mips64el@0.25.11':
117 | resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==}
118 | engines: {node: '>=18'}
119 | cpu: [mips64el]
120 | os: [linux]
121 |
122 | '@esbuild/linux-ppc64@0.25.11':
123 | resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==}
124 | engines: {node: '>=18'}
125 | cpu: [ppc64]
126 | os: [linux]
127 |
128 | '@esbuild/linux-riscv64@0.25.11':
129 | resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==}
130 | engines: {node: '>=18'}
131 | cpu: [riscv64]
132 | os: [linux]
133 |
134 | '@esbuild/linux-s390x@0.25.11':
135 | resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==}
136 | engines: {node: '>=18'}
137 | cpu: [s390x]
138 | os: [linux]
139 |
140 | '@esbuild/linux-x64@0.25.11':
141 | resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==}
142 | engines: {node: '>=18'}
143 | cpu: [x64]
144 | os: [linux]
145 |
146 | '@esbuild/netbsd-arm64@0.25.11':
147 | resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==}
148 | engines: {node: '>=18'}
149 | cpu: [arm64]
150 | os: [netbsd]
151 |
152 | '@esbuild/netbsd-x64@0.25.11':
153 | resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==}
154 | engines: {node: '>=18'}
155 | cpu: [x64]
156 | os: [netbsd]
157 |
158 | '@esbuild/openbsd-arm64@0.25.11':
159 | resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==}
160 | engines: {node: '>=18'}
161 | cpu: [arm64]
162 | os: [openbsd]
163 |
164 | '@esbuild/openbsd-x64@0.25.11':
165 | resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==}
166 | engines: {node: '>=18'}
167 | cpu: [x64]
168 | os: [openbsd]
169 |
170 | '@esbuild/openharmony-arm64@0.25.11':
171 | resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==}
172 | engines: {node: '>=18'}
173 | cpu: [arm64]
174 | os: [openharmony]
175 |
176 | '@esbuild/sunos-x64@0.25.11':
177 | resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==}
178 | engines: {node: '>=18'}
179 | cpu: [x64]
180 | os: [sunos]
181 |
182 | '@esbuild/win32-arm64@0.25.11':
183 | resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==}
184 | engines: {node: '>=18'}
185 | cpu: [arm64]
186 | os: [win32]
187 |
188 | '@esbuild/win32-ia32@0.25.11':
189 | resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==}
190 | engines: {node: '>=18'}
191 | cpu: [ia32]
192 | os: [win32]
193 |
194 | '@esbuild/win32-x64@0.25.11':
195 | resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==}
196 | engines: {node: '>=18'}
197 | cpu: [x64]
198 | os: [win32]
199 |
200 | '@giscus/vue@2.4.0':
201 | resolution: {integrity: sha512-QOxKHgsMT91myyQagP2v20YYAei1ByZuc3qcaYxbHx4AwOeyVrybDIuRFwG9YDv6OraC86jYnU4Ixd37ddC/0A==}
202 | peerDependencies:
203 | vue: '>=3.2.0'
204 |
205 | '@iconify-json/simple-icons@1.2.55':
206 | resolution: {integrity: sha512-9vc04pmup/zcef8hDypWU8nMwMaFVkWuUzWkxyL++DVp5AA8baoJHK6RyKN1v+cvfR2agxkUb053XVggzFFkTA==}
207 |
208 | '@iconify/types@2.0.0':
209 | resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
210 |
211 | '@jridgewell/sourcemap-codec@1.5.5':
212 | resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
213 |
214 | '@lit-labs/ssr-dom-shim@1.4.0':
215 | resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==}
216 |
217 | '@lit/reactive-element@2.1.1':
218 | resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==}
219 |
220 | '@rolldown/pluginutils@1.0.0-beta.29':
221 | resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==}
222 |
223 | '@rollup/rollup-android-arm-eabi@4.52.5':
224 | resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==}
225 | cpu: [arm]
226 | os: [android]
227 |
228 | '@rollup/rollup-android-arm64@4.52.5':
229 | resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==}
230 | cpu: [arm64]
231 | os: [android]
232 |
233 | '@rollup/rollup-darwin-arm64@4.52.5':
234 | resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==}
235 | cpu: [arm64]
236 | os: [darwin]
237 |
238 | '@rollup/rollup-darwin-x64@4.52.5':
239 | resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==}
240 | cpu: [x64]
241 | os: [darwin]
242 |
243 | '@rollup/rollup-freebsd-arm64@4.52.5':
244 | resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==}
245 | cpu: [arm64]
246 | os: [freebsd]
247 |
248 | '@rollup/rollup-freebsd-x64@4.52.5':
249 | resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==}
250 | cpu: [x64]
251 | os: [freebsd]
252 |
253 | '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
254 | resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
255 | cpu: [arm]
256 | os: [linux]
257 | libc: [glibc]
258 |
259 | '@rollup/rollup-linux-arm-musleabihf@4.52.5':
260 | resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
261 | cpu: [arm]
262 | os: [linux]
263 | libc: [musl]
264 |
265 | '@rollup/rollup-linux-arm64-gnu@4.52.5':
266 | resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
267 | cpu: [arm64]
268 | os: [linux]
269 | libc: [glibc]
270 |
271 | '@rollup/rollup-linux-arm64-musl@4.52.5':
272 | resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
273 | cpu: [arm64]
274 | os: [linux]
275 | libc: [musl]
276 |
277 | '@rollup/rollup-linux-loong64-gnu@4.52.5':
278 | resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
279 | cpu: [loong64]
280 | os: [linux]
281 | libc: [glibc]
282 |
283 | '@rollup/rollup-linux-ppc64-gnu@4.52.5':
284 | resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
285 | cpu: [ppc64]
286 | os: [linux]
287 | libc: [glibc]
288 |
289 | '@rollup/rollup-linux-riscv64-gnu@4.52.5':
290 | resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
291 | cpu: [riscv64]
292 | os: [linux]
293 | libc: [glibc]
294 |
295 | '@rollup/rollup-linux-riscv64-musl@4.52.5':
296 | resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
297 | cpu: [riscv64]
298 | os: [linux]
299 | libc: [musl]
300 |
301 | '@rollup/rollup-linux-s390x-gnu@4.52.5':
302 | resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
303 | cpu: [s390x]
304 | os: [linux]
305 | libc: [glibc]
306 |
307 | '@rollup/rollup-linux-x64-gnu@4.52.5':
308 | resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
309 | cpu: [x64]
310 | os: [linux]
311 | libc: [glibc]
312 |
313 | '@rollup/rollup-linux-x64-musl@4.52.5':
314 | resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
315 | cpu: [x64]
316 | os: [linux]
317 | libc: [musl]
318 |
319 | '@rollup/rollup-openharmony-arm64@4.52.5':
320 | resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
321 | cpu: [arm64]
322 | os: [openharmony]
323 |
324 | '@rollup/rollup-win32-arm64-msvc@4.52.5':
325 | resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==}
326 | cpu: [arm64]
327 | os: [win32]
328 |
329 | '@rollup/rollup-win32-ia32-msvc@4.52.5':
330 | resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==}
331 | cpu: [ia32]
332 | os: [win32]
333 |
334 | '@rollup/rollup-win32-x64-gnu@4.52.5':
335 | resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==}
336 | cpu: [x64]
337 | os: [win32]
338 |
339 | '@rollup/rollup-win32-x64-msvc@4.52.5':
340 | resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==}
341 | cpu: [x64]
342 | os: [win32]
343 |
344 | '@shikijs/core@3.13.0':
345 | resolution: {integrity: sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==}
346 |
347 | '@shikijs/engine-javascript@3.13.0':
348 | resolution: {integrity: sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==}
349 |
350 | '@shikijs/engine-oniguruma@3.13.0':
351 | resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==}
352 |
353 | '@shikijs/langs@3.13.0':
354 | resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==}
355 |
356 | '@shikijs/themes@3.13.0':
357 | resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==}
358 |
359 | '@shikijs/transformers@3.13.0':
360 | resolution: {integrity: sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA==}
361 |
362 | '@shikijs/types@3.13.0':
363 | resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==}
364 |
365 | '@shikijs/vscode-textmate@10.0.2':
366 | resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
367 |
368 | '@types/estree@1.0.8':
369 | resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
370 |
371 | '@types/hast@3.0.4':
372 | resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
373 |
374 | '@types/linkify-it@5.0.0':
375 | resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
376 |
377 | '@types/markdown-it@14.1.2':
378 | resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
379 |
380 | '@types/mdast@4.0.4':
381 | resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
382 |
383 | '@types/mdurl@2.0.0':
384 | resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
385 |
386 | '@types/trusted-types@2.0.7':
387 | resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
388 |
389 | '@types/unist@3.0.3':
390 | resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
391 |
392 | '@types/web-bluetooth@0.0.21':
393 | resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
394 |
395 | '@ungap/structured-clone@1.3.0':
396 | resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
397 |
398 | '@vitejs/plugin-vue@6.0.1':
399 | resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==}
400 | engines: {node: ^20.19.0 || >=22.12.0}
401 | peerDependencies:
402 | vite: ^5.0.0 || ^6.0.0 || ^7.0.0
403 | vue: ^3.2.25
404 |
405 | '@vue/compiler-core@3.5.22':
406 | resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==}
407 |
408 | '@vue/compiler-dom@3.5.22':
409 | resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==}
410 |
411 | '@vue/compiler-sfc@3.5.22':
412 | resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==}
413 |
414 | '@vue/compiler-ssr@3.5.22':
415 | resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==}
416 |
417 | '@vue/devtools-api@8.0.3':
418 | resolution: {integrity: sha512-YxZE7xNvvfq5XmjJh1ml+CzVNrRjuZYCuT5Xjj0u9RlXU7za/MRuZDUXcKfp0j7IvYkDut49vlKqbiQ1xhXP2w==}
419 |
420 | '@vue/devtools-kit@8.0.3':
421 | resolution: {integrity: sha512-UF4YUOVGdfzXLCv5pMg2DxocB8dvXz278fpgEE+nJ/DRALQGAva7sj9ton0VWZ9hmXw+SV8yKMrxP2MpMhq9Wg==}
422 |
423 | '@vue/devtools-shared@8.0.3':
424 | resolution: {integrity: sha512-s/QNll7TlpbADFZrPVsaUNPCOF8NvQgtgmmB7Tip6pLf/HcOvBTly0lfLQ0Eylu9FQ4OqBhFpLyBgwykiSf8zw==}
425 |
426 | '@vue/reactivity@3.5.22':
427 | resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==}
428 |
429 | '@vue/runtime-core@3.5.22':
430 | resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==}
431 |
432 | '@vue/runtime-dom@3.5.22':
433 | resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==}
434 |
435 | '@vue/server-renderer@3.5.22':
436 | resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==}
437 | peerDependencies:
438 | vue: 3.5.22
439 |
440 | '@vue/shared@3.5.22':
441 | resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==}
442 |
443 | '@vueuse/core@13.9.0':
444 | resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
445 | peerDependencies:
446 | vue: ^3.5.0
447 |
448 | '@vueuse/integrations@13.9.0':
449 | resolution: {integrity: sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==}
450 | peerDependencies:
451 | async-validator: ^4
452 | axios: ^1
453 | change-case: ^5
454 | drauu: ^0.4
455 | focus-trap: ^7
456 | fuse.js: ^7
457 | idb-keyval: ^6
458 | jwt-decode: ^4
459 | nprogress: ^0.2
460 | qrcode: ^1.5
461 | sortablejs: ^1
462 | universal-cookie: ^7 || ^8
463 | vue: ^3.5.0
464 | peerDependenciesMeta:
465 | async-validator:
466 | optional: true
467 | axios:
468 | optional: true
469 | change-case:
470 | optional: true
471 | drauu:
472 | optional: true
473 | focus-trap:
474 | optional: true
475 | fuse.js:
476 | optional: true
477 | idb-keyval:
478 | optional: true
479 | jwt-decode:
480 | optional: true
481 | nprogress:
482 | optional: true
483 | qrcode:
484 | optional: true
485 | sortablejs:
486 | optional: true
487 | universal-cookie:
488 | optional: true
489 |
490 | '@vueuse/metadata@13.9.0':
491 | resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
492 |
493 | '@vueuse/shared@13.9.0':
494 | resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
495 | peerDependencies:
496 | vue: ^3.5.0
497 |
498 | birpc@2.6.1:
499 | resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==}
500 |
501 | ccount@2.0.1:
502 | resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
503 |
504 | character-entities-html4@2.1.0:
505 | resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
506 |
507 | character-entities-legacy@3.0.0:
508 | resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
509 |
510 | comma-separated-tokens@2.0.3:
511 | resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
512 |
513 | copy-anything@3.0.5:
514 | resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
515 | engines: {node: '>=12.13'}
516 |
517 | csstype@3.1.3:
518 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
519 |
520 | dequal@2.0.3:
521 | resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
522 | engines: {node: '>=6'}
523 |
524 | devlop@1.1.0:
525 | resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
526 |
527 | entities@4.5.0:
528 | resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
529 | engines: {node: '>=0.12'}
530 |
531 | esbuild@0.25.11:
532 | resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==}
533 | engines: {node: '>=18'}
534 | hasBin: true
535 |
536 | estree-walker@2.0.2:
537 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
538 |
539 | fdir@6.5.0:
540 | resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
541 | engines: {node: '>=12.0.0'}
542 | peerDependencies:
543 | picomatch: ^3 || ^4
544 | peerDependenciesMeta:
545 | picomatch:
546 | optional: true
547 |
548 | focus-trap@7.6.5:
549 | resolution: {integrity: sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==}
550 |
551 | fsevents@2.3.3:
552 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
553 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
554 | os: [darwin]
555 |
556 | giscus@1.6.0:
557 | resolution: {integrity: sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==}
558 |
559 | hast-util-to-html@9.0.5:
560 | resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
561 |
562 | hast-util-whitespace@3.0.0:
563 | resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
564 |
565 | hookable@5.5.3:
566 | resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
567 |
568 | html-void-elements@3.0.0:
569 | resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
570 |
571 | is-what@4.1.16:
572 | resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
573 | engines: {node: '>=12.13'}
574 |
575 | lit-element@4.2.1:
576 | resolution: {integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==}
577 |
578 | lit-html@3.3.1:
579 | resolution: {integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==}
580 |
581 | lit@3.3.1:
582 | resolution: {integrity: sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==}
583 |
584 | magic-string@0.30.19:
585 | resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
586 |
587 | mark.js@8.11.1:
588 | resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
589 |
590 | mdast-util-to-hast@13.2.0:
591 | resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
592 |
593 | micromark-util-character@2.1.1:
594 | resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
595 |
596 | micromark-util-encode@2.0.1:
597 | resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
598 |
599 | micromark-util-sanitize-uri@2.0.1:
600 | resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
601 |
602 | micromark-util-symbol@2.0.1:
603 | resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
604 |
605 | micromark-util-types@2.0.2:
606 | resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
607 |
608 | minisearch@7.2.0:
609 | resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==}
610 |
611 | mitt@3.0.1:
612 | resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
613 |
614 | nanoid@3.3.11:
615 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
616 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
617 | hasBin: true
618 |
619 | oniguruma-parser@0.12.1:
620 | resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
621 |
622 | oniguruma-to-es@4.3.3:
623 | resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}
624 |
625 | perfect-debounce@2.0.0:
626 | resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==}
627 |
628 | picocolors@1.1.1:
629 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
630 |
631 | picomatch@4.0.3:
632 | resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
633 | engines: {node: '>=12'}
634 |
635 | postcss@8.5.6:
636 | resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
637 | engines: {node: ^10 || ^12 || >=14}
638 |
639 | property-information@7.1.0:
640 | resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
641 |
642 | regex-recursion@6.0.2:
643 | resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
644 |
645 | regex-utilities@2.3.0:
646 | resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
647 |
648 | regex@6.0.1:
649 | resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
650 |
651 | rfdc@1.4.1:
652 | resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
653 |
654 | rollup@4.52.5:
655 | resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
656 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
657 | hasBin: true
658 |
659 | shiki@3.13.0:
660 | resolution: {integrity: sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==}
661 |
662 | source-map-js@1.2.1:
663 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
664 | engines: {node: '>=0.10.0'}
665 |
666 | space-separated-tokens@2.0.2:
667 | resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
668 |
669 | speakingurl@14.0.1:
670 | resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
671 | engines: {node: '>=0.10.0'}
672 |
673 | stringify-entities@4.0.4:
674 | resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
675 |
676 | superjson@2.2.2:
677 | resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
678 | engines: {node: '>=16'}
679 |
680 | tabbable@6.2.0:
681 | resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
682 |
683 | tinyglobby@0.2.15:
684 | resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
685 | engines: {node: '>=12.0.0'}
686 |
687 | trim-lines@3.0.1:
688 | resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
689 |
690 | unist-util-is@6.0.1:
691 | resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
692 |
693 | unist-util-position@5.0.0:
694 | resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
695 |
696 | unist-util-stringify-position@4.0.0:
697 | resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
698 |
699 | unist-util-visit-parents@6.0.2:
700 | resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
701 |
702 | unist-util-visit@5.0.0:
703 | resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
704 |
705 | vfile-message@4.0.3:
706 | resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
707 |
708 | vfile@6.0.3:
709 | resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
710 |
711 | vite@7.1.11:
712 | resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==}
713 | engines: {node: ^20.19.0 || >=22.12.0}
714 | hasBin: true
715 | peerDependencies:
716 | '@types/node': ^20.19.0 || >=22.12.0
717 | jiti: '>=1.21.0'
718 | less: ^4.0.0
719 | lightningcss: ^1.21.0
720 | sass: ^1.70.0
721 | sass-embedded: ^1.70.0
722 | stylus: '>=0.54.8'
723 | sugarss: ^5.0.0
724 | terser: ^5.16.0
725 | tsx: ^4.8.1
726 | yaml: ^2.4.2
727 | peerDependenciesMeta:
728 | '@types/node':
729 | optional: true
730 | jiti:
731 | optional: true
732 | less:
733 | optional: true
734 | lightningcss:
735 | optional: true
736 | sass:
737 | optional: true
738 | sass-embedded:
739 | optional: true
740 | stylus:
741 | optional: true
742 | sugarss:
743 | optional: true
744 | terser:
745 | optional: true
746 | tsx:
747 | optional: true
748 | yaml:
749 | optional: true
750 |
751 | vitepress-plugin-comment-with-giscus@1.1.15:
752 | resolution: {integrity: sha512-1DJjgN+7SYvn5ZkjuSXPmz7nlqfcrh4qCGGviiZghA2ELXnaO2m9WY7m+RisPSaqCn90xqe0JbO2T4NMq8iUBg==}
753 |
754 | vitepress@2.0.0-alpha.12:
755 | resolution: {integrity: sha512-yZwCwRRepcpN5QeAhwSnEJxS3I6zJcVixqL1dnm6km4cnriLpQyy2sXQDsE5Ti3pxGPbhU51nTMwI+XC1KNnJg==}
756 | hasBin: true
757 | peerDependencies:
758 | markdown-it-mathjax3: ^4
759 | oxc-minify: ^0.82.1
760 | postcss: ^8
761 | peerDependenciesMeta:
762 | markdown-it-mathjax3:
763 | optional: true
764 | oxc-minify:
765 | optional: true
766 | postcss:
767 | optional: true
768 |
769 | vue@3.5.22:
770 | resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==}
771 | peerDependencies:
772 | typescript: '*'
773 | peerDependenciesMeta:
774 | typescript:
775 | optional: true
776 |
777 | zwitch@2.0.4:
778 | resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
779 |
780 | snapshots:
781 |
782 | '@babel/helper-string-parser@7.27.1': {}
783 |
784 | '@babel/helper-validator-identifier@7.27.1': {}
785 |
786 | '@babel/parser@7.28.4':
787 | dependencies:
788 | '@babel/types': 7.28.4
789 |
790 | '@babel/types@7.28.4':
791 | dependencies:
792 | '@babel/helper-string-parser': 7.27.1
793 | '@babel/helper-validator-identifier': 7.27.1
794 |
795 | '@docsearch/css@4.2.0': {}
796 |
797 | '@docsearch/js@4.2.0': {}
798 |
799 | '@esbuild/aix-ppc64@0.25.11':
800 | optional: true
801 |
802 | '@esbuild/android-arm64@0.25.11':
803 | optional: true
804 |
805 | '@esbuild/android-arm@0.25.11':
806 | optional: true
807 |
808 | '@esbuild/android-x64@0.25.11':
809 | optional: true
810 |
811 | '@esbuild/darwin-arm64@0.25.11':
812 | optional: true
813 |
814 | '@esbuild/darwin-x64@0.25.11':
815 | optional: true
816 |
817 | '@esbuild/freebsd-arm64@0.25.11':
818 | optional: true
819 |
820 | '@esbuild/freebsd-x64@0.25.11':
821 | optional: true
822 |
823 | '@esbuild/linux-arm64@0.25.11':
824 | optional: true
825 |
826 | '@esbuild/linux-arm@0.25.11':
827 | optional: true
828 |
829 | '@esbuild/linux-ia32@0.25.11':
830 | optional: true
831 |
832 | '@esbuild/linux-loong64@0.25.11':
833 | optional: true
834 |
835 | '@esbuild/linux-mips64el@0.25.11':
836 | optional: true
837 |
838 | '@esbuild/linux-ppc64@0.25.11':
839 | optional: true
840 |
841 | '@esbuild/linux-riscv64@0.25.11':
842 | optional: true
843 |
844 | '@esbuild/linux-s390x@0.25.11':
845 | optional: true
846 |
847 | '@esbuild/linux-x64@0.25.11':
848 | optional: true
849 |
850 | '@esbuild/netbsd-arm64@0.25.11':
851 | optional: true
852 |
853 | '@esbuild/netbsd-x64@0.25.11':
854 | optional: true
855 |
856 | '@esbuild/openbsd-arm64@0.25.11':
857 | optional: true
858 |
859 | '@esbuild/openbsd-x64@0.25.11':
860 | optional: true
861 |
862 | '@esbuild/openharmony-arm64@0.25.11':
863 | optional: true
864 |
865 | '@esbuild/sunos-x64@0.25.11':
866 | optional: true
867 |
868 | '@esbuild/win32-arm64@0.25.11':
869 | optional: true
870 |
871 | '@esbuild/win32-ia32@0.25.11':
872 | optional: true
873 |
874 | '@esbuild/win32-x64@0.25.11':
875 | optional: true
876 |
877 | '@giscus/vue@2.4.0(vue@3.5.22)':
878 | dependencies:
879 | giscus: 1.6.0
880 | vue: 3.5.22
881 |
882 | '@iconify-json/simple-icons@1.2.55':
883 | dependencies:
884 | '@iconify/types': 2.0.0
885 |
886 | '@iconify/types@2.0.0': {}
887 |
888 | '@jridgewell/sourcemap-codec@1.5.5': {}
889 |
890 | '@lit-labs/ssr-dom-shim@1.4.0': {}
891 |
892 | '@lit/reactive-element@2.1.1':
893 | dependencies:
894 | '@lit-labs/ssr-dom-shim': 1.4.0
895 |
896 | '@rolldown/pluginutils@1.0.0-beta.29': {}
897 |
898 | '@rollup/rollup-android-arm-eabi@4.52.5':
899 | optional: true
900 |
901 | '@rollup/rollup-android-arm64@4.52.5':
902 | optional: true
903 |
904 | '@rollup/rollup-darwin-arm64@4.52.5':
905 | optional: true
906 |
907 | '@rollup/rollup-darwin-x64@4.52.5':
908 | optional: true
909 |
910 | '@rollup/rollup-freebsd-arm64@4.52.5':
911 | optional: true
912 |
913 | '@rollup/rollup-freebsd-x64@4.52.5':
914 | optional: true
915 |
916 | '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
917 | optional: true
918 |
919 | '@rollup/rollup-linux-arm-musleabihf@4.52.5':
920 | optional: true
921 |
922 | '@rollup/rollup-linux-arm64-gnu@4.52.5':
923 | optional: true
924 |
925 | '@rollup/rollup-linux-arm64-musl@4.52.5':
926 | optional: true
927 |
928 | '@rollup/rollup-linux-loong64-gnu@4.52.5':
929 | optional: true
930 |
931 | '@rollup/rollup-linux-ppc64-gnu@4.52.5':
932 | optional: true
933 |
934 | '@rollup/rollup-linux-riscv64-gnu@4.52.5':
935 | optional: true
936 |
937 | '@rollup/rollup-linux-riscv64-musl@4.52.5':
938 | optional: true
939 |
940 | '@rollup/rollup-linux-s390x-gnu@4.52.5':
941 | optional: true
942 |
943 | '@rollup/rollup-linux-x64-gnu@4.52.5':
944 | optional: true
945 |
946 | '@rollup/rollup-linux-x64-musl@4.52.5':
947 | optional: true
948 |
949 | '@rollup/rollup-openharmony-arm64@4.52.5':
950 | optional: true
951 |
952 | '@rollup/rollup-win32-arm64-msvc@4.52.5':
953 | optional: true
954 |
955 | '@rollup/rollup-win32-ia32-msvc@4.52.5':
956 | optional: true
957 |
958 | '@rollup/rollup-win32-x64-gnu@4.52.5':
959 | optional: true
960 |
961 | '@rollup/rollup-win32-x64-msvc@4.52.5':
962 | optional: true
963 |
964 | '@shikijs/core@3.13.0':
965 | dependencies:
966 | '@shikijs/types': 3.13.0
967 | '@shikijs/vscode-textmate': 10.0.2
968 | '@types/hast': 3.0.4
969 | hast-util-to-html: 9.0.5
970 |
971 | '@shikijs/engine-javascript@3.13.0':
972 | dependencies:
973 | '@shikijs/types': 3.13.0
974 | '@shikijs/vscode-textmate': 10.0.2
975 | oniguruma-to-es: 4.3.3
976 |
977 | '@shikijs/engine-oniguruma@3.13.0':
978 | dependencies:
979 | '@shikijs/types': 3.13.0
980 | '@shikijs/vscode-textmate': 10.0.2
981 |
982 | '@shikijs/langs@3.13.0':
983 | dependencies:
984 | '@shikijs/types': 3.13.0
985 |
986 | '@shikijs/themes@3.13.0':
987 | dependencies:
988 | '@shikijs/types': 3.13.0
989 |
990 | '@shikijs/transformers@3.13.0':
991 | dependencies:
992 | '@shikijs/core': 3.13.0
993 | '@shikijs/types': 3.13.0
994 |
995 | '@shikijs/types@3.13.0':
996 | dependencies:
997 | '@shikijs/vscode-textmate': 10.0.2
998 | '@types/hast': 3.0.4
999 |
1000 | '@shikijs/vscode-textmate@10.0.2': {}
1001 |
1002 | '@types/estree@1.0.8': {}
1003 |
1004 | '@types/hast@3.0.4':
1005 | dependencies:
1006 | '@types/unist': 3.0.3
1007 |
1008 | '@types/linkify-it@5.0.0': {}
1009 |
1010 | '@types/markdown-it@14.1.2':
1011 | dependencies:
1012 | '@types/linkify-it': 5.0.0
1013 | '@types/mdurl': 2.0.0
1014 |
1015 | '@types/mdast@4.0.4':
1016 | dependencies:
1017 | '@types/unist': 3.0.3
1018 |
1019 | '@types/mdurl@2.0.0': {}
1020 |
1021 | '@types/trusted-types@2.0.7': {}
1022 |
1023 | '@types/unist@3.0.3': {}
1024 |
1025 | '@types/web-bluetooth@0.0.21': {}
1026 |
1027 | '@ungap/structured-clone@1.3.0': {}
1028 |
1029 | '@vitejs/plugin-vue@6.0.1(vite@7.1.11)(vue@3.5.22)':
1030 | dependencies:
1031 | '@rolldown/pluginutils': 1.0.0-beta.29
1032 | vite: 7.1.11
1033 | vue: 3.5.22
1034 |
1035 | '@vue/compiler-core@3.5.22':
1036 | dependencies:
1037 | '@babel/parser': 7.28.4
1038 | '@vue/shared': 3.5.22
1039 | entities: 4.5.0
1040 | estree-walker: 2.0.2
1041 | source-map-js: 1.2.1
1042 |
1043 | '@vue/compiler-dom@3.5.22':
1044 | dependencies:
1045 | '@vue/compiler-core': 3.5.22
1046 | '@vue/shared': 3.5.22
1047 |
1048 | '@vue/compiler-sfc@3.5.22':
1049 | dependencies:
1050 | '@babel/parser': 7.28.4
1051 | '@vue/compiler-core': 3.5.22
1052 | '@vue/compiler-dom': 3.5.22
1053 | '@vue/compiler-ssr': 3.5.22
1054 | '@vue/shared': 3.5.22
1055 | estree-walker: 2.0.2
1056 | magic-string: 0.30.19
1057 | postcss: 8.5.6
1058 | source-map-js: 1.2.1
1059 |
1060 | '@vue/compiler-ssr@3.5.22':
1061 | dependencies:
1062 | '@vue/compiler-dom': 3.5.22
1063 | '@vue/shared': 3.5.22
1064 |
1065 | '@vue/devtools-api@8.0.3':
1066 | dependencies:
1067 | '@vue/devtools-kit': 8.0.3
1068 |
1069 | '@vue/devtools-kit@8.0.3':
1070 | dependencies:
1071 | '@vue/devtools-shared': 8.0.3
1072 | birpc: 2.6.1
1073 | hookable: 5.5.3
1074 | mitt: 3.0.1
1075 | perfect-debounce: 2.0.0
1076 | speakingurl: 14.0.1
1077 | superjson: 2.2.2
1078 |
1079 | '@vue/devtools-shared@8.0.3':
1080 | dependencies:
1081 | rfdc: 1.4.1
1082 |
1083 | '@vue/reactivity@3.5.22':
1084 | dependencies:
1085 | '@vue/shared': 3.5.22
1086 |
1087 | '@vue/runtime-core@3.5.22':
1088 | dependencies:
1089 | '@vue/reactivity': 3.5.22
1090 | '@vue/shared': 3.5.22
1091 |
1092 | '@vue/runtime-dom@3.5.22':
1093 | dependencies:
1094 | '@vue/reactivity': 3.5.22
1095 | '@vue/runtime-core': 3.5.22
1096 | '@vue/shared': 3.5.22
1097 | csstype: 3.1.3
1098 |
1099 | '@vue/server-renderer@3.5.22(vue@3.5.22)':
1100 | dependencies:
1101 | '@vue/compiler-ssr': 3.5.22
1102 | '@vue/shared': 3.5.22
1103 | vue: 3.5.22
1104 |
1105 | '@vue/shared@3.5.22': {}
1106 |
1107 | '@vueuse/core@13.9.0(vue@3.5.22)':
1108 | dependencies:
1109 | '@types/web-bluetooth': 0.0.21
1110 | '@vueuse/metadata': 13.9.0
1111 | '@vueuse/shared': 13.9.0(vue@3.5.22)
1112 | vue: 3.5.22
1113 |
1114 | '@vueuse/integrations@13.9.0(focus-trap@7.6.5)(vue@3.5.22)':
1115 | dependencies:
1116 | '@vueuse/core': 13.9.0(vue@3.5.22)
1117 | '@vueuse/shared': 13.9.0(vue@3.5.22)
1118 | vue: 3.5.22
1119 | optionalDependencies:
1120 | focus-trap: 7.6.5
1121 |
1122 | '@vueuse/metadata@13.9.0': {}
1123 |
1124 | '@vueuse/shared@13.9.0(vue@3.5.22)':
1125 | dependencies:
1126 | vue: 3.5.22
1127 |
1128 | birpc@2.6.1: {}
1129 |
1130 | ccount@2.0.1: {}
1131 |
1132 | character-entities-html4@2.1.0: {}
1133 |
1134 | character-entities-legacy@3.0.0: {}
1135 |
1136 | comma-separated-tokens@2.0.3: {}
1137 |
1138 | copy-anything@3.0.5:
1139 | dependencies:
1140 | is-what: 4.1.16
1141 |
1142 | csstype@3.1.3: {}
1143 |
1144 | dequal@2.0.3: {}
1145 |
1146 | devlop@1.1.0:
1147 | dependencies:
1148 | dequal: 2.0.3
1149 |
1150 | entities@4.5.0: {}
1151 |
1152 | esbuild@0.25.11:
1153 | optionalDependencies:
1154 | '@esbuild/aix-ppc64': 0.25.11
1155 | '@esbuild/android-arm': 0.25.11
1156 | '@esbuild/android-arm64': 0.25.11
1157 | '@esbuild/android-x64': 0.25.11
1158 | '@esbuild/darwin-arm64': 0.25.11
1159 | '@esbuild/darwin-x64': 0.25.11
1160 | '@esbuild/freebsd-arm64': 0.25.11
1161 | '@esbuild/freebsd-x64': 0.25.11
1162 | '@esbuild/linux-arm': 0.25.11
1163 | '@esbuild/linux-arm64': 0.25.11
1164 | '@esbuild/linux-ia32': 0.25.11
1165 | '@esbuild/linux-loong64': 0.25.11
1166 | '@esbuild/linux-mips64el': 0.25.11
1167 | '@esbuild/linux-ppc64': 0.25.11
1168 | '@esbuild/linux-riscv64': 0.25.11
1169 | '@esbuild/linux-s390x': 0.25.11
1170 | '@esbuild/linux-x64': 0.25.11
1171 | '@esbuild/netbsd-arm64': 0.25.11
1172 | '@esbuild/netbsd-x64': 0.25.11
1173 | '@esbuild/openbsd-arm64': 0.25.11
1174 | '@esbuild/openbsd-x64': 0.25.11
1175 | '@esbuild/openharmony-arm64': 0.25.11
1176 | '@esbuild/sunos-x64': 0.25.11
1177 | '@esbuild/win32-arm64': 0.25.11
1178 | '@esbuild/win32-ia32': 0.25.11
1179 | '@esbuild/win32-x64': 0.25.11
1180 |
1181 | estree-walker@2.0.2: {}
1182 |
1183 | fdir@6.5.0(picomatch@4.0.3):
1184 | optionalDependencies:
1185 | picomatch: 4.0.3
1186 |
1187 | focus-trap@7.6.5:
1188 | dependencies:
1189 | tabbable: 6.2.0
1190 |
1191 | fsevents@2.3.3:
1192 | optional: true
1193 |
1194 | giscus@1.6.0:
1195 | dependencies:
1196 | lit: 3.3.1
1197 |
1198 | hast-util-to-html@9.0.5:
1199 | dependencies:
1200 | '@types/hast': 3.0.4
1201 | '@types/unist': 3.0.3
1202 | ccount: 2.0.1
1203 | comma-separated-tokens: 2.0.3
1204 | hast-util-whitespace: 3.0.0
1205 | html-void-elements: 3.0.0
1206 | mdast-util-to-hast: 13.2.0
1207 | property-information: 7.1.0
1208 | space-separated-tokens: 2.0.2
1209 | stringify-entities: 4.0.4
1210 | zwitch: 2.0.4
1211 |
1212 | hast-util-whitespace@3.0.0:
1213 | dependencies:
1214 | '@types/hast': 3.0.4
1215 |
1216 | hookable@5.5.3: {}
1217 |
1218 | html-void-elements@3.0.0: {}
1219 |
1220 | is-what@4.1.16: {}
1221 |
1222 | lit-element@4.2.1:
1223 | dependencies:
1224 | '@lit-labs/ssr-dom-shim': 1.4.0
1225 | '@lit/reactive-element': 2.1.1
1226 | lit-html: 3.3.1
1227 |
1228 | lit-html@3.3.1:
1229 | dependencies:
1230 | '@types/trusted-types': 2.0.7
1231 |
1232 | lit@3.3.1:
1233 | dependencies:
1234 | '@lit/reactive-element': 2.1.1
1235 | lit-element: 4.2.1
1236 | lit-html: 3.3.1
1237 |
1238 | magic-string@0.30.19:
1239 | dependencies:
1240 | '@jridgewell/sourcemap-codec': 1.5.5
1241 |
1242 | mark.js@8.11.1: {}
1243 |
1244 | mdast-util-to-hast@13.2.0:
1245 | dependencies:
1246 | '@types/hast': 3.0.4
1247 | '@types/mdast': 4.0.4
1248 | '@ungap/structured-clone': 1.3.0
1249 | devlop: 1.1.0
1250 | micromark-util-sanitize-uri: 2.0.1
1251 | trim-lines: 3.0.1
1252 | unist-util-position: 5.0.0
1253 | unist-util-visit: 5.0.0
1254 | vfile: 6.0.3
1255 |
1256 | micromark-util-character@2.1.1:
1257 | dependencies:
1258 | micromark-util-symbol: 2.0.1
1259 | micromark-util-types: 2.0.2
1260 |
1261 | micromark-util-encode@2.0.1: {}
1262 |
1263 | micromark-util-sanitize-uri@2.0.1:
1264 | dependencies:
1265 | micromark-util-character: 2.1.1
1266 | micromark-util-encode: 2.0.1
1267 | micromark-util-symbol: 2.0.1
1268 |
1269 | micromark-util-symbol@2.0.1: {}
1270 |
1271 | micromark-util-types@2.0.2: {}
1272 |
1273 | minisearch@7.2.0: {}
1274 |
1275 | mitt@3.0.1: {}
1276 |
1277 | nanoid@3.3.11: {}
1278 |
1279 | oniguruma-parser@0.12.1: {}
1280 |
1281 | oniguruma-to-es@4.3.3:
1282 | dependencies:
1283 | oniguruma-parser: 0.12.1
1284 | regex: 6.0.1
1285 | regex-recursion: 6.0.2
1286 |
1287 | perfect-debounce@2.0.0: {}
1288 |
1289 | picocolors@1.1.1: {}
1290 |
1291 | picomatch@4.0.3: {}
1292 |
1293 | postcss@8.5.6:
1294 | dependencies:
1295 | nanoid: 3.3.11
1296 | picocolors: 1.1.1
1297 | source-map-js: 1.2.1
1298 |
1299 | property-information@7.1.0: {}
1300 |
1301 | regex-recursion@6.0.2:
1302 | dependencies:
1303 | regex-utilities: 2.3.0
1304 |
1305 | regex-utilities@2.3.0: {}
1306 |
1307 | regex@6.0.1:
1308 | dependencies:
1309 | regex-utilities: 2.3.0
1310 |
1311 | rfdc@1.4.1: {}
1312 |
1313 | rollup@4.52.5:
1314 | dependencies:
1315 | '@types/estree': 1.0.8
1316 | optionalDependencies:
1317 | '@rollup/rollup-android-arm-eabi': 4.52.5
1318 | '@rollup/rollup-android-arm64': 4.52.5
1319 | '@rollup/rollup-darwin-arm64': 4.52.5
1320 | '@rollup/rollup-darwin-x64': 4.52.5
1321 | '@rollup/rollup-freebsd-arm64': 4.52.5
1322 | '@rollup/rollup-freebsd-x64': 4.52.5
1323 | '@rollup/rollup-linux-arm-gnueabihf': 4.52.5
1324 | '@rollup/rollup-linux-arm-musleabihf': 4.52.5
1325 | '@rollup/rollup-linux-arm64-gnu': 4.52.5
1326 | '@rollup/rollup-linux-arm64-musl': 4.52.5
1327 | '@rollup/rollup-linux-loong64-gnu': 4.52.5
1328 | '@rollup/rollup-linux-ppc64-gnu': 4.52.5
1329 | '@rollup/rollup-linux-riscv64-gnu': 4.52.5
1330 | '@rollup/rollup-linux-riscv64-musl': 4.52.5
1331 | '@rollup/rollup-linux-s390x-gnu': 4.52.5
1332 | '@rollup/rollup-linux-x64-gnu': 4.52.5
1333 | '@rollup/rollup-linux-x64-musl': 4.52.5
1334 | '@rollup/rollup-openharmony-arm64': 4.52.5
1335 | '@rollup/rollup-win32-arm64-msvc': 4.52.5
1336 | '@rollup/rollup-win32-ia32-msvc': 4.52.5
1337 | '@rollup/rollup-win32-x64-gnu': 4.52.5
1338 | '@rollup/rollup-win32-x64-msvc': 4.52.5
1339 | fsevents: 2.3.3
1340 |
1341 | shiki@3.13.0:
1342 | dependencies:
1343 | '@shikijs/core': 3.13.0
1344 | '@shikijs/engine-javascript': 3.13.0
1345 | '@shikijs/engine-oniguruma': 3.13.0
1346 | '@shikijs/langs': 3.13.0
1347 | '@shikijs/themes': 3.13.0
1348 | '@shikijs/types': 3.13.0
1349 | '@shikijs/vscode-textmate': 10.0.2
1350 | '@types/hast': 3.0.4
1351 |
1352 | source-map-js@1.2.1: {}
1353 |
1354 | space-separated-tokens@2.0.2: {}
1355 |
1356 | speakingurl@14.0.1: {}
1357 |
1358 | stringify-entities@4.0.4:
1359 | dependencies:
1360 | character-entities-html4: 2.1.0
1361 | character-entities-legacy: 3.0.0
1362 |
1363 | superjson@2.2.2:
1364 | dependencies:
1365 | copy-anything: 3.0.5
1366 |
1367 | tabbable@6.2.0: {}
1368 |
1369 | tinyglobby@0.2.15:
1370 | dependencies:
1371 | fdir: 6.5.0(picomatch@4.0.3)
1372 | picomatch: 4.0.3
1373 |
1374 | trim-lines@3.0.1: {}
1375 |
1376 | unist-util-is@6.0.1:
1377 | dependencies:
1378 | '@types/unist': 3.0.3
1379 |
1380 | unist-util-position@5.0.0:
1381 | dependencies:
1382 | '@types/unist': 3.0.3
1383 |
1384 | unist-util-stringify-position@4.0.0:
1385 | dependencies:
1386 | '@types/unist': 3.0.3
1387 |
1388 | unist-util-visit-parents@6.0.2:
1389 | dependencies:
1390 | '@types/unist': 3.0.3
1391 | unist-util-is: 6.0.1
1392 |
1393 | unist-util-visit@5.0.0:
1394 | dependencies:
1395 | '@types/unist': 3.0.3
1396 | unist-util-is: 6.0.1
1397 | unist-util-visit-parents: 6.0.2
1398 |
1399 | vfile-message@4.0.3:
1400 | dependencies:
1401 | '@types/unist': 3.0.3
1402 | unist-util-stringify-position: 4.0.0
1403 |
1404 | vfile@6.0.3:
1405 | dependencies:
1406 | '@types/unist': 3.0.3
1407 | vfile-message: 4.0.3
1408 |
1409 | vite@7.1.11:
1410 | dependencies:
1411 | esbuild: 0.25.11
1412 | fdir: 6.5.0(picomatch@4.0.3)
1413 | picomatch: 4.0.3
1414 | postcss: 8.5.6
1415 | rollup: 4.52.5
1416 | tinyglobby: 0.2.15
1417 | optionalDependencies:
1418 | fsevents: 2.3.3
1419 |
1420 | vitepress-plugin-comment-with-giscus@1.1.15(vue@3.5.22):
1421 | dependencies:
1422 | '@giscus/vue': 2.4.0(vue@3.5.22)
1423 | transitivePeerDependencies:
1424 | - vue
1425 |
1426 | vitepress@2.0.0-alpha.12(postcss@8.5.6):
1427 | dependencies:
1428 | '@docsearch/css': 4.2.0
1429 | '@docsearch/js': 4.2.0
1430 | '@iconify-json/simple-icons': 1.2.55
1431 | '@shikijs/core': 3.13.0
1432 | '@shikijs/transformers': 3.13.0
1433 | '@shikijs/types': 3.13.0
1434 | '@types/markdown-it': 14.1.2
1435 | '@vitejs/plugin-vue': 6.0.1(vite@7.1.11)(vue@3.5.22)
1436 | '@vue/devtools-api': 8.0.3
1437 | '@vue/shared': 3.5.22
1438 | '@vueuse/core': 13.9.0(vue@3.5.22)
1439 | '@vueuse/integrations': 13.9.0(focus-trap@7.6.5)(vue@3.5.22)
1440 | focus-trap: 7.6.5
1441 | mark.js: 8.11.1
1442 | minisearch: 7.2.0
1443 | shiki: 3.13.0
1444 | vite: 7.1.11
1445 | vue: 3.5.22
1446 | optionalDependencies:
1447 | postcss: 8.5.6
1448 | transitivePeerDependencies:
1449 | - '@types/node'
1450 | - async-validator
1451 | - axios
1452 | - change-case
1453 | - drauu
1454 | - fuse.js
1455 | - idb-keyval
1456 | - jiti
1457 | - jwt-decode
1458 | - less
1459 | - lightningcss
1460 | - nprogress
1461 | - qrcode
1462 | - sass
1463 | - sass-embedded
1464 | - sortablejs
1465 | - stylus
1466 | - sugarss
1467 | - terser
1468 | - tsx
1469 | - typescript
1470 | - universal-cookie
1471 | - yaml
1472 |
1473 | vue@3.5.22:
1474 | dependencies:
1475 | '@vue/compiler-dom': 3.5.22
1476 | '@vue/compiler-sfc': 3.5.22
1477 | '@vue/runtime-dom': 3.5.22
1478 | '@vue/server-renderer': 3.5.22(vue@3.5.22)
1479 | '@vue/shared': 3.5.22
1480 |
1481 | zwitch@2.0.4: {}
1482 |
--------------------------------------------------------------------------------