简介面向Java开发者的个人代码片段管理库用于集中保存和整理编程过程中积累的可复用代码。核心内容聚焦设计模式实战涵盖策略、抽象工厂、单例、观察者、建造者、模板方法、适配器、装饰器、桥接等经典实现同时收录LeetCode算法题解以及Effective Java和Effective C的读书笔记适合初中级开发者提升面向对象设计与编码能力。压缩包共21个文件其中19个Java源文件构成主体另有1个Markdown说明文档帮助理解整体结构1个C文件提供跨语言对照视角整体大小仅14KB轻量便捷。目前已有186人学习浏览既可作为设计模式与算法知识的速查手册也能从中学到模块划分、命名规范、异常处理等良好习惯是一份值得反复翻阅的代码营养品。1. 代码段的“藏宝图”乱成一团时是时候建一个真正的存储库了晚上十一点我把一个日期处理函数从头写了第三遍。三周前它在上一个项目里跑得好好的现在我要从聊天记录、旧分支和一个只有文件的目录里把它翻出来。这不是记性问题是“代码段”一直没有一个像样的家。codehub要解决的就是这件事一个只属于我的代码段存储库让我用命令把一段代码连上下文一起收进去三秒钟之后还能按语言、标签和正文把它捞出来。下面不卖什么现成服务而是带你从零搭一套自己说了算的代码段管理与检索方案适合后端开发、脚本频率高、热爱折腾自己工具箱的工程师。存储库不该是黑匣子读完后你会得到一个可以天天用、坑少、可审计的命令行工作流。2. 先想清楚存储模型文件系统与SQLite双轨代码段才有真家2.1 为什么笔记软件不是代码段的存储库很多人第一反应是把代码段剪进 Notion、语雀或飞书我在早期也这么干过后来全废了。笔记软件的核心问题是它设计给人读不设计给脚本读。剪藏一段代码高亮经常丢缩进会被“智能排版”弄乱更致命的是搜索几乎不能用。你想按“python 日期 格式化”这三个条件过滤笔记软件只能给你一个全文命中列表然后你逐条点开看。另一个痛点是复制成本。一段代码要拿去用得先点进笔记、找到代码块、鼠标框选、复制再切回终端。一次两次还好每天十几次就烦了。真正的一线方式是什么是让终端本身成为入口codehub add 文件名 --lang python存完直接复制到剪贴板全程不用离开键盘。所以笔记软件适合当“灵感收集箱”不适合当代码段的存储库。代码段需要的是结构化的元数据、可脚本化的接口、可版本化追踪的实体文件。这三个需求恰好指向一个朴素方案文件系统管内容SQLite管索引。2.2 双轨存储文件是唯一真源SQLite只是可重建的索引我在设计codehub时定了一条铁律文件系统里的原始文件是唯一真源数据库任何时候都可以删掉重建。这意味着每段代码在磁盘上必须有一个独立、可读、可diff的实体文件而不是只躺在SQLite的某个TEXT字段里。目录结构我用的是一套很直白的约定~/.codehub/ ├── raw/ │ ├── python/ │ │ ├── format_iso_date.py │ │ └── retry_with_backoff.py │ ├── bash/ │ │ └── kill_port.sh │ └── sql/ │ └── find_duplicate_rows.sql ├── meta/ │ ├── format_iso_date.meta.json │ └── kill_port.meta.json └── codehub.dbraw目录按语言分文件夹文件名即slug。meta目录放同名侧车JSON把标签、来源、创建时间这些索引信息与正文分离。这样做的好处是正文文件永远干净不会混入一堆front matter编辑器打开就能直接跑meta文件负责记录上下文未来想改格式也不伤内容。数据库的作用退化成“加速检索”它只存meta的副本和必要的搜索字段。删掉codehub.db只要raw和meta还在跑一条重建命令就能把库恢复得和原来一模一样。这种设计在灾难恢复时几乎是后悔药级别的安全感。2.3 最小schema三张表加一个冗余字段不搞一堆抽象建表语句是最小可运行版本我一般不引入ORM直接上SQL。下面是完整建表脚本PRAGMA foreign_keys ON; CREATE TABLE IF NOT EXISTS snippets ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT UNIQUE NOT NULL, title TEXT NOT NULL DEFAULT , language TEXT NOT NULL DEFAULT text, content TEXT NOT NULL DEFAULT , content_path TEXT NOT NULL, meta_path TEXT NOT NULL, tags TEXT NOT NULL DEFAULT , source TEXT NOT NULL DEFAULT , created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ); CREATE TABLE IF NOT EXISTS snippet_tags ( snippet_id INTEGER NOT NULL REFERENCES snippets(id) ON DELETE CASCADE, tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE, PRIMARY KEY (snippet_id, tag_id) );这里有一个刻意的冗余snippets表里既有tags文本字段又建了标准化的tags关联表。理由是搜索时FTS5需要一份平铺的标签字符串而关联表负责保证标签唯一性和后续统计。冗余字段的更新由同一段写入逻辑负责虽然多写几行代码但换来了简单直接的查询路径。注意content字段存的是代码正文的副本不是替代文件。文件仍然在那里数据库里的副本仅供全文索引和快速预览。同步时以文件的mtime为准发现文件被外部编辑过就重新读一遍更新DB。这样的设计让恢复流程变成三步删库、扫描raw目录、逐条重写DB一个codehub rebuild子命令搞定。3. 用CLI把代码段“收编”进来add子命令的完整落地3.1 构建CLIClick组织子命令比手写argparse省一半代码管理代码段的核心操作无非四个增、删、查、同步。我习惯用Click来组织子命令它天然支持嵌套命令、自动生成帮助信息、参数校验比手写argparse省一半心力。先搭一个最小骨架#!/usr/bin/env python3 codehub: 个人代码段存储库 CLI import json import hashlib import sqlite3 from pathlib import Path from datetime import datetime, timezone import click APP_DIR Path.home() / .codehub RAW_DIR APP_DIR / raw META_DIR APP_DIR / meta DB_PATH APP_DIR / codehub.db def get_db(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_dirs(): RAW_DIR.mkdir(parentsTrue, exist_okTrue) META_DIR.mkdir(parentsTrue, exist_okTrue) click.group() def cli(): codehub: manage code snippets from terminal init_dirs() if __name__ __main__: cli()这里init_dirs放在group回调里保证任何子命令一进来目录就存在。数据库连接用sqlite3.Row后续取字段可以写row[slug]而不是row[0]代码可读性好很多。click的click.group()允许后续不断挂子命令不需要在入口函数里堆一堆if args.command ...。3.2 add子命令入库、判重、写meta一步到位add是所有操作的起点也是最容易翻车的部分。目标是拿到一个文件路径复制内容进codehub生成slug写meta插入数据库。下面是完整实现def slugify(text: str) - str: # 保留字母数字和下划线空格转下划线 keep [] for ch in text.strip().lower(): if ch.isalnum() or ch in (-, _, .): keep.append(ch) elif ch.isspace(): keep.append(_) return .join(keep).strip(_)[:80] def add_entry(slug: str, title: str, lang: str, tags: list, content: str, source: str, conn: sqlite3.Connection, force: bool False) - bool: now datetime.now(timezone.utc).isoformat() digest hashlib.sha256(content.encode(utf-8)).hexdigest() # 相同内容 hash 命中直接跳过 cur conn.execute(SELECT slug FROM snippets WHERE content ?, (content,)) if cur.fetchone(): click.echo(f[skip] duplicate content: {slug}) return False # slug 冲突时默认加序号后缀 final_slug slug counter 1 while True: exists conn.execute( SELECT 1 FROM snippets WHERE slug ?, (final_slug,) ).fetchone() if not exists: break if force: conn.execute(DELETE FROM snippets WHERE slug ?, (final_slug,)) break final_slug f{slug}-{counter} counter 1 raw_path RAW_DIR / lang / f{final_slug}.txt meta_path META_DIR / f{final_slug}.meta.json raw_path.parent.mkdir(parentsTrue, exist_okTrue) raw_path.write_text(content, encodingutf-8) meta { slug: final_slug, title: title, language: lang, tags: tags, source: source, digest: digest, created_at: now, updated_at: now, } meta_path.write_text( json.dumps(meta, ensure_asciiFalse, indent2), encodingutf-8, ) tags_text .join(tags) conn.execute( INSERT INTO snippets (slug, title, language, content, content_path, meta_path, tags, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?), (final_slug, title, lang, content, str(raw_path), str(meta_path), tags_text, source, now, now), ) conn.commit() click.echo(f[ok] saved as {final_slug} ({lang})) return True这段代码的核心逻辑有三层。第一层用content做hash语义判重避免同一段代码在库里出现几百份拷贝第二层处理slug冲突默认自动追加序号而不是直接报错第三层保证文件写成功了才插数据库顺序不能反否则DB有记录但文件缺失重建时直接漏掉。参数上我建议保留--force。它的作用是删除同slug旧记录后覆盖写入适合修正一段已入库代码的错误版本。日常使用不要带这个参数因为一旦覆盖旧版本就只剩git历史能捞回来了。3.3 add主命令参数要让新手猜得中、熟手用得顺挂到click命令行上的入口长这样cli.command() click.argument(source, typeclick.Path(existsTrue)) click.option(--lang, defaulttext, helplanguage, e.g. python) click.option(--tags, default, helpcomma-separated tags) click.option(--title, default, helpdisplay title) click.option(--source, src, default, helpprovenance note) click.option(--force, is_flagTrue, helpoverwrite same slug) def add(source, lang, tags, title, src, force): Add a source file into the codehub store content Path(source).read_text(encodingutf-8) slug slugify(title) or slugify(Path(source).stem) tag_list [t.strip() for t in tags.split(,) if t.strip()] conn get_db() add_entry(slug, title, lang, tag_list, content, src, conn, force)注意click.argument(source)和click.option(--source, src, ...)同名冲突了。我给option起了别名src命令行里codehub add file.py --source from starlette issue读起来顺。默认slug取--title没传title就拿文件名。这块是习惯问题我的经验是title要显式传后续在搜索结果里一眼能认出是哪个场景的代码光靠文件名经常想不起来。4. 让代码段找得到三种检索方式与FTS5参数调优4.1 搜索选型严格过滤用tags反查正文用LIKE中文走降级代码段入库只是第一步真正决定工具有没有用的是“能否三秒内捞回正确的片段”。我把搜索分成三个层次。第一层是标签过滤适合宽进窄出给代码段打了python、datetime、timezone三个标签搜索时用AND关系直接收窄范围。这一层走的是标准化关联表查询快且结果精确。第二层是正文子串匹配也就是LIKE %format%。它简单直接但有两个毛病一是慢数据量过千后全表扫content字段性能会肉眼可见地下降二是对大小写和OCR式错字没辙。它的定位是兜底不该当主力。第三层是FTS5全文索引。它是SQLite自带的全文本搜索模块支持MATCH语法、排名和摘录。对英文和代码符号效果好比如搜retry AND (backoff OR timeout)它能给出带上下文的高亮片段。这一层是检索的主引擎。我的原则是FTS5查不到或者查询串里有中文时自动降级到LIKE。原因后面避坑章细说。4.2 用FTS5建立可增量同步的全文索引建FTS5索引不能直接建在普通表上我采用外部内容表的方式让SQLite保持内容同步CREATE VIRTUAL TABLE snippets_fts USING fts5( title, content, language, tags, contentsnippets, content_rowidid, tokenizeunicode61 ); CREATE TRIGGER snippets_ai AFTER INSERT ON snippets BEGIN INSERT INTO snippets_fts(rowid, title, content, language, tags) VALUES (new.id, new.title, new.content, new.language, new.tags); END; CREATE TRIGGER snippets_ad AFTER DELETE ON snippets BEGIN INSERT INTO snippets_fts(snippets_fts, rowid, title, content, language, tags) VALUES (delete, old.id, old.title, old.content, old.language, old.tags); END;这段SQL里最需要注意的就是tokenizeunicode61。它把连续的中文当成一整段不做词切分所以中文搜索要小心。字符级的LIKE反而对所有语言都有效只是慢。查询时我用一条SQL搞定匹配和摘录SELECT snippet_id, snippet(snippets_fts, 0, [, ], …, 12) AS preview FROM snippets_fts WHERE snippets_fts MATCH :query ORDER BY rank LIMIT :limit;snippet()函数是FTS5的摘录函数参数含义依次是第0列传title左右标记用[和]方便终端高亮省略号用…最多取12个词。这对预览代码片段很够用不会一股脑把整段代码打出来。4.3 输出与复制查得到还要拿得走才算闭环搜索子命令不能只打印行号。我的实现里内置两种输出模式默认模式打印“slug、语言、标签、首行摘要”--raw模式直接输出完整代码--copy模式把代码写进剪贴板。最后这个太关键了用剪贴板能让整个工作流彻底脱离鼠标。参数说明默认值--queryFTS5匹配表达式必填--lang限定语言如python空--tag限定标签可多次传入空--limit最多返回条数10--copy复制首条结果到剪贴板关闭--json以JSON输出结构化结果关闭剪贴板这块要注意平台差异。macOS直接用pbcopyLinux常见xclipWindows可以用clip。为了不增加依赖我写了一个小函数优先探测系统命令import subprocess import sys def copy_to_clipboard(text: str) - bool: if sys.platform darwin: cmd [pbcopy] elif sys.platform.startswith(linux): cmd [xclip, -selection, clipboard] else: cmd [clip] try: subprocess.run(cmd, inputtext.encode(utf-8), checkTrue) return True except FileNotFoundError: return False这个函数不引入pyperclip装一个python包只为剪贴板有点重。在没有xclip的Linux服务器上--copy会返回False并给出提示不阻塞正常打印。安全设计是默认不复制只有显式带--copy才触发系统调用。5. 代码段库的避坑指南五条血泪经验5.1 现象重装系统后精心维护的库变成空目录这是整个方案最不该犯的错。原因很简单我只备份了codehub.db以为数据库就是全部。raw目录一直躺在本机home目录下从来没进过远程仓库。解决方法是把~/.codehub整体纳入私有git仓库同时加密敏感片段。重建数据库只需要一条命令codehub rebuild它扫描raw和meta目录重新生成SQLite和FTS索引。从那以后我每次迁移环境的第一件事就是拉仓库第二件事跑rebuild五分钟后库恢复原样。5.2 现象中文关键词搜不出来明明是库里有的片段这是FTS5的坑。前面提到unicode61分词器对中文不做切词它把“日期格式化”当成一个整体token。你搜“格式化”时MATCH逻辑是在找完全独立的token自然搜不到“日期格式化”。解决思路不是换分词器而是做一次运行时降级。在search子命令里如果检测到query包含非ASCII字符就放弃FTS5改走LIKE子串匹配def is_ascii(text: str) - bool: try: text.encode(ascii) return True except UnicodeEncodeError: return False if is_ascii(query): sql SELECT ... FROM snippets_fts WHERE snippets_fts MATCH ? else: sql SELECT ... FROM snippets WHERE content LIKE ?这样英文和代码符号走FTS5的高速路中文走LIKE的普适路各取所长。5.3 现象库里出现几十段重复代码slug后缀越堆越长这坑是懒出来的。add命令设计为内容hash相同就跳过但如果你是从stdin粘贴的代码前面加了几行注释hash就变了于是同一逻辑存了3个变体。解决方法是把“内容指纹”升级为“逻辑指纹”。我用的是去掉空行和注释后再做sha256。这样只改了注释的重复代码能被识别出来。同时--force只应该在【确认要覆盖版本】时使用而不是日常铺路。我给自己定了个习惯每次add之前先问一句“这段代码和库里已有的那个到底差在哪”5.4 现象从Windows粘贴的代码入库存进库里后缩进全乱根因是行尾符。Windows的CRLF和Unix的LF混在一起Python、bash脚本跑起来直接报错。我吃过的亏是写文件用了默认的open()它会把\r\n悄悄转成\n入库时没报警git diff却显示整段重写。解决方法是入库前显式处理content content.replace(\r\n, \n).replace(\r, \n)这行代码放在read_text之后、hash之前。同时meta里记录line_ending字段万一原工程确实需要CRLF能从库里还原原样。5.5 现象git仓库疯狂膨胀raw目录里多了几百个自动生成文件这是“顺手add”的恶果。比如把整个node_modules目录拖进来、或把运行生成的临时文件也add了。codehub的raw目录只放你手工挑选的代码段不是文件同步盘。解决方法是给~/.codehub补一份扎实的.gitignore/tmp/ *.log *.pyc .DS_Store然后定期用du -sh ~/.codehub看一眼体积。一段代码该是几百字节到几KB如果volume到了几十MB多半混进了不该有的东西。6. 把codehub养成习惯一次健康度检查与索引重建演练6.1 用一条审计命令给库做体检我加了一个audit子命令专门做一致性校验。它逐个检查raw文件是否存在、meta JSON是否合法、DB记录是否与文件一致。跑完输出一个表格一眼看出哪里脱节codehub audit这个命令的核心就三步遍历数据库记录比对文件、扫描raw目录找出孤儿文件、重建FTS索引。半年用一次能发现早期的小问题不让它累积成大坑。6.2 灾难演练是给自己买的保险每个月我会做一次默哀式演练删除codehub.db然后codehub rebuild看库能不能完整回来。第一次做的时候紧张后来成了例行仪式。真到某天数据库损坏时肌肉记忆已经形成不需要临时翻文档。这套方案走到这里已经可以当日常工具用了。我的教训是一切自动化、搜索优化都比不上“每次存代码时认真写一句描述来源”。代码段存储库最贵的不是实现成本而是采集时的元数据质量。希望帮到你。本文还有配套的精品资源点击获取