Homelab CNB Frontend Build: 用 Cloud Native Buildpacks 构建前端镜像

这篇文档讲什么 Homelab 里两个前端 enx-ui(Next.js)和 rssx-ui(Vue + Vite)的镜像,现在都用 Cloud Native Buildpacks(CNB)构建,不再用 Dockerfile + Kaniko。本文按实际配置说明这套构建是怎么搭起来的:编译用哪个镜像、运行用哪个镜像、自己写的 buildpack 做了什么、Tekton 里怎么跑。 CNB 本身的概念(buildpack、builder、lifecycle、platform)见 CNB: Cloud Native Buildpacks 与 Paketo;为什么会有 CNB、它和 Dockerfile 的取舍见 Dockerfile/BuildKit vs CNB。这里只写 homelab 的落地。 为什么换掉 Kaniko Kaniko 上游(GoogleContainerTools/kaniko)2025-06 已归档,executor:v1.23.0 之后没有安全更新。 enx-ui 用 Kaniko 构建一次约 5 分 20 秒,其中解压缓存的 node_modules 层 78 秒,每次新建并挂载 Longhorn RWX 卷约 40 秒。 备选里 BuildKit 需要常驻 buildkitd,rootless 模式也要放宽 seccomp/AppArmor;CNB 的 lifecycle 以普通用户运行,不需要特权,还支持 rebase。所以选了 CNB。2026-10 rssx 也迁过来之后,集群里已经没有 Kaniko 了。 三个组件:builder、buildpack、run image CNB 构建出来的镜像,是把「构建产物」叠到「运行镜像」上。homelab 里这三样东西分别是: ...

Created 13136-10-13 · Updated 13136-10-13 · 5 min · 1027 words · -

Homelab Secrets Management: Bitwarden、rbw 与 OpenBao

我在整理 homelab 的配置仓库时发现,有不少密钥以明文形式进了 git:数据库密码、云服务的 API token、K8s Secret 的 manifest。还有一些放在 ~/.zshrc.local 里,作为环境变量。这篇文章记录我比较几种方案之后选定的思路,以及其中涉及的概念。 最后选的组合是:Bitwarden + rbw 管人在场的场景,OpenBao 管集群里的自动化,没有用 SOPS。 先分清「存放」和「使用」 讨论密钥放在哪里时,容易把两件事混在一起: 存放(数据源,长期) 使用(临时) git 里的密文 / Bitwarden / OpenBao → 环境变量、内存、临时文件 使用的那一刻必然是明文,任何方案都绕不开,程序总得拿到真实值。能做的是让它短暂、范围小:只在一个子进程里,不长期落盘。 真正可以选择的是存放在哪里,而且不一定要是 git。 对照这个划分,原来的两个问题分别是: 做法 问题 明文进 git(即使是私有仓库) 协作者授权、第三方 App 授权、CI 克隆、哪天转 public,都会把全部历史一起交出去 放在 ~/.zshrc.local 磁盘上是明文;shell 启动的所有子进程一直都能读到,包括各种 CLI、npm postinstall 脚本、AI agent 分类原则:谁在用,有没有人在场 场景 存在哪里 怎么取 是否需要人在场 本地开发和运维(tofu、Ansible、脚本) Bitwarden rbw get,只注入单条命令 需要 集群自举密钥(集群启动时就必须存在的少数几个) Bitwarden 重建集群时,由人执行脚本创建 需要,只在重建时 集群里应用运行时用的密钥 OpenBao 程序用自己的身份去取;数据库用动态凭据 不需要 核心思路:人在场就从 Bitwarden 取;没有人在场,就交给一个在线服务(OpenBao),程序用自己的身份去取。 ...

Created 12126-10-12 · Updated 12126-10-12 · 3 min · 638 words · -

SOPS: Secrets OPerationS 简介

SOPS 是什么 SOPS(Secrets OPerationS)是一个命令行工具,用 Go 写的。它最初由 Mozilla 开发,2023 年迁到 getsops/sops,现在是 CNCF 项目。 它只做一件事:加密和解密结构化文件,支持 YAML、JSON、ENV(dotenv)、INI 和二进制文件。典型用法是把密钥加密后提交到 git,这样 git 里只有密文。 它不是常驻服务,也不负责存储。密文就是普通文件,解密由你在需要的时候显式调用。 原理 信封加密 每个文件随机生成一把 data key,用 AES256-GCM 加密文件里的值。 再用一把或多把 master key 加密这把 data key,结果写进文件末尾的 sops: 元数据段。 master key 可以是 age、PGP、AWS/GCP/Azure 的 KMS、HashiCorp Vault(或 OpenBao)的 Transit 等。同一个文件可以同时加密给多把 master key,任意一把都能解开。 只加密值,不加密键 stringData: POSTGRES_PASSWORD: ENC[AES256_GCM,data:3mZ0...,iv:...,tag:...,type:str] sops: age: - recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p enc: | -----BEGIN AGE ENCRYPTED FILE----- ... mac: ENC[AES256_GCM,data:...,type:str] git diff 能看出改的是哪个字段,review 时不用解密也能看懂结构。 非敏感字段(比如 K8s 的 metadata)可以保持明文。 文件带 MAC,密文被篡改时解密会报错。 age age 是一个小巧的文件加密工具,可以看作 PGP 的现代替代品,SOPS 官方也建议优先用它。 ...

Created 12126-10-12 · Updated 12126-10-12 · 2 min · 270 words · -

CNB: Cloud Native Buildpacks 与 Paketo

是什么 Cloud Native Buildpacks(常缩写 CNB)是一套把应用源码或编译产物自动打成 OCI 容器镜像的规范与工具生态,站点在 buildpacks.io。思路类似早年 Heroku、Cloud Foundry 上的 buildpack:平台检测你推上来的是什么(Java jar、Node 项目等),自动选运行时、装依赖、写好启动命令,不必为每种语言手写 Dockerfile。 Paketo(paketo.io)是 CNB 生态里常用的开源实现,提供现成的 builder 镜像和一系列 language buildpack(Java、Node.js、Go、Python 等)。它与 Spring 无隶属关系,但 Spring Boot 的 bootBuildImage / spring-boot:build-image 默认使用 Paketo 的 builder。 CNB ← 规范 + 生态(buildpack / builder / lifecycle) │ ├── Paketo ← 开源实现(Spring Boot 默认路径) ├── Heroku buildpacks └── Google Cloud buildpacks … 调用方: pack CLI、Spring Boot 插件、Tekton、CI … → 拉 builder → 跑 buildpack → 出 OCI 镜像 Spring Boot 侧如何把 fat jar、分层 JAR、jlink 与 Buildpack 串起来,见 Spring Boot Container Packaging。 ...

Created 21216-06-21 · Updated 12126-10-12 · 3 min · 589 words · -

Dockerfile/BuildKit vs CNB: 为什么会有 Cloud Native Buildpacks

问题从哪来 构建容器镜像,最常见的做法是写一个 Dockerfile(或 Containerfile),交给 BuildKit、Buildah、Kaniko 之类的构建器执行。Cloud Native Buildpacks(CNB)则不需要 Dockerfile:把源码或 jar 交给 builder,自动产出 OCI 镜像。 既然 Dockerfile 已经能构建任意镜像,为什么还会出现 CNB?它解决的是 Dockerfile 解决不了的问题,还是别的问题?本文把这几个问题放在一起整理。CNB 的概念与 Paketo 用法见 CNB: Cloud Native Buildpacks 与 Paketo,Dockerfile 写法见 Containerfile。 先说结论:CNB 主要解决的不是「单个镜像能不能构建出来」,而是「几百上千个应用的镜像由谁来写、怎么统一、基础镜像出漏洞后怎么批量修」。这是组织规模上的问题,不是 Dockerfile 语法能力上的问题。 BuildKit 是什么 BuildKit 是 Moby 项目下的构建引擎,Docker Engine 23.0 起在 Linux 上成为 docker build 的默认后端,docker buildx 也基于它。 一个常被忽略的点:BuildKit 本身并不依赖 Dockerfile。它的结构分两层: 前端(frontend):把某种构建描述翻译成 LLB(Low-Level Build definition,一种描述构建步骤的有向无环图)。Dockerfile 只是其中一个前端,Dockerfile 首行的 # syntax=docker/dockerfile:1 指定的就是前端镜像。 求解器(solver):执行 LLB,负责并行、缓存、导出镜像。 flowchart LR DF[Dockerfile] OTHER[其他前端Dagger / Railpack 等] FE[Frontend] LLB[LLB DAG] SOLVER[BuildKit Solver并行 + 缓存] OUT[OCI 镜像 / 本地文件] DF --> FE OTHER --> LLB FE --> LLB --> SOLVER --> OUT 所以更准确的说法是:「Dockerfile 是一种构建描述语言,BuildKit 是执行引擎」。Dagger 的引擎、Railway 的 Railpack 等工具都直接生成 LLB,不写 Dockerfile 也能用 BuildKit。 ...

Created 12126-10-12 · Updated 12126-10-12 · 6 min · 1133 words · -

Turbopack

Turbopack 是 Vercel 开发的 JavaScript/TypeScript 打包工具(bundler),用 Rust 编写,定位是 Webpack 的继任者。它目前主要通过 Next.js 使用:从 Next.js 16 开始,next dev 和 next build 默认都用 Turbopack。 打包工具做什么 浏览器不能直接运行一个现代前端项目的源码:TypeScript 和 JSX 要编译成 JavaScript,import 了上百个 npm 包的模块图要合并、拆分成若干个文件,CSS、图片要处理,生产环境还要压缩、去掉没用到的代码(tree shaking)。打包工具做的就是这件事:从入口文件出发,沿着 import 建出完整的模块图,再按规则输出浏览器和 Node.js 能直接加载的文件。 Next.js 这类同时有服务端和客户端代码的框架,还要求打包工具同时产出两套结果:服务端渲染用的代码,和发给浏览器的代码。 常见的打包工具: 工具 实现语言 说明 Webpack JS 2012 年起的事实标准,插件和 loader 生态最大 Rollup JS 偏库打包,Vite 的生产构建原先基于它 esbuild Go 速度快,常被其他工具当作底层编译器 Vite JS 开发模式基于浏览器原生 ESM,正在把底层换成 Rust 实现 Turbopack Rust Vercel 出品,与 Next.js 绑定 背景 Webpack 统治前端工程化十余年,但它用 JavaScript 实现,项目一大,冷启动和热更新都慢。Vercel 在 2022 年随 Next.js 13 发布了 Turbopack 的 alpha 版。Webpack 的原作者 Tobias Koppers 是 Turbopack 的核心作者之一。 ...

Created 10106-06-10 · Updated 12126-10-12 · 2 min · 378 words · -

ENX Mark Known: 从等待接口改为乐观更新

背景 enX 的 Chrome 扩展会给查过的生词画下划线。主窗口点一个词会弹出浮层,浮层里有 Mark Known(标为已认识)按钮,点了之后这个词的下划线消失。 后来侧边栏的整句翻译也加上了同样的下划线(ADR-053),于是有了一个新问题:在主窗口标为已认识之后,侧边栏里这个词的下划线也应该一起消失。 原来的做法 content script → background → POST /api/mark → enx-api 写库 ↓ 成功 content script 收到回复 → 去掉主窗口下划线 主窗口要等接口成功才去掉下划线,侧边栏完全不知道这件事。 改成什么样 content script:先去掉主窗口下划线 └─ runtime.sendMessage({ type: 'markAcquainted', word }) ├─ 侧边栏:收到后立即去掉下划线,不回复 └─ background:调 enx-api,完成后回复 content script 两点变化: 主窗口先改界面,再让 background 在后台调接口,也就是常说的乐观更新(optimistic update),和它相对的是原来那种等服务端确认再改界面的做法。 侧边栏不需要新的通道。chrome.runtime.sendMessage 从 content script 发出时,会投递给扩展里所有监听 runtime.onMessage 的页面,background 和侧边栏都能收到。侧边栏只需要多注册一个监听。 为什么可以先改界面 标为已认识失败的代价很低:服务端没记上,用户下次读别的文章时这个词还会带下划线,再标一次就行。为了这个小概率的失败让用户每次都等一个网络往返,不划算。 如果改成「background 调接口成功后再通知侧边栏」,状态会更严格一致,但侧边栏要多等一个接口的时间。对这个操作来说,一致性没有那么重要。 多个接收方只能有一个回复 runtime.sendMessage 允许多个页面监听,但发送方只会收到第一个调用 sendResponse 的回复,其余的被忽略。侧边栏如果也回复,会抢在 background 前面,content script 就看不到接口的真实结果了。 ...

Created 12126-10-12 · Updated 12126-10-12 · 1 min · 95 words · -

Chrome Side Panel 侧边栏

Side Panel 是什么 Chrome 窗口右侧可以展开的一栏,和当前网页并排显示。Google 的说法是「a new companion surface that allows users to use tools alongside the content they are browsing」:一个伴随页面的界面,让用户一边看网页一边用工具。 它分两层: 浏览器自带的侧边栏:书签、阅读清单等 Chrome 自己的功能。 扩展的侧边栏:扩展通过 Side Panel API(chrome.sidePanel)在这一栏里显示自己的页面。 时间线 时间 版本 变化 2022-03 Chrome 99 稳定版出现侧边栏,内容是书签和阅读清单(9to5Google 报道;Google 自己的博客写的是 2022 年 5 月加入) 2023-05 Chrome 114 扩展可用 Side Panel API,只支持 Manifest V3,需要 sidePanel 权限 2023-08 Chrome 116 新增 sidePanel.open(),可以由扩展主动打开,但必须在用户操作(点击、快捷键、右键菜单等)里调用 为什么要有 Side Panel API API 出现之前,扩展想做侧边栏,只能用 content script 往每个网页里注入一个 <div>。Google 在发布 API 时给的理由: ...

Created 12126-10-12 · Updated 12126-10-12 · 1 min · 131 words · -

Git Worktree

是什么 一个 Git 仓库(一份 .git 目录)可以同时关联多个"工作树"(working tree),每个工作树是磁盘上一个独立的目录,各自可以检出不同的分支。也就是说不用反复 git checkout 切换分支、不用为了同时看两个分支的代码而 git clone 出第二份仓库。 普通仓库只有一个工作树(主工作树,main worktree);git worktree 可以在此基础上新增若干"链接工作树"(linked worktree),它们共享同一个 .git 对象库(commit、blob、tree 等),但有各自独立的工作区和索引(index)。 典型场景 紧急修 bug:正在 feature-a 分支开发到一半,不想 stash,直接开一个新工作树检出 main 改 hotfix,改完切回来继续原来的活。 同时对比多个分支的代码,或者在一个分支跑测试的同时在另一个分支继续写代码。 CI / 构建场景:需要同时构建多个分支时,避免重复克隆仓库浪费磁盘和时间。 review 别人的 PR:新建一个工作树检出对方分支,本地起服务验证,不影响自己当前的工作区。 常用命令 # git worktree add <path> [<branch>] # path 是新工作树的目录(不能已存在),branch 是要检出的分支 # 新建一个工作树,并基于新分支 feature-x(自动创建) git worktree add ../feature-x feature-x # 基于已存在的分支创建工作树 git worktree add ../hotfix hotfix-branch # -b <new-branch>:基于 commit-ish(这里是 main)新建分支并检出,一步完成 git worktree add -b hotfix-branch ../hotfix main # 基于某个 commit/tag 创建一个游离状态(detached HEAD)的工作树 git worktree add ../review v1.2.0 # 查看当前仓库关联的所有工作树 git worktree list # 删除一个工作树(目录和 Git 的关联记录一起清理) git worktree remove ../feature-x # 工作树目录被手动删掉了,用这个清理 Git 内部残留的记录 git worktree prune # 防止某个工作树被 prune 误删(比如它在一个可能断线的移动硬盘上) git worktree lock ../feature-x git worktree unlock ../feature-x 注意事项 同一个分支不能同时被两个工作树检出:main 分支已经在主工作树里检出了,就不能在另一个工作树里再检出一次 main,Git 会报错拒绝,这是为了避免同一分支在两处被修改造成冲突。 工作树目录是普通目录,删除前建议先 git worktree remove:直接 rm -rf 目录后,Git 会认为这个工作树"丢了",需要用 git worktree prune 清理记录,否则 git worktree list 里还会显示一个失效条目。 共享 .git 对象库:多个工作树之间共享 commit 历史和暂存的对象,占用磁盘远小于多次 clone;但每个工作树有独立的索引和 HEAD,互不影响。 链接工作树内部会生成一个 .git 文件(不是目录),内容指向主仓库 .git/worktrees/<name> 下的元数据。 锁定(lock) 锁定是 Git 自带的功能,不是哪个工具额外加的。锁定的工作树会被 Git 保护起来,不会被清理掉、移动或删除。 ...

Created 5056-07-05 · Updated 12126-10-12 · 2 min · 342 words · -

硬件看门狗:主板上的倒计时复位电路

背景 homelab 里的 r86s(R86S 小主机,Celeron N5100,跑 Proxmox VE)偶尔整机卡死。卡死后机器既不会自己恢复,也不会留下日志,前几次分别过了约 10.5 小时和 4 小时才被人发现并重启。排查过程见 R86S IRQ 16 nobody cared。 在修不了根因(BIOS 没有更新、microcode 已是最新)的情况下,第一件要做的事是让机器卡死后能自动恢复,这就是硬件看门狗的用途。这篇记录硬件看门狗是什么、Linux 和 Proxmox 怎么用它,以及它在 r86s 这次排查里起的作用。 看门狗是什么 看门狗(watchdog timer,WDT)是一个独立于 CPU 运行的倒计时器: 软件启动看门狗,设一个超时时间,比如 10 秒; 软件定期"喂狗"(kick / pet),每次喂狗把倒计时重置回 10 秒; 如果软件卡死、没人喂狗,倒计时走到 0,看门狗就直接拉复位信号,把整台机器重启。 关键在于独立:倒计时由芯片组里的电路自己走,不依赖 CPU 执行任何指令。CPU 死锁、内核卡死、关了中断,它都照样倒数,照样复位。这一点是软件手段做不到的。 这个思路最早来自嵌入式和工控领域:无人值守的设备死机了没人去按电源键,必须自己恢复。单片机几乎都内置看门狗。 硬件看门狗在哪里 它通常不是一块单独的板卡或模块,而是集成在芯片里的一小块电路。x86 机器上常见的几种: 位置 例子 Linux 驱动 Intel 芯片组(ICH/PCH)里的 TCO 定时器 从 1999 年的 ICH 一直到现在的 PCH,包括 Jasper Lake iTCO_wdt AMD 芯片组(FCH)里的 TCO 定时器 大部分 AMD 平台 sp5100_tco 主板上的 Super I/O 芯片 ITE IT87xx、Nuvoton NCT67xx、Fintek F718xx it87_wdt、w83627hf_wdt、f71808e_wdt 服务器的 BMC IPMI 看门狗 ipmi_watchdog ARM SoC 树莓派的 BCM2835 bcm2835_wdt 所以"几乎所有 x86 主板都有"是成立的,至少芯片组里会有一个 TCO 定时器。但"有"不等于"能用": ...

Created 10106-10-10 · Updated 10106-10-10 · 5 min · 1038 words · -

R86S IRQ 16 nobody cared:HD Audio 控制器引发的共享中断告警

背景 homelab 里的 r86s(R86S 小主机,Celeron N5100,Jasper Lake 平台)跑 Proxmox VE,上面是 K8s 的两台 VM:k8s-50 和 k8s-51,拓扑见 Homelab topology。这台机器偶尔会整机卡死,所以每天 04:00 有一个定时任务自动 apt full-upgrade 加重启。 排查卡死问题时,在内核日志里看到了这样一段: kernel: irq 16: nobody cared (try booting with the "irqpoll" option) kernel: CPU: 0 UID: 0 PID: 47 Comm: kworker/u16:1 Tainted: P O 7.0.14-20-pve #1 kernel: Hardware name: BROUNION R86S/R86S, BIOS 5.19 04/12/2022 kernel: Workqueue: pm pm_runtime_work ... kernel: handlers: kernel: [<...>] idma64_irq [idma64] kernel: [<...>] i2c_dw_isr kernel: [<...>] i801_isr [i2c_i801] kernel: [<...>] sdhci_irq [sdhci] threaded [<...>] sdhci_thread_irq [sdhci] kernel: [<...>] azx_interrupt [snd_hda_codec] kernel: Disabling IRQ #16 这篇记录这条告警是什么意思、根因是什么、影响什么,以及能用哪些 OS 或 BIOS 配置消除它。 ...

Created 10106-10-10 · Updated 10106-10-10 · 6 min · 1182 words · -

分布式事务 2PC, 3PC, TCC, Saga

数据库事务 在讲述分布式事务的概念之前,我们先来回顾下事务相关的一些概念。 事务的基本概念: 就是一个程序执行单元,里面的操作要么全部执行成功,要么全部执行失败,不允许只成功一半另外一半执行失败的事情发生。例如一段事务代码做了两次数据库更新操作,那么这两次数据库操作要么全部执行成功,要么全部回滚。 事务的基本特性: 我们知道事务有4个非常重要的特性,即我们常说的 (ACID) 。 Atomicity (原子性) 事务是一个不可分割的整体,所有操作要么全做,要么全不做。一旦其中某个操作出错,整个事务回滚到开始前的状态,已执行的操作全部撤销。比如 A 向 B 转账,扣款和入账这两步必须同时成功或同时失败,不能只扣了 A 的钱而 B 没收到。 Consistency (一致性) 是说事务执行前后,数据必须满足预定义的约束和规则,从一个合法状态转换到另一个合法状态。比如 A 向 B 转账,无论事务是否成功,A 和 B 的账户总金额必须保持不变——这个"总额守恒"就是一致性约束。注意:A 扣了钱 B 没收到,描述的是原子性被破坏;一致性关注的是业务规则(约束)是否始终成立。 Isolation (隔离性) 多个并发事务之间相互隔离,不能互相干扰。并发事务操作同一份数据时,可能出现三类读异常: 脏读:事务 A 读取了事务 B 尚未提交的数据,若 B 随后回滚,A 读到的就是无效数据。解决方法:隔离级别提升到"读已提交"。 不可重复读:事务 A 在同一事务内两次读取同一行,期间事务 B 修改并提交了该行,导致两次结果不同。解决方法:隔离级别提升到"可重复读"。 幻读:事务 A 在同一事务内两次执行相同的范围查询,期间事务 B 插入或删除了符合条件的行并提交,导致两次结果集的行数不同——凭空多出或少了几行,如同"幻象"。注意:幻读的前提是 A 本身处于事务中,且处于"可重复读"隔离级别(该级别能保证已有行不变,但挡不住新插入的行)。解决方法:隔离级别提升到"串行化",或使用间隙锁(Gap Lock)。 四个隔离级别(读未提交、读已提交、可重复读、串行化)依次能解决脏读、不可重复读、幻读,但隔离级别越高并发性能越低。 Durability (持久性) 事务一旦提交,其对数据库的修改就会被永久保存,即使系统崩溃或重启也不会丢失。数据库通过 WAL(Write-Ahead Log,预写日志)等机制来保证持久性。 关于数据库事务的基本概念大家可以去网上搜一下,这里只是给大家回顾下事务的基本概念及特性,诸如事务并发问题、事务隔离级别等大家如有遗忘可以去回顾下 (tips: 面试经常会问到的问题哦) 。 分布式事务 以上内容我们回顾了下事务的基本概念,那么分布式事务又是个什么概念呢?它与数据库事务之间又有什么区别呢? 其实分布式事务从实质上看与数据库事务的概念是一致的,既然是事务也就需要满足事务的基本特性 (ACID) ,只是分布式事务相对于本地事务而言其表现形式有很大的不同。举个例子,在一个JVM进程中如果需要同时操作数据库的多条记录,而这些操作需要在一个事务中,那么我们可以通过数据库提供的事务机制 (一般是数据库锁) 来实现。 ...

Created 7076-09-07 · Updated 10106-10-10 · 3 min · 506 words · -

godaddy

godaddy 买域名 在Godaddy搜索某个关键字, 比如 wiloon 在列表里找一个喜欢的或者价格低的, 有很多首年1.x$的, 比如 wiloon.online 购买并支付, 支付方式可以用 paypal. cloudflare> add site> free> 添加解析记录 type: A name: @ ipv4 address: <vps ip> proxy: false (DNS only) Save (保存设置然后等 DNS 生效) 配置域名解析, 配置到某一个 vps, DNS> Nameservers> Change Nameservers 维护记录 时间 修改内容 原因 2026-10-09 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码;categories 改为 Network 正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整

Created 2026-01-02 · Updated 9096-10-09 · 1 min · 58 words · -

Sign-Magnitude, Ones' Complement, Two's Complement 为什么要使用原码、反码、补码

为什么要使用原码 反码 补码. 前言: 随着学习计算机知识的加深,许多地方都遇到原码、反码、补码。很多关于计算机的书籍都介绍原码、反码、补码的表示方法,但是为什么要用到原码、反码、补码却没详细说。为什么要使用?它们的原理是什么呢?我搜索了许多资料,也查找了许多书籍,终于弄明白了这个问题,收集整理如下。 我们知道数值在计算机中表示形式为机器数,计算机只能识别0和1,使用的是二进制。而在日常生活中人们使用的是十进制,并且我们用的数值有正负之分。于是在计算机中就用一个数的最高位存放符号(0为正,1为负)。这就是机器数的原码了。 有了数值的表示方法就可以对数进行算术运算,但是很快就发现用带符号位的原码进行乘除运算时结果正确,而在加减运算的时候就出现了问题,如下: 假设字长为8bits (1) <sub>10</sub> - (1)<sub>10</sub> = (1)<sub>10</sub> + (-1)<sub>10</sub> = (0)<sub>10</sub> (0 0000001)<sub>原</sub> + (1 0000001)<sub>原</sub> = (1 0000010)<sub>原</sub> = ( -2 )显然不正确。 因为在两个整数的加法运算中是没有问题的,于是就发现问题出现在带符号位的负数身上。对除符号位外的其余各位逐位取反就产生了反码。反码的取值空间和原码相同且一一对应。下面是反码的减法运算: (1)<sub>10</sub> - (1)<sub>10</sub> = (1)<sub>10</sub> + (-1)<sub>10</sub> = (0)<sub>10</sub> (0 0000001)<sub>反</sub> + (1 1111110)<sub>反</sub> = (1 1111111)<sub>反</sub> = ( -0 )有问题。 (1)<sub>10</sub> - (2)<sub>10</sub> = (1)<sub>10</sub> + (-2)<sub>10</sub> = (-1)<sub>10</sub> (0 0000001)<sub>反</sub> + (1 1111101)<sub>反</sub> = (11111110)<sub>反</sub> = (-1)正确。 ...

Created 3036-07-03 · Updated 9096-10-09 · 1 min · 154 words · -

Java float, double, decimal

‘java float,double,decimal’ decimal:数字型,128bit,不存在精度损失,常用于银行帐目计算。 (28个有效位) float f = 345.98756f;//结果显示为345.9876,只显示7个有效位,对最后一位数四舍五入。 double d=345.975423578631442d;//结果显示为345.975423578631,只显示15个有效位,对最后一位四舍五入。 注: float和double的相乘操作,数字溢出不会报错,会有精度的损失。 decimal dd=345.545454879…..//可以支持28位,对最后一位四舍五入。 注: 当对decimal类型进行操作时,数值会因溢出而报错。 由于对float或double 的使用不当,可能会出现精度丢失的问题。问题大概情况可以通过如下代码理解: public class FloatDoubleTest { public static void main(String[] args) { float f = 20014999; double d = f; double d2 = 20014999; System.out.println("f=" + f); System.out.println("d=" + d); System.out.println("d2=" + d2); } } 得到的结果如下: f=2.0015E7 d=2.0015E7 d2=2.0014999E7 从输出结果可以看出double 可以正确的表示20014999 ,而float 没有办法表示20014999 ,得到的只是一个近似值。这样的结果很让人讶异。20014999 这么小的数字在float下没办法表示。于是带着这个问 题,做了一次关于float和double学习,做个简单分享,希望有助于大家对java 浮 点数的理解。 关于 java 的 float 和 double ...

Created 3036-07-03 · Updated 9096-10-09 · 2 min · 295 words · -

Java GC 监控, jstat

java gc监控, jstat jstat -<option> [-t] [-h] <vmid> [<interval> [<count>]] jstat -gc -h3 <jvm PID> 3000 -1 # -gc 显示gc的信息,查看gc的次数,及时间。 # -h: 即-h跟数字,代表隔几行显示标题 # interval: 毫秒,代表监控间隔时间段,默认毫秒做单位 # count: 代表取数次数 Jstat在分析java的内存GC时的应用 jstat可以查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id,和所选参数。 参数: jstat -class pid: Statistics on the behavior of the class loader. 显示加载class的数量,及所占空间等信息; jstat -compiler pid: Statistics of the behavior of the HotSpot Just-in-Time compiler.显示VM实时编译的数量等信息; jstat -gc pid: Statistics of the behavior of the garbage collected heap.可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。 jstat -gccapacity:可以显示,VM内存中三代 (young,old,perm) 对象的使用和占用大小,如: PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其他的可以根据这个类推, OC是old内纯的占用量。 ...

Created 9096-08-09 · Updated 9096-10-09 · 2 min · 353 words · -

javadoc

javadoc eclipse 在项目列表中按右键,选择Export (导出) ,然后在Export(导出)对话框中选择java下的javadoc. Java8下 忽略Javadoc编译错误 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.10.3</version> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> <configuration> <additionalparam>-Xdoclint:none</additionalparam> </configuration> </execution> </executions> </plugin> http://www.javajia.com/JAVAbiancheng/7713.html 维护记录 时间 修改内容 原因 2026-10-09 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码 正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整

Created 28286-08-28 · Updated 9096-10-09 · 1 min · 40 words · -

Java 数组, List, Set, 数组转 Set

java 数组 list set, 数组转set http://jerval.iteye.com/blog/1001643 //数组->Set String[] strs = {"AA","BB"}; Set<String> set2 = new HashSet<String>(Arrays.asList(strs)); System.out.println(set2); //Set->数组 Set<String> set3 = new HashSet<String>(Arrays.asList("PP","OO")); String[] strSet = new String[set3.size()]; set3.toArray(strSet); System.out.println(Arrays.toString(strSet)); List(interface): 次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。 ArrayList: 由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。 LinkedList: 对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。 #################################### Set(interface): 存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。 HashSet: 为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。采用散列函数对元素进行排序,这是专门为快速查询而设计的; TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。TreeSet采用红黑树的数据结构进行排序元素; LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。 需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。 维护记录 时间 修改内容 原因 2026-10-09 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码;title 改为「Java 数组, List, Set, 数组转 Set」 正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整

Created 8086-09-08 · Updated 9096-10-09 · 1 min · 68 words · -

OWL

OWL OWL Web本体语言指南 W3C推荐标准 2004年02月10日 摘要 目前这种结构的万维网,很像一本地图做得很差的地理书,我们对于Web中可以使用的文档和服务的了解,都是基于关键字搜索的, 同时还需要灵活地使用文档的链接和使用模式。如果没有强有力的工具的支持,这么大规模的数据是很难管理的,为了能够给Web绘制出更为详实的地图,计算代理需要对于网络上可用资源的内容和能力做一个机器能够读得懂的描述。这些描述是人类能够读得懂的信息的扩展。 OWL,这种本体描述语言,可以用来描述Web文档和应用中内在的类和关系。 这篇文章解释了OWL语言的使用: 通过定义类以及类的属性来形式化某个领域; 定义个体并说明它们之间的属性; 在OWL语言的形式化语义允许的层次上,对类和个体进行推理。 本文的各章节间是按照类、属性、个体的集合的定义给出来的,从最简单的概念开始,逐渐过渡到更为复杂的概念。 本文档的状态 本文档已被W3C成员及其他相关方面审阅,并已被W3C总监 (W3C Director) 批准为W3C推荐标准 (W3C Recommendation) 。W3C制定推荐标准的任务是使之受到关注,并促使其被广泛应用。这将增强Web的功能性与互操作性。 本文档是W3C关于Web本体语言OWL的推荐标准的六个部分之一。 它已经被Web 本体工作小组(小组章程) 作为W3C语义Web行动 (行动声明) 的一部分于2004年2月10日发布。 本文档的早期版本中所描述的关于OWL的设计已被广泛评阅,并已满足工作小组的技术需求。工作小组充分考虑所有收到的意见,并做了必要的修改。本文档自从候选推荐标准版本以来的所有修改都在文后的变更日志中。 欢迎通过public-webont-comments@w3.org (历史存档)提出您的意见,也可以通过www-rdf-logic@w3.org (mailto:www-rdf-logic@w3.org)(历史存档) 参与相关技术的讨论。 可以访问到有关实现的一个列表。 W3C维护着一个与这些工作相关的专利声明的目录。 这节描述了本文档在发布时的状态。其他文档可能替代这文档。一份当前W3C的最新出版物的目录和这个技术报告的最新版本可以在 W3C技术报告索引http://www.w3.org/TR/ 上找到。 目录 引言 1.1. OWL的种类 1.2. 本文档的结构 本体的结构 2.1. 命名空间 2.2. 本体头部 2.3. 数据集成与隐私 基本元素 (Basic Elements) 3.1. 简单的类和个体 3.1.1. 简单的具名类 3.1.2. 个体 3.1.3. 使用方面的考虑 3.2. 简单属性 3.2.1. 定义属性 (Defining Properties) 3.2.2. 属性和数据类型 ...

Created 22226-09-22 · Updated 9096-10-09 · 9 min · 1736 words · -

Servlet

Servlet servlet的由来 自从计算机软件开发进入网络时代,就开始涉及到通讯问题。在客户/服务器 (C/S) 时期,每个软件都有自己的客户端和服务器端软件。并且客户端和服务器端之间的通讯协议差别也很大。后来随着互联网的发展,基于浏览器/服务器(B/S)的应用逐渐成为主流,通讯协议也统一到HTTP协议。但是,在HTTP协议之上,如何处理来自客户端的请求信息,以及如何对请求进行回应,则经历了很长时间也没有统一下来。目前,对于这个问题的解决方案主要有两种,一个是CGI,另一个是Servlet。 servlet是在服务器上运行的小程序。这个词是在Java applet的环境中创造的,Java applet是一种当作单独文件跟网页一起发送的小程序,它通常用于在客户端运行,结果得到为用户进行运算或者根据用户互作用定位图形等服务。服务器上需要一些程序,常常是根据用户输入访问数据库的程序。这些通常是使用公共网关接口 (CGI(Common Gateway Interface)) 应用程序完成的。然而,在服务器上运行Java,这种程序可使用Java编程语言实现。在通信量大的服务器上,servlet的优点在于它们的执行速度更快于CGI程序。各个用户请求被激活成单个程序中的一个线程,而无需创建单独的进程,这意味着服务器端处理请求的系统开销将明显降低。最早支持Servlet技术的是JavaSoft的Java Web Server。此后,一些其它的基于Java的WebServer开始支持标准的ServletAPI。 Servlet最初是在1995年由James Gosling 提出的,因为使用该技术需要复杂的Web服务器支持,所以当时并没有得到重视,也就放弃了。后来随着Web应用复杂度的提升,并要求提供更高的并发处理能力,Servlet被重新捡起,并在Java平台上得到实现,现在提起Servlet,指的都是Java Servlet。Java Servlet要求必须运行在Web服务器当中,与Web服务器之间属于分工和互补关系。确切的说,在实际运行的时候Java Servlet与Web服务器会融为一体,如同一个程序一样运行在同一个Java虚拟机 (JVM) 当中。与CGI不同的是,Servlet对每个请求都是单独启动一个线程,而不是进程。这种处理方式大幅度地降低了系统里的进程数量,提高了系统的并发处理能力。另外因为Java Servlet是运行在虚拟机之上的,也就解决了跨平台问题。 在Servlet出现之后,随着使用范围的扩大,人们发现了它的一个很大的一个弊端。那就是为了能够输出HTML格式内容,需要编写大量重复代码,造成不必要的重复劳动。为了解决这个问题,基于Servlet技术产生了Java Server Pages (JSP) 。 Servlet和JSP两者分工协作,Servlet侧重于解决运算和业务逻辑问题,JSP则侧重于解决展示问题。Servlet与JSP一起为Web应用开发带来了巨大的贡献,后来出现的众多Java Web应用开发框架都是基于这两种技术的,更确切的说,都是基于Servlet技术的。 Servlet与Web容器之间的关系 Java是一种动态加载和运行的语言。也就是说当应用程序持有一个类的地址 (CLASSPATH) 和名称 (包名和类名) 的情况下,可以在程序运行期间任何时候加载这个类,并创建和使用该类的对象。Servlet就是基于这个机制与Web容器融合在一起的。目前已知的所有支持Java Servlet的Web容器都是采用Java开发的。当Web容器接收到来自客户端的请求信息之后,会根据URL中的Web元件地址信息到Servlet队列中查找对应的Servlet对象,如果找到则直接使用,如果没有找到则加载对应的类,并创建对象。也就是说,Servlet对象是在第一次被使用的时候才创建的,并且一旦创建就会被反复使用,不再创建新的对象。所有创建出的Servlet对象会在Web服务器停止运行的时候统一进行垃圾回收。 为了解决客户端请求地址与 Servlet之间对应关系问题,Web容器需要一个用来描述这种对应关系的文件,一般是web.xml文件。如果一个Web应用程序中存在很多个Servlet,那么web.xml会变得非常庞大。在Servlet 3.0规范推出之后,允许在Servlet代码中使用声明式语法(annotation)来代替web.xml中的描述信息,这才让web.xml瘦身下来. Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。 它担当客户请求 (Web浏览器或其他HTTP客户程序) 与服务器响应 (HTTP服务器上的数据库或应用程序) 的中间层。 Servlet是位于Web 服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。 Servlet的主要功能在于交互式地浏览和修改数据,生成动态Web内容。这个过程为: 客户端发送请求至服务器端; 服务器将请求信息发送至Servlet ; Servlet生成响应内容并将其传给Server。响应内容动态生成,通常取决于客户端的请求; 服务器将响应返回给客户端; Servlet看起来像是通常的Java程序。Servlet导入特定的属于Java ServletAPI的包。因为是对象字节码,可动态地从网络加载,可以说Servlet对Server就如同Applet对Client一样,但是,由于Servlet运行于Server中,它们并不需要一个图形用户界面。从这个角度讲,Servlet也被称为FacelessObject。 一个servlet就是Java编程语言中的一个类,它被用来扩展服务器的性能,服务器上驻留着可以通过"请求-响应"编程模型来访问的应用程序。虽然servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器的应用程序。 目前最新版本为3.0草案命名: Server + Applet =Servlet 意为服务器端的小程序 Servlet生命周期 装载Servlet。这项操作一般是动态执行的。然而,Server通常会提供一个管理的选项,用于在Server启动时强制装载和初始化特定的Servlet。 Server创建一个Servlet的实例 Server调用Servlet的init()方法 一个客户端的请求到达Server Server创建一个请求对象 Server创建一个响应对象 Server激活Servlet的service()方法,传递请求和响应对象作为参数 service()方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息 service()方法使用响应对象的方法,将响应传回Server,最终到达客户端。service()方法可能激活其它方法以处理请求,如doGet()或doPost()或程序员自己开发的新的方法。 对于更多的客户端请求,Server创建新的请求和响应对象,仍然激活此Servlet的service()方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用init()方法。一般Servlet只初始化一次(只有一个对象),当Server不再需要Servlet时(一般当Server关闭时),Server调用Servlet的Destroy()方法。 ...

Created 16166-10-16 · Updated 9096-10-09 · 3 min · 519 words · -