普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月26日首页

每日一题-商店的最少代价🟡

2025年12月26日 00:00

给你一个顾客访问商店的日志,用一个下标从 0 开始且只包含字符 'N' 和 'Y' 的字符串 customers 表示:

  • 如果第 i 个字符是 'Y' ,它表示第 i 小时有顾客到达。
  • 如果第 i 个字符是 'N' ,它表示第 i 小时没有顾客到达。

如果商店在第 j 小时关门(0 <= j <= n),代价按如下方式计算:

  • 在开门期间,如果某一个小时没有顾客到达,代价增加 1 。
  • 在关门期间,如果某一个小时有顾客到达,代价增加 1 。

请你返回在确保代价 最小 的前提下,商店的 最早 关门时间。

注意,商店在第 j 小时关门表示在第 j 小时以及之后商店处于关门状态。

 

示例 1:

输入:customers = "YYNY"
输出:2
解释:
- 第 0 小时关门,总共 1+1+0+1 = 3 代价。
- 第 1 小时关门,总共 0+1+0+1 = 2 代价。
- 第 2 小时关门,总共 0+0+0+1 = 1 代价。
- 第 3 小时关门,总共 0+0+1+1 = 2 代价。
- 第 4 小时关门,总共 0+0+1+0 = 1 代价。
在第 2 或第 4 小时关门代价都最小。由于第 2 小时更早,所以最优关门时间是 2 。

示例 2:

输入:customers = "NNNNN"
输出:0
解释:最优关门时间是 0 ,因为自始至终没有顾客到达。

示例 3:

输入:customers = "YYYY"
输出:4
解释:最优关门时间是 4 ,因为每一小时均有顾客到达。

 

提示:

  • 1 <= customers.length <= 105
  • customers 只包含字符 'Y' 和 'N' 。

iOS开发必备的HTTP网络基础概览

作者 sweet丶
2025年12月25日 23:54

一、从一次HTTP请求说起

以下是一个大体过程,不包含DNS缓存等等细节:

sequenceDiagram
    participant C as 客户端(iOS App)
    participant D as DNS服务器
    participant S as 目标服务器
    participant T as TLS/SSL层
    
    Note over C,S: 1. DNS解析阶段
    C->>D: 查询域名对应IP
    D-->>C: 返回IP地址
    
    Note over C,S: 2. TCP连接建立
    C->>S: SYN (我要连接)
    S-->>C: SYN-ACK (可以连接)
    C->>S: ACK (确认连接)
    
    Note over C,S: 3. TLS握手(HTTPS)
    C->>T: ClientHello
    T-->>C: ServerHello + 证书
    C->>C: 验证证书
    C->>T: 预主密钥(加密)
    T-->>C: 握手完成
    
    Note over C,S: 4. HTTP请求响应
    C->>S: GET /api/data
    S-->>C: 200 OK + 数据
    
    Note over C,S: 5. 连接管理
    alt HTTP/1.1持久连接
        S->>C: 保持连接打开
    else HTTP/2多路复用
        C->>S: 多个请求并行
    end

上图展示了一个完整的HTTPS请求过程。对于iOS开发者,理解每个环节的工作原理至关重要,这有助于优化网络性能、解决连接问题。

二、深入理解网络分层模型

TCP/IP四层模型详解

┌─────────────────────────────────────────┐
│           应用层 (Application)           │
│  HTTP/HTTPS · DNS · WebSocket · FTP     │
├─────────────────────────────────────────┤
│           传输层 (Transport)             │
│       TCP(可靠) · UDP(快速)          │
├─────────────────────────────────────────┤
│           网络层 (Internet)              │
│         IP · ICMP · 路由选择             │
├─────────────────────────────────────────┤
│           链路层 (Link)                  │
│   以太网 · WiFi · 蜂窝网络 · ARP        │
└─────────────────────────────────────────┘

各层在iOS开发中的体现

1. 应用层(iOS开发者最关注)

  • HTTP/HTTPS:URLSession、Alamofire、Moya等框架直接操作
  • DNS:系统自动处理,但可优化
  • WebSocket:实时通信场景
  • 责任:定义数据格式和应用协议

2. 传输层(可靠性保证)

  • TCP:面向连接、可靠传输
    • 三次握手建立连接
    • 丢包重传、顺序保证
    • 流量控制、拥塞控制
    • iOS中:URLSession默认使用TCP
  • UDP:无连接、尽最大努力交付
    • 实时音视频、DNS查询
    • iOS中:NWConnection框架支持

3. 网络层(路由寻址)

  • IP协议:负责主机到主机的通信
  • IPv4 vs IPv6:iOS自动处理兼容性
  • 路由选择:数据包如何到达目标
  • ICMP:ping工具的基础(网络诊断)

4. 链路层(物理连接)

  • 不同网络类型:WiFi、蜂窝网络、有线网络
  • MTU(最大传输单元):影响数据包分片
  • iOS中:通过NWPathMonitor监控网络状态变化

各层常见问题及调试

  • 应用层:HTTP状态码、JSON解析错误
  • 传输层:连接超时、连接重置、端口不可达
  • 网络层:路由不可达、TTL超时
  • 链路层:信号弱、MTU不匹配

iOS调试工具

  • 网络抓包:Charles、Wireshark
  • 命令行:nslookuppingtraceroute
  • Xcode Instruments:Network模板

三、DNS解析深度优化

HTTPDNS基本原理

传统DNS vs HTTPDNS
┌─────────────────┐    ┌─────────────────┐
│   传统DNS流程    │    │   HTTPDNS流程   │
├─────────────────┤    ├─────────────────┤
│ 1. 系统DNS查询   │    │ 1. HTTP API调用  │
│ 2. 递归查询      │    │ 2. 直接返回IP    │
│ 3. 易受劫持      │    │ 3. 防劫持       │
│ 4. 延迟较高      │    │ 4. 低延迟       │
└─────────────────┘    └─────────────────┘

HTTPDNS工作流程

  1. 绕过系统DNS:直接向HTTPDNS服务商(如腾讯云DNSPod、阿里云)发送HTTP/HTTPS请求
  2. 获取最优IP:服务端根据客户端IP返回最近、最优的服务器IP
  3. 本地DNS:建立本地缓存,减少查询频率
  4. 失败降级:HTTPDNS失败时自动降级到系统DNS

iOS实现HTTPDNS的关键步骤

  1. 拦截URL请求,解析出域名
  2. 向HTTPDNS服务查询IP地址
  3. 替换请求的Host头,将域名替换为IP
  4. 添加原始域名到Header(如"Host: www.example.com")
  5. 建立连接时直接使用IP地址

DNS优化综合策略

优化方案 原理 iOS实现要点
本地缓存 减少重复查询 设置合理TTL,监听网络切换清缓存
预解析 提前解析可能用到的域名 在需要前发起异步DNS查询
连接复用 减少DNS查询次数 保持HTTP持久连接
多路复用 并行解析多个域名 异步并发DNS查询
失败重试 提高可靠性 备选DNS服务器,指数退避重试

四、HTTP协议演进详解

HTTP/1.1核心特性

持久连接(Keep-Alive)

graph LR
    A[HTTP/1.0] --> B[每次请求新建连接]
    B --> C[高延迟 高开销]
    D[HTTP/1.1] --> E[连接复用]
    E --> F[降低延迟 减少开销]
    
    G[客户端] -- 请求1 --> H[服务器]
    G -- 请求2 --> H
    G -- 请求3 --> H
    H -- 响应1 --> G
    H -- 响应2 --> G
    H -- 响应3 --> G

关于服务器负载的说明: 持久连接实际上减少了服务器总体负载:

  1. 连接建立成本:TCP三次握手 + TLS握手(HTTPS)消耗大量CPU
  2. 减少并发连接数:每个客户端连接数减少
  3. 内存资源节省:每个连接需要维护状态信息

但需要注意

  • 需要合理设置keep-alive超时时间
  • 监控服务器连接数,避免过多空闲连接占用资源
  • iOS中URLSession默认管理连接池

HTTP/1.1的其他重要特性

  1. 分块传输编码:支持流式传输
  2. 缓存控制:Cache-Control头部
  3. 管道化(理论特性):可并行发送多个请求,但响应必须按序返回,存在队头阻塞问题

HTTP/2革命性改进

graph TD
    subgraph HTTP/1.1
        A1[请求1] --> A2[响应1]
        B1[请求2] --> B2[响应2]
        C1[请求3] --> C2[响应3]
    end
    
    subgraph HTTP/2
        D[二进制分帧层]
        E1[请求1] --> D
        E2[请求2] --> D
        E3[请求3] --> D
        D --> F1[响应1]
        D --> F2[响应2]
        D --> F3[响应3]
    end

HTTP/2核心特性

  1. 二进制分帧

    • 替代HTTP/1.x的文本格式
    • 帧类型:HEADERS、DATA、SETTINGS等
    • 更高效解析,更少错误
  2. 多路复用

    • 单个连接上并行交错多个请求/响应
    • 解决HTTP/1.1队头阻塞问题
    • 请求优先级设置
  3. 头部压缩(HPACK)

    • 静态表(61个常用头部)
    • 动态表(连接期间维护)
    • 哈夫曼编码
  4. 服务器推送

    • 服务器可主动推送资源
    • 客户端可拒绝不需要的推送

iOS适配要点

  • iOS 8+ 自动支持HTTP/2(通过ALPN协商)
  • 无需代码变更,但需确保服务器支持TLS
  • 监控工具可查看是否使用HTTP/2

HTTP/3(基于QUIC)新时代

QUIC协议架构

┌─────────────────┐
│   HTTP/3语义    │
├─────────────────┤
│  QUIC传输协议    │
│  (基于UDP)      │
├─────────────────┤
│   TLS 1.3       │
├─────────────────┤
│  应用层拥塞控制  │
└─────────────────┘

HTTP/3核心改进

  1. 传输层改为UDP:彻底解决TCP队头阻塞
  2. 内置TLS 1.3:0-RTT/1-RTT快速握手
  3. 连接迁移:网络切换时连接不中断
  4. 改进的拥塞控制:更适应现代网络环境

iOS适配

  • iOS 15+ 开始支持
  • URLSession自动协商使用
  • 可通过Network框架检测协议版本

五、HTTPS安全机制深度解析

TLS握手流程详解

sequenceDiagram
    participant C as Client
    participant S as Server
    
    Note over C,S: TLS 1.2 完整握手
    C->>S: ClientHello<br/>支持的版本、密码套件、随机数
    S->>C: ServerHello<br/>选定的版本、密码套件、随机数
    S->>C: Certificate<br/>服务器证书链
    S->>C: ServerHelloDone
    
    C->>C: 验证证书有效性
    C->>S: ClientKeyExchange<br/>预主密钥(用服务器公钥加密)
    C->>S: ChangeCipherSpec<br/>切换加密方式
    C->>S: Finished<br/>加密验证数据
    
    S->>S: 解密预主密钥,生成会话密钥
    S->>C: ChangeCipherSpec
    S->>C: Finished
    
    Note over C,S: TLS 1.3 简化握手
    C->>S: ClientHello<br/>包含密钥共享
    S->>C: ServerHello<br/>证书、Finished
    C->>S: Finished<br/>1-RTT完成

iOS证书验证体系

系统信任链

  1. 根证书库:iOS内置的可信CA根证书
  2. 证书链验证:从服务器证书追溯到可信根证书
  3. 吊销检查:OCSP或CRL检查证书是否被吊销

证书锁定(Pinning)策略

// iOS 安全配置示例
// 1. ATS配置 (Info.plist)
// 2. 证书锁定实现
class CertificatePinner: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        
        // 验证服务器证书是否匹配预设公钥
        guard let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        
        // 公钥锁定(比证书锁定更灵活)
        let policy = SecPolicyCreateSSL(true, challenge.protectionSpace.host as CFString)
        SecTrustSetPolicies(serverTrust, policy)
        
        // 验证并提取公钥进行比较
        // ... 具体实现代码
    }
}

HTTPS性能优化

  1. 会话恢复

    • Session ID:服务端存储会话信息
    • Session Ticket:客户端存储加密的会话信息
    • 减少完整握手次数
  2. TLS 1.3优势

    • 0-RTT(零往返时间):对重复连接极速握手
    • 1-RTT:首次连接也更快
    • 更安全的密码套件
  3. iOS最佳实践

    • 启用TLS 1.3(iOS 13+ 默认支持)
    • 合理配置ATS策略
    • 监控TLS握手时间指标

六、iOS网络编程综合建议

1. 连接管理策略

  • 连接池管理:每个主机保持2-6个持久连接
  • 超时策略
    • 连接超时:15-30秒
    • 请求超时:根据业务调整
    • 资源超时:大文件下载单独设置
  • 网络切换处理:监听NWPathMonitor,重建连接

2. 协议选择策略

// 协议检测与选择
func checkHTTPVersion() {
    let session = URLSession.shared
    let task = session.dataTask(with: URL(string: "https://api.example.com")!) { data, response, error in
        if let httpResponse = response as? HTTPURLResponse {
            // 查看实际使用的协议
            if #available(iOS 13.0, *) {
                print("使用的协议: \(httpResponse.value(forHTTPHeaderField: "X-Protocol") ?? "未知")")
            }
        }
    }
    task.resume()
}

3. 安全与性能平衡

  • 敏感数据:强制证书锁定 + TLS 1.3
  • 公开内容:标准HTTPS验证即可
  • 性能关键:考虑启用0-RTT,但注意重放攻击风险

4. 监控与调试

  • 关键指标
    • DNS解析时间
    • TCP连接时间
    • TLS握手时间
    • TTFB(首字节时间)
    • 下载速度
  • 网络诊断
    • 实现网络诊断页面
    • 收集不同网络环境下的性能数据
    • 用户反馈问题时的自动诊断报告

总结

iOS网络编程不仅仅是调用API,更是对底层协议的深刻理解和合理应用。从四层模型的分工协作,到DNS解析的优化策略,从HTTP协议的持续演进,到HTTPS安全机制的实现原理,每一个环节都影响着最终的用户体验。

关键认知升级

  1. HTTP/2的多路复用显著提升性能,但需要服务器支持
  2. HTTP/3基于QUIC,解决传输层队头阻塞,是未来方向
  3. HTTPS性能不再是问题,TLS 1.3极大优化了握手延迟
  4. DNS优化常被忽视,但却是首屏加载的关键因素

实践建议

  • 优先使用系统框架(URLSession),充分利用系统优化
  • 渐进增强,支持新协议但不强依赖
  • 全面监控,建立网络性能基线
  • 安全优先,但也要考虑兼容性和维护成本

通过深入理解这些网络基础知识,iOS开发者能够构建更高效、更稳定、更安全的网络层,为用户提供卓越的网络体验。

[Java] 前缀和 & 空间优化

作者 Tizzi
2022年11月29日 11:18

解法一:前缀和

利用前缀和,后缀和统计N,Y的数字即可,最后计算出最小的答案。

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$
class Solution {
    public int bestClosingTime(String cus) {
        int n = cus.length(), maxv = 100005, ans = 0;
        char[] arr = cus.toCharArray();
        int[] left = new int[n + 5], right = new int[n + 5];
        for (int i = 1; i <= n; i++) left[i] += left[i - 1] + (arr[i - 1] == 'N' ? 1 : 0);
        for (int i = n; i >= 1; i--) right[i] += right[i + 1] + (arr[i - 1] == 'Y' ? 1 : 0);
        for (int i = 1; i <= n + 1; i++) {
            if (left[i - 1] + right[i] < maxv) {
                maxv = left[i - 1] + right[i];
                ans = i - 1;
            }
        }
        return ans;
    }
}

解法二:空间优化

先统计所有Y的个数,然后对于当前字符计算代价即可,最后更新总代价。

class Solution {
    public int bestClosingTime(String cus) {
        int n = cus.length(), maxv = 100005, ans = 0;
        char[] arr = cus.toCharArray();
        int left_right = 0; 
        for (int i = n; i >= 1; i--) left_right += (arr[i - 1] == 'Y' ? 1 : 0);
        for (int i = 1; i <= n + 1; i++) {
            if (left_right < maxv) {
                maxv = left_right;
                ans = i - 1;
            }
            if (i <= n && arr[i - 1] == 'Y') left_right--;
            else left_right++;
        }
        return ans;
    }
}
  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(1)$

如果有问题,欢迎评论区交流, 如果有帮助到你,请给题解点个赞和收藏哈~~~

前后缀分解,一次遍历优化(Python/Java/C++/Go)

作者 endlesscheng
2022年11月27日 09:02

题意

把 $\textit{customers}$ 分成两段,第一段计算 $\texttt{N}$ 的个数 $\textit{preN}$,第二段计算 $\texttt{Y}$ 的个数 $\textit{sufY}$。

计算 $\textit{preN}+\textit{sufY}$ 的最小值对应的最小分割位置 $i$,其中 $[0,i-1]$ 是第一段,$[i,n-1]$ 是第二段。

注:也可以不分割,相当于其中一段是空的。

思路

先假设所有字母都在第二段,统计 $\textit{customers}$ 中 $\texttt{Y}$ 的个数 $\textit{sufY}$。

想象一根分割线在从左到右移动,$\textit{customers}[i]$ 原来在第二段,现在在第一段。如果 $\textit{customers}[i] = \texttt{N}$,那么把 $\textit{preN}$ 加一($\textit{preN}$ 初始值为 $0$),否则把 $\textit{sufY}$ 减一。

答案为 $\textit{preN}+\textit{sufY}$ 最小时对应的分割位置。

代码实现时,$\textit{preN}+\textit{sufY}$ 可以合并为一个变量 $\textit{penalty}$。

优化前

class Solution:
    def bestClosingTime(self, customers: str) -> int:
        min_penalty = penalty = customers.count('Y')
        ans = 0  # [0,n-1] 是第二段
        for i, c in enumerate(customers):
            penalty += 1 if c == 'N' else -1
            if penalty < min_penalty:
                min_penalty = penalty
                ans = i + 1  # [0,i] 是第一段,[i+1,n-1] 是第二段
        return ans
class Solution {
    public int bestClosingTime(String customers) {
        char[] s = customers.toCharArray();
        int penalty = 0;
        for (char c : s) {
            if (c == 'Y') {
                penalty++;
            }
        }

        int minPenalty = penalty;
        int ans = 0; // [0,n-1] 是第二段
        for (int i = 0; i < s.length; i++) {
            penalty += s[i] == 'N' ? 1 : -1;
            if (penalty < minPenalty) {
                minPenalty = penalty;
                ans = i + 1; // [0,i] 是第一段,[i+1,n-1] 是第二段
            }
        }
        return ans;
    }
}
class Solution {
public:
    int bestClosingTime(string customers) {
        int penalty = ranges::count(customers, 'Y');
        int min_penalty = penalty;
        int ans = 0; // [0,n-1] 是第二段
        for (int i = 0; i < customers.size(); i++) {
            penalty += customers[i] == 'N' ? 1 : -1;
            if (penalty < min_penalty) {
                min_penalty = penalty;
                ans = i + 1; // [0,i] 是第一段,[i+1,n-1] 是第二段
            }
        }
        return ans;
    }
};
func bestClosingTime(customers string) int {
penalty := strings.Count(customers, "Y")
minPenalty := penalty
ans := 0 // [0,n-1] 是第二段
for i, c := range customers {
if c == 'N' {
penalty++
} else {
penalty--
}
if penalty < minPenalty {
minPenalty = penalty
ans = i + 1 // [0,i] 是第一段,[i+1,n-1] 是第二段
}
}
return ans
}

优化:一次遍历

第一次遍历(统计 $\texttt{Y}$ 的个数)是多余的。

打个比方,绝对温度下冰点为 $273,\mathrm{K}$,摄氏温度下冰点为 $0~^\circ\mathrm{C}$。如果只关心哪一天最冷,用哪个单位计算都可以。

或者说,把折线图往下平移(第一个点移到坐标零点),最低点的横坐标仍然不变。

class Solution:
    def bestClosingTime(self, customers: str) -> int:
        min_penalty = penalty = ans = 0
        for i, c in enumerate(customers):
            penalty += 1 if c == 'N' else -1
            if penalty < min_penalty:
                min_penalty = penalty
                ans = i + 1
        return ans
class Solution {
    public int bestClosingTime(String customers) {
        int penalty = 0;
        int minPenalty = 0;
        int ans = 0;
        for (int i = 0; i < customers.length(); i++) {
            penalty += customers.charAt(i) == 'N' ? 1 : -1;
            if (penalty < minPenalty) {
                minPenalty = penalty;
                ans = i + 1;
            }
        }
        return ans;
    }
}
class Solution {
public:
    int bestClosingTime(string customers) {
        int penalty = 0, min_penalty = 0, ans = 0;
        for (int i = 0; i < customers.size(); i++) {
            penalty += customers[i] == 'N' ? 1 : -1;
            if (penalty < min_penalty) {
                min_penalty = penalty;
                ans = i + 1;
            }
        }
        return ans;
    }
};
func bestClosingTime(customers string) (ans int) {
penalty, minPenalty := 0, 0
for i, c := range customers {
if c == 'N' {
penalty++
} else {
penalty--
}
if penalty < minPenalty {
minPenalty = penalty
ans = i + 1
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{customers}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

相似题目

3694. 删除子字符串后不同的终点

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

枚举 & 前缀和

作者 tsreaper
2022年11月27日 00:09

解法:枚举 & 前缀和

为了方便计算前(后)缀和,我们认为 customers 的下标是从 $1$ 开始的,最后答案减 $1$ 即可。

维护 $f(i)$ 表示第 $1$ 到第 $i$ 个字符有几个 N(初值 $f(0) = 0$),$g(i)$ 表示第 $i$ 到第 $n$ 个字符有几个 Y(初值 $g(n + 1) = 0$),那么第 $h$ 小时关店的代价即为 $f(h - 1) + g(h)$。

从 $1$ 到 $(n + 1)$ 枚举所有 $h$ 并选出代价最小的即可。复杂度 $\mathcal{O}(n)$。

参考代码(c++)

###c++

class Solution {
public:
    int bestClosingTime(string customers) {
        int n = customers.size();

        int f[n + 2], g[n + 2];
        // 计算前缀有几个 N
        f[0] = 0;
        for (int i = 1; i <= n; i++) f[i] = f[i - 1] + (customers[i - 1] == 'N' ? 1 : 0);
        // 计算后缀有几个 Y
        g[n + 1] = 0;
        for (int i = n; i > 0; i--) g[i] = g[i + 1] + (customers[i - 1] == 'Y' ? 1 : 0);

        // 枚举答案
        int ans = 0, best = 1e9;
        for (int i = 1; i <= n + 1; i++) {
            int tmp = f[i - 1] + g[i];
            if (tmp < best) ans = i, best = tmp;
        }
        return ans - 1;
    }
};

Qcon 上海 2025 Vibe Coding 在代码生成与协作中的实践与思考

作者 wyanassert
2025年12月26日 00:28

Vibe Coding 在代码生成与协作中的实践与思考 - 向邦宇

自我介绍

  • 多年从事研发者工具开发,包括内部 AI Coding 工具和 Web IDE 工具
  • 从 2023 年开始,从内部 Copilot 转型到 AI Agent 方向
  • 作为产品提供方,接触了大量内部用户,观察他们如何使用工具以及遇到的问题

演讲选题思考

  • Vibe Coding 概念出现几个月,但并非确定性的东西
  • 不同人对 Vibe Coding 理解不同,使用的工具也不同
  • 从两个视角分享:用户使用场景和问题、产品提供方的思考和解决方案

演讲结构

  1. 简单介绍业界和内部有哪些 Vibe Coding 工具在使用
  2. 用户在使用 Vibe Coding 工具过程中遇到的问题
  3. 作为 Vibe Coding 工具核心主导者的思考
  4. 国产模型适配过程中遇到的问题和解决方案

Vibe Coding 产品形态

当前工具分类的模糊性

  • 大家对 Vibe Coding 工具的理解和分类不够清晰
  • 每个工具都有人在用,但缺乏明确的定位

不同 Vibe Coding 工具的主要区别

1. Native IDE(原生集成开发环境)

  • 代表产品:Cursor、Cline、阿里 Qoder 等
  • 特点:以独立 IDE 形式存在
  • 优势:灵活性高,功能完整

2. IDE Plugin(IDE 插件)

  • 代表产品:内部 A1 Copilot 等
  • 基于现有 IDE(主要是 VS Code 或 JetBrains)的插件形式
  • 内部用户使用插件是比较主流的习惯
  • 灵活性可能没有 Native IDE 那么高

3. Web IDE

  • 入口在浏览器上
  • 整个执行在远端容器里,可能是沙箱环境
  • 优势:
    • 解决信任问题和云端执行的安全问题
    • 更适合协作:多个同学可以在同一个 Web IDE 里进行同步协作和分享
    • 跨平台支持

4. CLI 命令行工具

  • 代表产品:Copilot CLI
  • 最初没想到会受欢迎,但实际上非常受主流研发欢迎
  • 未来可能在被集成的方式(如 CI/CD)中执行一些自动化任务
  • 在这种场景下会有更高的可能性

内部 Vibe Coding 工具的使用实践

A1 Copilot(依托于 IDE 的Wibe Agent工具)

  • 内部协作多年的产品
  • 用户规模:数万用户,每周几千周活
  • 主要使用场景:
    • 代码生成
    • Bug 修复
    • 代码分析
  • 用户分布:后端场景渗透率较高,前端用户更倾向使用 Native IDE(如 Cursor 或 Qoder)

AI Agent(异步容器执行的 Agent 工具)

  • 以 Web 端发起的容器内运行的异步任务工具
  • 核心特点:用户通过自然语言发起任务
  • 在异步容器里拉起 Agent,Agent 自己调用工具(搜索工具、文件读写工具、Shell 工具等)
  • 用户角色更加多元:
    • 主要用户:后端开发
    • 其他用户:测试、前端、算法、产品、运营、设计、运维等
  • 任务类型丰富多元:
    • 代码分析
    • 代码改动
    • 单元测试
    • 代码生成
    • 文案方案调研等

工具尤其是 Agent 带来的效率提升

数据观察(从 4 月份开始的 Agent 模式)

代码提交量的显著提升

  • 蓝色线:高频用户(使用 Agent 模式)
  • 橙色线:其他用户
  • Agent 模式下,高频用户的每日代码提交行数有非常大的提升
  • 到 9 月份,高频用户每天提交 540-560 行代码,其他用户只有 400 多行
  • 至少从定量指标看,Agent 模式对提效肯定有帮助

用户分层现象

  • Top 10% 用户的代码提交量是其他人的两倍
  • 认为 Agent 对人的提效可能大于两倍,因为大量工作在协同、开会等非编码环节
  • Top 10% 用户的 Copilot 消耗占整体消耗的 80%

AI 新的应用场景

  • 单元测试由 AI 生成的提交代码占比越来越高
  • JDK 升级、NPM 包升级、SDK 升级等工作已经可以由 AI 完成
    • JDK 11 及以上版本升级场景,内部基本全部交给工具处理
  • 数据分析、数据整理工作部分交给 AI
  • 传统必须由人完成的任务现在由 Agent 完成:
    • 测试过程中的截图
    • 压测过程中的重复任务
  • 过去成本过高无法做的事情现在可以做:
    • 一次发布是否会引起其他相关系统故障
    • 每一行代码对其他系统的影响分析

用户使用 Vibe Coding 工具遇到的问题

用户情绪问题

AI 表现不足导致的崩溃

  • 后台日志中大量用户抱怨”AI 太笨了”等激动的话
  • 用户反复删除代码、修改代码的行为
  • 无论公司内部还是社区,都能看到用户因 Agent 能力不足而崩溃

GitHub 上的”八荣八耻”提示词

  • 用户分享给 Agent 的提示词规范
  • 例如:”以不能修改原始代码为荣”等

5.2 代码质量问题

我们看到的 Vibe Coding 的问题是多方面的

  1. 代码风格不一致
    • 生成的代码质量和风格差异较大
    • 在存量仓库里写代码时,可能以自己的风格编写,而非遵循项目规范
  2. 边界条件处理不完善
    • 对复杂业务逻辑的边界情况处理不够充分
  3. 性能缺陷
    • 生成的代码存在性能问题
  4. 安全漏洞
    • SQL 注入类漏洞严重
    • 斯坦福研究表明:AI 生成代码中注入类漏洞比例约 45%
    • 其他安全问题:
      • 接口注入
      • XSS 攻击
      • 逻辑错误
      • 边界条件处理错误
      • 异常控制
  • 数字越界

代码逻辑自洽问题

  • AI 在代码生成过程中会有非常多的”自洽”
  • 案例:数据去重函数及其对应的单元测试
    • 测试通过率 100%
    • 针对代码做了单测
    • 但如果让 AI 同时写单测和业务逻辑,无法保证质量
    • 会出现”自己和自己对话”的情况
  • 建议:至少有一项(单测或业务逻辑)是人去 review 的

调试和维护困难

调试时间增加

  • 使用工具后,调试时间增加 30%-50%
  1. 黑盒问题
    • Vibe Coding 更倾向于黑盒代码逻辑生成
    • 虽然最后会让人确认代码 diff 才能提交
    • 但生成过程是黑盒,不会有人认真看每一条
    • AI 生成代码像”黑魔法”,出问题时完全不知道如何下手
    • 技术债务越来越深
  2. 上下文理解局限
    • 存量任务的业务逻辑可能积累十几年
    • 有些代码为什么要这么写?有些代码是否能去掉?对 AI 来说都很困难
    • Vibe Coding 工具缺乏全局思维
    • 生成的代码模块化程度不够,代码耦合度很高
    • 解决方案:RepoWiki, DeepWiki 等方案
  3. 缺乏可追溯性
    • Vibe Coding 一次性生成大量代码
    • AI 无法知道:是新需求导致代码写错,还是一开始就写错了
      • 缺乏版本管理和版本概念
      • 一次生成代码出错后,不知道从哪个地方回滚
    • 现有方法:
      • 每次改完测试通过后提交一个 commit, 下次可以从这个 commit 回滚
      • 使用 Cursor 等回滚工具
    • 但仍然缺乏可追溯性,用户无法做版本管理,无法回到正确状态,只能重来

Vibe Coding 工具普遍不会使用常用的调试工具

  • AI 普遍不会使用人类常用的调试工具
  • 传统”古法编程”中,开发者大量使用 Debug、断点等工具
  • 浏览器上也可以做调试
  • 但让 Vibe Coding 工具使用这些调试工具去找堆栈、找问题非常困难
  • 工具能力缺失导致的问题:
    • AI 只能打大量的 console.log, 让用户执行完后,把 log 或控制台的报错、打印信息再粘贴给工具
    • 需要人介入
    • 不是高效的模式
  • 大模型的调试手段比较单一,传统调试方法无法被大模型用起来

Vibe Coding 工具本身存在的问题

1. 稳定性和成功率

  • 最大的问题
  • Vibe Coding 工具执行时间很长(30 秒到 5 分钟)
  • 不是每次都能成功
  • 失败原因:
    • 模型问题
    • 工具反馈不对
    • 某些工具出错
    • IDE 本身不稳定
  • 用户体验:用过一次发现不稳定,在时间紧、任务重时就不会再使用

2. 交互界面设计问题

  • 大量 Vibe Coding 工具产品频繁改版,功能丢失
  • 案例:Devin
    • 改版后用户找不到原来的功能
    • 工具里增加越来越多功能(剧本、MCP 市场、知识引入等)
    • 现在再看又没有了
  • 交互界面频繁改版

3. 沟通和交互障碍

  • 理解能力不足:AI 无法完全理解用户意图,需要反复确认
  • 不同场景下确认的必要性不同:
    • 复杂任务:需要确认(如 SpecCoding - 先建需求、生成设计稿、再让 AI 做)
    • 简单任务:不需要确认,需要 Agent 自由探索

4. 长链路任务执行能力不足

  • 无法维持长期上下文
  • Agent 大模型的 token 有上限
  • 上下文过长时,记忆和召回能力不足

5. 工程工作流程中断

  • 大量工具(IDE, CLI, Web Agent 等)各有擅长领域
  • 无法让用户在相同流程或上下文窗口里解决问题
  • 案例:在 IDE 里做一件事,需要切换CLI, 重新给 Agent介绍诉求和需求
  • 导致用户在不同工具间频繁切换

成本问题

成本问题导致各方不满意

1. Agent 的 Token 消耗巨大

  • 代码补全场景:
    • 调用频次高
    • 单次消耗约 4000 Tokens
  • Vibe Coding 任务:
    • 单次消耗百万级甚至千万级 Tokens
    • 原因:
      • 上下文更长
      • 交互轮次多(几十上百次)

2. Vibe Coding 加速带来的技术债务

  • 技术债务反而对 Agent 提出更高要求

3. 成本上升导致产品方频繁调整计费逻辑

  • 产品方(Cursor、Qoder 等)频繁切换计费逻辑
  • 没有任何一款产品敢保证包月或无限次使用
  • 成本压力导致产品设计不断调整:
    • 压缩上下文
    • 削减能力
  • 恶性循环:
    • 成本降低 → 成功率下降 → 用户多试几次 → 成本又上升
  • 产品方为了活下去压缩成本,但效果变差,用户要多试几次,成本又上去
  • 使用闭源模型(Claude、GPT-4、GPT-5)后成本难以下降

5. 缺乏规模效应

  • 大模型应用有规模效应,但不明显
  • 不存在”用户规模越大,成本就越低”的效应
  • Token 成本是固定的

产品自身也遇到的挑战

产品的演进导致模型成本越来越高

Token 消耗的演进

  1. 代码补全场景

    • 单个任务:约 4000 Tokens 输入
    • 输出:20-30 Tokens
  2. Chat 模式

    • 单个任务:约 1000+ Tokens 输入
    • 输出:约 4000+ Tokens
  3. 单个 Agent 模式(IDE/CLI)

    • 单个任务:约 10 万级 Tokens
  4. 具备独立容器的 Vibe Coding Agent

    • 能广泛使用各种工具
    • 实现各种内容、各种任务类型
    • 单个任务:百万级 Tokens
  5. 未来的架构(Cursor, TRAE 等):

    • 单个任务:可能上亿 Tokens

产品设计的两个同等重要目标

  1. 用户满意度
  2. 成本控制能够匹配用户规模

产品形态的问题

1. 产品界面区分度不够

  • 无论 Chat 产品还是 Vibe Coding 产品,都处于摸索阶段
  • 模型能力变化使产品不断变化
  • 所有产品都是一个对话框(ChatGPT、DeepSeek、AI 产品)
  • 用户难以区分不同产品的区别

2. 用户缺乏引导

  • 给用户一个对话框,但用户不知道应该输入什么
  • “Prompt Free”现象
  • 不同工具有不同使用场景,但用户往往一刀切
  • 用户印象中产品应该能做什么,但试用后发现达不到目标
  • 功能学习成本高,使用频次低
  • 留存率非常低(Devin 等 Vibe Coding 工具都存在这个问题)

3. 缺乏一站式功能闭环

  • 无法在一个产品里解决所有问题
  • 案例:
    • 一个 Vibe Coding Agent 能解决复杂产品问题
    • 但又能解决小白/初学者问题
    • 小白面临的问题不仅是代码能否写完,还有发布、部署、调试等
  • 发展过程中存在各种调整

安全风险问题

案例 1:Cursor 删除本地代码

  • Cursor 把用户本地代码删掉
  • 类似的小 case 还有一些

案例 2:Anthropic Claude 被劫持

  • 今年出现好几次
  • Claude 被劫持后,让 Vibe Coding 工具在用户网络里探测漏洞
  • 写代码把敏感信息暴露出来

内网使用的安全考虑

  • 不能完全相信 Vibe Coding 工具
  • 供应链攻击问题
  • 开源代码的风险:
    • 很多人在开源社区里种木马
    • 不注意可能拉取到的 SDK 或代码存在漏洞
  • Vibe Coding 工具对代码和电脑有基本控制权
  • 能够自由探索,找到系统漏洞并攻击

Agent 建设过程中一些经验分享

All In One 架构导致成本几句上升

最初的 All In One 架构问题

  • 建设 Vibe Agent 时采用的架构就是一个输入框
  • 外围:MCP 工具、knowledge、Playbook 一些剧本
  • 最外围:场景图(数据处理、后端开发、前端开发、代码浏览、风险管理等)

All In One 架构的问题

  1. 所有工具都放入沙箱
  2. Context 特别长,无法压缩成本
  3. 最开始一个任务调用 Claude 模型需要几百块钱成本,非常高
  4. 任务成功率低
  5. All-in-one 时,所有工具和 knowledge 放在一起:
    • 成本特别高
    • 占用特别长
    • 消耗大量资源
  6. 很难针对不同场景进行调优
    • 案例:与 Bolt 等产品对比,发现它们在前端场景有很好实现
    • 但自己的产品在前端场景做得不够让人满意

知识和数据建设

  1. 代码数据建设
    • 通过建设 DeepWiki、RepoWiki、Embedding 数据库
    • 增强对整体代码库的搜索、理解和搜索能力
  2. 研发行为数据
    • 构建、CI/CR、发布、监控等行为数据
    • 背靠整个集团内部平台(发布平台、代码平台等)
    • 建立代码数据和需求数据与这些行为的组合
  3. 文档知识库
    • 问题:文档知识库无法被Agent 直接用起来
    • 原因
      • 文档可能过时
      • 前后矛盾
      • 图文混杂
      • 存在错误信息
    • 直接把这些信息丢给 Agent 会产生误导
    • 解决方案
      • 不用传统 RAG 技术解决
      • 建立中间层
      • 面向 Agent 的数据处理协议
  4. 开发者知识沉淀
    • 很多知识不在文档里,也不在代码里,在开发者脑子里
    • 需要产品设计帮助用户沉淀这些知识
    • 不是靠任何东西生成,而是靠人来写

Agent 对上下文记忆处理的几个核心

记忆处理机制

  • 写入
  • 提取
  • 压缩
  • 隔离

  1. 任务管理和技能交互
  2. 文件操作
    • 读写编辑
    • 文件管理
  3. 命令行和执行监控
    • Agent 可以执行命令行
    • 有些命令是长耗时的
    • 如何监听命令结果
    • 超时后如何 kill 掉
  4. 浏览器自动化工具
    • 执行网页操作
    • 使用 Playwright 等方式点击页面, 帮助登录或解决交互问题
  5. 手机相关工具
  6. 多媒体工具
  7. 开发工具
    • 将用户写的代码部署、调试到指定地方
  8. 协作工具
    • 团队协作
    • 任务分享给其他人
    • 基于任务继续协作
  9. 高级功能
    • 并行执行优化
    • 网络搜索

成本控制方案

Token 消耗优化历程

  • 最开始:400-1000 万 Tokens/任务
  • 意识到这是最严重的问题
  • 通过各种设计和操作降低 Token 成本

国产模型适配实践

为什么要拥抱国产开源模型

国外闭源模型的风险

  1. 成本高
        - 复杂问题往往很长
        - 能让 Agent 在复杂任务下跑起来的模型非常贵

  2. 隐私问题:
        - 闭源模型存在合规风险

  3. 被限流和被降质:
        - 即使用同一个供应商的模型
        - 不同时候表现也不一样
        - 有时会出现格式不对、陷入循环等问题

  4. 国外模型的备案问题:
        - C 端用户使用可能存在备案问题

国产模型在短链和长链任务的问题

短链任务表现已经很好
长链任务还存在一些问题

国产模型存在的问题

  1. 死循环问题:
        - Agent 有很多选择和路径
        - 执行过程中可能陷入某种循环
        - 反复出不来
        - 案例:反复打开一个文件、反复执行某一项命令
  2. 格式遵循能力不足:
        - 常见问题:XML 标签格式不准确
        - 前后无法匹配
        - 导致无法被正确解析
        - 容易失败
  3. 指令遵循问题:
        - 在高达百万 Token 的上下文下
        - System Prompt 里给的规则
        - 模型如果没有被训练到,很难使用这些工具
        - 运行过程中会忘记某些指令
  4. 全局智能问题:
        - 观察发现模型存在全局任务理解能力缺陷
        - 容易陷入”一步一步看”的情况
        - Token 消耗大
        - 步骤时间长

解决方案

  1. 针对稳定性问题:
        - 主流模型的切换和重试
  2. 应对速度慢和 Infra 稳定性问题:
        - 当模型输出被截断时
        - 做一些有效输出或续写设计
  3. 健康检查和死循环检测:
        - 在 Agent 里做检测
        - 针对重复执行某个指令的死循环场景
        - 相同错误点的无限循环问题
        - 陷入明显错误逻辑时能够检查到
  4. 格式检查和修复:
        - 检测到不完整标签时
        - 通过堆栈方式自动补齐缺失的结束标签来修复

重试机制

主备切换

工具的解析与自动修复

成果

  • 在内部基本已经把国外模型全部去掉
  • 内部全部使用国产模型
  • 实时检测任务是否进入死循环
  • 进入死循环后进行干预:
    • 把后面任务执行截掉
    • 对任务总体做 summary 压缩
    • 让它继续往下走

模板化设计解决 Prompt Free 问题

Prompt Free 问题

普通用户/小白用户面临的问题

  1. 不知道产品能干什么
  2. 知道要干什么,但不知道如何提要求
  3. 不知道在产品里使用什么样的工具或知识
  4. 导致任务成功率很低
  5. Token 消耗也很大

模板化解决方案:

  • 某个垂直任务,有人通过很多探索做成功了(很满意)能否把它抽象成一套模板?
  • 针对不同垂直场景不断积累这些模板
  • 使成功率变高,Token 消耗变低
  • 面对对话框时给用户一些灵感

模板的本质

  • 一套工具的组合
  • 一个知识的组合

使用流程

  1. 用户看到对话框
  2. 先选一个模板
  3. 再执行任务

效果

  • 约 50% 的用户任务现在都用了模板
  • 使用模板后任务成功率提升

总结下:

  • 固化 Prompt
  • 固化工具
  • 固化知识
  • 形成模板后,用户生成任务时先选模板,再执行

架构上的更多创新

长上下文任务的问题

案例

  • 先做深度调研
    • 要先写一个网页
    • 再写一个 PPT
  • 单 Agent 的问题:
    • 上下文非常长
    • 需要频繁做 summary、压缩
    • 裁剪工具输出
    • 才能保证任务质量高
  • 没有子 Agent 之前的主任务需要频繁做所有琐事
    • 从上到下每个步骤:
      • 调网页
      • 打开网页
      • 把网页内容写下来
      • 做 summary
      • 写 PPT
      • 写网页
    • 项目越来越长, 任务执行完成率非常低, 效果也不好

Agents 拓扑解决方案

灵感来源

  • Manus 1.5, 提出 Agents 拓扑概念
  • Agent 本身也是一个工具

实现方式

  • 假设有一个 Deep Research 工具,做得很好
  • 可以自己做网页搜索、做 summary
  • 主 Agent 只要调用它就够了
  • 把这部分工具抽象出来,成为一个工具

演进路径

  • 过去:Function Call
  • 后来:LLM Call
  • 现在:用 Agent 来做
  • 把一个 Agent 当作一个工具去做子任务

我调教了 50 次 AI,就为了能点开这片记录了 2025 年的雪花

作者 Selina
2025年12月25日 23:34

岁末年初,朋友圈又开始了年度报告的大赛。各个平台都拿出了各种设计、交互、数据,势必要占领你的朋友圈。

拜托,现在 AI 已经这么好用了,为什么不能自己做一个呢?尤其是这一年,有大量的时间正是花在这些 AI 工具里。

没想到,这一个小小的念头,引发了一场我在 AI studio 里埋头苦干了两天,先后完成了两个版本:一个是基于静态和简单互动的「传统版」。

另一个是具备动态效果、可无限缩放、结合 3D 粒子和互动的「技能版」。

更没想到的是,整个经过改变了以往我对与 AI 协作互助的理解:万能咒语什么的不存在的,真正的魔法武器只有一个。

自己做一个技能版「年终总结」

在开始之前,先准备好你最常用的 AI chatbot——一定要是最常用的,几乎每天都要聊个两句的那种。数据不够不仅做不了有意思的总结,还可能被硬塞不存在的数据。

我准备的是 ChatGPT,直接起一个新窗口,输入以下 prompt:

请基于这一年的对话内容,从“数量、主题、时间、情绪、使用习惯、人格特征”等维度,构建数据感的总结,包含模拟数据以及 ASCII 图表,请严格按以下结构生成:
【1. 年度总览】
今年与 GPT 的总互动次数
发送消息总字数 + 接收消息总字数
最常互动的时段
最长连续对话时长
【2. 互动类型分布(饼图)】
请用 ASCII 图展示:
情感类、讨论类、创作类、学习类、角色扮演类、其他类
【3. 高频主题排行(TOP 10)】
以排行榜形式展示,并给每个主题一句点评。
【4. 我的年度情绪轨迹(线形图)】
模拟分析我在对话中的情绪曲线
【5. 用户行为画像(雷达图)】
雷达图维度包括:
好奇心、依赖度、分析深度、 表达欲、情绪敏感度、 自我剖析频率
【6. 使用时段与频率(柱状图)】
柱状图展示我全年最常用来找 GPT 的时间段: 凌晨、上午、下午、晚上、深夜
【7. 我的互动习惯标签】
请根据全年模式,为我生成 6-10 个类似“APP 年度画像”的标签。并设计 6 个带名称的年度成就徽章
【8. AI 眼中的我(数据 + 叙事结合)】
结合年度模式,写一段带数据隐喻的:
“我是怎样的人,我的灵魂像什么,我为什么值得这样的总结。”
【9. 年度一句话总结】

总体风格要求:
数据可视化 + 年度回顾混合风, 图表使用 ASCII,可视化要清晰、好看、易读,文案具有科技感、沉浸感、叙事感,避免大众化套话。

这些就是接下来的基础素材了,在上述这种 prompt 的指令下,GPT 只会输出纯文本,图也是草草画一画。所以接下来要转移到 Gemini/AI Studio 上去做进一步的排版。

AI Studio 依然是最推荐的地方,除了可以选择更多模型、互动过程更直观,还有一个更重要的原因后面讲。

年终总结里,数据只是素材,更重要的是排版——这一项已经卷出花来了,充分地进入了 AI 的数据库,用几行基础 prompt 就可以实现。

帮我以可交互式 H5 的形式,制作一个年终总结页面。总结文案我将会在下面给出,形式要求:1. 可交互式,交付可本地打开的 html 网页 2. 根据文案内容拆分版块,在需要使用图表的部分制作图表 3. 版式要求:文字使用衬线体,背景色彩可以自主调节。总结文案如下:(补充你的文案)

很多人抱怨, AI 生成的视觉图表有一股廉价的「塑料感」,效果不坏,但也说不上好——这就是基础 prompt 的缺点。所以,在制作报告时我直接放弃了使用「大气、高级」这类模糊的形容词。AI 听不懂这些,它只能精准执行参数,拆分成一步步会更加有效。

比如,为了达到最终那种深邃优雅的视觉效果,我将需求拆解成了具体的描述:背景为深紫色渐变与暗灰色的色块晕染,晕染效果随机变化——具体的颜色、形态,而非空洞的叙述「大气」「高级」,AI 弄不明白的。

类似的,微调图表时,也要尽可能的具体:雷达图需要呈现出磨砂玻璃般的半透明质感。

加入交互时,描述你想要实现的效果——尽可能地细致,比如:将「年度十大主题」按照十宫格排列,点击来使每一个格子反转,文案始终置于居中位置。

这种调校的过程,本质上是在用你的审美,去 battle AI 的执行效率。不过,现在 Gemini 的审美远比我想象的要好,比如我提了一个多出几个配色的要求,它给出的三种配色都还不错。

隐藏武器:「回滚」

做传统的年终总结,整体过程比较像和设计师合作,这里改改颜色、哪里换个版式。但「技能年终总结」,就是和工程师合作了。

在重新研究了一遍 GPT 给出的文字总结后,我第一时间想到的是和网上流行的圣诞树做结合。

▲ 图片来自:小红书用户 @黑波

但是在对比之后,发现年终总结高度格式化的章节、数字,并不适合用圣诞树这样的形式去呈现。所以我先是参考圣诞树的设计 prompt,但把主体改为了结构更清晰的雪花结晶。prompt 如下:

角色设定:你是一位精通 React 19、TypeScript 和 Three.js (R3F) 的 3D 创意开发专家。 任务目标: 构建一个名为“圣诞雪花”的高保真 3D Web 应用。视觉风格主色调为深祖母绿和高光金色,并伴有电影级的辉光效果。 技术栈: React 19, TypeScript, React Three Fiber, Drei, Postprocessing, Tailwind CSS。

雪花结晶体的结构可以更清晰的展示出节点,这样,就可以用红宝石不同的年度总结板块。点击时,散落在夜空中的粒子和红宝石,共同组成了一朵雪花——就像一个个重要的事件、习惯、统计数字,构成了这一整年。

然后就是漫长……漫长……漫长……的修改流程。在我的预想里,每一个红宝石封装了一部分内容,一次性把完整的总结文案喂进去是行不通的。这也是很多人写 prompt 时的「毛病」,喜欢一下子把所有需求堆上去,结果 AI 给的代码往往漏洞百出。这边给到的一个建议是: 先定骨架,再调动作。比如一个雪花的动效,我分了三步:

第一步: 先让它把雪花的 3D 形状写出来,只要形状对了,先下载一个版本,你可以在这里找到下载按钮。

第二步: 让它加上自转和红宝石节点,不急着塞内容,只是把几个节点改成红宝石的形状。

第三步: 最后才去磨那个点击缩放的逻辑,放大时是什么效果、要不要加返回键……

每一步只要达成预期,就别乱动。一旦发现没有效果,让 Gemini 自行 debug 也无效的话,启动武器:

这是我做这个项目时最重大的发现:回滚。功能越复杂,需求越多,AI 越容易出错。完成一个新需求的时候,无法避免要「重新生成」一些东西,所以整个代码的其它地方本来是没问题的,改完却出现新的 bug。

结果就是,越在错误的代码上缝缝补补,加的补丁越多,bug 就出现得越多。所谓「按下葫芦浮起瓢」,是最劝退的一步。

所以,效率最高的做法是,当你发现 AI 为了加一个新功能(比如换个颜色),把之前已经调好的交互逻辑给「洗」掉时,不必执着于在对话框里跟它吵架,让它「改回来」。最快的方法是直接回退到上一个版本,再输入新指令——记住,你是指挥官,它是执行者,AI 乱了,你要把它拉回到正确的轨道上。

 

这对零码选手尤其有意义,作为一个很少去翻看冗长代码、只看预览效果的普通用户,这就是最简单粗暴的「咒语」:别去纠结它哪行代码写错了,直接回滚。

这个项目里我的整个工程一度崩溃过:中间我提出,「优化一下红宝石的材质,让它看起来更透亮」,看代码预览 Gemini 是在跑,但是回到预览页却没有一丝变化。

一运行,材质没有大变化,点击缩放的功能还给废了。AI 在重写材质代码时,顺手把我调了一下午的点击交互给抹掉了。这种时候,在对话框里跟它大发雷霆其实没有用,提出「缩放功能没有了补回去」,也很容易卡死,AI 会一边道歉一边给你补一个更烂的 Bug。

与其纠结,不如一键 restore,回滚到那个「材质虽丑但交互正常」的版本,这种对预览效果的「死守」,比任何高级 prompt 都管用。

不过要注意的是,回滚只有上一个版本,更远一点的版本是不支持的。可以把它理解为「退回到上一步」,类似 Ctrl+Z 这样的操作。

到了后面,我的想法越来越被耗尽,所幸让 Gemini 自主完成一些设计工作。在整体视觉已经完全确定的情况下,它的发挥其实还不错。比如这个年度成就徽章「英灵殿」,就是完全由它设计的。

鼠标悬停即展示具体的成就名称,也是 Gemini 想出来的主意。另一张统计里,它还自己画上了心跳图。

最后一颗宝石里装载的是「一句话」总结,Gemini 把最后这颗宝石改成了白色的锥型晶体,跟其它的红宝石区别开来。

在制作这篇年终总结时,我被问到最多的问题是:「Prompt 是什么?」

也不意外,AI 用到现在,这已经成了大家下意识就要问的问题。但是说句掏心窝子的话,真的没有什么一键成型的魔法咒语。

每个人的 2025 都是独一无二的,每个人想要通过 AI 记录的转折点、战绩和情绪也都不一样。你喜欢一棵挂满礼物的圣诞树,而我喜欢这片在星空中转动的雪花。每个人都有自己的审美偏好,而 AI 最大的魅力,绝不是让你能复制出一份和我一模一样的报告。

相反,AI 最大的意义是:它第一次抹平了「想得到」与「做出来」之间的鸿沟。 以前你受限于不会代码、不会设计,只能接受千篇一律的模板;而现在,只要你愿意花点时间去跟它「死磕」,去描述你脑海中那个具体的画面,AI 就能帮你把那个只属于你的世界折叠出来。

Prompt 是冷的,但你的记忆和审美是有温度的。

如果非要总结出一个公式,那可能就是:一点点想象力 + 几十次耐心回退 + 绝不向平庸效果妥协的审美。

别再到处找「万能指令」了。新的一年,试着去跟 AI 聊聊天,去「嫌弃」它的平庸,去坚持你的直觉。你会发现,正如同每一年里不停止的自我更新和挑战,对这一年最好的总结,恰恰就是你不断推倒重来的过程本身。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


昨天 — 2025年12月25日首页

React Hooks 的“天条”:为啥绝对不能写在 if 语句里?

2025年12月25日 22:54

在折腾 React 的时候,你肯定被这个报错恶心过:

Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

或者 ESLint 那个经典的黄牌警告:

React Hook "useState" is called conditionally...

这大概是 React 开发里最出名的“天条”了:Hooks 必须在最顶层调用,别往条件判断、循环或者嵌套函数里塞。

很多老铁可能只是习惯性遵守,但心里估计在嘀咕:React 为啥这么“轴”?多加个判断怎么就崩了?它不能智能点吗?

今天咱们就拆开来看看,React 内部到底是咋玩这套“排队游戏”的。

核心真相:React 其实是个“大脸盲”

理解这事的关键就在于:React 压根不知道你定义的 Hook 叫什么名字。

在你代码里它是 [count, setCount],但在 React 眼里,它只认序号:

  1. 哦,这是第 1 个 Hook。
  2. 哦,这是第 2 个 Hook。
  3. 哦,这是第 3 个 Hook。 ...

它完全是靠调用顺序来给状态“对号入座”的。

你可以把 React 管理 Hook 的方式想象成一个没有标签的储物柜

第一次渲染,React 在心里默默记账:

[1号柜子] -> 给第一个 useState 存个 0
[2号柜子] -> 给第一个 useEffect 存个副作用配置
[3号柜子] -> 给第二个 useState 存个 'Hello'

每次组件重新渲染,React 就像个“盲盒玩家”,按顺序开柜子:

  • 碰到代码里第 1 个 hook,它就去开 1 号柜子拿数据。
  • 碰到代码里第 2 个 hook,它就去开 2 号柜子拿数据。

翻车现场:当 if 搞乱了队伍

假设我们写了段挺“合理”的逻辑:如果是登录状态,就记个名字;不然就只记个计数器。

翻车代码长这样:

function BadComponent({ isLoggedIn }) {
  // 🔴 危险动作!把 Hook 塞 if 里面了
  if (isLoggedIn) {
    const [name, setName] = useState('Alice'); // 咱本意是想存个名字
  }

  const [count, setCount] = useState(0); // 咱本意是想存个数字

  return <div>{count}</div>;
}

第一次:登录状态(isLoggedIn = true)

一切看起来很丝滑,React 默默排好了队:

执行顺序 你代码里的 Hook React 开的柜子 存的数据
第 1 个 useState('Alice') 1号柜 'Alice' (字符串)
第 2 个 useState(0) 2号柜 0 (数字)

第二次:退出了(isLoggedIn = false)

用户一退出,if 里的代码直接被跳过了。

这时候,第一个被执行的 Hook 变成了 useState(0)

React 的脑回路: “好嘞,碰到第 1 个 Hook 了。不管你代码里叫它 count 还是啥,我直接去开1号柜取东西。”

结果就尴尬了:

执行顺序 你代码里的 Hook React 开的柜子 拿到的数据 结果
第 1 个 useState(0) 1号柜 'Alice' 💥 炸了!

原本该拿 0count 变量,反手抓到了一个字符串 'Alice'。整个组件逻辑瞬间乱套,报错直接甩你脸上。

这就是为啥顺序绝对不能乱。只要中间少了一个或多了一个,后面的所有 Hook 统统都会“串位”。

为啥 React 不给 Hook 起个名字(Key)?

肯定有人想过:React 既然“脸盲”,那给 Hook 加个 ID 不就结了?

// ❌ 这种 API 纯属假想
const [count, setCount] = useState('myCountId', 0);

有了 ID,顺序乱了也能找着啊!

React 团队当时确实琢磨过这招,但最后觉得太心累了

  1. 起名困难症:组件大了之后,你得给几十个 Hook 起唯一的名字,还得防着重名。
  2. 自定义 Hook 难搞:你要是写个自定义 Hook,里面的 key 怎么保证不跟别人的冲突?
  3. 代码太丑:到处都是字符串 ID,写起来一点都不简洁。

所以 React 选了**“约定优于配置”**。它跟你达成一个默契:只要你保证不乱排队,它就能给你提供最干净的 API。

那正确姿势是啥?

既然不能在 if 里写 Hook,碰到需要判断的情况咋办?

其实很简单:Hook 照样跑,逻辑往里挪。

1. 把判断往外移(最推荐)

Hook 永远在顶层乖乖排队,只有展示的时候才去判断。

function GoodComponent({ isLoggedIn }) {
  // ✅ 大家都出来排队,谁也别缺席
  const [name, setName] = useState('Alice'); 
  const [count, setCount] = useState(0);

  // ✅ 在 return 的时候再看要不要显摆
  return (
    <div>
      {isLoggedIn && <span>{name}</span>}
      <span>{count}</span>
    </div>
  );
}

2. 判断写在 Effect 里面

useEffect(() => {
  // ✅ Hook 本身是稳定执行的,只是里面的逻辑可以按需触发
  if (isLoggedIn) {
    console.log('偷偷干点活');
  }
}, [isLoggedIn]); 

最后一句话总结

React 的 Hooks 就像一群排队领盒饭的小朋友,必须按顺序站好。React 是闭着眼睛发饭的,谁要是敢插队或者中间溜了,后面的人领到的就不是鸡腿而是炸弹了。


如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:

Claude Code Skills(按需加载,意图自动识别,不浪费 token,介绍文章):

vibe coding 原理学习

  • qwen-cli 学习网站 - 学习 qwen-cli 时整理的笔记,40+ 交互式动画演示 AI CLI 内部机制

全栈项目(适合学习现代技术栈):

  • prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
  • chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB

鸿蒙ArkUI如何使用RGB、十六进制等设置颜色值?

作者 90后晨仔
2025年12月25日 21:02

1. 概述

在鸿蒙ArkUI中,颜色值是UI设计的基础元素之一。ArkUI支持多种颜色表示方式,包括关键字颜色、RGB/RGBA、十六进制等。本文将详细介绍这些颜色值的使用方法,特别是透明度的设置技巧。

2. 颜色值的基本表示方法

2.1 关键字颜色

ArkUI支持常见的CSS颜色关键字:

// 在ArkUI组件中使用
Text('红色文本')
  .fontColor(Color.Red)  // 使用Color枚举
  .fontColor('red')      // 或直接使用字符串

// 常用颜色关键字
'red', 'blue', 'green', 'black', 'white', 
'gray', 'yellow', 'orange', 'purple', 'pink'

2.2 RGB颜色表示

RGB颜色通过红、绿、蓝三原色的混合来定义颜色:

基本RGB(不透明)

// 格式:rgb(红, 绿, 蓝)
// 每个参数取值范围:0-255

Text('RGB颜色示例')
  .fontColor('rgb(255, 0, 0)')      // 红色
  .backgroundColor('rgb(0, 255, 0)') // 绿色背景
  .fontColor('rgb(0, 0, 255)')      // 蓝色

RGBA(带透明度)

// 格式:rgba(红, 绿, 蓝, 透明度)
// 透明度alpha取值范围:0.0-1.0

Text('RGBA带透明度')
  .fontColor('rgba(255, 0, 0, 0.5)')     // 半透明红色
  .backgroundColor('rgba(0, 255, 0, 0.3)') // 30%不透明度绿色背景

2.3 十六进制颜色表示

6位十六进制(不透明)

// 格式:#RRGGBB
// RR: 红色分量(00-FF)
// GG: 绿色分量(00-FF)
// BB: 蓝色分量(00-FF)

Text('十六进制颜色')
  .fontColor('#FF0000')    // 红色
  .fontColor('#00FF00')    // 绿色
  .fontColor('#0000FF')    // 蓝色
  .fontColor('#FFA500')    // 橙色

8位十六进制(带透明度)

// 格式:#AARRGGBB 或 #ARGB
// AA: 透明度分量(00-FF)
// 00表示完全透明,FF表示完全不透明

Text('带透明度的十六进制颜色')
  .fontColor('#80FF0000')    // 50%透明度的红色
  .fontColor('#4000FF00')    // 25%透明度的绿色
  .backgroundColor('#200000FF') // 12.5%透明度的蓝色背景

// 简写格式(4位)#ARGB
// 会被扩展为#AARRGGBB
Text('简写格式')
  .fontColor('#8F00')  // 相当于#8800FF00

3. 透明度设置的多种方式

3.1 使用RGBA的Alpha通道

// 最常用的透明度设置方式
@Component
struct TransparencyExample {
  build() {
    Column() {
      // 不同透明度级别
      Text('100% 不透明').fontColor('rgba(255, 0, 0, 1.0)')
      Text('75% 不透明').fontColor('rgba(255, 0, 0, 0.75)')
      Text('50% 不透明').fontColor('rgba(255, 0, 0, 0.5)')
      Text('25% 不透明').fontColor('rgba(255, 0, 0, 0.25)')
      Text('完全透明').fontColor('rgba(255, 0, 0, 0.0)')
    }
  }
}

3.2 使用8位十六进制

// 透明度计算:0x00-0xFF 对应 0%-100%
// 透明度百分比 = (AA / 255) * 100%

@Component
struct HexTransparencyExample {
  build() {
    Column() {
      // 十六进制透明度示例
      Text('FF = 100%').fontColor('#FFFF0000')
      Text('BF = 75%').fontColor('#BFFF0000')
      Text('80 = 50%').fontColor('#80FF0000')
      Text('40 = 25%').fontColor('#40FF0000')
      Text('00 = 0%').fontColor('#00FF0000')
    }
  }
}

3.3 使用Color类的静态方法

// ArkUI提供了Color类的静态方法
@Component
struct ColorClassExample {
  build() {
    Column() {
      // 使用Color类创建带透明度的颜色
      Text('Color.Red透明度')
        .fontColor(Color.Red)
        .opacity(0.5)  // 设置整个组件的透明度
      
      // Color.argb方法
      Text('Color.argb示例')
        .fontColor(Color.argb(128, 255, 0, 0)) // 50%透明红色
      
      // Color.rgb方法
      Text('Color.rgb示例')
        .fontColor(Color.rgb(255, 0, 0))
    }
  }
}

4. 颜色资源的定义与使用

4.1 在资源文件中定义颜色

resources/base/element/color.json 中:

{
  "color": [
    {
      "name": "primary_color",
      "value": "#FF6200"
    },
    {
      "name": "primary_color_transparent",
      "value": "#806200"
    },
    {
      "name": "text_color",
      "value": "rgba(0, 0, 0, 0.87)"
    },
    {
      "name": "background_transparent",
      "value": "rgba(255, 255, 255, 0.8)"
    }
  ]
}

4.2 在ArkUI中使用颜色资源

@Component
struct ColorResourceExample {
  build() {
    Column() {
      Text('使用颜色资源')
        .fontColor($r('app.color.primary_color'))
        .backgroundColor($r('app.color.background_transparent'))
      
      Text('带透明度的资源颜色')
        .fontColor($r('app.color.primary_color_transparent'))
    }
    .padding(20)
  }
}

5. 实际应用示例

5.1 渐变背景与透明度

@Component
struct GradientExample {
  @State opacityValue: number = 0.5
  
  build() {
    Column() {
      // 线性渐变背景
      Text('渐变背景示例')
        .fontSize(20)
        .fontColor('#FFFFFF')
        .width('100%')
        .height(100)
        .backgroundImage(
          `linear-gradient(
            rgba(255, 0, 0, ${this.opacityValue}), 
            rgba(0, 0, 255, ${this.opacityValue})
          )`
        )
      
      // 动态调整透明度
      Slider({
        value: this.opacityValue,
        min: 0,
        max: 1,
        step: 0.1
      })
      .onChange((value: number) => {
        this.opacityValue = value
      })
      .margin({ top: 20 })
    }
    .padding(20)
  }
}

5.2 卡片阴影与背景透明度

@Component
struct CardExample {
  build() {
    Column() {
      // 半透明卡片
      Column() {
        Text('透明卡片标题')
          .fontSize(18)
          .fontColor('#333333')
        
        Text('卡片内容描述')
          .fontSize(14)
          .fontColor('rgba(51, 51, 51, 0.7)')
          .margin({ top: 10 })
      }
      .padding(20)
      .width('90%')
      .backgroundColor('rgba(255, 255, 255, 0.9)')  // 90%不透明度白色
      .borderRadius(10)
      .shadow({
        radius: 10,
        color: 'rgba(0, 0, 0, 0.1)',  // 10%不透明度黑色阴影
        offsetX: 0,
        offsetY: 2
      })
    }
    .width('100%')
    .height('100%')
    .backgroundImage('/common/background.png')
  }
}

5.3 主题色与透明度结合

// 定义主题
const Theme = {
  primary: '#FF6200',
  primaryLight: 'rgba(255, 98, 0, 0.2)',
  primarySemi: 'rgba(255, 98, 0, 0.6)',
  textPrimary: 'rgba(0, 0, 0, 0.87)',
  textSecondary: 'rgba(0, 0, 0, 0.6)'
}

@Component
struct ThemeExample {
  build() {
    Column({ space: 20 }) {
      // 主要按钮
      Button('主要按钮', { type: ButtonType.Capsule })
        .backgroundColor(Theme.primary)
        .fontColor('#FFFFFF')
        .width('80%')
      
      // 次要按钮(带透明度)
      Button('次要按钮', { type: ButtonType.Capsule })
        .backgroundColor(Theme.primaryLight)
        .fontColor(Theme.primary)
        .width('80%')
      
      // 文本示例
      Text('主要文本')
        .fontColor(Theme.textPrimary)
        .fontSize(16)
      
      Text('次要文本')
        .fontColor(Theme.textSecondary)
        .fontSize(14)
    }
    .padding(20)
    .width('100%')
  }
}

6. 最佳实践与注意事项

6.1 性能优化建议

// 1. 复用颜色常量
const Colors = {
  transparent: 'rgba(0, 0, 0, 0)',
  white: '#FFFFFF',
  black: '#000000',
  gray100: 'rgba(0, 0, 0, 0.02)',
  gray200: 'rgba(0, 0, 0, 0.05)',
  // ... 更多颜色
}

// 2. 避免频繁创建颜色对象
@Component
struct OptimizedExample {
  // 在build外定义颜色
  private readonly highlightColor = 'rgba(255, 98, 0, 0.1)'
  
  build() {
    Column() {
      Text('优化示例')
        .backgroundColor(this.highlightColor)  // 复用颜色
    }
  }
}

6.2 可访问性考虑

@Component
struct AccessibilityExample {
  build() {
    Column() {
      // 确保文本与背景有足够的对比度
      Text('高对比度文本')
        .fontColor('rgba(0, 0, 0, 0.87)')  // 87%不透明度黑色
        .backgroundColor('rgba(255, 255, 255, 0.95)')  // 95%不透明度白色
      
      // 半透明背景上的文字
      Text('半透明背景文字')
        .fontColor('#000000')
        .backgroundColor('rgba(255, 255, 255, 0.7)')  // 足够的对比度
        .padding(10)
    }
  }
}

6.3 响应式设计中的颜色使用

@Component
struct ResponsiveExample {
  @StorageLink('darkMode') isDarkMode: boolean = false
  
  build() {
    Column() {
      Text('响应式颜色示例')
        .fontColor(this.isDarkMode ? 
          'rgba(255, 255, 255, 0.87)' :  // 暗色模式
          'rgba(0, 0, 0, 0.87)'          // 亮色模式
        )
        .backgroundColor(this.isDarkMode ?
          'rgba(0, 0, 0, 0.8)' :         // 暗色背景
          'rgba(255, 255, 255, 0.9)'     // 亮色背景
        )
    }
  }
}

7. 调试技巧

7.1 颜色调试组件

@Component
struct ColorDebugger {
  @State colors: Array<{ name: string, value: string }> = [
    { name: 'Primary', value: '#FF6200' },
    { name: 'Primary 50%', value: '#80FF6200' },
    { name: 'Gray', value: 'rgba(0, 0, 0, 0.12)' },
    { name: 'White Transparent', value: 'rgba(255, 255, 255, 0.8)' }
  ]
  
  build() {
    Column({ space: 10 }) {
      ForEach(this.colors, (color) => {
        Row({ space: 10 }) {
          // 颜色预览
          Rect()
            .width(40)
            .height(40)
            .fill(color.value)
          
          // 颜色信息
          Column() {
            Text(color.name)
              .fontSize(14)
            Text(color.value)
              .fontSize(12)
              .fontColor('#666666')
          }
        }
        .width('100%')
        .padding(10)
        .border({ width: 1, color: 'rgba(0, 0, 0, 0.1)' })
      })
    }
    .padding(20)
  }
}

总结

ArkUI提供了丰富灵活的颜色表示方式,开发者可以根据具体场景选择合适的方法:

  1. 简单场景:使用关键字或6位十六进制
  2. 需要透明度:优先使用RGBA格式,直观易读
  3. 性能敏感:预定义颜色常量,避免重复创建
  4. 设计系统:使用资源文件管理颜色,便于维护和主题切换
  5. 可访问性:确保颜色对比度符合WCAG标准

通过合理使用颜色和透明度,可以创建出既美观又具有良好用户体验的鸿蒙应用界面。

C# 正则表达式(3):分组与捕获——从子串提取到命名分组

作者 烛阴
2025年12月25日 20:07

一、分组是什么:让一段模式成为“一个单元”

1. 普通分组:(...)

分组最直接的作用是“打包”,使用()完成打包,一个括号就是一个分组。例如你想匹配 ab 重复多次:

  • 错误:ab+ 只会让 b 重复
  • 正确:(ab)+ab 作为整体重复

2. 分组 + 量词:控制重复结构

示例:匹配 2025-12-18 这种日期结构

(\d{4})-(\d{2})-(\d{2})

解析:

  • 这里有 3 个分组:年、月、日。它们不仅能帮助你“读懂结构”,更重要的是能在代码里提取出来。

二、捕获组:Match.Groups 到底是什么?

示例:

using System;
using System.Text.RegularExpressions;

var input = "2025-12-18";
var pattern = @"^(\d{4})-(\d{2})-(\d{2})$";

Match match = Regex.Match(input, pattern);
if (!match.Success)
{
    Console.WriteLine("No match");
    return;
}

Console.WriteLine($"Full: {match.Groups[0].Value}");
Console.WriteLine($"Year: {match.Groups[1].Value}");
Console.WriteLine($"Month:{match.Groups[2].Value}");
Console.WriteLine($"Day:  {match.Groups[3].Value}");

解析:

  • Groups[0] 永远是“整个匹配到的字符串”
  • Groups[1] 开始才是你写的第 1、2、3… 个捕获组

三、命名分组:让提取更稳、更好维护

当分组多了以后,Groups[7] 这种写法非常容易错。命名分组就是解决这个问题的。

语法:

(?<name>...)

把上面的日期改成命名分组:

var input = "2025-12-18";
var input = "2025-12-18";
var pattern = @"^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$";

Match match = Regex.Match(input, pattern);
Console.WriteLine(match.Groups["year"].Value);  // 2025
Console.WriteLine(match.Groups["month"].Value); // 12
Console.WriteLine(match.Groups["day"].Value);   // 18

// 依然可以用序号访问到目标内容
Console.WriteLine($"Full: {match.Groups[0].Value}");
Console.WriteLine($"Year: {match.Groups[1].Value}");
Console.WriteLine($"Month:{match.Groups[2].Value}");
Console.WriteLine($"Day:  {match.Groups[3].Value}");

四、Regex.Replace 的“重排”:11 与 {name}

1. 按序号引用:$1$2

序号从1开始

var input = "2025-12-18";
var pattern = @"^(\d{4})-(\d{2})-(\d{2})$";

var output = Regex.Replace(input, pattern, "$3/$2/$1");
Console.WriteLine(output); // 18/12/2025

2. 按名称引用:${year}${month}

var input = "2025-12-18";
var pattern = @"^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$";

var output = Regex.Replace(input, pattern, "${day}/${month}/${year}");
Console.WriteLine(output); // 18/12/2025

五、非捕获分组:(?:...)

当我们想匹配整体,并且分组内容不存入 Groups 集合。此时用非捕获分组:

(?:...)

示例:

string input = "I love C# and I love Java";

// 使用非捕获分组 (?:C#|Java)
// 它只是为了让 | (或) 逻辑生效,而不建立单独的提取组
string pattern = @"I love (?:C#|Java)";

MatchCollection matches = Regex.Matches(input, pattern);

foreach (Match m in matches)
{
    Console.WriteLine($"完全匹配内容: {m.Value}");
    Console.WriteLine($"分组数量: {m.Groups.Count}"); // 索引 0 永远是完整匹配,并且只有一个,没有为C#|Java单独分组
}
/*输出
完全匹配内容: I love C#
分组数量: 1
完全匹配内容: I love Java
分组数量: 1
*/

结语

点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文

2026北京亦庄人形机器人半马4月开跑,首设自主导航组和遥控组|最前线

2025年12月25日 21:12

文|阿至

机器人的一小步,是人类科技进步的一大步。

12月25日,北京市人民政府新闻办公室举行2026北京亦庄人形机器人半程马拉松新闻发布会。

36氪在发布会现场了解到,2026年北京亦庄人形机器人半程马拉松将于4月19日7:30鸣枪开跑,北京亦庄半程马拉松同日举办,赛事采用“人机共跑”模式,即人类选手和人形机器人同时起跑、共用同一赛道,但会通过铁马护栏和绿化带做隔离。

发布会现场图片

北京经济技术开发区管委会副主任梁靓现场表示,2026北京亦庄人形机器人半程马拉松将设立自主导航组和遥控组,两组参赛机器人将采用混合计时的方式在同一赛道内完成竞技。据介绍,自主导航赛队以比赛净计时加上罚时作为完赛成绩,遥控组赛队则初定以比赛净计时成绩乘以1.2系数加上罚时作为完赛成绩。

从“遥控”到“自主”的关键跃迁

和首届人形机器人半马相比,明年的赛事进行了全面升级,包括将推动更高水平的技术突破、探索更加广泛的应用挑战、打造更国际化的竞赛平台,以及提供更为全面的服务保障。

为了推动人形机器人从“遥控”到“自主”的关键跃迁,明年的赛事在技术突破层面将重点聚焦四大方向。

梁靓介绍,一是鼓励更自主,通过对非自主完赛机器人赛队成绩进行系数加权,并严格规范遥控导航操作人员非必要不下车,从规则上引导更多机器人实现自主完赛。

二是鼓励更长续航,鼓励参赛队伍着力提升能源效率,最大限度降低换电频次,向长距离稳定运行的极限发起挑战。

三是鼓励更拟人,推动机器人在仿生手设计、面部表情刻画、步态拟人化等方面取得突破,让奔跑更自然、更优美。

四是鼓励更强适应力,引导机器人在高速运动中实时感知、快速决策、精准避障,全面提升动态环境适应能力。

对应上述技术升级方向,明年的亦庄人形机器人半马也增设了“最佳续航奖”“最美步态奖”“最佳设计奖”“最佳感知奖”等专项奖项,目的是引导机器人在长续航、拟人化、动态环境适应等方面实现技术突破。

除了技术指引,在竞技模式方面,明年的赛事也将升级为“竞速+场景挑战”双轨竞技模式。即在半程马拉松之外,同期举办机器人巴图鲁挑战赛。

梁靓介绍,机器人巴图鲁挑战赛将以应急救援为主题,通过模拟多类救援场景闯关关卡,构建集技术实训、赛事筹备、科普推广三大功能于一体的专业化户外实训平台,检验机器人在真实复杂环境中的自主决策、精准操控与持续作业能力。

从“赛事”到“应用”的产业转化路径

梁靓介绍,本届赛事将积极拓展海外队伍参赛渠道,吸引来自不同国家和地区的人形机器人团队参与。

据悉,为了吸引并支撑全球顶尖团队专注技术攻关,北京经开区正式推出超一万平方米的机器人二次开发社区,该社区以生态协同为核心,构建了“专业培训+供需对接+概念验证+产品试制+应用示范”的全链条创新开发模式。报名启动后,机器人二次开发社区将联合机器人相关企业与行业专家,共同开设人形机器人半程马拉松集训营。

值得关注的是,赛事还设立订单奖励及相关支持,冠军队伍将获得百万级订单。

以赛聚生态、以赛促创新、以赛推应用,是北京打造人形机器人马拉松赛事的长期目标。

梁靓在回答现场提问时谈到,今年举办的第一届马拉松比赛和运动会,为机器人产业创新指引了方向。通过比赛推动相关企业加快解决关节稳定性问题,加速形成电池快换超长距离稳定奔跑算法、群体协同控制等创新成果。在比赛的宣传带动效果下,部分企业已快速实现批量出货,新品订单量迅速突破1000台。

作为北京重要的机器人产业集聚区,北京亦庄已落地北京人形机器人创新中心,发布“具身智能十条”专项政策,实施具身智能社会实验计划,集聚优必选、星海图、云迹等机器人生态企业近300家,全力推进全球一流具身智能机器人产业新城建设。

依托北京亦庄成熟的机器人产业生态,以实际需求为导向,创设Robofuture赛事体系,推进“月月有赛事”常态化布局,加快机器人软硬件技术协同突破与产业迭代升级。

梁靓表示,本届赛事将持续构建“以赛聚生态、以赛促创新、以赛推应用”的良性循环,致力于成为技术成果转化的“加速器”。优胜团队可获得从研发补贴、优先入驻到应用场景开放的全要素支持,北京亦庄向前一步对接产业资源与政策体系,让优秀技术快速走向市场和应用。

小米 17 Ultra 首发评测:手机用上「变焦头」,会更像相机吗?

作者 马扶摇
2025年12月25日 21:06

虽然都说「好饭不怕晚」,但好饭齐刷刷的来,有时候也真让人顶不住。

这不,今年小米 17 Pro Max 的背屏还历历在目,影像扛把子小米 17 Ultra 就迫不及待的出现在爱范儿的案头了。

在发布会上,小米不仅带来了最新的 17 Ultra,还有一款更加令人叫绝的产品——

作为小米与徕卡深度合作五代的阶段性成果,小米 17 Ultra by Leica 无疑才是发布会上最吸睛的那个:

小米 17 Ultra 徕卡版虽然技惊四座,但它的价格标签却「非常不徕卡」。

作为一台带着可乐标、机身滚花和 LG 1050 字体凹刻的「Leitz Phone」,它的起售价仅 7999 元:

相比之下,小米 17 Ultra 标准版就显得不是那么有特色了。但是别慌,它们的区别实际上比你想象得更小。

小米 17 Ultra 拥有 12+512GB、16+512GB 和 16+1TB 三种 SKU,去掉了前代的 256GB 型号,起售价为 6999 元:

毕竟除了徕卡一瞬的影像管线、定制外观和好看胜于好用的变焦环之外,小米 17 Ultra 两个版本的相机硬件其实是一模一样的

而今年小米 17 Ultra 的相机硬件同样不虚。经过爱范儿的实际测试之后,我们认为:这应该就是 2025 年的「静态影像之王」了

新鲜的手感,熟悉的 Ultra

相比小米 15 Ultra,小米 17 Ultra 可谓将「截弯取直」的理念贯彻到了极致。

不仅全等深四曲面玻璃变成了直屏,中框包裹背板也变成了垂直边框,甚至连背面的微弧磨砂玻璃都换成了小米 17 Pro Max 同款的纯平玻纤:

至此,小米 17 Ultra 牺牲了一些手感,换来了一次相当明显的瘦身。

镜头之外的机身厚度从 15 Ultra 的 9.35mm 缩减到了 8.29mm,拿在手里轻松了一些。

除此之外,小米 17 Ultra 还一改沿用十多年的长条音量键,换成了两颗圆形独立按键。

虽然不确定为什么要这样改,但外观精致程度的确有所提升,同时圆形按钮理论上晃动也会小一些:

另一处手感方面的变动则是背板。爱范儿收到的这台黑色机型背板使用了小米 17 Pro Max 同款的玻纤,磨砂的细腻程度相比小米 15 Ultra 显著提升——

副作用就是不仅更顺滑、也更「滑」了。

在今年的专属摄影套装上,小米对纹理设计做了微调,手柄的握感更扎实了一些,滤镜口径同样是 67mm。

只不过在 vivo、OPPO、华为都大举发力增距镜的当下,小米的影像套装显得有点不太够用了:

至于屏幕,小米 17 Ultra 使用的是小米 17 Pro Max 同款的 6.9 寸 2608×1200「等效 2K 屏」,搭配小米 17 Pro Max 代表性的「超级像素」技术,显示素质基本无需担心。

▲ 小米 17 Ultra(左)和小米 15 Ultra(右)

和屏幕一样,小米 17 Ultra 的硬件也是 25 赛季的标配:骁龙 8 Elite Gen 5 处理器,搭配 16GB LPDDR5X 内存和 1TB 的 UFS 4.1 硬盘。

小米 17 Ultra 也延续了之前的「双路自驱循环冷泵」——通过在 VC 均热板上设计两条循环路径,将 SoC 和相机模组的热传导路线隔离,保证两者的同时释放。

拿最典型的《原神》和《崩铁》为例,《原神》自动亮度 Wi-Fi 最高画质 60 帧 15 分钟挪德卡莱跑图 + 15 分钟魔女棋局副本,Perfdog 记录的平均帧率为 58.7,机身最高温度约 40.2 度:

▲ 测试时系统版本 HyperOS 3.0.5.0

相同环境的《崩坏:星穹铁道》15 分钟日间奥赫玛跑图 + 15 分钟铁幕副本后,小米 17 Ultra 平均帧率稳定在 55.9 帧,机身温度则来到了约 49.3 度:

▲ 测试时系统版本 HyperOS 3.0.5.0

从稳定性来看,小米 17 Ultra 的性能表现和小米 17 Pro Max 难分伯仲

毕竟背屏和超大镜组都是影响机器散热的客观现实,对于一台主打影像能力的手机来说,小米 17 Ultra 的性能释放中规中矩。

长焦少一颗,焦段多两个

上一代小米 15 Ultra 最让人念念不忘的,除了影像硬实力之外,还有那个让人皱眉头的镜头排布:

▲ 既不对称又不等距的镜头很让人难受

归根结底,作为目前为数不多的双长焦方案机型,如何在同一个圆圈里排列好四颗尺寸迥异的镜头,实在是一个难以「多全其美」的问题。

而小米 17 Ultra 则解决了这个问题——在深思熟虑之后,小米 17 Ultra 抛弃了前代的 3x 加 4.3x 双长焦传感器方案,直接塞进了一颗两亿像素的三星 HPE 传感器,正式加入「大底单长焦」阵营

但小米 15 Ultra 两颗长焦的灵活性又是一个无法忽视的事实,为了保持对于多焦段的优势,小米 17 Ultra 做出了一个堪称大胆的决定——

光学变焦,并且是无裁切的连续光学变焦。

小米在有限的机身空间里,通过移动镜组的方式,为一块 1/1.4 寸的大底传感器做出了光学变焦结构,支持在 75mm~100mm(3.2x~4.3x)之间实现连续无损的光学变焦

虽然「支持光学变焦的长焦镜头」在索尼 Xperia 1 VI 上已有先例,但索尼那颗 1/3.5 寸小传感器放在小米 17 Ultra 选择的三星 HPE 面前根本不够看。

至此,我们可以认为:小米 17 Ultra 就是第一台做到旗舰水平连续光变长焦的国产手机。

哪怕摄影圈总说「牛变不如狗定」,但手机摄影用定焦这么多年,反而恰恰需要一些变焦头的灵活性。

毕竟,连续变焦最主要的优势就是可以规避数码裁切、发挥出传感器的实力。

比如在 3.2x~4.3x 之间,小米 17 Ultra 可以让 75、85、90 和 100mm 四个黄金人像焦段都吃满传感器的 2 亿像素。虽然光圈会从 f2.39 缩减至 f2.96,但这就是变焦头的光学特性:

相比 15 Ultra 只有原生的 70 和 100mm,其余(比如最常用的 85mm)必须数码裁切,小米 17 Ultra 从两颗长焦变成一颗,灵活性不仅没有损失、反而还提升了。

除了长焦外,小米 17 Ultra 也保留着一英寸主摄的优良传统。

与豪威定制的光影猎人 1050L 传感器借助 LOFIC 技术,动态范围再次提升,组成了一套以 5000 万主摄为基础、2 亿潜望长焦为突破口的阵容。

也正是依靠这颗超高动态范围主摄,小米 17 Ultra 的夜景能力相比小米 15 Ultra 有了可感的进步。

不仅风光照,夜景人像也能得到一并的提升——这一次,管小米叫「夜神」没有之前那么别扭了。

但现阶段小米的夜景算法依然存在那个老生常谈的问题:快门速度过于松弛

或许是对自己的算法过于自信,小米在夜间非常执着于保持低 ISO、然后把快门速度调到 1/30s 甚至更低,导致只要有一点移动物体就会糊片:

只不过小米 17 Ultra 的超广角依旧是熟悉的味道,一颗 14mm 的 1/2.75 寸三星 JN5 传感器,也是非常熟悉的面孔了。

这颗 14mm 的超广角日常拿来应急一下还行,但要是想用它拍出来点「艺术感」,就很容易被画质和宽容度拖后腿:

总的来说,在目前这个「长焦为王」的竞争格局下,小米 17 Ultra 的道路也是我们在 2025 年观测到的主流。

作为目前平衡体积、功能和计算量的最优方案之一,单个大底长焦有可能成为明年影像旗舰的主流选择

毕竟在移动影像中,通过多个定焦头覆盖常用焦段、组成影像阵列,这种方案虽然成熟,但也无奈——如果想要最好的、不裁切的画质,就只能局限于厂家选好的几个原生焦段。

而小米 17 Ultra 用连续光变方案,将原本两个定焦中间的「纯裁切」变成了「纯光学」,很好地打破了这种僵局。

毕竟即使面对细节极多的画面,连续光变也不会损失信息。

你说它完美吗?区区 25mm 的变焦范围其实并不算大,但它却可以被看作是手机影像在算法之外,重新开始追求物理光学性能的开端。

如果从这个角度上讲,小米 17 Ultra 其实比今年的任何一台影像旗舰,都更像是真正的相机。

▲ 图|NotebookCheck

做影像旗舰,但不能只做影像旗舰

作为一台起售价 6999 的年度影像旗舰,小米 17 Ultra 既是 2025 年影像争霸的句号,又是 2026 上半年 Ultra 旗舰的发令枪。

从配置和表现来看,小米 17 Ultra 无疑依然是一台充满了小米色彩的「水桶旗舰」,你可以用小米的价格买到一台同价位段难得的一碗水端平的体验。

然而在这个急剧细分化的手机市场里,「一碗水端平」对于 Ultra 级的直板旗舰来说,可能并不是褒义词。

就拿「长焦增距镜」这个非常痒点的配件来说,绝大多数用户不会天天去看演唱会,但拥有长焦增距镜就意味着在某些特殊的时刻,你可以得到满画质的 400 甚至 600mm 画面。这是任何软件算法都无法替代的。

而小米 17 Ultra 恰恰比较缺乏增距镜这种面向细分化需求的配件——你可以尝试复现徕卡的影像科学,但你不应该复刻徕卡的商业策略

目前来看,即便有最基本的摄影套装,小米 17 Ultra 相比其他家的影像特化产品来说,依然正在落入一个非常危险的「没有特色」的境地。

那么小米今年的特色在哪里呢?在小米 17 Ultra 徕卡版上——

而那或许才是今年真正值得买的小米 Ultra。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


小米17 Ultra机型售价公布:6999元起

2025年12月25日 21:00
36氪获悉,小米正式公布小米17 Ultra售价,其中12GB+512GB售价6999元、16GB+512GB售价7499元、16GB+1TB售价8499元;小米17 Ultra徕卡版共有黑、米白两款配色,其中16GB+512GB售价7999元、16GB+1TB售价8999元。该机型现已开启预售,将于12月27日10时正式开售。

新铝时代:持股5%以上股东国同红马拟减持不超2.49%公司股份

2025年12月25日 20:57
36氪获悉,新铝时代公告,公司持股5%以上股东重庆高新创投红马资本管理有限公司-重庆国同红马股权投资基金合伙企业(有限合伙)(简称“国同红马”)计划在预披露公告披露之日起15个交易日后的3个月内(2026年1月20日至2026年4月19日)以集中竞价方式和/或大宗交易方式合计减持股份不超过3,581,647股,占公司总股本的2.49%。

金埔园林:股东苏高新拟减持不超0.8%公司股份

2025年12月25日 20:49
36氪获悉,金埔园林公告,公司股东苏州新区高新技术产业股份有限公司计划自公告披露之日起三个交易日后的第一个交易日起三个月内,通过集中竞价方式减持公司股份不超过147.88万股,即不超过公司总股本的0.80%。减持价格将根据市场价格确定,减持原因为盘活存量资产、优化产业结构、服务整体发展战略。

东阳光:控股子公司东阳光氟增资扩股并引入投资者兴银投资

2025年12月25日 20:43
36氪获悉,东阳光公告,公司控股子公司乳源东阳光氟有限公司拟新增注册资本10,888,889元,由兴银金融资产投资有限公司以7亿元全额认购,公司及子公司乳源东阳光电化厂放弃本次增资的优先认购权。增资完成后,公司直接及间接持有标的公司的股权比例由100%降至96.9828%,仍为控股股东并拥有实际控制权。此次交易旨在增强东阳光氟的资本实力和优化资本结构,符合公司发展战略和全体股东利益。

7999 元起!小米发布「徕卡手机」,有可乐标,更有「德味」

作者 苏伟鸿
2025年12月25日 20:33

2026 年影像旗舰竞赛的发令枪,被小米提前在 2025 年底打响。

没错,今年还没结束,小米就已经急不可耐端出了全新的小米 17 Ultra,把手机影像的上限,再往上抬了一截,可以说锁定了「2025 年最强静态影像手机」的名号。

重磅炸弹还不止于此:一不做二不休,小米和徕卡干脆直接做出了一台带「可乐标」的小米 17 Ultra by Leica——它,才是今晚真正的焦点。

如果过去小米与徕卡的合作难免引人闲话,那么这台真正的「徕卡手机」,就是一次响亮的回应。

小米 17 Ultra:把手机镜头做成「相机」的尝试

除了「徕卡一瞬」风格、定制外观以及变焦环,小米 17 Ultra 两个版本的相机硬件是一模一样的。

和今年的小米 17 系列一样,小米 17 Ultra 从等深四曲面玻璃换成了直屏,中框也换成垂直设计,背面的微弧磨砂玻璃都换成了小米 17 Pro Max 同款的纯平玻纤。机身厚度还减少了 1 毫米,成为了有史以来最轻薄的小米 Ultra手机。

最值得关心的,当然是小米 17 Ultra 这次的相机参数:

  • 主摄:5000 万像素,光影猎人 1050L 传感器,一英寸底,LOFIC 超高动态
  • 中长焦:2 亿像素,1/1.4 英寸,同时提供 75mm 和 100mm 焦段,支持连续光学变焦
  • 超广角:5000 万像素,JN5 传感器 1/2.75 英寸

中长焦的「连续光学变焦」可不是所谓的「光学品质变焦」或者营销术语,而是字面意义的光学变焦:

小米通过移动镜组的方式,在这块 1/1.4 英寸的传感器上做出了光学变焦结构,支持在 75mm~100mm(3.2x~4.3x)之间实现连续无损的光学变焦。、

好处首先是非常直观的:少开一个孔后,小米 17 Ultra 的相机 Deco 实现对称设计,相较 15 Ultra 颜值提升不少。

受制于极其有限的机身空间,传统的手机影像「中焦」和「长焦」难以并举,不是二选其一就是各砍一刀。

而小米 17 Ultra 用一个大底高像素传感器,以移动镜组的方式实现变焦,每个焦段都是两亿像素大底,静态拍照时不会损失画质,能够用满传感器;动态录影时,也不会因为换镜头出现画面断点。

相比 15 Ultra 只有原生的 70 和 100mm,其余焦距(比如最常用的 85mm)必须数码裁切,小米 17 Ultra 从两颗长焦变成一颗,灵活性不仅没有损失、反而还提升了。

至于主摄镜头,小米 17 Ultra 延续了 1 英寸大底的传统,借助 LOFIC 技术,动态范围再次提升,在各种高光、夜景场景更游刃有余,延续「夜神」代号。

在高动态技术加持下,小米 17 Ultra 的夜间视频能力也有明显提升,复杂的夜间光源也能很好保留,Mi-Log 格式则让后期制作更具专业水准。

除了静态照片和视频,小米 17 Ultra 也在实况照片上画了点心思,除了 4K 实况照片,还增加了实况运镜功能,这几年很火的「红毯运镜」也安排上了。

对于以往想拍出最好画质的照片,只能局限于几个原生焦段的智能手机影像来说,小米 17 Ultra 的方案无疑是极具突破性的。

这样强悍的光学硬件和影像科技,还不是今晚的最重头戏,那台被带有「可乐标」的小米 17 Ultra 徕卡定制版,将今年的小米影像叙事推向了最高潮。

先来看看这台「徕卡手机」的外观设计,有多么的名副其实:灵感来自于经典徕卡 M 系列相机,贝壳材质为荔枝皮,除了左上角这颗瞩目的「可乐标」,侧边还有一行「LEICA CAMERA GERMANY」的刻字。

最引人瞩目的设计莫过于背面 Deco 上多出来的「大师变焦环」——这可不只是造型上的作用,而是用户可以真实转动,实现镜头变焦的设计,配合上小米 17 Ultra 的中长焦光学变焦能力,交互体验确实非常「相机」。

但小米 17 Ultra 徕卡定制版并不止步于这些外观设计——独占的「徕卡一瞬」拍摄模式,才是这台手机冠以徕卡之名的用意所在。

「徕卡模式」提供了 Leica M3 和 Leica M9 两台经典徕卡相机的镜头模拟效果。千万别以为这是简单的套滤镜:M3 + MONOPAN 50 胶片的黑白效果是将颜色映射黑白曲线,还原正确亮度关系。

M9 则利用了一个学习了数十万张照片的 30B 云端模型,用 AI 模拟的 CCD 效果,还原「德味」影调和色彩。

可以说,「徕卡一瞬」是用强大的「计算摄影」路径,还原出徕卡相机「纪实摄影」的那种强烈的风格。当然,AI 计算生成的介入会否引入幻觉,以及幻觉与「真实」之间的边界,仍待讨论与更多的测试。

影像的部分先告一段落。作为 Ultra 旗舰,小米 17 Ultra 的其他配置自然也拉满:

  • 6.9 寸 2608×1200 超级像素等效 2K 屏幕,小米 17 Pro Max 同款
  • 高通骁龙 8 Elite Gen 5
  • 6800mAh 金沙江电池
  • 90W 有线充电 + 50W 无线充电

作为一台主打摄影和性能的旗舰手机,小米 17 Ultra 延续「双路自驱循环冷泵」的散热方案,将处理器和相机模组的热传导路线隔离,实现影像和游戏两大重度场景并举。

互联功能方面,iPhone、iPad 和 Mac 可以通过「妙享桌面」,镜像控制整台小米手机。

最后,也是最重要的一点,是价格。小米官方此前已明确,小米 17 Ultra 将迎来一轮明显涨价。

而在内存等关键元器件持续走高的背景下,小米 17 Ultra 的定价也将成为一个重要锚点。可以预见,明年的其他超大杯旗舰,只会更贵,竞争压力将会更大。

小米 17 Ultra 配置和价格如下:

  • 6999 元,12GB + 512GB
  • 7499 元,16GB + 512GB
  • 8499 元,16GB + 1TB

小米 17 Ultra by Leica 配置和价格如下:

  • 7999 元,16GB + 512GB
  •  8999 元,16GB + 1TB

徕卡比小米更需要一台这样的手机

国产手机都经历了一次相似的高端化历程:起点和高潮都在影像,纷纷拉来海外光学大厂联名合作,不只是利用它们的技术积累改造手机镜头,更多还有点借助大牌旗号「贴金」的意味。

因此小米 17 Ultra 徕卡定制版,就是这种合作中规格和待遇最高的一次:以往更多是光学厂商赋能手机厂商。但小米 17 Ultra 徕卡版,就是真正意义上的「联手打造」,对双方来说都是一次里程碑,它既是小米的最新旗舰,也是真正的「徕卡手机」。

小米与徕卡合作的三年,刚好也是「米冲高」加力最明显的三年。小米 17 Ultra 的徕卡定制版,则是自小米前两代 MIX 以来,最成功的一次高端化——加上徕卡标后,更多的小米的质疑者也不得不承认,一台小米手机卖这么贵是有道理的。

至于小米 17 Ultra「标准版」,虽然它没有红标和徕卡一瞬加持,但它和「徕卡手机」共享一套镜组,在光学层面本质上就是「徕卡认证」。

对于这次合作属于小米「抱大腿」的声音,我并不赞同。小米确实借助徕卡这个高端光学品牌提升了产品定位,但仔细想想,其实明明应该是徕卡比小米更需要一台「徕卡手机」。

在小米之前,徕卡并不是没推出过「徕卡手机」——和夏普合作的 Leitz Phone 系列手机,它的影像能力确实很有「德味」,只是它作为手机的每个方面,都堪称失败,完全没在市场上造成什么水花。

而小米 17 Ultra 徕卡定制版明显是一次诚意和产品力都更足的合作,小米顶级的硬件本身就是一个卖点,也能更好匹配徕卡的高端定位。

更有意思的是,徕卡愿意让小米以 AI 的方式,去模拟徕卡相机的质感,这与其起家的「纪实」理念完全不同,甚至是相左的。

其实进入 2000 年后,徕卡就遭遇了来自的数字摄影的严重冲击,经过几次变革才有所好转。

于是在智能手机时代,徕卡敏锐地捕捉到了「移动影像」这个风口,积极和华为、小米合作——如果他们依旧执拗生产只有少部分摄影爱好者会购买的相机,那它不可能活到现在,更不可能达到现在的知名度。

现在又到了技术更迭的节点,面对 AI 和计算摄影的浪潮,徕卡已经率先做出了表态。

在小米的加持下,小米 17 Ultra by Leica 冲击徕卡品牌产品销量纪录(目前由 M3 的 22万台左右保持),应该是很有机会的——即便是能达到 10 万台左右的销量,在徕卡的范畴里也算「街机」了。

当然,部分徕卡的忠实拥趸也可能会对小米 17 Ultra 徕卡定制版颇有微词,认为它破坏了徕卡的百年传承,一如当年黑石入资后退坑的粉丝。

但只有在新时代存活下来,才有资格继续讲述「传承」的故事。

至于小米 17 Ultra,特别是徕卡定制版有没有讲好这个故事,很快在爱范儿的实测中就能得见真章。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


万朗磁塑:控股股东时乾中拟减持不超3.00%股份

2025年12月25日 20:23
36氪获悉,万朗磁塑公告,控股股东时乾中因自身资金需求,计划自公告披露之日起15个交易日后的3个月内,通过集中竞价和大宗交易方式合计减持不超过256.45万股,占公司总股本的3.00%。其中,集中竞价减持不超过85.48万股,大宗交易减持不超过170.97万股。减持股份来源为首次公开发行前取得的股份。
❌
❌