跳到主要内容

yaml 的数组语法

· 阅读需 1 分钟

块格式

fruits:
- Apple
- Orange
numbers:
- 1
- 2

流格式

fruits: ["Apple", "Orange"]
numbers: [1, 2]

对象数组

users:
- name: Alice
job: Engineer
active: true
- name: Bob
job: Designer
active: false

- 表示一个新的数组元素的开始。 name,hob,active 属于同一个对象。

多维数组

matrix_flow:
- [1, 2, 3]
- [11, 22, 33]
- [111, 222, 333]

yaml 的长文本语法

· 阅读需 1 分钟

保留换行 |

script: |
echo "hello wrold"
date

| 会保留每次换行。 (末尾有多个换行符时,只会保留一个)

{"script": "echo \"Hello World\"\ndate\n"}

折叠换行 >

description: >
aaa1
bbb2
ccc3

ddd4

> 会将换行符替换为空格。 如果包含空行,会保留一个换行符

{"description":"aaa1 bbb2 ccc3\ndddd4\n"}

剪裁控制

|- 删除末尾的 \n

|+ 保留末尾的 \n

MySQL with 的用法

· 阅读需 1 分钟

使用 with 创建临时结果集(Common Table Expression - CTE)。

  1. 简单 CTE
WITH sales_summary AS (
SELECT
product_id,
SUM(amount) as total_sales
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY product_id
)
SELECT
p.product_name,
s.total_sales
FROM products p
JOIN sales_summary s ON p.id = s.product_id;
  1. 多个 CTE
WITH
regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
),
top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT
region,
product,
SUM(quantity) AS product_units
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;
  1. 递归 CTE, 类比为递归函数
WITH RECURSIVE factorial AS (
SELECT 1 AS n, 1 AS fact
UNION ALL
SELECT n + 1, (n + 1) * fact
FROM factorial
WHERE n < 1
)
SELECT * FROM factorial;
WITH RECURSIVE number_seq (num, squared) AS (
-- Anchor: 初始值
SELECT 1, 1

UNION ALL

-- Recursive: num < 5 是终止条件
SELECT num + 1, (num + 1) * (num + 1)
FROM number_seq
WHERE num < 2
)
SELECT * FROM number_seq;

partition by 与 group by 的区别

· 阅读需 1 分钟

group by x1,x2 ...

将除了 x1,x2 以外的字段通过聚合函数 “压缩” 成一行。

select department,avg(salary) from employees group by department;

窗口函数() over (partition by 分组列 order by 排序列)

根据分组列进行分组,然后对每个组通过排序列进行排序,最后通过窗口函数计算每列的值。

select name,department,avg(salary) over (partition by department) as '平均工资',salary - avg(salary) over (partition by department) as '和平均差距' from employees;

Java 泛型擦除机制(Type Reasure)

· 阅读需 2 分钟

Java 编译器在编译时会移除所有的泛型类型信息。

case1: 编译时 vs 运行时

// 编译时(源代码)
List<String> stringList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();

// 运行时(字节码)- 泛型信息被擦除
List stringList = new ArrayList(); // 变成 Object
List intList = new ArrayList(); // 变成 Object

// 验证:运行时类型相同
System.out.println(stringList.getClass() == intList.getClass()); // true

case2: 泛型类型被替换

// 源代码
public class Box<T> {
private T value;

public T getValue() {
return value;
}
}

// 编译后等价于
public class Box {
private Object value; // T 被擦除为 Object

public Object getValue() {
return value;
}
}

case3: 有上界的泛型

// 源代码
public class NumberBox<T extends Number> {
private T value;

public T getValue() {
return value;
}
}

// 编译后等价于
public class NumberBox {
private Number value; // T 被擦除为上界类型 Number

public Number getValue() {
return value;
}
}

带来的问题:

  1. 无法获取泛型的类型
public class Test {
public <T> void printType(List<T> list) {
// ❌ 编译错误:无法获取 T 的实际类型
// System.out.println(T.class);

// ❌ 运行时只能获取到 List,不知道 T 是什么
System.out.println(list.getClass()); // class java.util.ArrayList
}
}
  1. 无法重载
public class Example {
// ❌ 编译错误:方法签名冲突
public void method(List<String> list) { }
public void method(List<Integer> list) { } // 擦除后签名相同
}

解决方案: 使用 hutoolcn.hutool.core.util.TypeUtil#getTypeArgument(java.lang.reflect.Type, int) 方法可以获取到泛型类型。

需要注意的是:Lambda 会丢失泛型信息,具体匿名内部类和实现类(public xxx implement xxx)不会。

学习方法

· 阅读需 1 分钟

Append-only 记笔记方法

回想起来,好像在让 AI 写一个简单的工具方法之后,一直没仔细研究下它是如何实现的,只是能用就行。这是个坏习惯

要反思下,这简单的功能为什么要让 AI 来实现呢?

是只是简单的重复性的工作?

还是在找记不起名字的 API?

还是你根本没有思路? 如果是这个情况,就需要仔细 review AI 给的代码。

端口占用问题后续

· 阅读需 1 分钟

https://blog.wangzhy.com/blog/windows-port-occupancy-issue 后续

发现是因为开启了 Clash Verge 的 tun 模式。

tun 模式的工作原理

  1. 创建虚拟网卡,所有的流量都会被重定向到这个虚拟网卡
  2. 接管所有的网络
    1. 系统级代理
    2. 本地回环地址
    3. 进程间的本地通信
  3. 端口绑定冲突,Clash 为了转发流量可能会做一下操作
    1. 预先占用或监听某些端口
    2. 拦截端口绑定操作
    3. DNS 劫持和流量重定向
  4. 防火墙级别拦截

高效完成任务而不感到疲惫的方法

· 阅读需 4 分钟

是否每天都觉得过筋疲力尽,这大概归因于两点。

  1. 你每天都要干很多事
  2. 每件事对你来说都有阻力(或许是事情太难,也或许是你太懒了)

然后你就需要用意志力(willpower)去克服阻力(resistance),获得干活的动力(motivation),久而久之就产生了倦怠(burnout)

willpower -> resistance -> motivation ..... burnout

为了解决这个现象,需要搞明白三个问题。

  1. 阻力来自哪里
  2. 如何应付阻力
  3. the exhaustion trap

第一个问题:阻力来自哪里?

每天都有很多事要做,但是直到一天都快过完了,任务却没有完成多少,觉得自己今天啥都没干又浑浑噩噩的过了一天,躺在床上,思考今天本应该要完成的事。大脑突然充满干劲,但你现在什么都干不了(总不能起来吧,你明天还有很多事要做呢,还得上班呢)然后,你失眠了。第二天,又重复这个过程,形成一个恶性循环。

第二个问题:如何应付阻力?

养成一个好习惯(habit),因为习惯是 autopilot 的(理解为本能),你只用花费很少的意志力就能克服阻力。

养成一个好习惯需要经历 4 个阶段。决策(decision),启动(initiation),强化(reinforcement),养成习惯(habit)

decision: 做一个决策是很容易的,比如想要干一件事,想要学习什么东西。做这个决定是很容易的,几乎不需要克服什么阻力(也许是强加给你的,比如上级安排的任务)。

initiation: 启动的阻力和门槛也很低。开始干活/学习,制定一个计划也没有什么难度。

reinforcement: 强化这个阶段就需要克服极大的阻力了,需要重复,直到养成一个好习惯。

habit: 这是我们的终极目标。

将强化阶段根据认为心理学划分为四个象限,横轴是 reinforcement 和 punishment,纵轴是 positive 和 negative。

正向强化就是通过增加某些东西(如多巴胺)来强化系统。一般是通过奖励机制来获得多巴胺,但这个会随着时间的推移,逐渐适应(3-6周)。它虽然有效,但难以维持。

负向强化通过移除某些负面行为来强化习惯。比如强烈的警告(未系安全带时的紧迫提示音,快到 DDL 的工作任务)等。通过解决这些东西来进行强化习惯(这不是一个好习惯)。 这个短期有效,不能持续。因为这个是依赖压力或者某些 bad things. 通过解决压力或 bad things 来进行强化。所以有的人就会把所有的事堆在一起,等到 DDL 的时候 才会开始处理。然后得到了一种强化,然后下次还这样。于是一个坏的习惯就养成了。但是对现在并没有任何改变。每天还是会有很多事情等着你。一个好的负向强化方案是:当你 感觉到压力大的时候,尝试冥想,这会使你放松,压力减小,使你得到了一种强化。

正向惩罚,就是通过增加一些不好的东西使你得到惩罚(低效且不可靠)。因为正向惩罚只教你不应该做什么,而没有教你应该怎么做。正向惩罚会导致负强化增强,这是一个恶性循环。 只有在一些情况下,正向惩罚是有效的/推荐的。那就是在危机人身安全的时候。

负向惩罚,通过移除某些东西来进行惩罚。如不准玩游戏,不准出门。

还有一个概念就是 extinction(灭绝)。extinction 指的是通过移除维持行为的强化物来避免某种行为。某种不好的行为存在是因为有某种强化物导致它存在,你要做的就是移除这种强化物。

拖延症就是一种负向强化。

解决拖延症一个好的方案是,给一个 3-5 分钟的时间,不以完成任务为目的开始做这件事,到时间之后你可以选择是否要继续。持续这样,知道养成习惯。

另一个好的方法是,准备好干这件事需要的东西,如你要学习什么东西,那就提前一天把文档打开,工具打开,第二天直接干就好了。 坐下来好好想想,要从现状到完成目标,需要完成哪些步骤,不应该做什么。保持以解决问题为导向,思考 what can i do.把能提前做的都提前做

想想所有阻碍你前进的障碍,what can i do

你应该寻找解决方案,而非仅发现问题。

Windows 端口被占用

· 阅读需 7 分钟

问题的解决过程

今天碰到了一个很诡异的端口占用问题。

项目使用的端口是 9052,在启动的时候出现了下面的提示。

Description:
Web server failed to start. Port 9052 was already in use.

Action:

Identify and stop the process that's listening on port 9052 or configure this application to listen on another port.

这是一个很常见的端口占用问题,通过下面的命令就能解决了。

netstat -aon | findstr "9052"
taskkill /T /F /PID 1234

但是,在我执行 netstat -aon | findstr "9052" 之后,什么都没有输出。

奇了怪了,不应该呀。然后用 TCPView 搜索 9052 端口占用情况,也什么都没有输出。

然后我把我的问题丢给了 AI。

netstat -ano | findstr 9052  查找不到占用 9052 的应用程序,但是在启动 springboot 的时候,出现了以下提示,我应该如何排查问题? 找出占用 9052 的应用程序?

Description:

Web server failed to start. Port 9052 was already in use.

Action:

Identify and stop the process that's listening on port 9052 or configure this application to listen on another port.

AI 给了我一些的建议

  1. 用管理员运行 cmd,然后再主席 netstat 命令

  2. 使用 TCPView 软件查看

  3. 检查是否是被 IPv6 占用

    netstat -ano -p tcp | findstr 9052
    netstat -ano -p tcpv6 | findstr 9052
  4. 使用 PowerShell 执行下面的命令 (好使)

    Get-NetTCPConnection -LocalPort 9052 | Format-Table -Property LocalAddress, LocalPort, State, OwningProcess

    # 然后查看进程详情
    Get-Process -Id <PID>

    得到下面的输出

    Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
    ------- ------ ----- ----- ------ -- -- -----------
    2865 686 227392 34016 204.83 16184 1 Foxmail

最后查出来是 FoxMail 占用了 9052 端口。

思考

那现在的问题是为什么 netstat 没有结果输出, Get-NetTCPConnection -LocalPort 9052 却可以找到占用 9052 端口的应用呢?

我继续向 AI 追问。

为什么 netstat -ano | findstr 9052 没有结果输出,而 Get-NetTCPConnection -LocalPort 9052 | Format-Table -Property LocalAddress, LocalPort, State, OwningProcess 却可以找出占用  9052 端口的应用?

AI 的解释是 2 个命令的工作原理不一样。

netstat 比较老,是从 TCP/IP 协议栈的缓存中读取数据,数据不是实时的,有刷新延迟。在某些情况下,短连接或者刚建立的连接可能不会立即显示。

Get-NetTCPConnection 直接查询内核, 更准确及时。

还有一个问题,Tcpview.exe 也是从内核中读取数据,为什么当时我也没有找到呢?

继续向 AI 追问

那为什么 Tcpview.exe 没有读取到数据,Get-NetTCPConnection -LocalPort 9052 | Format-Table -Property LocalAddress, LocalPort, State, OwningProcess 却读到了数据?

AI 给出了几个可能的原因:

  1. Tcpview.exe 设置不对,如监听的协议,过滤器等设置的有问题。
  2. TCPView 的刷新时机问题,虽然都是从内核读取数据,但是 Tcpview.exe 还是会有刷新时间间隔的。默认是 1 秒。
  3. 没有以管理员权限启动 Tcpview.exe, 但是却以管理员权限启动 PowerShell。
  4. 使用问题,我在搜索的时候只输入了 9052,但是 TCPView 的显示可能是 *:9052. 我应该输入 :9052

但是现在这个问题已无法复现了,无法得知具体是哪一个原因。只能等到后续这个问题再次出现才能知道原因了。

那么,如果这个问题再次出现,我应该如何执行什么命令,来确定它的根本问题?

继续就这个问题向 AI 追问。

由于 netstat -ano | findstr 9052 和 TCPView 查询不出数据,Get-NetTCPConnection -LocalPort 9052 | Format-Table -Property LocalAddress, LocalPort, State, OwningProcess 却可以找出占用  9052 端口的应用的问题已无法复现,那么下次这个问题再次出现,我应该执行什么命令来确定这个问题的根本原因?

AI 给我返回了一个 .ps1 文件,是的,一个 .ps1 文件,之前我从来没有接触过 ps1 的任何语法。

我粗略的看了下这个 .ps1 文件,用到的语法大概是定义变量/方法,执行方法,搭配一些 if-else 语句。

然后我给 AI 提问,让它给我介绍下这些语法。

我之前从来没接触过 ps1 脚本请向我解释 ps1 中如何定义变量,方法,如何执行方法,if-else 语句的语法。

PowerShell 脚本学习

.ps1 语法学习时间

变量

  1. $name = value 来定义变量

  2. $arr = @("apple","banana","orange") 定义数组

  3. 定义哈希表

     $person = @{
    Name = "wangzhy"
    Age = 29
    City = "zhuhai"
    }
  4. 定义布尔值

     $isActive = $true
    $isDeleted = $false
  5. 特殊变量

    • $_
    • $?
    • $null
    • $true
    • $false
    • $HOME
    • $PSVersionTable
  6. 变量的使用

    • 双引号内可以直接插入变量 "my name is $name" => my name is wangzhy
    • 单引号内不会替换变量 'my name is $name' => my name is $name

函数

function name {
# function body
}

在函数体内通过 param($name) 获取参入的参数。默认按照参数传递顺序进行赋值,也可以指定。

function add {
param($a,$b)
Write-Host "a = $a"
Write-Host "b = $b"
return $a + $b
}

Write-Host " a + b = " (add 2 10) # a=2,b=10
Write-Host " a + b = " (add -b 2 -a 10) # a=10,b=2

给函数参数设置默认值 param($a,$b = 11) 如果没有给 b 传递值,那么 b 的默认值是 11。

条件判断

if(conditon){

} elseif(conditon) {

} else {

}

比较运算符

  • -eq

  • -ne

  • -gt

  • -ge

  • -lt

  • -le

  • -eq

  • -ceq 忽略大小写

  • -like 模糊匹配

  • -math 正则匹配

  • -and

  • -or

  • -not

三元运算符 $result = ($age -ge 18) ? "成年" : "未成年"

$() 的用途

  • 用于访问对象的属性或方法 $($process.Id) 执行 $process 对象的 Id 属性
  • 执行复杂表达式 $($a + $b)
  • 命令替换 Write-Host "当前时间: $(Get-Date -Format 'HH:mm:ss')"
  • 嵌套属性访问 $($connection[0].LocalPort)

.ps1 脚本文件的格式要是 **UTF-8 with BOM** 的,不然的话,如果包含中文,会出现报错。

管道符 | 的使用:将左边命令的输出传递给右边命令的输入。

  • Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 获取进程 → 排序 → 选择前 5 个
  • 123 | Set-Clipboard 将 123 输出到剪贴板

最后得到一个 .ps1 文件

# ============================================
# 指定端口(通过 -port 传入,默认是 9052 端口)占用问题诊断脚本
# 当问题出现时立即运行此脚本
# ============================================

param($port = 9052)
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$logFile = "port_diagnosis_$timestamp.txt" # 日志文件

function Run-Write-Log {
param($Command)
$CommandResult = iex $Command
$Message = $CommandResult
if ($Message -isnot [string]) {
$Message = $Message | Out-String
}

Write-Host "========== $Command ==========" -ForegroundColor "GREEN"
Write-Host $Message
"========== $Command ==========" | Out-File -FilePath $logFile -Append -Encoding UTF8
$Message | Out-File -FilePath $logFile -Append -Encoding UTF8
return $CommandResult
}

$ignore = Run-Write-Log "netstat -ano | findstr :$port"
$cr = Run-Write-Log "Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue"

if ($null -ne $cr) {
# 这里要对 $($conn.OwningProcess) 去重
foreach ($conn in ($cr | Select-Object -Property OwningProcess -Unique)) {
$ignore = Run-Write-Log "Get-Process -Id $($conn.OwningProcess)"
}
}

# 通过 vscode 打开这个日志文件
code $logFile

AI 对软件行业的影响

· 阅读需 1 分钟

原文: https://www.chrisgregori.dev/opinion/code-is-cheap-now-software-isnt

code is cheap now, software isn't

构建软件的门槛已经坍塌,任何人都可以通过 Claude Code 成功构建出一个程序。

这个程序只是为了解决一个一次性问题的,用之即弃。就像 txt 文本,临时记录一些内容。

软件真正的成本不在于初始编写,而在于后续维护、边界情况处理、不断累计的 UX debt(用户体验债务)以及数据所有权的复杂性。

软件工程正在迈入新时代。