[{"content":"Redis高可用架构与分布式锁 基本模型 Redis常见的部署形态可以分为单节点、primary-replica、Sentinel和Cluster 复制和锁解决的是两类不同问题： 复制把数据从主节点同步到副本节点，用于冗余、故障转移和读扩展 分布式锁让多个客户端在一个时间窗口内竞争某个资源的使用权 “高可用”不等于“强一致”：Redis复制默认是异步的，故障转移可能丢失尚未复制的写入，副本读也可能读到旧数据 官方文档： Replication Redis Sentinel Redis Cluster Distributed locks 架构总览 单节点 单节点拓扑只有一个 Redis实例：\ntextClient ──\u0026gt; Redis 适合本地开发、测试环境和允许缓存丢失的场景\n通过 RDB或AOF可以恢复部分数据，但持久化不能替代在线故障转移\n实例故障时，客户端连接、内存数据和正在执行的请求都会受到影响\nPrimary-Replica 主节点负责写入，副本节点从主节点复制数据：\ntextClient ──\u0026gt; Primary ├── Replica A └── Replica B 副本连接主节点后，可以在配置文件中声明：\nredisreplicaof 192.168.1.1 6379 复制连接通常是异步的，因此主节点返回成功时，副本节点不一定已经收到该写入\n副本节点适合承担读请求，但读到的数据可能落后于主节点\n主节点故障后，需要人工或其它协调组件提升副本节点，否则副本只会继续提供已有数据\n所有副本通常保存完整数据集，这种架构能增加冗余和读能力，但不能把一个大数据集水平拆到多个实例\n复制连接与命令流 复制连接通常由副本节点主动向主节点建立，复制方向是单向的：主节点把写命令按顺序发送给副本，副本依次执行这些命令；客户端的读请求不会进入复制流 副本节点不会把自己的写入自动转发回主节点，因此这不是双主复制，也不能通过同时向两个节点写入来获得冲突合并能力 副本节点可以再作为其它副本的上游，形成级联复制。这样可以减少主节点的网络发送压力，但会增加复制延迟，并且中间副本故障后需要重新连接或由协调组件重新配置下游节点 可以通过INFO replication观察role、复制偏移量、上游连接状态和副本数量；这些状态反映的是复制进度，不代表主节点与副本已经形成共识 首次同步与断线重连 副本第一次连接，或者主节点无法提供副本所需的历史命令时，会进行全量同步： 主节点生成一个数据快照，通常是RDB文件，也可以根据配置采用无盘方式传输 生成快照期间产生的新写入会暂存在主节点的复制缓冲区 主节点把快照传给副本，副本加载快照并替换自己的数据集 主节点再发送快照期间缓存的写命令，之后切换到持续命令流 短暂断线后，副本会使用PSYNC携带自己保存的复制ID和偏移量请求继续同步。只要主节点的复制积压缓冲区仍保留这段命令历史，就可以进行部分重同步，不必重新传输完整数据集 复制积压缓冲区是有限大小的环形缓冲区，不是永久日志。副本离线时间过长、缓冲区太小，或者副本保存的复制历史与主节点不匹配时，PSYNC会退化为全量同步 复制链路中断时，副本是否继续响应读请求由replica-serve-stale-data等配置决定；即使继续响应，返回的也只是最近一次成功应用的旧数据 副本的读写行为 replica-read-only yes通常用于防止误写副本。关闭该选项后可以向副本写入，但这些写入不会回传主节点，后续重新同步时也可能被主节点的数据覆盖，因此不能把它当作独立写副本 主节点写入成功时，副本可能尚未收到或执行该命令。WAIT可以等待指定数量的副本确认某个复制偏移量，但它仍然不是共识协议，也不能保证故障转移时绝对不丢数据 可以使用min-replicas-to-write和min-replicas-max-lag让主节点在可用副本不足或副本延迟过大时拒绝部分写入，以缩小潜在丢失窗口；代价是网络故障时写入可用性下降，而且这仍不是零数据丢失保证 主节点故障与副本提升 发生故障时，副本不会因为连接断开就自动互相选举主节点。没有Sentinel或其它协调组件时，需要人工执行：\nredisREPLICAOF NO ONE 被提升的副本只能提供它已经收到并执行的数据。旧主节点尚未复制出去的写入可能丢失，多个副本之间的复制偏移量也可能不同，因此提升前通常要根据复制进度选择更合适的副本\n副本提升后，其它副本仍可能指向旧主节点，客户端也可能仍然连接旧地址；新主节点、其它副本和客户端都需要重新配置或重新发现\n提升后的节点会保留旧复制历史作为辅助复制ID，使部分下游副本在条件满足时可以继续部分重同步；这能减少恢复成本，但不能恢复已经丢失的写入\nSentinel Sentinel是运行在数据节点之外的控制平面进程，本身不保存业务数据，也不代理客户端的读写流量\nSentinel在主从复制之上提供监控、通知、自动故障转移和客户端配置发现\n典型拓扑是多个 Sentinel共同监控一组主从节点：\ntextApplication │ ├── Sentinel A ├── Sentinel B └── Sentinel C │ └── Primary ── Replica A └── Replica B 一个 Sentinel配置示例：\ntextport 5000 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1 监控循环与节点发现 每个Sentinel会周期性向主节点和副本节点发送PING，并通过INFO replication了解角色、复制偏移量和副本列表；它还会通过__sentinel__:hello等机制发现同一监控组中的其它Sentinel Sentinel会记录每个节点的最后响应时间、连接状态、复制进度和配置版本。它自己判断某个节点不可达时，只能形成一个本地观点，不会直接替其它Sentinel作决定 某个Sentinel在down-after-milliseconds时间内没有得到主节点的有效响应，会把主节点标记为主观下线（SDOWN） 发现SDOWN后，它会向其它Sentinel询问主节点是否可达。达到sentinel monitor配置的quorum后，主节点才被标记为客观下线（ODOWN），才具备发起自动故障转移的条件 主观下线、客观下线与投票 SDOWN是单个Sentinel的本地判断，可能由网络抖动、进程暂停或该Sentinel自身的网络故障造成；ODOWN是多个Sentinel对同一主节点不可达的共同判断 quorum只表示客观下线判断需要多少个Sentinel同意，不等于所有Sentinel的多数，也不表示写入已经复制到多少个数据节点 发起故障转移的Sentinel还需要在一个配置纪元中竞选领导者，并取得足够多的Sentinel投票和故障转移授权。即使达到了quorum，如果没有足够的Sentinel可通信，故障转移仍可能无法开始 一个常见的三节点部署中，quorum可以设为2，同时把三个Sentinel分散到不同故障域；只部署两个Sentinel时，任意一个故障都可能使剩余节点无法取得多数授权 故障转移期间的节点行为 一次典型故障转移可以概括为：\ntextPING超时 ↓ 本地 SDOWN ↓ 其它 Sentinel 投票达到 quorum 客观 ODOWN ↓ 竞选领导者并取得授权 选择合适的 Replica ↓ REPLICAOF NO ONE，提升为新的 Primary ↓ 重新配置其它 Replica 和恢复后的旧 Primary ↓ 发布 switch-master，客户端重新发现地址 领导者会先筛选副本：排除断开时间过长或状态不合格的副本，跳过replica-priority 0的副本，再优先考虑复制偏移量更靠前的副本；复制偏移量越新，通常越可能包含旧主节点最近的写入 “断开时间过长”不是简单看当前是否在线，Sentinel还会结合主节点进入SDOWN的时间和down-after-milliseconds估算副本是否过于落后。这个筛选只能降低数据丢失概率，不能把异步复制变成同步复制 选中的副本会被要求执行REPLICAOF NO ONE，确认其角色变成主节点后，Sentinel让其它副本执行类似REPLICAOF \u0026lt;new-primary\u0026gt; \u0026lt;port\u0026gt;的重配置 sentinel parallel-syncs限制同一故障转移中可以同时向新主节点同步的副本数量：数值较小对新主节点压力较低，但整个拓扑恢复为完整副本集所需的时间更长 旧主节点重新上线后，Sentinel通常会把它改配置为新主节点的副本，而不是把两个主节点的数据自动合并；分区期间只写入旧主节点的数据可能在重新同步时被覆盖或丢失 配置传播与客户端行为 Sentinel之间会交换主从拓扑、节点状态和配置纪元，并把最新的监控配置重写到本地配置文件；因此配置最终会收敛，但网络分区期间不同Sentinel可能暂时持有不同观点\nSentinel不在应用的数据路径上。客户端应连接任意可用的Sentinel，通过以下命令按服务名查询当前主节点，然后直接连接返回的host:port：\nredisSENTINEL get-master-addr-by-name mymaster 客户端可以订阅+switch-master等事件作为快速提示，但不能只依赖通知；连接失败时仍应重新查询主节点并建立连接，因为通知可能丢失\nSentinel不负责分片，所有主从节点仍然共同承载同一份数据集。客户端切换连接也不会撤销已经发给旧主节点的请求，因此超时命令必须结合幂等键、事务或版本检查处理\n网络分区期间，旧主节点可能仍然被一部分客户端访问；分区恢复后它通常会被降级为副本，期间产生的写入可能丢失。这是异步复制和最终一致配置共同造成的结果\n生产环境通常应把多个Sentinel和数据节点分散到不同故障域，并为客户端配置连接重试、主节点重新发现和命令失败后的幂等处理\nRedis Cluster Cluster把数据分布到多个主节点，每个主节点负责一个或多个哈希槽：\ntextCluster-aware Client ├── Shard 1: Primary ── Replica ├── Shard 2: Primary ── Replica └── Shard 3: Primary ── Replica Redis Cluster固定使用 16384个哈希槽，键通过 CRC16(key) % 16384映射到槽位，再由负责该槽位的主节点处理\n每个分片可以配置一个或多个副本，主节点故障时副本可以被提升为新的主节点\n添加或移除节点的本质是迁移哈希槽，迁移过程可以在服务运行时进行\n客户端需要理解集群拓扑并维护槽位到节点的映射\n客户端访问错误节点时，节点会返回 MOVED或ASK重定向：\nredisGET x -MOVED 3999 127.0.0.1:6381 多键命令、事务和脚本通常要求相关键位于同一个槽位，可以使用哈希标签把相关键放进同一槽位：\nredisSET user:{42}:profile Alice SET user:{42}:quota 100 如果某个主节点及其全部副本都不可用，负责这些槽位的服务可能不可用，即使其它分片仍然健康\nCluster解决的是水平分片和分片级故障转移，不会自动提供跨槽位事务、全局强一致或业务级幂等\n复制与一致性边界 Primary-Replica、Sentinel和Cluster都应先按异步复制来理解： 主节点先处理写入，再把复制流发送给副本 主节点在副本确认之前就返回成功时，主节点突然故障可能丢失这部分写入 副本读适合对短暂旧数据不敏感的查询，不适合直接承担强一致读 Sentinel的自动故障转移缩短了恢复时间，但不能撤销已经发生的网络分区和异步复制窗口 Cluster的副本只负责对应分片，不能把一个分片的副本当成另一个分片的独立数据源 需要强一致的业务事实时，应把数据库事务、唯一约束、版本号或共识系统作为最终裁决，Redis更适合作为缓存、会话、计数器、短期状态和协调加速层 分布式锁 单实例锁 单实例锁的获取需要把“仅当键不存在时写入”和“设置过期时间”放在一个原子命令中：\nredisSET lock:order:1001 lock-token-\u0026lt;unique\u0026gt; NX PX 30000 锁值必须是当前持有者生成的唯一随机值，不能只写固定字符串，否则释放锁时无法确认所有者\n释放锁不能直接执行 DEL，因为客户端可能在锁过期后恢复，此时它删除的已经是另一个客户端持有的新锁\nRedis 8.4及更新版本可以使用按值删除的 DELEX，旧版本可以用 Lua 脚本原子地比较并删除：\nluaif redis.call(\u0026#34;get\u0026#34;, KEYS[1]) == ARGV[1] then return redis.call(\u0026#34;del\u0026#34;, KEYS[1]) else return 0 end 锁的过期时间是租约上限，不是业务完成保证；业务执行时间超过租约后，其他客户端可能重新获得同一把锁\n续租也必须比较唯一值并原子执行，而且无法阻止进程暂停期间租约已经过期的问题\nRedlock Redlock假设存在 N个相互独立的 Redis主实例，而不是同一主从组中的主节点和副本节点\n客户端获取锁的过程可以抽象为：\ntext记录开始时间 ↓ 使用较短超时并行请求 N 个独立主实例的 SET NX PX ↓ 统计成功实例数量和已经消耗的时间 ↓ 成功数达到 N / 2 + 1 且剩余租约仍大于零 ├── 成功：进入临界区 └── 失败：释放所有已经写入的锁并重试或返回失败 获取成功后的有效租约需要扣除获取过程耗时、网络延迟和时钟漂移余量，不能直接把初始 TTL当成剩余可用时间\n例如 N = 5时，至少需要 3个实例在有效时间内成功加锁，少于多数派就应释放已经获取的锁\nRedlock把互斥性建立在多个实例相互独立、时钟漂移受控、客户端能在租约内完成工作的假设之上\nRedlock不是共识协议，也不会向业务资源发放自动递增的所有权版本，因此“成功获得锁”不能单独证明后续写入一定安全\nRedlock的缺点 租约过期后旧客户端仍可能继续执行 客户端获得锁后发生长时间的进程暂停、虚拟机暂停或调度延迟 锁在 Redis中已经过期，另一个客户端获得新锁并开始执行 旧客户端恢复后并不知道自己已经失去所有权，仍可能向数据库、文件系统或第三方服务提交操作 异步故障转移可能丢失锁状态 在普通主从加Sentinel的拓扑中，锁先写入主节点 主节点在复制锁值之前故障，副本被提升后可能没有这把锁 新客户端于是可以获得同一资源的锁，所以不能把一个主从组的主节点、副本节点和Sentinel当作多个独立投票者 时钟和网络假设会压缩安全窗口 网络延迟、实例响应慢和时钟漂移都会减少实际临界区时间 如果客户端只按墙上时钟判断租约而忽略耗时和漂移，计算出的有效时间会过于乐观 缺少 Fencing token 锁服务只能告诉客户端“当前尝试获得了租约”，不能强制下游资源拒绝旧客户端 没有围栏令牌时，即使锁算法在大多数时间工作正常，也无法从协议层阻止迟到的旧写入 可用性和运维成本更高 客户端要同时管理多个实例、超时、重试、部分成功和清理逻辑 网络分区时可能无法取得多数派，多个实例的监控、升级和故障域规划也会增加成本 锁不能代替业务幂等和事务 客户端超时并不能证明加锁失败，重试可能与第一次请求并存 业务写入仍应使用幂等键、唯一约束、版本检查或事务保护 选型与最佳实践 按数据规模和可用性选拓扑 数据量小、业务允许缓存丢失：使用单节点，重点做好监控和重启恢复 数据集需要完整复制但不需要水平分片：使用 Primary-Replica + Sentinel 使用多个 Sentinel并分散故障域 客户端通过主节点名称发现主节点 把故障转移看成恢复机制，而不是零数据丢失机制 数据量或吞吐量超过单个主节点：使用 Redis Cluster 选择支持槽位映射、MOVED和ASK的集群客户端 设计键名时提前规划哈希标签和跨槽位操作 为每个分片配置副本，并演练主节点、全副本和网络分区故障 不要因为启用了 Sentinel或Cluster就默认获得强一致锁；数据高可用拓扑和锁的安全证明需要分别设计 可重复工作的互斥 对缓存击穿保护、重复计算抑制和短时任务协调，可以使用单实例锁 获取锁时使用唯一值、NX和有限 PX租约 释放锁时使用 DELEX或比较并删除的 Lua 脚本 临界区应短于租约，并为获取失败配置有限重试、指数退避和随机抖动 锁只作为减少并发的优化手段，任务本身仍应支持重复执行、超时恢复和幂等提交 需要严格所有权的资源 库存扣减、资金变更、同一文件写入和不可重复的外部副作用不应只依赖 Redlock\n更稳妥的做法是让最终资源验证一个单调递增的 fencing token：\ntext获取租约并分配 fencing_token ↓ 携带 fencing_token 写入下游资源 ↓ 下游只接受大于已记录令牌的请求 数据库可以通过事务、唯一约束和乐观锁版本号拒绝重复或过期写入，外部服务则需要提供版本条件或幂等键\n如果下游资源不能验证 fencing token，就不能把锁的成功响应当成防止旧客户端写入的充分证明\n必须使用 Redlock时 使用 3或5个相互独立的主实例，并把实例放在不同故障域，不要把一个复制组拆成多个“独立节点”计票 并行请求各实例，并为单个实例设置远小于总租约的连接和命令超时 只有在多数派成功且扣除耗时后仍有足够租约时才进入临界区，失败时释放所有已尝试的锁 使用可靠的唯一令牌和安全释放逻辑，记录获取耗时、成功节点数、剩余租约、续租失败和清理失败 把 Redlock定位为有明确时间假设的租约机制；当正确性依赖绝对互斥和旧客户端不可写时，应增加 fencing token或改用带共识和租约语义的协调系统 ","permalink":"https://echo.ishian.me/2026/09/14/redis-ha-redlock/","summary":"讲解Redis主从复制、Sentinel、Cluster高可用架构及Redlock分布式锁原理","title":"Redis: 主从、Sentinel、Cluster 与 Redlock"},{"content":"B+树 概述 B+树 是一种多路平衡搜索树，主要面向磁盘、固态硬盘等外部存储场景 与普通二叉搜索树每个结点只保存一个键不同，B+树 的一个结点可以保存很多个键和子指针，因此树的分支数很大 B+树 将索引导航信息放在内部结点，将记录指针或记录本身统一放在叶子结点，并把所有叶子结点连接成有序链表 数据库索引使用 B+树 的核心原因，是在较少的页访问次数下完成有序查找和范围扫描 基本结构 结点组成 B+树 是一棵多路搜索树，常用 m 阶表示内部结点最多拥有 m 个子指针和 m-1 个分隔键，不同教材对阶的定义可能略有差异\n根结点可以是叶子结点，也可以拥有较少的子结点 非根内部结点通常至少半满，以避免树的空间利用率过低，具体下限由实现的页布局决定 内部结点中的键用于划分子树的取值范围，键本身通常只是导航信息，不保存对应记录的完整内容 叶子结点保存有序的键，以及与键对应的记录指针、主键值或记录内容\n在聚簇索引中，叶子结点可能直接保存整行记录 在二级索引中，叶子结点通常保存索引键和主键值，再通过主键定位整行记录 所有叶子结点位于同一层，因此从根到任意叶子的路径长度相同\n内部结点中的分隔键可能在叶子结点中再次出现，这种重复是为了让内部结点承担路由职责\n下面的示意图中，20 和 40 只是根结点的分隔键，真正的记录都在叶子结点中\ntext [20 | 40] +-------+-------+ | | | [5, 10, 15] \u0026lt;-\u0026gt; [20, 25, 35] \u0026lt;-\u0026gt; [40, 50, 60] records records records 小于 20 的键进入最左子树，大于等于 20 且小于 40 的键进入中间子树，大于等于 40 的键进入最右子树 叶子之间的双向连接不是树的父子关系，而是为了支持按键的顺序向后扫描 查找路径 查找一个键时，从根结点开始比较分隔键，选择可能包含目标键的子指针，重复这个过程直到叶子结点 内部结点只承担导航职责，使单个内部页可以容纳更多分隔键和子页指针 到达叶子结点后，在叶子页内查找目标键，再根据记录指针或主键值访问记录 B+树 的高度通常接近 log_m n，其中 m 是较大的扇出，n 是索引项数量 数据结构特性 高扇出：一个结点可以连接很多子树，树的高度远小于同样数据量的二叉树 平衡：所有叶子处于同一层，不会像未平衡的二叉搜索树一样因为插入顺序退化成链表 有序：叶子中的键按照索引键排序，父结点的分隔键也按照从小到大的顺序排列 适合范围访问：定位到范围起点后，可以沿叶子链表依次访问后续叶子，不需要反复从根结点开始查找 适合页式存储：一个内部结点通常可以设计成一个或几个存储页，访问结点时可以批量读取多个键和指针 查询成本可以拆成两部分：先沿树高定位起点，再沿叶子链表读取结果，点查询接近 O(log_m n)，返回 k 条连续结果的范围查询接近 O(log_m n + k) 为什么索引通常使用 B+树 磁盘页访问比比较次数更重要 数据库索引通常持久化在存储设备上，执行查询时真正昂贵的部分往往是随机页访问，而不是在一个页内比较几个整数 B+树 通过较大的扇出降低树高，点查询通常只需要访问根页、若干内部页和一个叶子页 内部结点不保存完整记录，单页可以放下更多分隔键和子指针，因此在相同数据量下往往比保存记录指针的树更矮 根页和上层内部页还容易被缓存在内存中，后续查询可能只需要访问较少的叶子页 有序结构适合范围查询 \u0026gt;、\u0026lt;、BETWEEN 等范围条件可以先在树中定位第一个叶子位置，再沿叶子链表顺序读取后续键 ORDER BY、按时间区间读取、前缀匹配等需求都能利用索引键的全局有序性 如果记录按键在叶子中连续排列，范围扫描可以沿兄弟指针顺序访问，并有机会被存储引擎预取，这比在大量分散结点之间跳转更适合存储设备 结构规则稳定 B+树 始终保持平衡，索引规模增长时树高只会缓慢增加，查询延迟更容易预测 结点的分裂、合并和页回收会维护每个结点的容量约束，因此不会因为数据输入顺序而出现普通二叉搜索树的极端退化 这并不表示所有数据库索引都必须使用 B+树，只是它同时满足低树高、有序访问和范围扫描，覆盖了关系型数据库最常见的索引需求 为什么不使用 B树 B树 和 B+树 都是多路平衡搜索树，主要差异在于 B树 可以在内部结点和叶子结点保存记录，而 B+树 将记录统一放到叶子结点 B树 的点查询有机会在内部结点提前命中记录，这是它的一个局部优势，但数据库查询的主要成本通常是页访问 B树 的内部结点带有更多记录信息，单个页能容纳的路由键和子指针更少，扇出可能降低，树高和页访问次数可能增加 B树 没有要求所有叶子通过一条统一的有序链表连接，范围查询需要在树的多个层级之间进行中序遍历，访问模式不如 B+树 直接 B+树 可能重复保存分隔键，换取内部结点更轻、叶子范围扫描更简单，这种空间上的重复通常值得 B树 并不是错误的选择，在数据完全位于内存、点查询占绝大多数且记录较小时，它仍然可以有合适的实现，只是通常不是关系型数据库磁盘索引的优先方案 为什么不使用红黑树 红黑树是二叉平衡搜索树，每个结点通常只有一个键和少量指针，B+树 则把大量键和指针放进一个面向页的结点 两者在内存模型下都可以达到 O(log n) 的查找复杂度，但这个记号没有体现存储设备上的页访问次数 红黑树沿查找路径一次跳转一个结点，结点可能分散在内存或磁盘中，容易产生大量指针跳转和缓存未命中 B+树 每次读取一个内部页就能获得大量比较对象，并且相邻叶子可以沿兄弟指针访问并有机会预取，更适合批量读取，物理页是否连续则取决于存储布局 红黑树的中序遍历也能完成范围查询，但结果结点之间通常通过树指针连接，不能天然转化为叶子页的顺序扫描 红黑树更适合进程内的有序映射、集合等数据结构，数据库磁盘索引更关心页布局和范围访问，因此通常选择 B+树 B+树的缺点 维护成本：索引规模变化时可能发生页分裂、页合并和父结点更新，这些结构调整会带来额外的写入和日志开销 空间成本：分隔键可能在内部结点和叶子结点中重复出现，叶子链表还需要额外的兄弟指针 宽键问题：联合索引包含的列越多、键值越长，单页能容纳的索引项越少，扇出下降后树高和存储空间可能增加 低选择性问题：如果范围覆盖了大部分叶子，扫描索引后再回表可能比直接扫描数据表更慢，优化器可能放弃使用索引 不适合所有查询：纯等值查询在具体引擎支持哈希索引时可能更适合哈希结构，而 B+树 的主要优势是有序性和范围访问 最左前缀原则 联合索引的排序方式 假设联合索引的列顺序为 (a, b, c)，叶子中的索引键按照字典序比较，先比较 a，a 相同时再比较 b，前两列都相同时才比较 c text(1, 1, 1) -\u0026gt; (1, 1, 2) -\u0026gt; (1, 2, 1) -\u0026gt; (1, 3, 1) -\u0026gt; (2, 1, 1) 可以把联合索引理解为两个方向同时发生 创建索引时，列顺序从左到右固定为 a、b、c，组合键的比较也从左到右进行 查找时，B+树从根页向下导航，先依据 a 划分较大的子树范围，再在确定的 a 范围内依据 b 细分，最后依据 c 定位 这里的“从树顶向下”描述的是搜索过程，不是说树的每一层只存一列，实际内部结点保存的通常仍是完整的联合键 只有前面的列已经相等或确定边界，后面的列才有机会继续参与比较和计算 联合索引不是先为 a、再为 b、再为 c 各建一棵独立的树，而是把它们组合成一个有序键，在自上而下的搜索中逐步确定范围 固定 a=1 可以得到一段连续的叶子区间，固定 a=1,b=2 可以在这段区间中继续得到更小的连续区间 如果只给出 b=2，满足条件的键会分散在 a 的每个取值区间中，根结点无法仅凭 b 选择一条连续的子树范围 如果给出 a=1,c=3 而没有固定 b，可以先利用 a=1 定位较大的区间，但 c=3 会分散在不同的 b 分组中，通常只能在扫描时过滤 因此，最左前缀原则不是语法限制，而是联合键的物理排序决定了只有连续的前导列才能构成一次连续的树搜索范围 遇到范围查询为什么通常不能继续 在 (a,b,c) 上，如果 a=1 且 b\u0026gt;3，可以定位到 a=1 分组中 b 大于 3 的连续后缀 但在这个后缀中，c 的排序会在每个新的 b 值下重新开始，叶子顺序可能是 text(1, 4, 1) -\u0026gt; (1, 4, 3) -\u0026gt; (1, 4, 5) -\u0026gt; (1, 5, 1) -\u0026gt; (1, 5, 3) -\u0026gt; ... 条件 a=1 AND b\u0026gt;3 AND c=3 需要从每个 b 分组中挑出 c=3，这些键之间夹着其它 c 值，不再是一个连续区间 B+树 的一次范围扫描只能从某个位置开始向后读取连续叶子，不能在同一条树路径上跳过每个 b 分组中的其它 c 值 因此通常把 a 和 b 用于确定索引扫描范围，把 c=3 作为扫描过程中的索引条件或回表后的过滤条件 b\u0026gt;3 对应的不是一个具体 b 分组，而是多个 b 分组及其子页，每个子页内部都重新按照 c 排序 如果还要让 c=3 参与树上的定位，就必须对这些子页分别搜索，搜索过程中反复回到父页选择其它子页，或者沿叶子链表扫描后再过滤，这已经不是一条连续的 (a,b,c) 范围 从 B+树连续扫描范围的角度，通常会停止继续利用后续列的是 \u0026gt;、\u0026lt; 这类开放范围 为什么 \u0026gt;=、\u0026lt;=、BETWEEN 和前缀 LIKE 可以继续匹配 这里的“继续匹配”是指后续列可以参与某个扫描边界的计算，不等于后续列在整个范围内都保持全局有序 \u0026gt;= 可以拆成 a=10 OR a\u0026gt;10，其中 a=10 这一段仍然按照 b、c 排序，因此 a\u0026gt;=10 AND b=20 可以从接近 (10,20) 的位置开始扫描，跳过 a=10,b\u0026lt;20 的无关键 进入 a\u0026gt;10 的部分后，b 值仍然分散在每个 a 分组中，b=20 主要用于扫描过程中的继续匹配或过滤 \u0026lt;= 可以拆成 a\u0026lt;10 OR a=10，其中 a=10 是包含在结果中的等值端点，因此 a\u0026lt;=10 AND b=20 可以用接近 (10,20) 的位置作为上界 BETWEEN 10 AND 20 等价于 a\u0026gt;=10 AND a\u0026lt;=20，左右端点都包含，后续列可以帮助收紧两个端点，例如用 (10,20) 和 (20,20) 作为边界 在 a 位于 10 和 20 之间的部分，b 仍会随着不同的 a 分组重新排序，因此后续列并不是把整个内部区间都变成等值查找 前缀 LIKE 'abc%' 可以转换为从 abc 到下一个字典前缀之间的连续区间，左端点相当于包含了 a=abc 的等值边界，所以 LIKE 'j%' AND b=22 可以从接近 (j,22) 的位置开始扫描 扫描到 name 大于 j 的其它前缀值后，age 不再全局有序，仍需要沿叶子链表继续读取并过滤 LIKE '%abc' 没有固定的起始前缀，不能用同样的方式定位 与之相对，a\u0026gt;10 和 a\u0026lt;10 没有包含在结果中的等值端点，开放范围内部的后续列会在多个子页或分组中反复出现，通常只能由前面的列确定扫描范围 因此，执行计划中的 key_len 或后续索引条件表示后续列参与了边界计算、索引条件下推或扫描过滤，不应直接理解为后续列在整段范围内都能继续二分定位 判断最左前缀是否真正继续时，应区分两件事：索引是否用于定位扫描边界，以及扫描到索引项后是否还用索引键做过滤 ","permalink":"https://echo.ishian.me/2026/08/27/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-b-%E6%A0%91/","summary":"讲解B+树的结构原理、插入删除操作及其在数据库索引中的应用","title":"数据结构: B+树"},{"content":"Outbox：把数据库事务可靠地接到消息队列 在电商订单、支付、库存、积分等系统中，经常需要在修改数据库之后通知另一个服务：\n创建订单后通知库存服务 支付成功后通知履约服务 扣减库存后通知营销服务 修改用户状态后刷新搜索索引 最直观的代码通常是：\njava@Transactional public void createOrder(OrderCommand command) { orderRepository.insert(createOrder(command)); messageBroker.publish(new OrderCreated(...)); }但数据库事务只管理数据库，不能自动管理外部的消息队列。只要数据库提交和消息发送之间存在两个独立的操作，就存在双写不一致的故障窗口。\nOutbox 的核心做法是：\n在同一个数据库事务中保存业务数据和待发送的事件；事务提交后，再由独立的投递程序把事件发送到 MQ；MQ 确认成功后，才把事件标记为已发送。\n它通常实现的是：\ntext业务事务可靠提交 + 消息至少一次投递 + 消费端幂等数据库和 MQ 为什么会双写不一致 假设代码按下面的顺序执行：\ntext保存订单 提交数据库事务 发送 OrderCreated 消息可能出现以下情况：\n数据库提交成功，应用在发送消息前宕机：订单已经存在，但库存服务永远收不到消息 MQ 发送失败：订单存在，但下游状态没有推进 MQ 实际已经接收消息，生产者因为网络超时认为发送失败：重试后出现重复消息 MQ 发送成功，数据库事务随后回滚：下游收到一个不存在的订单事件 数据库提交结果未知：客户端或应用超时，重试可能创建重复订单 即使把发送消息放进带有事务注解的方法中，普通本地事务也不会自动覆盖 MQ。Publisher Confirm 也只能告诉生产者 Broker 是否接收了消息，不能保证数据库事务和消息发送是同一个原子操作。\nOutbox 的核心原理 Outbox 会在业务数据库中增加一张事件表。业务表和事件表必须参与同一个本地数据库事务：\nsqlBEGIN; INSERT INTO orders(order_id, status, total_amount) VALUES (\u0026#39;O1001\u0026#39;, \u0026#39;PENDING_PAY\u0026#39;, 199.00); INSERT INTO outbox_events( event_id, event_type, aggregate_id, payload, status, attempts, created_at ) VALUES ( \u0026#39;E9001\u0026#39;, \u0026#39;OrderCreated\u0026#39;, \u0026#39;O1001\u0026#39;, \u0026#39;{...}\u0026#39;, \u0026#39;PENDING\u0026#39;, 0, CURRENT_TIMESTAMP ); COMMIT;提交后，订单和 Outbox 事件只有两种结果：\n两者一起提交成功 两者一起回滚 之后由后台投递程序读取 PENDING 事件：\ntext读取 PENDING 事件 ↓ 发送到 MQ ↓ 等待 Publisher Confirm ↓ 确认成功 ↓ 标记为 SENT所以 Outbox 的顺序是：\ntext先保存，后发送；发送确认后再标记。不能先发送 MQ，再补写 Outbox。否则如果 MQ 已经发送成功，而数据库保存失败，就会产生一个没有业务事实支撑的孤立事件。\nOutbox 事件的状态 常见状态如下：\ntextPENDING ↓ 领取 SENDING ↓ Broker 确认 SENT SENDING --租约超时--\u0026gt; PENDING PENDING --确定性错误--\u0026gt; FAILED 或 DEAD状态的含义需要提前约定：\nPENDING：事件已经和业务数据一起提交，但还没有完成可靠投递 SENDING：某个投递程序暂时领取了事件 SENT：Broker 已经按配置确认接收，不代表消费者已经处理 FAILED：本次投递失败，可以继续重试或等待人工处理 DEAD：超过重试上限，进入死信或异常处理流程 SENT 不应该被理解成“订单已经扣库存”或“消息已经被业务消费”。如果需要知道消费结果，应由消费者更新自己的业务状态，或者发布新的成功/失败事件。\n业务数据和 Outbox 的保存 保存成功 业务数据和 Outbox 事件在一个事务中提交成功：\ntextorders = 成功 outbox_events = PENDING此时即使 MQ 完全不可用，事件也仍然保存在数据库中，投递程序恢复后可以继续发送。\n业务校验失败 例如商品不存在、金额不合法、用户没有权限：\ntext业务数据不保存 Outbox 事件也不保存这种情况不应该写一条需要重试的 Outbox 事件。业务拒绝和基础设施故障要区分开。\n事务回滚 数据库死锁、连接断开、唯一键冲突或业务主动抛出异常时，业务数据和 Outbox 事件都应回滚。\n如果只保存了业务数据，之后再用 afterCommit 回调发送消息，应用可能在回调执行前宕机。afterCommit 只能降低一部分延迟，不能代替可靠持久化。\n数据库提交结果未知 应用提交事务后，响应在网络中丢失，应用不知道数据库究竟成功还是失败。客户端重试时不能简单地重新插入一条订单。\n应使用请求幂等键或业务唯一键：\n同一个请求键只允许创建一笔订单 重试时先查询已有订单和状态 不能仅依赖订单号由客户端随机生成后直接插入 Outbox 保存失败 如果业务数据和 Outbox 在同一个数据库事务中，Outbox 写失败应该让整个事务回滚。不能接受“订单已经创建，但 Outbox 写失败后继续返回成功”。\n如果业务库和 Outbox 表不在同一个事务边界内，Outbox 就不能解决这两个存储之间的原子性问题。此时需要使用 CDC、事务消息、事件溯源或重新划分事实来源。\n投递程序如何领取事件 投递程序可以定时轮询，也可以通过数据库 CDC 发现新事件。最简单的轮询逻辑是：\ntext查询 status = PENDING 且 available_at \u0026lt;= 当前时间的事件 领取事件 提交领取状态 发送 MQ 根据结果更新状态多个投递实例同时运行时，不能让它们无限制地读取同一批事件。常见做法有：\n数据库行锁和 SKIP LOCKED 条件更新：只有 PENDING 才能改成 SENDING 租约：记录 lease_until 和 worker_id 分片扫描：按事件 ID、聚合 ID 或时间范围分配任务 网络发送期间不建议一直持有数据库行锁。更常见的方式是先用短事务领取事件并提交，再在事务外发送；发送完后用带条件的更新标记结果。\n例如：\nsqlUPDATE outbox_events SET status = \u0026#39;SENDING\u0026#39;, worker_id = \u0026#39;worker-a\u0026#39;, lease_until = CURRENT_TIMESTAMP + INTERVAL \u0026#39;1\u0026#39; MINUTE WHERE event_id = \u0026#39;E9001\u0026#39; AND status = \u0026#39;PENDING\u0026#39;;更新成功的实例才有资格继续发送。\n没有可发送事件 这是正常状态，不应该当成错误。投递程序可以等待下一次轮询或由 CDC 触发。\n领取成功后投递程序宕机 事件可能停留在 SENDING。租约到期后，其他投递程序应把它重新放回 PENDING：\ntextSENDING + lease_until \u0026lt; now → PENDING不能依赖人工修改数据库，否则少量异常就会变成永久积压。\n多个投递程序重复领取 即使使用了锁或条件更新，租约过短、网络抖动、进程暂停等情况仍可能造成同一事件被多个实例处理。因此领取机制只能减少重复发送，不能从根本上消除重复，event_id 幂等仍然需要存在。\n消息发送阶段 发送成功并收到 Confirm 流程如下：\ntext事件仍为 SENDING 发送 MQ Broker 返回成功确认 事件更新为 SENT此时可以认为 MQ 已经按照自己的持久化和副本配置接收了消息，但不能认为消费者已经完成业务处理。\n发送立即失败 例如 Topic 不存在、交换机不存在、权限错误、消息格式不合法：\n临时性错误：网络抖动、Broker 过载、连接断开，可以重试 确定性错误：权限错误、序列化错误、配置错误，不应该无限重试 确定性错误应记录详细错误，进入 FAILED 或 DEAD，并触发告警。\n发送超时 发送超时不等于 Broker 没有收到消息。可能是：\ntext消息没有到达 Broker 消息已经到达 Broker，但 Confirm 返回途中丢失 Broker 已确认，但生产者连接断开因此超时后不能直接认为发送失败。最安全的处理是使用同一个 event_id 重试，并让消费者幂等。\nBroker 已接收，投递程序还没有标记 这是最典型的重复发送窗口：\ntext发送成功 进程在更新 SENT 前宕机 事件仍然是 SENDING 或 PENDING 恢复后再次发送结果是 MQ 中出现两条相同 event_id 的消息。这个结果是可接受的，因为 Outbox 的设计目标是避免丢失，重复则交给消费者幂等处理。\nMQ 不可用或持续过载 事件应继续保留在 PENDING 或进入带退避时间的状态：\ntextattempts = attempts + 1 available_at = now + backoff(attempts) last_error = ...重试应使用指数退避和随机抖动，避免所有投递程序同时重试形成重试风暴。还需要限制单次扫描数量，防止积压把业务数据库读压力打满。\n发送后的标记阶段 Confirm 成功，标记 SENT 成功 这是理想路径：\ntextPENDING → SENDING → SENT可以记录 sent_at、broker_message_id 和最后一次确认信息，方便排查。\nConfirm 成功，标记 SENT 失败 例如数据库暂时不可用，状态更新事务失败。下一轮会再次发送同一事件。\n这会产生重复，但不能为了避免重复而在没有 Confirm 时提前标记 SENT，否则更容易造成消息丢失。\n标记 SENT 成功，但消费者还没有处理 这是正常的。SENT 只描述生产端和 Broker 之间的关系，不描述消费者状态。\n消费者如果宕机，消息应留在 MQ 中等待重新投递；如果消费者已经 ACK 但业务状态异常，则需要由消费者自己的幂等记录和补偿机制处理。\n旧投递程序覆盖新状态 如果使用租约，旧实例可能在租约过期后恢复，并继续把事件标记为 SENT 或 FAILED，覆盖新实例的结果。\n更新状态时应携带 worker_id 或 lease token：\nsqlUPDATE outbox_events SET status = \u0026#39;SENT\u0026#39;, sent_at = CURRENT_TIMESTAMP WHERE event_id = \u0026#39;E9001\u0026#39; AND status = \u0026#39;SENDING\u0026#39; AND worker_id = \u0026#39;worker-a\u0026#39; AND lease_token = \u0026#39;token-a\u0026#39;;这样旧实例的过期操作不会覆盖新实例。\n是否应该立即删除 SENT 记录 通常不应该立即删除。Outbox 记录可以用于：\n审计某个业务事件是否生成 排查消息发送延迟 重放消息 对账和补偿 分析某段时间的故障 可以在保留足够长的时间后归档或删除。删除前要确认消费者不再需要回放，也要避免清理任务和投递程序互相干扰。\n消费阶段 消费者的正确顺序通常是：\ntext收到消息 ↓ 校验消息和版本 ↓ 检查幂等记录 ↓ 开启本地业务事务 ↓ 写入消费记录并修改业务数据 ↓ 提交事务 ↓ ACK消费者的 Inbox 表可以记录：\ntextevent_id consumer_name aggregate_id status processed_at last_errorevent_id 和 consumer_name 通常建立唯一约束。\n消费前宕机 消息还没有执行业务逻辑，消费者宕机后重新收到消息。没有产生业务副作用，直接重试即可。\n业务事务执行失败 例如数据库死锁、连接超时，可以重新投递。消费者不要在失败时 ACK，否则消息会被认为已经处理完成。\n业务事务提交成功，但 ACK 失败 这是消费端最常见的重复窗口：\ntext业务数据已经提交 消费者还没来得及 ACK 就宕机 MQ 再次投递同一消息消费者必须通过 event_id 或业务唯一键识别重复消息，然后直接返回成功并 ACK，不能再次扣库存、发积分或创建订单。\n先写 Inbox，后写业务数据 这种顺序有风险：\ntextInbox 标记已处理 业务数据写入失败 消息再次到来时被 Inbox 拦截 业务数据永远不会被处理如果 Inbox 和业务数据在同一个数据库中，应让两者进入同一个事务：\ntextBEGIN 写入 Inbox 修改业务数据 COMMIT ACK如果两者不在同一个事务边界内，就需要状态机、补偿任务或另一个 Outbox，不能只把 Inbox 当成简单的“已读标记”。\n消费者调用外部服务成功，但本地事务失败 例如消费者已经调用支付、短信或发货服务成功，随后本地数据库提交失败。外部调用无法随着本地事务自动回滚。\n解决方式包括：\n外部接口使用业务幂等键 记录调用状态并可查询 用本地 Outbox 推进下一步 使用 Saga 补偿，例如退款、释放库存 使用定时对账发现并修复异常状态 不要假设“数据库事务回滚后外部服务也会回滚”。\n确定性业务失败 例如订单已关闭、库存不足、金额不一致，这类错误重试通常不会改变结果。可以：\n推进业务状态到失败 发布失败事件 进入人工处理或补偿流程 ACK 原消息，避免无限重试 暂时性技术失败 例如数据库短暂不可用、依赖服务超时，应该进行有限次重试。重试次数、退避间隔和最大延迟都要有上限。\n毒消息和死信 如果一条消息无论如何都无法成功，持续重试会阻塞同一队列或分区。超过阈值后应进入 DLQ，并保留：\n原始消息 event_id 失败原因 重试次数 最后一次异常堆栈 首次失败时间和最后失败时间 DLQ 不是丢弃区，而是需要告警、查询、修复和安全重放的异常队列。\nOutbox 表应该保存什么 典型字段如下：\ntextevent_id 全局唯一事件 ID event_type 事件类型 aggregate_type 聚合类型，例如 Order aggregate_id 聚合 ID，例如 order_id aggregate_version 业务聚合版本 payload 事件快照 headers trace_id、租户等元数据 status PENDING、SENDING、SENT、FAILED attempts 已尝试次数 available_at 下次可投递时间 worker_id 当前领取者 lease_until 领取租约截止时间 lease_token 防止旧实例覆盖状态 last_error 最近一次错误 created_at 创建时间 sent_at 发送成功时间payload 最好是不可变的事件快照。消费者不应该只拿 order_id，再去读取一个已经变化的订单状态来猜测事件发生时的内容，否则重放时可能得到不同结果。\n事件格式还应包含 schema version。以后增加字段时尽量向后兼容，避免旧消费者因为新字段直接失败。\n顺序、重复和幂等 Outbox 本身不自动保证顺序。\n如果同一个订单依次产生：\ntextOrderCreated PaymentSucceeded OrderClosed多个投递程序、不同事务提交时间和 MQ 分区策略都可能导致事件乱序。\n需要顺序时，通常采用：\n以 order_id、user_id 或 sku_id 作为消息 Key 相同 Key 路由到同一队列或分区 同一个 Key 同时只允许一个消费任务 每个事件带 aggregate_version 消费者拒绝旧版本，或等待缺失版本 严格有序时，前一条失败要阻塞同一 Key 的后续事件 如果业务只需要最终状态，不需要每一个中间事件，那么发送状态快照或使用版本号覆盖旧事件，可能比强行保证所有事件有序更简单。\n幂等也要区分范围：\nevent_id 幂等：同一条逻辑消息重复投递不会重复处理 业务键幂等：同一订单、支付单或库存操作不会重复执行 外部调用幂等：重试外部 API 不会重复扣款或发货 Outbox 解决了什么问题 Outbox 主要解决以下问题：\n数据库事务提交和消息发布之间的双写不一致 应用在发送消息前宕机造成的事件丢失 MQ 暂时不可用时的可靠积压 发送失败后的可控重试 业务事件的审计、补偿和重放 生产端消息发送与业务数据库之间的清晰事实边界 它把“必须发送的事件”先变成数据库中的事实，再把“发布到 MQ”变成一个可以重试的后台过程。\nOutbox 不解决什么问题 Outbox 不是万能的分布式事务方案：\n不保证端到端 exactly-once 不保证消息不重复 不自动保证消费者顺序 不自动保证多个服务最终状态一致 不会回滚已经成功的外部 API 调用 不会替代 MQ 的持久化、副本和确认配置 不会自动处理库存、支付和订单之间的业务补偿 不会消除数据库和投递程序本身的运维成本 它提供的是可靠的事件出口，消费者仍然需要 Inbox、幂等、重试、死信和对账。\nPublisher Confirm 和 Outbox 的关系 Publisher Confirm 只确认生产者到 Broker 的一段链路：\ntextProducer → BrokerOutbox 解决的是：\ntext业务数据库事务 → 待发布事件两者应组合使用：\ntext业务数据 + Outbox：同一个数据库事务 ↓ 投递程序发送 MQ ↓ Publisher Confirm ↓ Outbox 标记 SENTPublisher Confirm 不能代替 Outbox，因为它无法解决：\n数据库提交成功但还没有发送消息 MQ 发送成功但数据库事务回滚 应用在数据库提交和发送消息之间宕机 如果 MQ 本身就是系统的事实来源，数据库只是消费者生成的查询副本，才可以考虑不使用 Outbox，而直接依赖 MQ 的持久化、确认、重放和消费者幂等。\nOutbox 和其他方案 轮询式 Outbox 投递程序定期扫描数据库，是最容易理解和落地的方式。\n优点：\n实现简单 依赖普通数据库和 MQ 失败记录容易查询 可以人工重放 缺点：\n轮询会产生额外数据库负载 需要处理批量领取和并发投递 高吞吐时数据库扫描可能成为瓶颈 投递延迟受轮询周期影响 中小规模订单系统通常从这种方式开始。\nOutbox 加 CDC 业务事务仍然先写 Outbox，但不再由应用轮询，而是由数据库日志或 Change Stream 捕获新记录，再投递到 MQ。\n优点：\n减少轮询负载 事件发现更及时 更适合高吞吐系统 发布器可以独立扩展 缺点：\n引入 CDC 组件和运维复杂度 需要处理位点、断点恢复和重复投递 需要维护数据库变更到业务事件的映射 CDC 只能发现数据库变化，不能自动设计出正确的业务事件 当 Outbox 记录量很大、轮询已经明显影响数据库时，Outbox 加 CDC 往往比继续堆轮询线程更好。\nMQ 事务消息 Kafka 事务、RocketMQ 事务消息等可以把消息发布和 MQ 内部的事务协调起来。\n它们适合：\nMQ 本身是核心事实来源 业务状态也在消息系统能够协调的事务范围内 希望原子写入多个 Topic 或同时提交消费位点 但外部数据库通常不自动参加这个事务。数据库仍然需要 Outbox、CDC 或其他一致性方案。事务消息不是把任意数据库和任意 MQ 变成 XA 事务。\nXA 或两阶段提交 XA 可以尝试让数据库和消息中间件参加一个全局事务。\n优点：\n理论上的原子提交和回滚 调用方不需要自己处理部分状态 缺点：\n持有资源时间长 协调器和参与者故障时容易阻塞 性能和可用性下降 中间件支持和运维复杂 网络分区时很难同时满足一致性和可用性 互联网订单系统通常更倾向于本地事务加 Outbox，再用幂等和补偿完成最终一致，而不是把所有操作放进长时间的 XA 事务。\nMQ 作为第一事实来源 也可以先把订单命令或业务事件可靠写入 MQ，再由消费者写数据库。\n这种模式适合事件流、日志采集和事件溯源系统，但它会改变系统的读取方式：\n数据库变成派生状态 需要能够从消息重放构建数据库 用户查询可能暂时读不到最新状态 事件 schema、版本迁移和回放都变得重要 它不是 Outbox 的简单替代，而是另一种系统架构。\n不使用 MQ 的同步处理 如果操作必须立即返回确定结果，且业务量并不需要削峰，直接在一个本地数据库事务中完成可能更简单：\ntext校验 扣库存 创建订单 提交事务 返回结果没有必要为了“看起来分布式”而给一个本来可以同步完成的操作增加 MQ、Outbox、重试和补偿。\n电商订单中的典型用法 一个常见的订单状态流转是：\ntextPENDING ↓ 库存锁定成功 WAIT_PAY ↓ 支付回调验签成功 PAID ↓ FULFILLING ↓ COMPLETED WAIT_PAY --超时--\u0026gt; CLOSED CLOSED --补偿--\u0026gt; 释放库存订单服务可以在本地事务中保存订单和 OrderCreated Outbox 事件：\ntext订单 = PENDING Outbox = OrderCreated / PENDING库存服务消费 OrderCreated：\n以 order_id 做幂等 成功则锁定库存 失败则发布库存不足事件或关闭订单 只有库存锁定成功后，才允许用户进入支付，或者明确告诉用户订单仍在排队确认 支付回调成功后，支付服务或订单服务在同一个本地事务中：\ntext更新订单为 PAID 保存 OrderPaid Outbox 事件履约、积分、优惠券等服务再分别消费 OrderPaid。每个服务都有自己的本地事务和幂等边界。\n库存、订单、支付跨服务时，不能指望一个 Outbox 把它们变成一个全局事务。需要 Saga 状态机、超时关闭、库存释放、退款和定时对账。\n生产环境的注意事项 业务数据和 Outbox 必须在同一个可提交事务中 不要在业务事务提交后只依赖内存回调发送消息 event_id 必须稳定且唯一，消费者必须幂等 发送超时按“结果未知”处理，允许重复，不要贸然丢弃 Publisher Confirm 成功后才能标记 SENT 发送 MQ 时不要长时间持有数据库行锁 使用租约避免 SENDING 永久卡住 用条件更新防止旧投递程序覆盖新状态 瞬时错误和确定性错误要分开处理 重试应有上限、退避和随机抖动 死信队列必须可查询、可告警、可安全重放 ACK 必须在业务事务提交之后 Inbox 记录和业务更新尽量放在同一个消费者事务中 外部 API 必须使用幂等键，并准备查询和补偿 事件 payload 要版本化，不要依赖随时变化的数据库记录重建历史 对同一聚合需要顺序时，使用业务 Key 和版本号 监控 Outbox 积压数量、最老事件年龄、发送延迟、失败次数、SENDING 超时、消费者重复率、消费延迟和 DLQ 数量 SENT 记录不要立即删除，保留足够时间用于审计、重放和对账 对保存后、发送前、Confirm 后、标记前、消费提交后、ACK 前等故障窗口做故障注入测试 Outbox 最重要的理解可以压缩成一句话：\ntext把业务事件先作为数据库事实保存，再把发布消息变成可重试的异步动作； 接受至少一次投递，用幂等和补偿处理重复与跨服务失败。","permalink":"https://echo.ishian.me/2026/08/25/outbox/","summary":"讲解Outbox模式如何解决数据库与消息队列双写不一致，实现可靠事件投递","title":"Outbox：把数据库事务可靠地接到消息队列"},{"content":"Go的模板 基本概念 Go通过text/template标准库提供数据驱动的模板，用于生成文本输出，配置文件、邮件正文等都可以使用它\n模板由普通文本和action组成，普通文本会原样复制到输出，action使用{{和}}包围：\ngotemplateHello, {{.Name}}! Execute()执行模板时会把传入的数据绑定到.，.称为dot，模板执行过程中可以随着with、range等语句切换\ntext/template默认不会对输出进行转义，模板作者必须是可信的；生成HTML时应该使用接口相同但带上下文自动转义的html/template\n模板解析成功后可以并发执行，但多个执行过程如果共用同一个io.Writer，输出可能互相交错\n执行模板 最基本的流程是创建模板、解析模板文本、传入数据执行：\ngopackage main import ( \u0026#34;os\u0026#34; \u0026#34;text/template\u0026#34; ) type Inventory struct { Material string Count uint } func main() { data := Inventory{Material: \u0026#34;wool\u0026#34;, Count: 17} tmpl, err := template.New(\u0026#34;inventory\u0026#34;).Parse( \u0026#34;{{.Count}} items are made of {{.Material}}\u0026#34;, ) if err != nil { panic(err) } if err := tmpl.Execute(os.Stdout, data); err != nil { panic(err) } } template.New(name)创建模板对象，name用于标识模板本身，不是模板文件名\nParse(text)只负责解析模板文本，语法错误会在这里返回\nExecute(writer, data)执行模板并写入writer，执行阶段的字段访问、函数调用、方法调用错误也会通过返回值报告\ntemplate.Must(tmpl, err)在err不为nil时直接panic，适合模板文本固定且程序启动时就应该失败的场景，不适合解析用户输入\n数据访问与dot 初始的.就是传递给Execute()的data，可以访问结构体字段、map键和无参数方法：\ngotemplate{{.Title}} {{.User.Name}} {{.Config.timeout}} {{.DisplayName}} 结构体字段必须是公有字段；map的键名可以是小写字母开头的标识符\n方法必须是无参数方法，并且返回一个值，或者返回(value, error)；第二种形式返回非空错误时，模板执行会立即结束\n访问链可以混合字段、键和方法，例如.User.Profile.Name\n初始的根数据也可以通过$访问。进入range或with后.可能已经改变，但$仍然指向本次执行的根数据：\ngotemplate{{range .Items}} {{$.Title}}: {{.Name}} {{end}} action与管道 普通表达式 {{.}}输出当前dot的默认文本表示，{{.Name}}输出字段值\n可以使用布尔值、字符串、数字、nil、变量、字段、键、方法和函数作为参数：\ngotemplate{{\u0026#34;hello\u0026#34;}} {{42}} {{.Name}} 注释写在action内部，不会出现在输出中：\ngotemplate{{/* 这是一段模板注释 */}} 空白裁剪 action外部的普通文本默认全部保留，包括换行和缩进；可以在分隔符中加入-来裁剪相邻的空白\n{{-会裁剪action前面普通文本末尾的空格、制表符、回车和换行：\ngotemplateA {{- \u0026#34;B\u0026#34;}} C上面的模板会输出AB\\nC，左侧的换行和缩进都会被裁剪\n-}}会裁剪action后面普通文本开头的空白：\ngotemplateA{{\u0026#34;B\u0026#34; -}} C上面的模板会输出ABC，右侧的换行、制表符和缩进都会被裁剪\n两侧同时使用可以去掉模板控制语句制造的空行：\ngotemplateA {{- \u0026#34;B\u0026#34; -}} C输出为ABC\n-旁边的空白是语法的一部分：{{-3}}表示输出负数-3，不是左裁剪；{{- 3}}才表示左裁剪后输出3\n空白裁剪只作用于action两侧原本存在的普通文本，不会修改函数或字段本身产生的字符串内容\n管道 管道使用|连接多个表达式，前一个表达式的结果会作为后一个函数的最后一个参数：\ngotemplate{{.Name | printf \u0026#34;Hello, %s!\u0026#34;}}上面的写法等价于调用printf(\u0026quot;Hello, %s!\u0026quot;, .Name)\n管道适合将格式化逻辑串起来，例如：\ngotemplate{{.Title | printf \u0026#34;%q\u0026#34;}} text/template内置了常用函数：\nand、or、not：逻辑运算 eq、ne、lt、le、gt、ge：比较值 len：获取数组、切片、map、字符串等值的长度 index：按索引或键访问数组、切片、字符串、map slice：对数组、切片或字符串进行切片 print、printf、println：格式化输出 call：调用一个函数值 空值 在if、with和range中，以下值会被当作空值：false、0、nil指针或接口，以及长度为0的数组、切片、map和字符串\n空值的判断不需要显式调用函数：\ngotemplate{{if .User}} 欢迎回来，{{.User.Name}} {{else}} 请先登录 {{end}} 条件与循环 if if只在管道结果非空时渲染内容，支持else以及直接连接的else if：\ngotemplate{{if eq .Status \u0026#34;success\u0026#34;}} 成功 {{else if eq .Status \u0026#34;pending\u0026#34;}} 处理中 {{else}} 失败 {{end}} if不会改变.，条件块内仍然可以访问外层数据\nrange range用于遍历数组、切片、map、通道等值：\ngotemplate{{range .Items}} - {{.Name}} {{else}} 暂无数据 {{end}} 循环体中的.会切换为当前元素，因此需要访问根数据时使用$\n可以声明索引和元素变量。对数组、切片和字符串来说，第一个变量是索引；对map来说是键：\ngotemplate{{range $index, $item := .Items}} {{$index}}: {{$item.Name}} {{end}} map的遍历顺序不应依赖；当键是有确定顺序的基本类型时，模板会按键的排序顺序访问\nwith with适合在数据非空时切换.，可以减少重复的字段链：\ngotemplate{{with .User.Profile}} 姓名：{{.Name}} 邮箱：{{.Email}} {{else}} 没有个人资料 {{end}} with块内的.是.User.Profile，但$仍然是根数据\n模板变量 模板变量以$开头，可以保存管道结果：\ngotemplate{{$title := .Title}} 标题：{{$title}} 变量可以在后续通过=重新赋值，但变量的作用域只到当前控制结构的end，或当前模板的结尾\nrange可以同时声明变量，因此通常用来获取索引和元素；range的else分支中，这些变量不会被设置为新的循环值\n自定义函数 通过template.FuncMap可以把Go函数暴露给模板：\ngopackage main import ( \u0026#34;strings\u0026#34; \u0026#34;text/template\u0026#34; ) var funcs = template.FuncMap{ \u0026#34;upper\u0026#34;: strings.ToUpper, \u0026#34;join\u0026#34;: strings.Join, } var tmpl = template.Must( template.New(\u0026#34;page\u0026#34;). Funcs(funcs). Parse(`{{upper .Title}}: {{join .Tags \u0026#34;, \u0026#34;}}`), ) 函数必须在Parse()之前通过Funcs()注册，因为解析阶段就需要确认函数名存在\n函数可以返回一个值，也可以返回(value, error)；如果第二个返回值是非空错误，模板执行会停止并返回这个错误\n暴露给模板的函数应该保持简单、确定且容易测试，避免在模板函数中修改状态或访问外部资源\n模板适合展示和格式化，数据查询、权限判断和复杂计算应该在执行前完成，避免把业务逻辑堆积到模板函数中\nSprig Masterminds/sprig是一个专门为Go模板提供函数的库，弥补了标准库只提供少量基础函数的不足 Sprig不改变text/template的解析和执行模型，本质上仍然是通过Funcs()向模板注册一组函数 引入依赖：go get github.com/Masterminds/sprig/v3 注册函数 使用Sprig时，先把函数表注册到模板，再进行Parse()：\ngoimport ( \u0026#34;text/template\u0026#34; sprig \u0026#34;github.com/Masterminds/sprig/v3\u0026#34; ) var tmpl = template.Must( template.New(\u0026#34;page\u0026#34;). Funcs(sprig.TxtFuncMap()). Parse(`{{.Name | upper}}: {{.Tags | join \u0026#34;, \u0026#34;}}`), ) TxtFuncMap()适用于text/template，HtmlFuncMap()适用于html/template\nFuncs()必须在Parse()之前调用，因为解析阶段就需要确认模板中的函数名存在\nSprig函数可以和标准库内置函数、自己的FuncMap混合使用；同名函数后注册的定义会覆盖先注册的定义，因此应该避免命名冲突\n函数分类 字符串：upper、lower、trim、trimPrefix、trimSuffix、replace、contains、quote、cat\n列表：list、first、last、rest、append、prepend、concat、uniq、without、has\n字典：dict、get、set、hasKey、pluck、merge、mergeOverwrite、dig\n数学与逻辑：add、sub、mul、div、max、min、default、coalesce、ternary\n编码与摘要：toJson、fromJson、b64enc、b64dec、sha256sum\n时间和格式化：now、date、dateModify、ago\n上面只是常用函数的分类，具体函数的参数类型仍然要以当前引入的Sprig版本为准；模板函数通常要求参数类型可赋值，不能像动态语言一样任意转换\n列表与字典 list可以创建模板列表，dict可以创建字符串键的字典，再结合join、get和hasKey生成配置或消息：\ngotemplate{{$ports := list 80 443}} ports: {{join \u0026#34;,\u0026#34; $ports}} {{$labels := dict \u0026#34;app\u0026#34; .Name \u0026#34;tier\u0026#34; \u0026#34;web\u0026#34;}} {{if hasKey $labels \u0026#34;app\u0026#34;}} app: {{get $labels \u0026#34;app\u0026#34;}} {{end}} 管道可以把多个函数组合起来，例如：\ngotemplateimage: {{.Tag | default \u0026#34;latest\u0026#34; | quote}} tags: {{.Tags | uniq | join \u0026#34;, \u0026#34;}} default、coalesce等函数沿用模板的“空值”判断，空字符串、0、false、空列表和nil都可能触发默认分支，不能把“未配置”和“显式配置为零值”混为一谈\nset、merge等字典函数会修改或合并字典；如果同一个字典在多个模板片段之间复用，需要留意它们共享同一份数据带来的副作用\n可复现性与安全边界 Sprig中有些函数依赖当前时间、随机数或环境变量，这些函数会降低输出的可复现性；配置和构建模板应该谨慎使用 Sprig本身不负责HTML上下文转义；生成HTML时仍然应该使用html/template和合适的函数表，不能因为引入Sprig就把不可信数据标记为安全内容 不要把完整的函数表无条件暴露给不可信的模板作者；模板源代码本身需要可信，涉及环境、文件或随机性的函数也应按场景限制 命名模板 可以通过define声明可复用的命名模板，通过template执行它：\ngotemplate{{define \u0026#34;item\u0026#34; -}} - {{.}} {{- end}} {{define \u0026#34;list\u0026#34; -}} {{range .Items}}{{template \u0026#34;item\u0026#34; .}} {{end -}} {{- end}} {{template \u0026quot;item\u0026quot; .}}会把当前.传给item；如果省略数据参数，命名模板收到的是nil\n解析后通过ExecuteTemplate(writer, name, data)可以执行指定名称的模板：\ngovar output bytes.Buffer if err := tmpl.ExecuteTemplate(\u0026amp;output, \u0026#34;list\u0026#34;, data); err != nil { return err } 多个文件可以通过ParseFiles()、ParseGlob()或ParseFS()解析，适合把页面布局、片段和具体页面拆分到不同文件\n从embed.FS加载模板 ParseFS()可以直接从io/fs读取模板，配合embed可以把模板文件编译进二进制：\ngoimport ( \u0026#34;embed\u0026#34; \u0026#34;text/template\u0026#34; ) //go:embed templates/*.tmpl var templateFS embed.FS var tmpl = template.Must( template.ParseFS(templateFS, \u0026#34;templates/*.tmpl\u0026#34;), ) 模板文件中的命名模板可以通过ExecuteTemplate()选择执行；直接解析的文件模板通常使用文件的基础名称作为模板名\n固定模板可以在包初始化时解析并复用，不要在每次请求到来时重复读取和解析文件\n处理缺失键与执行错误 当.是map时，访问不存在的键默认不会在解析阶段报错，执行结果可能是\u0026lt;no value\u0026gt;\n可以通过Option(\u0026quot;missingkey=error\u0026quot;)让缺失键直接使执行失败：\ngotmpl, err := template.New(\u0026#34;config\u0026#34;). Option(\u0026#34;missingkey=error\u0026#34;). Parse(`name={{.Name}}`) if err != nil { return err } Execute()可能已经写入一部分结果后才遇到错误，因此需要对重要输出先写入bytes.Buffer，确认执行成功后再写入最终的io.Writer：\ngovar output bytes.Buffer if err := tmpl.Execute(\u0026amp;output, data); err != nil { return err } _, err := writer.Write(output.Bytes()) return err 模板解析错误和执行错误都应该显式处理；只检查Parse()而忽略Execute()的返回值，会把运行时数据错误伪装成成功响应\nhtml/template html/template和text/template的模板语法基本一致，但会根据输出位置对数据进行上下文转义：\ngopackage main import ( \u0026#34;bytes\u0026#34; \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; ) var page = template.Must(template.New(\u0026#34;page\u0026#34;).Parse(` {{define \u0026#34;page\u0026#34;}} \u0026lt;!doctype html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;{{.Title}}\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt;\u0026lt;h1\u0026gt;{{.Title}}\u0026lt;/h1\u0026gt;\u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; {{end}} `)) func handler(w http.ResponseWriter, r *http.Request, data any) { var output bytes.Buffer if err := page.ExecuteTemplate(\u0026amp;output, \u0026#34;page\u0026#34;, data); err != nil { http.Error(w, \u0026#34;render failed\u0026#34;, http.StatusInternalServerError) return } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;text/html; charset=utf-8\u0026#34;) _, _ = w.Write(output.Bytes()) } 同一个值放在普通文本、HTML属性、URL、CSS或JavaScript上下文中时，html/template会采用不同的转义策略\ntext/template不会自动转义，不能用来直接生成包含不可信数据的HTML\ntemplate.HTML等安全类型可以让开发者声明一段内容已经安全，但这会绕过对应的自动转义，只能用于已经验证过的可信内容\n模板源代码本身仍然必须可信：自动转义保护的是执行数据，不是恶意模板作者\n常见用法与项目 云原生工具通常复用Go template的语法模型，再注入自己的数据上下文和函数集合；因此同样的{{if}}、{{range}}在不同项目中可用的对象并不完全相同\nHelm：helm/helm在Chart的templates/目录中渲染Kubernetes清单，向模板提供.Values、.Release、.Chart等对象，并额外提供include、required、tpl等函数；大量基础辅助函数来自Sprig\ninclude、required、tpl是Helm自己的扩展，nindent、toYaml等则来自Helm或其依赖；这些函数都不能直接复制到只有标准库的Go程序中\nkubectl：Kubernetes的kubectl支持-o go-template和-o go-template-file，可以直接从资源对象中提取字段：\nshkubectl get pods -o go-template=\u0026#39;{{range .items}}{{.metadata.name}}{{\u0026#34;\\n\u0026#34;}}{{end}}\u0026#39; 监控告警：Prometheus Alertmanager和Grafana都使用Go template语法生成告警标题、正文和通知消息，但.Alerts、.Labels以及查询函数等上下文由各自项目定义\n配置生成：hashicorp/consul-template使用模板从Consul、Vault等外部数据源生成配置文件或触发命令，适合需要随外部配置变化自动渲染的场景\n这些项目的共同点是复用模板的语法模型，差异则主要来自数据上下文和函数集合；阅读云原生模板时，首先要确认它是标准库模板、Sprig扩展，还是Helm、Grafana等项目的进一步扩展方言\n","permalink":"https://echo.ishian.me/2026/08/18/go-template/","summary":"介绍 Go 模板的基本概念、action 与管道、条件循环等语法及执行方式","title":"Go: 模板"},{"content":"Helm 快速入门 简介 Helm 是 k8s 官方推荐的包管理器，定位类似于 apt/brew：把一组相互关联的 k8s 资源(Deployment、Service、Ingress\u0026hellip;)打包为 Chart，一条命令完成安装、升级与回滚 相比手写一堆 yaml 再 kubectl apply，helm 解决了三个问题： 参数化：同一份模板通过不同的 values 部署到不同环境，而不是复制出多份几乎相同的 yaml 生命周期：Release 记录每次发布的版本与清单，升级失败可一键回滚到任意历史版本 复用：第三方应用(mongodb、prometheus\u0026hellip;)直接复用社区 Chart，不必自己从零编写 官方文档 核心概念 Chart：一个应用完整的模板集合，即 helm 的\u0026quot;包\u0026quot; Release：Chart 的一次安装实例，同一 Chart 可以在同一集群安装多次，每次都是独立的 Release，靠 Release 名称区分 Repo：存放 Chart 的仓库，类似 docker hub，每个仓库由一个 index.yaml 索引 Chart 结构 textmy-chart/ ├── Chart.yaml # Chart 的元信息与依赖声明 ├── values.yaml # 默认参数，渲染模板时的兜底值 ├── values-prod.yaml # 环境覆盖文件，可选 ├── Chart.lock # 依赖锁文件，由 dependency update 生成 ├── charts/ # 依赖的子 Chart，由 dependency update 下载 ├── templates/ │ ├── _helpers.tpl # 命名模板(下划线开头不渲染为资源)，沉淀命名规范 │ ├── deployment.yaml │ ├── service.yaml │ └── NOTES.txt # 安装成功后打印的使用说明 └── .helmignore # 打包时忽略的文件，类似 .gitignore Chart.yaml 关键字段：\nyamlapiVersion: v2 name: my-chart description: Helm Chart for my app type: application # application(可部署)/library(仅提供模板) version: 0.0.0 # Chart 自身版本，生产实践可固定为 0.0.0 交给 CI 注入 appVersion: \u0026#34;1.0.0\u0026#34; # 内含应用的版本，仅作描述，不参与渲染 dependencies: # 依赖的子 Chart - name: mongodb # 上游 Chart 名称 version: 15.6.1 # 依赖版本 repository: https://charts.bitnami.com/bitnami # 上游仓库 alias: mongodb # 别名，同一依赖可按别名多次引入 condition: mongodb.enabled # 根据 values 开关决定是否渲染 命名规范：Chart 名称小写字母与数字；version 与 appVersion 是两个独立概念，前者是模板的版本，后者是应用的版本\n常用命令 安装与升级 helm install [release] [chart] -n [ns] --create-namespace -f values-prod.yaml：安装一个 Release，命名空间不存在时自动创建 helm upgrade [release] [chart] -f values-prod.yaml：升级已存在的 Release --install：常与 upgrade 合用为 helm upgrade --install(可缩写 -i)，不存在则安装、存在则升级，是 CI 中的标准写法 --atomic：升级失败自动回滚到上一版本，生产发布必备 --wait：等待所有资源就绪才返回，配合探针保证发布串行化 --timeout 10m：--wait 的超时时间 helm uninstall [release] -n [ns]：卸载 Release，删除其管理的所有资源(Secret/PVC 等是否保留取决于资源的安装顺序注解) 查询与回滚 helm list -n [ns] -a：列出 Release，-a 包含已卸载的 helm history [release]：查看发布历史与状态 helm rollback [release] [revision]：回滚到指定版本，不指定版本则回退一个 helm get values [release]：查看当前生效的 values；helm get manifest [release]：查看实际渲染进集群的清单，排查\u0026quot;模板到底渲染了什么\u0026quot;的第一入口 helm status [release]：查看 Release 状态 本地调试 helm create [name]：生成一个带示例模板的 Chart 骨架 helm lint：静态检查 Chart 的规范问题 helm template [chart] -f values-prod.yaml：本地渲染模板并打印结果，不接触集群，调试模板最常用的命令 --show-only templates/deployment.yaml：只看单个文件的渲染结果 --debug：渲染报错时输出完整堆栈 helm diff upgrade [release] [chart](需安装 helm-diff 插件)：升级前预览集群内清单与待应用清单的差异，Code Review 的好帮手 依赖与仓库 helm dependency update：按 Chart.yaml 下载依赖到 charts/ 并生成 Chart.lock helm dependency build：按 Chart.lock 锁定的版本下载，保证可复现构建，CI 中应使用此命令 helm repo add [name] [url] / helm repo update：添加仓库并刷新索引 helm package [chart]：打包为 .tgz；helm push 推送到仓库 模板语法 对象与取值 渲染上下文的顶层对象用 . 访问，常用的有：\n.Values：合并后的参数(默认 values.yaml + -f 文件 + --set) .Release.Name / .Release.Namespace：Release 的名称与命名空间，是资源命名的第一来源 .Chart.Name / .Chart.AppVersion：Chart 元信息 .Files / .Capabilities：访问 Chart 内文件、查询集群版本 {{ }} 输出值，{{- }} 去掉前侧空白，{{ -}} 去掉后侧空白，常用于压缩渲染结果中的空行\nyamlmetadata: name: {{ .Release.Name }} # 资源一律以 Release 命名，同一 Chart 可多实例部署 常用函数与管道 函数通过管道 | 串联，最常用的组合是 toYaml | nindent：把一段 values 结构原样转成 yaml 并整体缩进，是\u0026quot;参数块透传\u0026quot;的标准写法\nnindent 比 indent 多一个前置换行，避免值被拼接到上一行尾部，优先使用 nindent\nyamltolerations: {{- .Values.tolerations | toYaml | nindent 8 }} 其他高频函数：\nquote：加引号，数字类值(cpu: 2)必须加，否则被渲染为数字而校验失败 default：兜底默认值，{{ .Values.replicas | default 1 }} required：缺失即报错并中断渲染，用于强制用户提供关键参数 upper / replace：转换 values 键名，如把 feishu-app-id 转成环境变量名 FEISHU_APP_ID randAlphaNum 5：随机字符串，配合注解实现\u0026quot;每次发布强制滚动重启\u0026quot; printf：格式化字符串，{{ printf \u0026quot;%s-%s\u0026quot; .Release.Namespace .Release.Name }} 控制结构 if/else if/else：条件渲染，判空用 not (empty .Values.x) 而非直接判布尔(0、\u0026quot;\u0026quot;、空列表都是假值，容易误判)\nyaml{{- if not (empty .Values.secrets) }} env: {{- range $key, $value := .Values.secrets }} - name: {{ $key | upper | replace \u0026#34;-\u0026#34; \u0026#34;_\u0026#34; }} valueFrom: secretKeyRef: name: {{ $.Release.Name }}-secrets key: {{ $key }} {{- end }} {{- end }} range：遍历列表或字典，range $idx, $bucket := .Values.storage.buckets 同时取下标与值\nwith：切换作用域，with .Values.gateway 内部直接用 .hosts；作用域内访问顶层需用根对象 $(如 $.Release.Name)\nhasKey：判断键是否存在，{{ if and (not .Values.hpa.enabled) (hasKey .Values \u0026quot;replicas\u0026quot;) }}\n命名模板 复杂的命名规则(截断长度、拼接 Chart 名与 Release 名)沉淀在 _helpers.tpl 中，用 define 定义、include 调用\nyaml# templates/_helpers.tpl {{- define \u0026#34;my-chart.fullname\u0026#34; -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix \u0026#34;-\u0026#34; }} {{- else }} {{- printf \u0026#34;%s-%s\u0026#34; .Release.Name (include \u0026#34;my-chart.name\u0026#34; .) | trunc 63 | trimSuffix \u0026#34;-\u0026#34; }} {{- end }} {{- end }} include 比 template 多了管道能力(可接 | nindent)，统一用 include 即可\n资源间的引用(Deployment 引用 Secret 名、PVC 名)一律通过同一命名模板或同一 printf 表达式生成，避免改一处漏一处\nvalues 管理策略 覆盖优先级从低到高：values.yaml → -f 指定的文件(后者覆盖前者) → --set/--set-string --set 适合 CI 中注入动态值(镜像 tag、版本号)；复杂结构用文件管理，--set 写嵌套极易出错 数字与特殊字符类值用 --set-string 强制为字符串，避免类型漂移 环境差异组织为 values-staging.yaml / values-prod.yaml，公共默认值留在 values.yaml，覆盖文件只写差异项 未使用的参数显式置空(~)，模板中用 if 判断空值决定是否渲染，保持渲染输出最小化 敏感信息不进 values 文件与 git，由 CI 在发布时从密钥管理系统注入，模板中用 secretKeyRef 引用 发布编排(Hooks) helm 在发布生命周期的固定时点执行带 helm.sh/hook 注解的资源，解决\u0026quot;先有鸡还是先有蛋\u0026quot;的顺序问题：\nyamlmetadata: annotations: \u0026#34;helm.sh/hook\u0026#34;: pre-install,pre-upgrade # 在主资源之前执行 \u0026#34;helm.sh/hook-weight\u0026#34;: \u0026#34;-5\u0026#34; # 数值越小越先执行，默认 0 \u0026#34;helm.sh/hook-delete-policy\u0026#34;: before-hook-creation # 重复执行前先删旧 Job(默认行为) 常用钩子时点：\npre-install/pre-upgrade：主资源创建前，适合创建前置依赖，如私有镜像的拉取凭证(Secret 必须先于第一个 Pod 存在) post-install/post-upgrade：主资源就绪后，适合数据库迁移、数据初始化等任务 pre-delete/post-delete：卸载前后，适合清理外部资源 钩子资源须是能\u0026quot;跑完\u0026quot;的类型(Job、Secret 等)，Job 类钩子配合 restartPolicy: Never + backoffLimit 快速失败\n工程实践 umbrella chart 封装第三方依赖：不直接 install 社区 Chart，而是声明为依赖并打上 alias 与 condition，统一在自家 values 里覆盖上游参数，升级只改一处版本号，定制与追踪都集中在一个仓库 版本交给 CI：自建 Chart 的 version 固定为 0.0.0，由 CI 发布时注入，发布产物天然可回溯到 commit 一切命名源于 Release：资源名、Secret 名、PVC 名统一用 .Release.Name/.Release.Namespace 生成，同一 Chart 即可在同一集群多实例部署 用命名空间表达环境：模板以 .Release.Namespace 推导默认 nodeSelector/tolerations 等，values 不配置则自动按环境隔离，用户仅在需要时覆盖 互斥渲染避免冲突：存在依赖互斥的字段(如 HPA 与 spec.replicas)在模板层做条件互斥，不依赖使用者自觉 发布命令标准化：CI 统一使用 helm upgrade --install --atomic --wait，失败自动回滚，配合 helm diff 在合并请求中展示变更 ","permalink":"https://echo.ishian.me/2026/08/16/helm/","summary":"介绍 Helm 的核心概念、Chart 结构、常用命令与模板语法","title":"Helm: Kubernetes 的包管理器"},{"content":"生产级配置技巧 以下都是生产环境中的普遍做法(见 Kubernetes 一文的「Charts 实践」)，多数解决的是文档不会明说、但线上一定会踩的坑\n优雅停机 Pod 收到终止信号后，Service/Istio 的端点列表传播存在延迟，若容器立即退出，仍会有少量请求被路由到已死的 Pod\n解决方式是让容器先睡一会儿再退出，用 preStop 钩子桥接端点传播延迟，同时放宽 terminationGracePeriodSeconds 给应用留足清理时间\nyamlspec: terminationGracePeriodSeconds: 60 # 给 preStop + 应用关闭留足时间(默认仅 30s) containers: - name: app lifecycle: preStop: exec: command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep 15\u0026#34;] # 等待 LB/网格端点列表更新，期间继续处理存量请求 HPA 与 replicas 的冲突 Deployment 的 spec.replicas 与 HPA 会互相打架：HPA 扩容后，下一次 apply 会把副本数改回 yaml 里的值\n解决方式是在模板层做互斥：仅当 HPA 未启用时才渲染 replicas 字段，启用 HPA 后副本数完全交给自动扩缩\nyamlspec: {{- if and (not .Values.hpa.enabled) (hasKey .Values \u0026#34;replicas\u0026#34;) }} replicas: {{ .Values.replicas }} {{- end }} 配置变更也触发滚动重启 滚动更新只比较 Pod 模板哈希，若仅改了环境变量之外的间接配置(如依赖的配置中心)，helm upgrade 后 Pod 不会重建，容易造成“升级了但没生效”\n解决方式是注入一个每次渲染都变化的随机注解，强制每次发布都滚动重启\nyamlspec: template: metadata: annotations: rollme: {{ randAlphaNum 5 | quote }} # 每次 helm upgrade 都变化，强制重建 Pod Job 与 sidecar 的死锁 被注入 Istio sidecar 的 Job 可能永远不结束：业务容器退出后，Envoy sidecar 仍在运行，Pod 无法到达 Completed 状态\n解决方式是给任务类 Pod 显式关闭注入\nyamlspec: template: metadata: annotations: sidecar.istio.io/inject: \u0026#34;false\u0026#34; # 数据库迁移等一次性 Job 不需要网格能力 backoffLimit: 2 # 快速失败，避免反复重试污染数据 同理，CronJob 若只是访问数据库或外部 API，也应关闭注入以避免资源浪费与完成阻塞\n探针的节奏控制 探针不是越灵敏越好：periodSeconds 过短 + 应用 GC 停顿会导致误判重启，形成重启风暴(流量涌入冷启动应用 → 更慢 → 更多失败)\n生产实践：存活探针放宽到 60s 一次，就绪探针保持 15s，timeoutSeconds: 10 容忍慢响应\nSpring Boot 应用直接使用 actuator 的分层健康端点，避免把依赖检查混入存活探针\nyamllivenessProbe: httpGet: path: /actuator/health/liveness # 只关心进程是否活着 port: 8080 initialDelaySeconds: 10 periodSeconds: 60 # 放宽节奏，避免 GC 停顿引发误杀 timeoutSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness # 关心依赖是否就绪 port: 8080 initialDelaySeconds: 10 periodSeconds: 15 timeoutSeconds: 10 免注解的指标采集 不部署 ServiceMonitor 时，Prometheus 可通过 Pod 注解自动发现抓取目标，应用零配置接入监控\nyamlspec: template: metadata: annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;8080\u0026#34; prometheus.io/path: \u0026#34;/actuator/prometheus\u0026#34; 容器感知的 JVM 调优 JVM 默认按宿主机内存分配堆，在容器里会严重超配；同时 CPU limit 会被 JVM 识别为核心数，影响 GC 线程数\n生产实践：requests 与 limits 设为相同值保证资源独占，再用环境变量覆盖 JVM 行为\nyamlresources: requests: cpu: \u0026#34;2\u0026#34; memory: 4Gi limits: # 与 requests 一致，避免 CPU 限流引发长尾延迟 cpu: \u0026#34;2\u0026#34; memory: 4Gi env: - name: JAVA_TOOL_OPTIONS value: \u0026gt;- -XX:ActiveProcessorCount=2 -XX:MaxDirectMemorySize=128M -XX:InitialRAMPercentage=50 -XX:MinRAMPercentage=75 -XX:MaxRAMPercentage=75 ActiveProcessorCount 显式指定核心数，不受 CPU 配额干扰 RAMPercentage 按容器内存的百分比分配堆，替代写死的 -Xmx helm hook 编排发布顺序 私有镜像的拉取凭证必须在第一个 Pod 创建前就绪，否则首个 Deployment 会卡在 ImagePullBackOff\n用 pre-install/pre-upgrade 钩子提前创建，hook-weight 保证其先于其他钩子资源执行；数据库迁移等任务则用 post-install 钩子跟上，weight 决定多个任务的先后\nyamlapiVersion: v1 kind: Secret type: kubernetes.io/dockerconfigjson metadata: name: my-app-regcred annotations: \u0026#34;helm.sh/hook\u0026#34;: pre-install,pre-upgrade # 在 Deployment 之前创建 \u0026#34;helm.sh/hook-weight\u0026#34;: \u0026#34;-5\u0026#34; # 数值越小越先执行 data: .dockerconfigjson: {{ template \u0026#34;imagePullSecret\u0026#34; . }}yamlapiVersion: batch/v1 kind: Job metadata: name: my-app-migration annotations: \u0026#34;helm.sh/hook\u0026#34;: post-install,post-upgrade # 应用就绪后执行数据库迁移 \u0026#34;helm.sh/hook-weight\u0026#34;: \u0026#34;0\u0026#34; 凭证 Secret 需要在使用它的每个命名空间各放一份(如 cert-manager 拉取镜像的命名空间)，模板中渲染多份即可\n对象存储挂载为本地盘 通过各云厂商的 CSI 驱动，把对象存储桶直接挂载为 PV，应用像读写本地目录一样访问 OSS/GCS，无需引入 SDK\nGCS 的方式最简单，注解即可启用 GKE 托管的 gcsfuse sidecar，还能单独限制 sidecar 资源\nyamlspec: template: metadata: annotations: gke-gcsfuse/volumes: \u0026#34;true\u0026#34; # 让 GKE 注入 gcsfuse sidecar gke-gcsfuse/cpu-limit: \u0026#34;250m\u0026#34; gke-gcsfuse/memory-limit: \u0026#34;256Mi\u0026#34; OSS 需要手写静态 PV，两个细节：每个桶用独立的 storageClassName 防止默认供给器抢占绑定；PVC 用 selector 按标签钉死要绑定的 PV\nyamlapiVersion: v1 kind: PersistentVolume metadata: name: my-app-oss-csi-pv-0 labels: alicloud-pvname: my-app-oss-0 # 供 PVC selector 匹配 spec: storageClassName: my-app-csi-oss-0 # 每桶唯一，避免被动态供给拦截 accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain csi: driver: ossplugin.csi.alibabacloud.com volumeHandle: my-app-oss-csi-pv-0 volumeAttributes: bucket: my-bucket url: oss-cn-hangzhou-internal.aliyuncs.com akId: \u0026#34;\u0026lt;access-key\u0026gt;\u0026#34; akSecret: \u0026#34;\u0026lt;secret-key\u0026gt;\u0026#34; --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-app-pvc-0 spec: storageClassName: my-app-csi-oss-0 accessModes: - ReadWriteMany resources: requests: storage: 10Gi # 对象存储不校验，仅作形式声明 selector: matchLabels: alicloud-pvname: my-app-oss-0 # 按标签绑定指定 PV 有状态监控组件(如 Prometheus)重装时应保留数据：手写 PV 直接引用已存在的云盘(volumeHandle 填云盘 ID)，配合 persistentVolumeReclaimPolicy: Retain，删除 chart 重装后数据仍在\nyamlapiVersion: v1 kind: PersistentVolume metadata: name: monitoring spec: storageClassName: monitoring accessModes: - ReadWriteOnce capacity: storage: 500Gi csi: driver: udisk.csi.ucloud.cn volumeHandle: \u0026#34;\u0026lt;已有云盘 ID\u0026gt;\u0026#34; # 复用存量云盘 persistentVolumeReclaimPolicy: Retain # PVC 删除后保留云盘与数据 出站默认拒绝与元数据防护 应用被 SSRF 攻击时，攻击者最先尝试的就是云厂商的元数据服务地址，拿到的是临时云凭证，可直接操纵云资源\n生产实践：NetworkPolicy 默认拒绝所有出站，仅放行 DNS，并显式排除元数据网段\nyamlapiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: my-app spec: podSelector: matchLabels: app: my-app policyTypes: - Egress egress: - to: # 放行集群 DNS - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 - to: # 放行其余公网，但屏蔽元数据网段 - ipBlock: cidr: 0.0.0.0/0 except: - 100.64.0.0/10 # 阿里云等 CG-NAT 网段(含 metadata 100.100.100.200) - 169.254.0.0/16 # link-local(含 AWS/GCP/Azure 169.254.169.254) 在此之上可为特殊角色开白名单，例如调试用的 admin shell Pod 只允许访问 API Server 端口，不开内网通行证\nyamlegress: - to: # 仅允许访问 apiserver - ipBlock: cidr: 10.0.0.0/8 ports: - protocol: TCP port: 443 - protocol: TCP port: 6443 应用内嵌调试终端 让应用持有专属 ServiceAccount 与最小 RBAC(创建 Job、pods/exec、读写 Secret)，即可在网页里一键拉起带完整工具链的调试 Pod，进容器排查线上问题\n关键是权限最小化：Role 只授予单个命名空间内的少量动词，调试 Pod 再用 NetworkPolicy 隔离\nyamlapiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: my-app-shell rules: - apiGroups: [\u0026#34;batch\u0026#34;] resources: [\u0026#34;jobs\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;delete\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/exec\u0026#34;] verbs: [\u0026#34;create\u0026#34;, \u0026#34;get\u0026#34;]yaml# 应用 Pod 将自身命名空间下发给调试 Pod，无需硬编码 env: - name: TOOLS_SHELL_POD_NAMESPACE valueFrom: fieldRef: # Downward API：把 Pod 自身信息注入环境变量 fieldPath: metadata.namespace 网关证书的域名数量上限 Istio Gateway 的单个 server 块在证书 SAN 数量上有实际上限(约 100 个域名)，域名多时会直接报错\n解决方式是按 99 个一组拆分 server 块，每组配独立的证书 Secret，模板层用循环批量生成\nyamlservers: - port: number: 443 name: https protocol: HTTPS tls: mode: SIMPLE credentialName: my-app-tls-1 # 第一组证书 hosts: # ≤ 99 个域名 - a.example.com - port: number: 443 name: https-2 # 端口相同，name 必须不同 protocol: HTTPS tls: mode: SIMPLE credentialName: my-app-tls-2 # 第二组证书 hosts: - b.example.com 入口层的最小暴露面 业务不需要的端点一律在网关层直接 404，而不是靠应用代码防御：管理端点(/actuator/*)只放行健康检查，文档路由(/docs)按环境开关\nyamlhttp: - match: # 禁用文档路由 - uri: prefix: /docs directResponse: # 不经过后端直接返回 status: 404 - match: # 仅放行健康检查 - uri: exact: /actuator/health route: - destination: host: my-app port: number: 80 - match: # 其余管理端点全部 404 - uri: prefix: /actuator directResponse: status: 404 - route: # 正常业务流量 - destination: host: my-app port: number: 80 多产品共用一套部署时，可在网关层按域名路由并注入产品标识 Header，后端无感知区分租户\nyamlhttp: - match: - headers: \u0026#34;:authority\u0026#34;: # Envoy 的 Host 头 exact: product-a.example.com route: - destination: host: my-app port: number: 80 headers: request: set: X-Product: \u0026#34;a\u0026#34; # 后端读取该 Header 区分产品 全链路请求 ID 透传 外部客户端带来的 X-Request-Id 默认不会在网关处保留，导致跨服务的日志无法串联\n用 EnvoyFilter 直接修改网关配置，让网关透传外部请求 ID，全链路日志与 trace 得以对齐\nyamlapiVersion: networking.istio.io/v1 kind: EnvoyFilter metadata: name: ingress-preserve-request-id spec: workloadSelector: labels: istio: ingress # 作用于 ingress gateway Pod configPatches: - applyTo: NETWORK_FILTER match: context: GATEWAY listener: filterChain: filter: name: envoy.filters.network.http_connection_manager patch: operation: MERGE value: typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager preserve_external_request_id: true 环境即命名空间 用污点 + 容忍度把节点按环境隔离：staging 命名空间的发布只调度到 staging 节点，互不抢资源；命名空间统一打上 istio-injection 标签，新服务自动入网格\n模板层以 Release.Namespace 推导默认值，用户不配置则自动隔离\nyaml# 节点打污点: kubectl taint node node1 example.io/environment=production:NoSchedule tolerations: - key: example.io/environment operator: Equal value: {{ .Release.Namespace | quote }} # 容忍度跟随命名空间，自动匹配环境节点 effect: NoScheduleyamlapiVersion: v1 kind: Namespace metadata: name: staging labels: istio-injection: enabled # 命名空间内新建 Pod 自动注入 sidecar ","permalink":"https://echo.ishian.me/2026/08/16/k8s-production-tricks/","summary":"总结 Kubernetes 生产环境的优雅停机、探针调优、HPA 冲突等配置技巧","title":"Kubernetes: 生产级配置技巧"},{"content":"PLG 快速入门 简介 PLG 指以 Prometheus + Loki + Grafana 为核心的可观测性体系，分别负责指标、日志与可视化/告警，采集日志还需要 Promtail，四者的关系：\ntext应用/容器 ──(pull 指标)──\u0026gt; Prometheus ──┐ 应用/容器 ──(tail 日志)──\u0026gt; Promtail ──push──\u0026gt; Loki ──┐ ├──\u0026gt; Grafana(查询/仪表盘/告警) 相比 ELK 全文索引的重量级方案，Loki 只对标签建索引、日志正文压缩存储，以极低的成本换来\u0026quot;够用\u0026quot;的日志检索能力，与 k8s 的标签体系天然契合\nGrafana 是唯一面向用户的入口：指标与日志统一查询、仪表盘与告警规则统一管理，且全部资源支持配置化供给(provisioning)，可以像管理应用一样用 git + helm 管理监控体系\nPrometheus 简介 pull 模型：Prometheus 主动周期性抓取(scrape)目标的 /metrics 端点，而非等待推送，天然适合 k8s 的自注册环境 数据以时序形式本地存储(TSDB)，配合 PromQL 做聚合查询 基本配置结构：scrape_configs 定义一组抓取任务(job)，每个任务通过服务发现找到目标，再通过 relabel 改写标签 服务发现与注解发现 k8s 环境用 kubernetes_sd_configs 发现目标，role 可选 pod/service/endpoints 等\n生产实践是注解驱动：应用只在 Service/Pod 上打 prometheus.io/* 注解即可被发现，零额外配置\nyamladditionalScrapeConfigs: - job_name: kubernetes-service-endpoints kubernetes_sd_configs: - role: endpoints relabel_configs: - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] action: keep # 只保留声明了注解的 Service regex: true - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_port] action: replace target_label: __address__ regex: (.+)(?::\\d+);(\\d+) # 用注解端口替换默认地址 replacement: $1:$2 - source_labels: [__meta_kubernetes_service_name] action: replace target_label: application # 服务名映射为 application 标签，告警信息直接可用 Istio 网格的指标同样可以低成本接入：按容器端口名过滤，抓取 sidecar 暴露的 Envoy 指标\nyaml- job_name: envoy-stats metrics_path: /stats/prometheus kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_container_port_name] action: keep regex: .*-envoy-prom 存储 数据目录挂在 PVC 上即可持久化；重装时应复用已有云盘：PVC 通过 volumeName + selector 钉死既有的 PV，监控历史不因重装丢失\nyamlstorageSpec: volumeClaimTemplate: spec: selector: matchLabels: app.kubernetes.io/name: monitoring storageClassName: monitoring volumeName: monitoring # 直接绑定已有 PV resources: requests: storage: 300Gi Promtail 简介 Promtail 是 Loki 官方采集代理，以 DaemonSet 跑在每个节点，tail 容器日志文件并 push 给 Loki 采集时把 k8s 元数据(命名空间、Pod 名、标签)作为日志的标签附上，这就是 Loki 查询的主要维度 基本配置 yamlconfig: clients: - url: http://loki-write-headless:3100/loki/api/v1/push # 推送地址 snippets: pipelineStages: - cri: {} # 解析 CRI 日志格式(containerd 运行时的标准格式，拆出时间戳/级别/正文) pipelineStages 是加工管道，常用阶段： cri/docker：解析容器运行时日志头 regex：正则提取字段，配合 labels 阶段提升为标签(标签基数要克制，高基数值不要做标签) timestamp/multiline：修正时间戳、合并多行日志(如 Java 堆栈) 简单可扩展模式下的 Loki 有多个写入副本，推送地址用 headless service(loki-write-headless)让客户端负载均衡到所有 write 实例 Loki 简介 Loki 只索引标签，日志正文按块(chunk)压缩存储到对象存储，查询时按标签定位再过滤正文(LogQL) LogQL 速记：{namespace=\u0026quot;production\u0026quot;, app=~\u0026quot;api.+\u0026quot;} 选流，| json 解析正文，| line_format 格式化，指标查询再套聚合如 rate(...[5m]) 部署模式 单体模式(单体 SingleBinary)：所有组件一个进程，小规模够用 简单可扩展模式(SimpleScalable)：拆为 read/write/backend 三组，read/write 可独立扩容，日志量大时的性价比之选；注意两条数据面都要配持久化，且 write 的 PVC 开启随 StatefulSet 删除(enableStatefulSetAutoDeletePVC) 关键配置 存储与索引结构，对象存储放 chunk，TSDB 索引按天滚动：\nyamlloki: storage: type: s3 bucketNames: chunks: loki-chunks # 日志块 ruler: loki-ruler admin: loki-admin s3: s3: s3://loki endpoint: http://s3.internal # 兼容 S3 协议即可(各云厂商对象存储均可) schemaConfig: configs: - from: 2024-01-01 store: tsdb object_store: s3 schema: v13 index: period: 24h 保留策略：全局保留 + 按流精细覆盖，低价值环境短保留，成本立省\nyamllimits_config: retention_period: 744h # 默认保留 31 天 retention_stream: - selector: \u0026#39;{namespace=\u0026#34;staging\u0026#34;}\u0026#39; priority: 1 period: 168h # staging 只留 7 天 - selector: \u0026#39;{app=~\u0026#34;.+job.+\u0026#34;}\u0026#39; priority: 2 period: 168h # 任务类 Pod 日志也只留 7 天 compactor: retention_enabled: true # 老版本用 tableManager，新版统一由 compactor 执行删除 Grafana 数据源 数据源用 provisioning 配置，不做手工点击；uid 显式固定，仪表盘与告警规则都靠 uid 引用数据源\nyamldatasources: datasources.yaml: apiVersion: 1 datasources: - name: Prometheus type: prometheus uid: prometheus url: http://monitoring-kube-prometheus-prometheus:9090 isDefault: true - name: Loki type: loki uid: loki url: http://loki-read-headless:3100 jsonData: timeout: 60 maxLines: 1000 # 限制单次查询行数，防止误查打爆浏览器 仪表盘与配置即代码 仪表盘 json 放进 ConfigMap，打上标签(如 grafana_dashboard: \u0026quot;1\u0026quot;)，由 Grafana 的 sidecar(k8s-sidecar)监听全集群的同标签 ConfigMap，自动加载与更新\n目录归类用注解指定，文件结构即目录结构：\nyamlkind: ConfigMap metadata: name: grafana-logs labels: grafana_dashboard: \u0026#34;1\u0026#34; # sidecar 按该标签发现 annotations: k8s-sidecar-target-directory: \u0026#34;/tmp/dashboards/Logs\u0026#34; # 归入 Logs 目录 data: logs.json: | { \u0026#34;title\u0026#34;: \u0026#34;Logs\u0026#34;, ... } # 仪表盘 JSON 全文yamlsidecar: dashboards: enabled: true provider: foldersFromFilesStructure: true # 按文件结构生成目录 datasources: enabled: true 社区仪表盘(grafana.com 的 gnetId)可直接导入 json 后沉淀为 ConfigMap，再做定制\n这套\u0026quot;标签 + sidecar\u0026ldquo;的供给模式不仅用于仪表盘与数据源，同样适用于告警规则等其余 Grafana 资源\n","permalink":"https://echo.ishian.me/2026/08/16/plg/","summary":"介绍Prometheus+Loki+Grafana可观测体系的组成、配置与服务发现等核心用法","title":"PLG: Prometheus + Loki + Grafana 可观测体系"},{"content":"PLG: 部署与飞书告警实践 本篇记录如何把 PLG 监控体系以 git + helm + CI 的方式部署进集群，并把告警送达飞书群，所有做法都是生产环境的通用玩法\n监控栈的 CI 部署 整体形态 监控栈是一个 umbrella chart：聚合 kube-prometheus-stack(含 Prometheus/Alertmanager)+ loki + promtail + grafana 依赖，环境差异全部放在 values-*.yaml 中\n部署不走手工命令，而是 GitHub Actions 在 push 到 main 时按路径触发，路径过滤保证只有监控配置变更才触发部署：\nyamlon: push: branches: - main paths: - charts/monitoring/** - charts/istio/** - charts/ingress/** 多集群、多产品通过 matrix 展开，每个条目指定 chart、命名空间、values 文件与 kubeconfig 上下文：\nyamlstrategy: matrix: product: - name: monitoring namespace: monitoring values: values-example-prod.yaml cluster: - name: example-prod runner: example-runner # 自托管 runner，持有集群访问权 context: example-prod 密钥注入 密钥永远不进 git：values 文件里写占位符，CI 用 envsubst 在发布时替换\nyaml# values 文件中的占位 grafana: env: BOT_GRAFANA_WEBHOOK_TOKEN: $BOT_GRAFANA_WEBHOOK_TOKENbashenvsubst \u0026#39;$FEISHU_APP_ID $FEISHU_APP_SECRET $BOT_GRAFANA_WEBHOOK_TOKEN\u0026#39; \\ \u0026lt; charts/monitoring/values-example-prod.yaml \u0026gt; $values_file 简单值直接用 --set 注入(如 Cloudflare Token)，不必经过 values 文件\n发布与自愈 统一使用 helm upgrade --install --create-namespace，并对\u0026quot;上一个操作未完成\u0026quot;的常见冲突做自动回滚 + 重试：\nbashfor retry in {1..3}; do res=$(helm -n monitoring upgrade --install --create-namespace monitoring \\ ./charts/monitoring -f \u0026#34;$values_file\u0026#34; \\ --set cloudflare.apiToken=$CF_TOKEN 2\u0026gt;\u0026amp;1) [[ $? -eq 0 ]] \u0026amp;\u0026amp; exit 0 if [[ \u0026#34;$res\u0026#34; = *\u0026#34;another operation (install/upgrade/rollback) is in progress\u0026#34;* ]]; then helm -n monitoring rollback monitoring # 卡住的发布先回滚再重试 continue fi sleep 10 done exit 1 runner 通过 kubeconfig secret 切换目标集群上下文，一个 runner 可以发布多个集群\nyaml- uses: azure/k8s-set-context@v4 with: method: kubeconfig kubeconfig: ${{ secrets.KUBECONFIG }} context: ${{ matrix.cluster.context }} 告警即代码 Grafana 统一告警 选择 Grafana 统一告警(Unified Alerting)而非 Alertmanager 作为告警出口：规则、联系点、通知策略都住在 Grafana 里，与仪表盘共享数据源与标签体系，Alertmanager 仅作为 kube-prometheus-stack 的兜底保留\n告警规则不进 Grafana 界面手点，而是写成 ConfigMap，打上约定标签，由 Grafana sidecar 全命名空间监听并同步进 Grafana，改动走 git 评审：\nyamlkind: ConfigMap metadata: name: pod-status-alert namespace: monitoring labels: grafana_alert: \u0026#34;1\u0026#34; # sidecar 按该标签发现告警资源 data: pod-status-alert.yaml: | apiVersion: 1 ...yaml# values 中开启 sidecar 的告警同步，搜索全命名空间 grafana: sidecar: alerts: enabled: true searchNamespace: ALL 同一文件描述四类资源：groups(规则)、contactPoints(联系点)、policies(通知策略)、templates(通知模板)\n告警规则的结构 规则按 group 组织，folder 决定在 Grafana 中的目录归属，interval 是评估周期：\nyamlapiVersion: 1 groups: - orgId: 1 name: Kubernetes folder: Infra interval: 1m # 每分钟评估一次 rules: - uid: a9a96a82-f6f5-4bfe-a18e-6f1ac6c9e72f title: 服务健康状态 condition: 不健康服务增多 # 指向起决定作用的表达式 refId data: - refId: 不健康服务 datasourceUid: prometheus # 查询 Prometheus model: expr: | sum by (namespace, pod, phase) ( kube_pod_status_phase{namespace !~ \u0026#39;staging|dev-.*\u0026#39;, phase=~\u0026#34;Pending|Unknown|Failed\u0026#34;} == 1 and on (namespace, pod) ((time() - kube_pod_created) \u0026gt; 180) ) - refId: 不健康服务增多 datasourceUid: __expr__ # 表达式阶段做阈值判断 model: type: threshold expression: 不健康服务 conditions: - evaluator: params: [0] type: gt # 结果 \u0026gt; 0 即触发 for: 5m # 持续满足 5 分钟才真正触发，抑制抖动 noDataState: OK # 无数据视为正常 annotations: summary: 命名空间 {{ $labels.namespace }} 下 Pod {{ $labels.pod }} 处于 {{ $labels.phase }} 状态 labels: product: infra # 供通知模板与策略路由使用 注意三个工程细节：\nPromQL 里先做过滤与降噪：排除 staging 等低价值命名空间、只看存活超过 3 分钟的 Pod for: 5m + noDataState: OK 是防告警风暴的两道闸 labels.product 是整条告警链路的路由键，从 values 按产品开关(alerts.\u0026lt;product\u0026gt;.enabled)决定渲染哪些规则 飞书告警链路 联系点：webhook + 自建机器人 Grafana 原生不支持飞书，与其等官方支持，不如加一层自建转发服务：联系点配置为普通 webhook，指向自建的机器人服务，由它把 Grafana 的告警 payload 转成飞书群消息\n好处是格式完全自主、鉴权自己掌控、以后接任何新 IM 都只改这一个服务：\nyamlapiVersion: 1 contactPoints: - orgId: 1 name: 服务端告警群 receivers: - uid: 80192ad5-0865-4075-a69a-eeccccbf82d8 type: webhook settings: url: https://bot.example.com/grafana/webhook # 自建机器人 httpMethod: POST authorization_scheme: Bearer # 出口鉴权，防止伪造告警 authorization_credentials: $BOT_GRAFANA_WEBHOOK_TOKEN Bearer Token 通过环境变量注入 Grafana(grafana.env)，再由 envsubst 在 CI 阶段填充，全程不落 git\n通知策略 策略决定\u0026quot;谁收到什么、多久提醒一次\u0026quot;，repeat_interval 是防轰炸的关键参数：\nyamlapiVersion: 1 policies: - orgId: 1 receiver: grafana-default-email # 兜底接收器 group_by: [\u0026#39;...\u0026#39;] repeat_interval: 5m # 未恢复告警每 5 分钟重复提醒 routes: - receiver: 服务端告警群 # 全部告警转发到飞书群 continue: true # 继续匹配后续路由，便于后续按产品分流 通知模板 模板用 Grafana 的 Go template 语法，自定义标题与正文，把关键信息(product/application 标签、当前数值、图表与静默链接)组织成飞书消息卡片的字段：\nyamlapiVersion: 1 templates: - orgId: 1 name: \u0026#34;example.title\u0026#34; template: | {{- if len .Alerts.Firing -}} 🚨🚨🚨 有 {{ len .Alerts.Firing }} 条告警规则正在触发 {{- end }} {{- if and (len .Alerts.Firing) (len .Alerts.Resolved) -}}; {{ end -}} {{- if len .Alerts.Resolved -}} ✅✅✅ 有 {{ len .Alerts.Resolved }} 条告警规则已经恢复 {{- end }} - orgId: 1 name: \u0026#34;example.message\u0026#34; template: | - 产品名称: {{ .Labels.product }} - 产品应用: {{ or (or .Labels.application .Labels.pod) \u0026#34;没有数据\u0026#34; }} - 告警规则: {{ .Labels.alertname }} - 当前数值: {{ range $k, $v := .Values }}{{ $k }}={{ $v }} {{ end }} {{- if .DashboardURL }} - 查看图表: [点击查看]({{ .DashboardURL }}) {{- end }} {{- if .SilenceURL }} - 抑制告警: [点击抑制]({{ .SilenceURL }}) {{- end }} .Labels.application 正是 Prometheus 抓取配置里 relabel 出来的服务名标签——采集端打的标签决定通知端的信息量，这是整条链路设计的一体性所在\n飞书登录(OAuth) Grafana 的登录同样接入飞书，用 generic_oauth 对接飞书开放平台的 OAuth 端点，账号体系与公司 IM 统一：\nyamlgrafana.ini: auth.generic_oauth: enabled: true name: 飞书 client_id: $FEISHU_APP_ID # CI 注入 client_secret: $FEISHU_APP_SECRET auth_url: https://passport.feishu.cn/suite/passport/oauth/authorize token_url: https://passport.feishu.cn/suite/passport/oauth/token api_url: https://passport.feishu.cn/suite/passport/oauth/userinfo login_attribute_path: name role_attribute_path: name == \u0026#39;someone\u0026#39; \u0026amp;\u0026amp; \u0026#39;Admin\u0026#39; || \u0026#39;Viewer\u0026#39; # 按用户名映射角色 role_attribute_path 实现\u0026quot;白名单管理员、其余只读\u0026quot;，避免给全员开编辑权限\n链路总结 textPod/Envoy 指标 ──\u0026gt; Prometheus(注解发现/relabel 打标) ──┐ 容器日志 ──\u0026gt; Promtail(CRI 解析) ──\u0026gt; Loki(S3/分级保留) ──┤ v Grafana(数据源/仪表盘/告警，全部 ConfigMap + sidecar 供给) | 告警规则(interval/for/labels.product) v 通知策略(repeat_interval) ─\u0026gt; 联系点(webhook + Bearer) v 自建机器人 ──\u0026gt; 飞书群卡片(title/message 模板)","permalink":"https://echo.ishian.me/2026/08/16/plg-alert/","summary":"记录用git+helm+CI部署PLG监控栈到Kubernetes并把告警送达飞书群的实践","title":"PLG: 部署与飞书告警实践"},{"content":"实现一个最小可用的 LSP Server LSP（Language Server Protocol）名称中虽然带有 \u0026ldquo;Language\u0026rdquo;，但它本质上是一套基于 JSON-RPC 2.0 的补全/诊断/跳转协议，服务的对象不限于编程语言。这篇笔记总结 一个最小可用的 LSP server 需要暴露哪些 endpoint，以及 Neovim 侧如何用 Lua 接入。协议细节与实现语言无关。\n文中会以 nvim-codex-lsp 作为贯穿全文 的示例：它为 Codex CLI 的聊天输入框提供补全，在 Neovim 中编辑 Codex 的 prompt 缓冲区时，可以补全 /slash 命令、$skill 引用和 @文件/插件 提及。这只是 LSP 应用于非编程语言场景的一个例子，笔记本身的方法论是通用的。\nLSP server 需要暴露什么 一个只提供补全和 hover 的 server，只需处理 4 组消息：\n生命周期：initialize / initialized / shutdown initialize 是客户端连接后的第一个请求，server 在响应中声明自己的能力 （capabilities），客户端只会在声明过的能力范围内发起请求。提供补全只需声明 以下几项：\njson{ \u0026#34;capabilities\u0026#34;: { \u0026#34;textDocumentSync\u0026#34;: 2, // Incremental 增量同步 \u0026#34;completionProvider\u0026#34;: { \u0026#34;triggerCharacters\u0026#34;: [\u0026#34;/\u0026#34;, \u0026#34;$\u0026#34;, \u0026#34;@\u0026#34;], // 输入这些字符时客户端自动请求补全 \u0026#34;resolveProvider\u0026#34;: true // 支持补全项的懒加载详情 }, \u0026#34;hoverProvider\u0026#34;: true } }initialize 请求中还包含 rootUri/rootPath（项目根目录），这是 server 做 文件补全、扫描项目级配置的锚点，应当妥善处理。\n文档同步：textDocument/didOpen / didChange / didClose 这些是通知（notification，无响应）。客户端在打开/修改/关闭文件时把全文或增量 推送给 server，server 维护一份内存副本（vscode-languageserver 的 TextDocuments 已经封装了这一逻辑）。之后所有补全、hover 都基于这份副本计算——server 不应直接 读取磁盘上已打开的文件，而应以客户端的版本为准。\n补全：textDocument/completion → CompletionItem[] 这是核心请求。客户端传入光标位置 position，server 返回补全列表。要点：\n上下文自行计算：取光标所在行、从行首到光标的文本，用正则判断当前所处的 上下文（示例中 / 后返回命令、$ 后返回 skill、@ 后返回文件+插件）。协议 对\u0026quot;何时返回什么\u0026quot;没有任何约束，完全由 server 决定。 label 是菜单中显示的内容；实际插入的内容用 insertText（补齐光标后缺少的 字符）或 textEdit（替换一个 range，适合需要改写已输入文本的场景，比如把 @xx 替换成 $skill-name）。 data 字段可以携带任意自定义数据（比如命令名），它会在后续的 resolve 请求中 原样带回，用于关联\u0026quot;补全时\u0026quot;与\u0026quot;详情时\u0026quot;两个阶段。 较长的文档不应放在 completion 响应中——见下一条。 懒加载详情：completionItem/resolve + textDocument/hover resolve：客户端在用户选中某个补全项时才发送。适合把 markdown 文档、耗时 查询推迟到这里，使 completion 响应保持轻量。 hover：光标停在一个词上时，返回 { contents: { kind: \u0026quot;markdown\u0026quot;, value }, range }。实现方式是以光标处的词进行查表。 以上就是最小实现所需的全部内容。诊断（publishDiagnostics）、格式化、跳转定义等 都是可选能力，不需要就不必声明。\n传输层：stdio + Content-Length 帧 LSP 最常用的传输方式是 stdio 上的 JSON-RPC，消息格式是先写 header 再写 body：\ntextContent-Length: 123\\r\\n \\r\\n {\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:1,\u0026#34;method\u0026#34;:\u0026#34;initialize\u0026#34;,...} 使用官方 SDK（Node 的 vscode-languageserver、Rust 的 tower-lsp、Python 的 pylsp 生态）可以省去手写帧解析 把 server bundle 成单文件（esbuild 之类），用户只需要一个 node 运行时， 无需 npm install。把产物 commit 进仓库，插件即可开箱即用。 另外 server 进程是长驻的，一次性的发现工作（示例中是扫描 skill 目录、 spawnSync 调用 CLI 获取插件列表）应在 initialize 中完成一次并缓存，避免在每次 补全请求中重复执行。 Neovim connection 0.11+ 的 Neovim 只需要 vim.lsp.config + vim.lsp.enable：\nluavim.lsp.config(\u0026#34;codex_lsp\u0026#34;, { cmd = { \u0026#34;node\u0026#34;, server_js_path, \u0026#34;--stdio\u0026#34; }, -- server 路径相对插件根目录解析 filetypes = { \u0026#34;markdown.codex\u0026#34; }, -- 只在特定 filetype 上附加 root_dir = function(_, on_dir) on_dir(vim.fn.getcwd()) end, }) vim.lsp.enable(\u0026#34;codex_lsp\u0026#34;)真正需要仔细处理的是让目标缓冲区被正确识别。示例场景中，Codex 通过 $EDITOR 打开的临时文件名是随机的 .tmpXXXXXX.md，因此使用 autocmd + 回调过滤：\nluavim.api.nvim_create_autocmd({ \u0026#34;BufReadPost\u0026#34;, \u0026#34;BufNewFile\u0026#34; }, { pattern = \u0026#34;*.md\u0026#34;, callback = function(ev) if not is_target_buffer(vim.api.nvim_buf_get_name(ev.buf)) then return end vim.bo[ev.buf].filetype = \u0026#34;markdown.codex\u0026#34; -- 复合 filetype end, })markdown.codex 是复合 filetype：前者使 markdown 的语法高亮/treesitter 继续生 效，后者用于精确限定 LSP 的附加范围。两个配套细节：\nvim.treesitter.language.register(\u0026quot;markdown\u0026quot;, \u0026quot;markdown.codex\u0026quot;)——把 markdown 的 treesitter parser 注册到复合 filetype 上，否则高亮会失效； 若要为 /命令、$skill 添加自定义高亮，用 vim.cmd([[syntax match ...]]) 叠加一层正则匹配即可。 总结 Endpoint 类型 作用 initialize 请求 声明 capabilities，获取 rootPath textDocument/didOpen/didChange/didClose 通知 维护文档内存副本 textDocument/completion 请求 按 triggerCharacters 触发，返回 CompletionItem[] completionItem/resolve 请求 选中项的懒加载文档 textDocument/hover 请求 光标处词的 markdown 说明 一个\u0026quot;补全 + hover\u0026quot;级别的 LSP server，核心逻辑不过两三百行：声明能力、同步文档、 按行上下文过滤列表。剩余的工作量全部在领域侧——扫描哪些目录、调用哪个 CLI、 上下文正则如何编写。这也是 LSP 设计的成功之处：协议只负责管道，语义完全留给 server。\n","permalink":"https://echo.ishian.me/2026/08/15/nvim-lsp-server/","summary":"介绍从零实现最小可用 LSP Server 的生命周期、补全与 stdio 传输层","title":"实现一个最小可用的 LSP Server"},{"content":"GitHub Actions与GitHub Workflows GitHub Actions、Workflow与CI/CD GitHub Actions是GitHub提供的自动化平台，可以在代码仓库中自动执行测试、构建、发布和部署等任务 CI（Continuous Integration，持续集成）主要解决“代码提交后自动检查”的问题，例如安装依赖、代码检查、单元测试和构建 CD通常有两种含义：持续交付（Continuous Delivery）和持续部署（Continuous Deployment） 持续交付：自动生成可以发布的产物，但生产环境部署通常还需要人工批准 持续部署：验证通过后自动部署到目标环境 Workflow是保存在仓库中的一份自动化流程定义，使用YAML编写，文件必须放在.github/workflows/目录下 Action是一个可复用的步骤，例如actions/checkout用于检出代码、actions/setup-node用于安装Node.js Runner是实际执行任务的机器，可以使用GitHub-hosted runner，也可以配置自己的self-hosted runner 官方文档入口：GitHub Actions官方文档。\n一条常见的流水线可以抽象成下面的关系：\ntext事件（push / pull_request） ↓ Workflow（完整流程） ↓ Jobs（相互独立的任务，默认并行） ↓ Steps（任务中的步骤，按顺序执行） ↓ Runner（执行命令的机器）例如，开发者向main分支提交代码后，工作流可以自动完成：\ntext检出代码 → 安装运行环境 → 安装依赖 → 检查与测试 → 构建产物 → 部署Workflow示例 新建.github/workflows/ci.yml：\nyamlname: Node CI on: push: branches: [main, develop] pull_request: branches: [main] workflow_dispatch: permissions: contents: read jobs: test: name: Test on Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: matrix: node-version: [\u0026#34;18\u0026#34;, \u0026#34;20\u0026#34;, \u0026#34;22\u0026#34;] steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: npm - name: Install dependencies run: npm ci - name: Lint run: npm run lint --if-present - name: Test run: npm test将这个文件提交到仓库后：\npush事件会在推送到main或develop时触发 pull_request事件会在针对main创建或更新Pull Request时触发 workflow_dispatch允许在Actions页面手动运行工作流 matrix会为三个Node.js版本分别创建一个任务 同一个job中的steps按顺序执行；不同的矩阵任务默认并行执行 示例中的actions/checkout@v4和actions/setup-node@v4是可复用的现成Action。版本标签会随着维护者发布新版本而变化，生产流水线可以进一步将uses固定到经过审查的提交SHA，避免标签被移动后执行到未经预期的代码。\nWorkflow文件的基本语法 Workflow级配置：决定工作流的名称、权限、变量和并发行为\nname：工作流在Actions页面显示的名称 run-name：某次运行的动态名称，可以使用表达式 permissions：GITHUB_TOKEN可以访问的权限 env：工作流、任务或步骤使用的环境变量 defaults：默认的run配置，例如默认工作目录和shell concurrency：控制同一组运行是否排队或取消 on：触发事件\npush：推送提交时触发，可以用branches、tags和paths过滤分支、标签和文件路径\npull_request：Pull Request打开、更新或重新打开时触发，常用于代码检查\nworkflow_dispatch：手动触发；多数部署 Workflow 不声明输入参数\nschedule：按cron表达式定时触发，时间使用UTC\nworkflow_run：另一个工作流运行结束后触发，适合拆分构建和部署\nworkflow_call：把当前文件作为可复用工作流供其他工作流调用\non可以写成一个事件名、事件数组，或者带过滤条件的映射：\nyaml# 任意 push 都触发 on: push # 多个事件都触发 on: [push, pull_request] # 带过滤条件 on: push: branches: - main paths: - \u0026#39;src/**\u0026#39; - \u0026#39;package.json\u0026#39; pull_request: types: [opened, synchronize, reopened] pull_request的目标分支过滤写在branches中；它与提交者推送代码的源分支不是同一个概念。对于来自外部贡献者的Pull Request，不要为了读取密钥而随意改用pull_request_target，否则不可信代码可能获得仓库权限。\njobs：任务\nruns-on：选择执行环境，例如ubuntu-latest、windows-latest、macos-latest\nneeds：声明依赖的任务；没有依赖关系的任务默认并行\nif：根据表达式决定是否运行任务\nstrategy.matrix：为多个系统、语言版本或配置生成任务组合\npermissions：在任务级别覆盖令牌权限\nenvironment：关联staging或production等环境，可以使用环境保护规则和环境密钥\noutputs：把任务中的步骤输出传给后续任务\ntimeout-minutes：限制任务最长运行时间\ncontinue-on-error：允许任务失败后继续，但应谨慎使用，否则容易隐藏真正的问题\ncontainer和services：分别为任务指定容器或启动数据库等服务容器\njobs下的每个键都是一个任务ID，任务至少要指定runs-on和steps：\nyamljobs: build: runs-on: ubuntu-latest steps: - run: npm run build deploy: needs: build runs-on: ubuntu-latest steps: - run: ./deploy.sh needs既可以保证顺序，也可以读取前置任务的输出：\nyamljobs: build: runs-on: ubuntu-latest outputs: artifact-version: ${{ steps.version.outputs.value }} steps: - id: version run: echo \u0026#34;value=build-${GITHUB_SHA::7}\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; deploy: needs: build runs-on: ubuntu-latest steps: - run: echo \u0026#34;Deploy ${{ needs.build.outputs.artifact-version }}\u0026#34; 如果多个任务都依赖build，它们在build完成后可以并行；只有明确写出needs，才会形成串行关系。\nsteps：步骤\nname：步骤在任务日志中的显示名称\nuses：调用一个可复用的Action\nrun：在Runner中执行命令\nwith：向Action传入输入参数\nenv：为步骤设置环境变量\nid：给步骤命名，之后可以通过steps.\u0026lt;id\u0026gt;.outputs.\u0026lt;name\u0026gt;读取输出\nif：按条件跳过步骤\nworking-directory：指定命令执行目录\nshell：指定命令解释器，例如bash或pwsh\ntimeout-minutes：限制步骤运行时间\ncontinue-on-error：允许当前步骤失败后继续\n一个步骤通常使用run或uses二选一：\nyamlsteps: - name: Run a shell command run: echo \u0026#34;hello\u0026#34; - name: Run multiple commands run: | npm ci npm test - name: Use an Action uses: actions/checkout@v4 - name: Pass inputs to an Action uses: actions/setup-node@v4 with: node-version: \u0026#34;20\u0026#34; - name: Set environment variables env: NODE_ENV: test run: npm test run会在Runner的操作系统上执行命令，默认情况下同一个任务中的步骤共享工作目录。uses调用一个已经封装好的Action，with是传给该Action的输入参数。\n常用通用 Action 下面这些Action不依赖具体业务，通常可以组合出大多数项目的CI流程。示例中的版本号沿用本文其他示例，实际使用时应根据对应Action的维护状态更新版本，并在重要流水线中考虑固定到提交SHA。\nactions/checkout@v4：将仓库代码检出到Runner的工作目录\nref：指定要检出的分支、标签或提交 fetch-depth：指定检出的提交历史深度，设置为0表示检出完整历史 submodules：设置为true或recursive以检出Git submodule actions/setup-node@v4：安装并配置Node.js\nnode-version：指定Node.js版本 cache：启用包管理器缓存，可设置为npm、yarn或pnpm registry-url：配置npm注册表地址 actions/setup-python@v5：安装并配置Python\npython-version：指定Python版本 cache：缓存包管理器依赖，常用值为pip actions/setup-java@v4：安装并配置JDK\ndistribution：指定JDK发行版，例如temurin java-version：指定Java版本 cache：缓存Maven或Gradle依赖 actions/cache@v4：缓存依赖目录或中间构建结果，减少重复下载和计算\npath：需要缓存的文件或目录 key：当前缓存的唯一键，通常包含操作系统和锁文件哈希 restore-keys：精确键未命中时使用的备用键前缀 缓存只负责复用文件，不能代替npm ci、pip install等安装命令 actions/upload-artifact@v4：保存本次运行生成的构建结果、测试报告等产物\nname：产物名称 path：需要上传的文件或目录 retention-days：产物保留天数 actions/download-artifact@v4：在后续任务中下载构建任务上传的产物\nname：要下载的产物名称 path：产物下载目录 upload-artifact和download-artifact通常成对使用；它们适合在不同job之间传递构建结果，不能与依赖缓存混用 docker/login-action@v3：登录容器镜像仓库\nregistry：镜像仓库地址，例如ghcr.io username和password：登录凭据，敏感值应通过Secrets传入 docker/build-push-action@v6：构建并可选地推送Docker镜像\ncontext：镜像构建上下文目录 file：Dockerfile路径 push：是否推送构建结果 tags：镜像标签 platforms：需要构建的目标平台 azure/k8s-set-context@v4：使用 kubeconfig 设置当前任务的 Kubernetes 上下文，后续的kubectl和helm命令都会连接到这个集群\nmethod：认证方式，使用 kubeconfig 时填写kubeconfig kubeconfig：kubeconfig 内容，通常通过Secrets传入 context：kubeconfig 中要使用的集群上下文 yaml- uses: azure/k8s-set-context@v4 with: method: kubeconfig kubeconfig: ${{ secrets.KUBECONFIG }} context: example-cluster - run: helm upgrade --install example-app ./charts/example-app 自定义 actions 自定义 Action 用于把重复步骤封装成一个可复用单元。下面以 composite Action 为例，示例不依赖具体仓库或业务。\n目录：在 .github/actions/\u0026lt;name\u0026gt;/action.yml 或 action.yaml 中定义，Workflow 通过相对路径调用。\ntext.github/ └── actions/ └── setup-node/ └── action.yml name：Action 的显示名称\ndescription：Action 的用途说明\ninputs：调用方可以传入的参数；composite Action 接收到的输入按字符串处理，布尔开关通常比较 'true' 或 'false'\nruns：指定 Action 的实现方式；composite 用于组合 Shell 命令和其他 Action，node20 用于 JavaScript Action，docker 用于 Docker container Action\nyamlname: Setup Node.js if needed description: Set up Node.js only when it is not installed inputs: node-version: description: Node.js version required: false default: \u0026#34;22\u0026#34; install-yarn: description: Install Yarn with Corepack required: false default: \u0026#34;false\u0026#34; version-file-path: description: Directory containing the package manager files required: false default: ${{ github.workspace }} runs: using: composite steps: - id: nodejs-installed shell: bash -exo pipefail {0} run: | if command -v node \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;version=$(node -v)\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; fi - if: ${{ steps.nodejs-installed.outputs.version == \u0026#39;\u0026#39; }} uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - if: ${{ inputs.install-yarn == \u0026#39;true\u0026#39; }} shell: bash -exo pipefail {0} run: | cd ${{ inputs.version-file-path }} corepack enable corepack install shell：composite Action 中的 run 步骤显式设置 Shell，否则元数据文件校验会失败。\nyamlsteps: - uses: ./.github/actions/setup-node with: node-version: \u0026#34;22\u0026#34; install-yarn: \u0026#34;true\u0026#34; version-file-path: ${{ github.workspace }}/example-site gh workflow run：自定义 Action 可以把另一个 Workflow 当作子流程调用，并用 -F 传入它的 workflow_dispatch.inputs。\nbashgh auth login --with-token \u0026lt;\u0026lt;\u0026lt; \u0026#34;$GITHUB_TOKEN\u0026#34; gh workflow run domain-transfer.yaml \\ -F domain=\u0026#34;$ROOT_DOMAIN\u0026#34; \\ -F nameservers=\u0026#34;$NAMESERVERS\u0026#34; \\ -F create-zone=false build-images：先记录 Runner 是否已有 Java；没有时才安装，再根据 push 输入决定只构建还是发布镜像。\nyamlinputs: push: required: false default: \u0026#34;false\u0026#34; project: required: true runs: using: composite steps: - id: installed-java shell: bash run: | if command -v java \u0026amp;\u0026gt; /dev/null; then echo \u0026#34;version=$(java -version 2\u0026gt;\u0026amp;1 | head -n 1 | awk -F \u0026#39;\u0026#34;\u0026#39; \u0026#39;{print $2}\u0026#39;)\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; fi - if: ${{ steps.installed-java.outputs.version == \u0026#39;\u0026#39; }} uses: actions/setup-java@v4 with: distribution: temurin java-version: \u0026#34;21\u0026#34; - if: ${{ inputs.push != \u0026#39;true\u0026#39; }} shell: bash run: ./gradlew :${{ inputs.project }}:bootBuildImage -x spotlessCheck - if: ${{ inputs.push == \u0026#39;true\u0026#39; }} shell: bash run: ./gradlew :${{ inputs.project }}:bootBuildImage --publishImage --parallel -x spotlessCheck setup-node-if-needed：把“检查命令是否存在、按需安装、配置缓存”封装成一个步骤。\nyaml- id: nodejs-installed shell: bash -exo pipefail {0} run: | if command -v node \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;version=$(node -v)\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; fi - if: steps.nodejs-installed.outputs.version == \u0026#39;\u0026#39; uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - if: inputs.install-yarn == \u0026#39;true\u0026#39; shell: bash -exo pipefail {0} run: | cd ${{ inputs.version-file-path }} corepack enable corepack install - if: steps.nodejs-installed.outputs.version == \u0026#39;\u0026#39; uses: actions/setup-node@v4 with: cache: ${{ inputs.install-yarn == \u0026#39;true\u0026#39; \u0026amp;\u0026amp; \u0026#39;yarn\u0026#39; || \u0026#39;npm\u0026#39; }} deploy-job：把完整镜像名拆成 registry、repository 和 tag，再把多行 key=value 转换成多个 Helm --set 参数。\nbashFULL_IMAGE=\u0026#34;${{ inputs.image }}\u0026#34; EXTRA_SET_ARGS=\u0026#34;${{ inputs.extra-set-args }}\u0026#34; REGISTRY=\u0026#34;${FULL_IMAGE%%/*}\u0026#34; REST=\u0026#34;${FULL_IMAGE#*/}\u0026#34; REPO=\u0026#34;${REST%:*}\u0026#34; TAG=\u0026#34;${REST##*:}\u0026#34; HELM_EXTRA=() while IFS= read -r line; do [[ -z \u0026#34;$line\u0026#34; ]] \u0026amp;\u0026amp; continue key=\u0026#34;${line%%=*}\u0026#34; val=\u0026#34;${line#*=}\u0026#34; HELM_EXTRA+=(--set \u0026#34;${key}=${val//,/\\\\,}\u0026#34;) done \u0026lt;\u0026lt;\u0026lt; \u0026#34;$EXTRA_SET_ARGS\u0026#34; helm upgrade --install \u0026#34;$RELEASE_NAME\u0026#34; ./charts/example-job \\ --namespace \u0026#34;$NAMESPACE\u0026#34; \\ --values \u0026#34;$VALUES_FILE\u0026#34; \\ --set \u0026#34;image.registry.url=${REGISTRY}\u0026#34; \\ --set \u0026#34;image.repository=${REPO}\u0026#34; \\ --set \u0026#34;image.tag=${TAG}\u0026#34; \\ \u0026#34;${HELM_EXTRA[@]}\u0026#34; \\ --wait \\ --timeout=5m \\ --atomic 表达式、上下文与环境变量 表达式语法 GitHub Actions使用${{ }}计算表达式：\nyamlenv: short-sha: ${{ github.sha }} jobs: deploy: if: ${{ github.ref == \u0026#39;refs/heads/main\u0026#39; }} runs-on: ubuntu-latest steps: - run: echo \u0026#34;${{ github.repository }} @ ${{ github.sha }}\u0026#34;在if字段中通常可以省略${{ }}，但保留它更容易辨认这是工作流表达式。常用运算符和函数包括：\n比较：==、!=、\u0026amp;\u0026amp;、|| 字符串：contains()、startsWith()、endsWith()、format() 文件：hashFiles('**/package-lock.json') 状态：success()、failure()、cancelled()、always() 转换：fromJSON()、toJSON() 例如只允许main分支部署，并在失败时执行清理：\nyamljobs: deploy: if: github.event_name == \u0026#39;push\u0026#39; \u0026amp;\u0026amp; github.ref == \u0026#39;refs/heads/main\u0026#39; runs-on: ubuntu-latest steps: - name: Deploy run: ./deploy.sh - name: Collect logs if: ${{ failure() || cancelled() }} run: ./collect-logs.sh常用上下文 上下文是由GitHub Actions提供的只读数据：\n上下文 常见用途 github 仓库、分支、提交、事件和触发者，例如github.ref env 当前工作流、任务或步骤定义的环境变量 vars 仓库、组织或环境级别的非敏感配置变量 secrets 仓库、组织或环境级别的敏感信息 runner 执行机器的信息，例如操作系统和临时目录 matrix 当前矩阵任务的参数 steps 当前任务中带有id的步骤输出 needs 前置任务的结果和任务输出 inputs 手动触发或可复用工作流的输入 表达式和run脚本中的环境变量不是同一种语法：\nyamlsteps: - name: Compare syntaxes env: COMMIT_SHA: ${{ github.sha }} run: | echo \u0026#34;Shell variable: $COMMIT_SHA\u0026#34; echo \u0026#34;Expression: ${{ github.ref }}\u0026#34;在步骤之间传递数据 步骤输出写入GITHUB_OUTPUT，环境变量写入GITHUB_ENV：\nyamlsteps: - id: version run: echo \u0026#34;value=1.2.3\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; - name: Use step output run: echo \u0026#34;version is ${{ steps.version.outputs.value }}\u0026#34; - name: Set an environment variable run: echo \u0026#34;RELEASE_CHANNEL=stable\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_ENV\u0026#34; - name: Use the environment variable run: echo \u0026#34;$RELEASE_CHANNEL\u0026#34;GITHUB_OUTPUT只适合传递步骤输出，不能把一个任务中的文件直接带到另一个任务。每个任务通常使用全新的Runner，跨任务传文件应该使用Artifact。\nMatrix：批量执行同一类任务 Matrix会把一个job展开成多个独立任务，每个任务都有自己的Runner、工作目录、日志和状态。它适合处理一批结构相同但参数不同的任务，不需要为每个模块、镜像或部署对象复制一份job。\n简单值矩阵：API 工作流把多个模块拆成并行测试任务；needs: test的后续任务会等待所有模块测试结束。\nyamljobs: test: if: ${{ !needs.skip-workflow.outputs.skip }} needs: [skip-workflow] name: \u0026#34;test: ${{ matrix.module }}\u0026#34; runs-on: example-runner-low-docker strategy: fail-fast: true matrix: module: - example-bot - example-jobs steps: - uses: actions/checkout@v4 - run: ./gradlew :${{ matrix.module }}:test 对象矩阵：镜像构建任务把项目名和 Runner 规格放在同一行；没有指定 Runner 的项目使用默认值。\nyamljobs: push-docker: name: \u0026#34;docker: ${{ matrix.project.name }}\u0026#34; runs-on: ${{ matrix.project.runner || \u0026#39;example-runner-low-docker\u0026#39; }} strategy: fail-fast: true matrix: project: - name: example-api runner: example-runner-high - name: example-bot steps: - uses: actions/checkout@v4 - uses: ./.github/actions/build-docker-images with: project: ${{ matrix.project.name }} push: ${{ github.event_name != \u0026#39;pull_request\u0026#39; }} 动态矩阵：部署上下文不是直接写死在 Workflow 中，而是先由脚本扫描目录生成 JSON，再用fromJson()把输出转换为矩阵。\nbashresults=\u0026#34;[]\u0026#34; while read -r context; do runner=example-runner-mini results=$(echo \u0026#34;$results\u0026#34; | jq \u0026#34;. += [{context: \\\u0026#34;$context\\\u0026#34;, runner: \\\u0026#34;$runner\\\u0026#34;}]\u0026#34;) done \u0026lt; \u0026lt;(ls deploy/values) echo \u0026#34;$results\u0026#34; | jq -c .yamljobs: contexts-matrix: runs-on: example-runner outputs: contexts: ${{ steps.contexts.outputs.contexts }} steps: - uses: actions/checkout@v4 - id: contexts run: | matrix=$(./scripts/contexts-matrix.sh) echo \u0026#34;contexts\u0026lt;\u0026lt;EOF\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; echo \u0026#34;$matrix\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; echo \u0026#34;EOF\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; deploy-apps: needs: contexts-matrix strategy: fail-fast: false matrix: context: ${{ fromJson(needs.contexts-matrix.outputs.contexts) }} runs-on: ${{ matrix.context.runner }} steps: - uses: actions/checkout@v4 - uses: azure/k8s-set-context@v4 with: method: kubeconfig kubeconfig: ${{ secrets.KUBECONFIG }} context: ${{ matrix.context.context }}这个矩阵的每一行同时携带 Kubernetes context 和 Runner 名称，因此矩阵不仅决定部署多少次，还决定每次部署连接哪个集群、使用哪类 Runner。\ninclude矩阵：多个 CronJob 共用同一套部署步骤，只把名称、镜像和 values 文件作为每一行的参数。\nyamljobs: deploy-jobs: strategy: fail-fast: false matrix: include: - name: sync-data image-name: example-job - name: refresh-cache image-name: example-job steps: - uses: actions/checkout@v4 - uses: ./.github/actions/deploy-job with: release-name: example-${{ matrix.name }} values-file: deploy/example/${{ matrix.name }}.yaml image: ${{ matrix.image-name }} 基础设施矩阵：基础设施工作流把产品配置和集群配置组合起来，每个矩阵任务部署一个组件。\nyamljobs: deploy: name: \u0026#34;Deploy ${{ matrix.product.name }} ${{ matrix.cluster.name }}\u0026#34; runs-on: ${{ matrix.cluster.runner }} strategy: fail-fast: false matrix: product: - name: example-ingress chart: ingress namespace: istio-system cluster: - name: default runner: example-runner-mini context: example-cluster steps: - uses: actions/checkout@v4 - uses: azure/k8s-set-context@v4 with: method: kubeconfig kubeconfig: ${{ secrets.KUBECONFIG }} context: ${{ matrix.cluster.context }} - run: | helm upgrade --install ${{ matrix.product.name }} \\ ./charts/${{ matrix.product.chart }} \\ --namespace ${{ matrix.product.namespace }} fail-fast：测试和镜像构建矩阵使用true，一个任务失败时尽快停止同组任务；部署矩阵使用false，避免一个组件失败后取消其他组件的部署。\n缓存、产物与任务依赖 Cache与 Artifact的区别 Cache用于加速重复执行，例如缓存npm、pip或Maven依赖；缓存失效时仍然应该能够重新安装 Artifact用于保存本次运行生成的文件，例如构建目录、测试报告和安装包，并在后续任务中下载 不要把缓存当作可靠的发布存储，也不要把密钥或敏感文件上传为产物 使用setup-node缓存npm依赖：\nyamlsteps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: \u0026#34;20\u0026#34; cache: npm - run: npm ci - run: npm test使用产物连接构建任务和部署任务：\nyamljobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: \u0026#34;20\u0026#34; cache: npm - run: npm ci - run: npm run build - name: Upload build artifact uses: actions/upload-artifact@v4 with: name: site path: dist/ deploy: needs: build runs-on: ubuntu-latest steps: - name: Download build artifact uses: actions/download-artifact@v4 with: name: site path: dist/ - run: ./deploy.sh dist部署任务不应该再次从分支检出并重新构建，而应该部署已经通过检查的构建产物。这样可以减少“测试的代码”和“实际部署的代码”不一致的可能性。\n常见 CI/CD 流程 Pull Request 检查 最基础的CI只做验证，不写入仓库，也不部署生产环境：\nyamlname: Pull Request CI on: pull_request: branches: [main] push: branches: [main] permissions: contents: read jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: \u0026#34;20\u0026#34; cache: npm - run: npm ci - run: npm run lint --if-present - run: npm test - run: npm run build如果push和pull_request都指向同一个提交，可能会产生两次运行。可以根据团队习惯只保留一种触发方式，或者使用concurrency取消同一分支上过时的运行：\nyamlconcurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true构建后部署 一个典型的持续部署工作流是“构建和验证成功后才能部署”：\nyamlname: Build and deploy on: push: branches: [main] workflow_dispatch: permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: \u0026#34;20\u0026#34; cache: npm - run: npm ci - run: npm test - run: npm run build - uses: actions/upload-artifact@v4 with: name: app path: dist/ deploy: needs: build if: github.event_name == \u0026#39;push\u0026#39; \u0026amp;\u0026amp; github.ref == \u0026#39;refs/heads/main\u0026#39; runs-on: ubuntu-latest environment: name: production permissions: contents: read steps: - uses: actions/download-artifact@v4 with: name: app path: dist/ - run: ./deploy.sh distenvironment: production可以配合环境保护规则配置审批人、部署分支限制和环境级别的密钥。这样，Pull Request仍然可以执行构建和测试，但不会因为deploy任务存在而自动发布。\n发布容器镜像 发布到GitHub Container Registry时，通常只在main或发布标签上执行，并授予任务最小的packages: write权限：\nyamlname: Publish image on: push: branches: [main] tags: [\u0026#34;v*.*.*\u0026#34;] permissions: contents: read packages: write jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: context: . push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}tricks Workflow 的执行顺序 Workflow 中的任务由 needs 连接，文件中的先后顺序不决定执行顺序。以 API 部署流程为例：\ntextskip-workflow ├── test ├── push-docker ├── build-helper-images ├── contexts-matrix └── spotless-check（仅 pull_request） test + push-docker + build-helper-images + contexts-matrix └── deploy-apps（非 pull_request） └── notification（非 pull_request） needs：没有依赖的任务可以并行运行；deploy-apps 等待测试、镜像构建和部署上下文都完成，notification 再等待部署完成。\nyamljobs: test: needs: [skip-workflow] push-docker: needs: [skip-workflow] build-helper-images: needs: [skip-workflow] contexts-matrix: needs: [skip-workflow] deploy-apps: if: ${{ github.event_name != \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; !needs.skip-workflow.outputs.skip }} needs: - skip-workflow - push-docker - build-helper-images - contexts-matrix - test notification: if: ${{ github.event_name != \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; !needs.skip-workflow.outputs.skip }} needs: - skip-workflow - push-docker - deploy-apps push 与 pull_request 合理的顺序是先准备环境，再恢复缓存，然后检查代码和测试，最后构建镜像。在这个流程里，只有 push 才登录镜像仓库、推送镜像和部署：\ntextpull_request: checkout -\u0026gt; setup -\u0026gt; cache -\u0026gt; test/lint -\u0026gt; build（push=false） -\u0026gt; report push: checkout -\u0026gt; setup -\u0026gt; cache -\u0026gt; test -\u0026gt; build（push=true） -\u0026gt; deploy -\u0026gt; notification 共同 steps：两个事件都先检出代码、准备工具链、恢复缓存，再运行测试和静态检查。Gradle 的缓存由 setup-gradle 在这一步处理。\nyamlsteps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: \u0026#34;21\u0026#34; - uses: gradle/actions/setup-gradle@v6 - run: ./gradlew test pull_request：只验证和构建，不登录私有镜像仓库，不推送镜像；下面的构建和报告步骤接在共同 steps 后。\nyaml- uses: docker/build-push-action@v5 with: context: . push: false - if: failure() uses: actions/upload-artifact@v4 with: name: test-reports path: \u0026#34;**/build/reports/tests/test/\u0026#34; push：共同 steps 成功后登录镜像仓库、构建并推送镜像；部署作为后续 job，通过 needs 等待构建完成。\nyaml - uses: docker/login-action@v3 with: registry: ${{ secrets.DOCKER_REGISTRY }} username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - uses: docker/build-push-action@v5 with: context: . push: true deploy: if: ${{ github.event_name == \u0026#39;push\u0026#39; }} needs: build-image steps: - uses: actions/checkout@v4 - run: helm upgrade --install example-app ./charts/example-app spotless-check：格式检查只放在 Pull Request 分支上，不进入 push 的部署链路；步骤顺序是检出代码、准备 Java、准备 Gradle、执行格式检查。\nyamljobs: spotless-check: if: ${{ github.event_name == \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; !needs.skip-workflow.outputs.skip }} needs: [skip-workflow] runs-on: example-runner-mini steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: \u0026#34;21\u0026#34; - uses: gradle/actions/setup-gradle@v6 - run: ./gradlew spotlessCheck 缓存 实际有三种缓存方式，以及一个共享缓存后端，作用不同。\n缓存 Action 执行到对应步骤时先恢复缓存；如果没有命中，任务继续执行，作业结束时再保存新缓存。显式 actions/cache 的 key 由系统、项目和锁文件哈希组成，restore-keys 只用于回退到同一项目的旧缓存。\ngradle/actions/setup-gradle：缓存 Gradle User Home；cache-read-only: false 允许构建任务写入缓存。\nyaml- uses: gradle/actions/setup-gradle@v6 with: cache-read-only: false - run: ./gradlew test actions/setup-node：通过 cache 缓存 npm 或 Yarn 依赖。\nyaml- uses: actions/setup-node@v4 with: node-version: \u0026#34;22\u0026#34; cache: yarn - run: yarn install --immutable actions/cache：自己指定目录、精确键和备用键；Yarn 缓存使用锁文件哈希区分版本。\nyaml- uses: actions/cache@v4 with: path: websites/example-site/.yarn/cache key: ${{ runner.os }}-yarn-example-site-${{ hashFiles(\u0026#39;websites/example-site/yarn.lock\u0026#39;) }} restore-keys: | ${{ runner.os }}-yarn-example-site- ACTIONS_RESULTS_URL：self-hosted Runner 将 Action 结果和缓存请求指向集群内的 cache server；Runner 的 runner 容器使用自定义镜像，Docker 构建使用 dind 容器。\nyamlcontainers: - name: runner image: registry.example.com/example/actions-runner:${DOCKER_TAG} env: - name: ACTIONS_RESULTS_URL value: http://example-cache-github-actions-cache-server.arc-systems.svc.cluster.local/ - name: dind image: registry.example.com/example/docker:28.1.1-dind args: - dockerd - --host=unix:///var/run/docker.sock Docker 构建没有配置 cache-from 或 cache-to；Docker-in-Docker 数据目录只是 Runner 层面的本地存储，不等于共享的 Docker registry 缓存。\nRunner、镜像和缓存服务器 runs-on：普通构建和测试使用 self-hosted Runner；部署 cache server 或 Runner 控制器的任务可以使用 ubuntu-latest。\nyaml# 测试、镜像构建 runs-on: example-runner-low-docker # 部署、通知 # runs-on: example-runner BASE_IMAGE_REGISTRY：PR 使用 mirror.gcr.io 获取基础镜像；push 使用私有 registry。Testcontainers 也按事件切换镜像前缀。\nyamlenv: BASE_IMAGE_REGISTRY: ${{ github.event_name == \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; \u0026#39;mirror.gcr.io\u0026#39; || format(\u0026#39;{0}/example\u0026#39;, secrets.DOCKER_REGISTRY) }} TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX: ${{ github.event_name == \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; \u0026#39;mirror.gcr.io/\u0026#39; || format(\u0026#39;{0}/example/\u0026#39;, secrets.DOCKER_REGISTRY) }} cache server 使用公开镜像 ghcr.io/falcondev-oss/github-actions-cache-server:8.0.0，通过 Helm 部署，并用持久卷保存 /app/.data。\nyamlimage: repository: ghcr.io/falcondev-oss/github-actions-cache-server tag: \u0026#34;8.0.0\u0026#34; persistentVolumeClaim: enabled: true template: metadata: name: github-cache-0 spec: accessModes: - ReadWriteOnce resources: requests: storage: 300Gi storageClassName: github-cache-0bashhelm upgrade --install example-cache \\ --namespace arc-systems \\ --create-namespace \\ ./charts/github-actions-cache-server 触发器与并发控制 workflow_dispatch：部署文件大多写成空配置，只提供手动重跑入口；它和 push、pull_request 并列。\n手动运行时选择的是 Workflow 的 ref；普通部署的环境由 github.ref_name、github.ref 或固定的 NAMESPACE 决定，这类部署文件不把环境做成手动输入。 yamlon: push: branches: - main paths: - websites/gen2/** - websites/chart/** - .github/workflows/deploy-websites.yaml - .github/workflows/deploy-root-websites.yaml workflow_dispatch: paths-ignore：排除大目录后，用!恢复需要参与触发的子目录。\nyamlon: push: paths-ignore: - websites/** - .github/actions/** - \u0026#34;!.github/actions/build-docker-images/**\u0026#34; - .github/workflows/** - \u0026#34;!.github/workflows/deploy-api.yaml\u0026#34; - charts/** - \u0026#34;!charts/spring-app/**\u0026#34; - \u0026#34;!charts/spring-job/**\u0026#34; workflow_run：部署 Workflow 同时支持手动触发和上游 Workflow 成功后的触发；检出head_sha，并从上游运行的分支计算命名空间。\nyamlon: workflow_dispatch: workflow_run: workflows: - Deploy example-app.com types: - completed branches: - main jobs: deploy: if: ${{ github.event_name == \u0026#39;workflow_dispatch\u0026#39; || github.event.workflow_run.conclusion == \u0026#39;success\u0026#39; }} runs-on: example-runner env: NAMESPACE: ${{ (github.event.workflow_run.head_branch || github.ref_name) == \u0026#39;main\u0026#39; \u0026amp;\u0026amp; \u0026#39;production\u0026#39; || \u0026#39;staging\u0026#39; }} HEAD_BRANCH: ${{ github.event.workflow_run.head_branch || github.ref_name }} HEAD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.workflow_run.head_sha || github.sha }} - id: image-tag run: | SHORT_SHA=\u0026#34;${HEAD_SHA:0:7}\u0026#34; echo \u0026#34;tag=${HEAD_BRANCH}-${SHORT_SHA}\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; workflow_dispatch.inputs：只有少数运维 Workflow 定义输入：transfer-domains.yaml接收域名和 nameserver，copy-docker-image.yaml接收镜像名，create-ai-deployment.yaml接收生成部署文件所需的参数。这些输入描述要执行的运维动作，不是普通部署 Workflow 的环境选择。\nyamlon: workflow_dispatch: inputs: domain: required: true nameservers: required: false create-zone: required: false type: boolean default: truebashDOMAINS=\u0026#34;${{ inputs.domain }}\u0026#34; IFS=\u0026#39;,\u0026#39; read -ra DOMAIN_ARRAY \u0026lt;\u0026lt;\u0026lt; \u0026#34;$DOMAINS\u0026#34; for DOMAIN in \u0026#34;${DOMAIN_ARRAY[@]}\u0026#34;; do DOMAIN=$(echo \u0026#34;$DOMAIN\u0026#34; | xargs) # 处理单个 DOMAIN donebashsource=\u0026#34;${{ github.event.inputs.image }}\u0026#34; ref=\u0026#34;${source##*/}\u0026#34; if [[ \u0026#34;$ref\u0026#34; != *:* \u0026amp;\u0026amp; \u0026#34;$ref\u0026#34; != *@* ]]; then source=\u0026#34;${source}:latest\u0026#34; fi target=\u0026#34;${DOCKER_REGISTRY}/${source}\u0026#34; docker buildx imagetools create -t \u0026#34;$target\u0026#34; \u0026#34;$source\u0026#34;yamlon: workflow_dispatch: inputs: api_only: required: false default: false type: boolean prod_only: required: false default: false type: boolean env: FULL_DOMAIN: ${{ inputs.full_domain || format(\u0026#39;{0}.{1}\u0026#39;, inputs.id, inputs.domain) }} concurrency.group：deploy-api.yaml使用 Pull Request 编号，否则使用分支或标签引用；同一目标的新运行会取消旧运行。\nyamlconcurrency: group: deploy-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true 任务级配置 defaults.run：统一 Shell，并在需要时设置默认工作目录。\nyamldefaults: run: shell: bash -exo pipefail {0} working-directory: websites/scripts/domain-transfer env：在工作流级别根据分支和事件生成后续任务使用的值。\nyamlenv: NAMESPACE: ${{ github.ref_name == \u0026#39;main\u0026#39; \u0026amp;\u0026amp; \u0026#39;production\u0026#39; || \u0026#39;staging\u0026#39; }} BASE_IMAGE_REGISTRY: ${{ github.event_name == \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; \u0026#39;mirror.gcr.io\u0026#39; || format(\u0026#39;{0}/example\u0026#39;, secrets.DOCKER_REGISTRY) }} 任务跳过、输出与矩阵 outputs：skip-workflow是仓库自定义的检查任务，不是 GitHub Actions 的特殊任务名。提交信息包含[skip workflow]时，它输出skip=true；后续任务通过needs.skip-workflow.outputs.skip跳过。\nyamljobs: skip-workflow: runs-on: example-runner outputs: skip: ${{ steps.skip.outputs.skip }} steps: - uses: actions/checkout@v4 - name: Check commit message id: skip run: | commit_message=$(cat \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; ${{ github.event.head_commit.message }} EOF ) if echo \u0026#34;$commit_message\u0026#34; | grep -qF \u0026#39;[skip workflow]\u0026#39;; then echo \u0026#34;skip=true\u0026#34; | tee -a \u0026#34;$GITHUB_OUTPUT\u0026#34; fi test: needs: skip-workflow if: ${{ !needs.skip-workflow.outputs.skip }} runs-on: example-runner-low-docker strategy: matrix: module: - example-bot - example-jobs steps: - uses: actions/checkout@v4 - run: ./gradlew :${{ matrix.module }}:testbashgit commit -m \u0026#34;docs: update [skip workflow]\u0026#34; git push GITHUB_OUTPUT：deploy-api.yaml对 Pull Request 全部构建；其他事件先执行git diff，也支持[rebuild helpers]强制重建。\nyamljobs: build-helper-images: strategy: matrix: project: [init-database, set-dns, shell-sandbox] steps: - uses: actions/checkout@v4 with: fetch-depth: 2 - id: detect run: | if [[ \u0026#34;${{ github.event_name }}\u0026#34; == \u0026#34;pull_request\u0026#34; ]]; then echo \u0026#34;changed=true\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; elif grep -qF \u0026#39;[rebuild helpers]\u0026#39; \u0026lt;\u0026lt;\u0026lt; \u0026#34;${{ github.event.head_commit.message }}\u0026#34;; then echo \u0026#34;changed=true\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; elif git diff --quiet HEAD~1 HEAD -- \u0026#34;docker/${{ matrix.project }}\u0026#34;; then echo \u0026#34;changed=false\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; else echo \u0026#34;changed=true\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; fi - if: ${{ steps.detect.outputs.changed == \u0026#39;true\u0026#39; }} uses: docker/build-push-action@v5 with: context: ./docker/${{ matrix.project }} push: ${{ github.event_name != \u0026#39;pull_request\u0026#39; }} strategy.matrix：先生成上下文 JSON，再用fromJson()生成部署矩阵。\nyamljobs: contexts-matrix: if: ${{ !needs.skip-workflow.outputs.skip }} needs: skip-workflow runs-on: example-runner outputs: contexts-matrix: ${{ steps.contexts-matrix.outputs.contexts-matrix }} steps: - uses: actions/checkout@v4 - id: contexts-matrix run: | matrix=$(./scripts/contexts-matrix.sh) echo \u0026#34;contexts-matrix\u0026lt;\u0026lt;EOF\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; echo \u0026#34;$matrix\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; echo \u0026#34;EOF\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; deploy-apps: if: ${{ github.event_name != \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; !needs.skip-workflow.outputs.skip }} needs: - skip-workflow - contexts-matrix strategy: fail-fast: false matrix: context: ${{ fromJson(needs.contexts-matrix.outputs.contexts-matrix) }} runs-on: ${{ matrix.context.runner }} steps: - uses: actions/checkout@v4 workflow_call：deploy-app.yaml把产品配置作为 JSON 字符串传入，在被调用 Workflow 中解析。\nyamlon: workflow_call: inputs: product: required: true type: string namespace: required: true type: string github-sha: required: true type: string commit-message: required: true type: string jobs: deploy-app: name: ${{ fromJson(inputs.product).context }}: ${{ fromJson(inputs.product).name }} runs-on: ${{ fromJson(inputs.product).runner || \u0026#39;example-runner\u0026#39; }} steps: - run: | helm -n \u0026#34;${{ inputs.namespace }}\u0026#34; upgrade \u0026#34;${{ fromJson(inputs.product).name }}\u0026#34; \\ --install \u0026#34;./charts/${{ fromJson(inputs.product).chart }}\u0026#34; 镜像构建与部署 github.event_name：Pull Request 只构建不推送，部署任务直接跳过。\nyamljobs: build-image: runs-on: example-runner-low-docker steps: - uses: actions/checkout@v4 - uses: docker/build-push-action@v5 with: context: ./websites/example-site platforms: linux/amd64 push: ${{ github.event_name != \u0026#39;pull_request\u0026#39; }} deploy: if: ${{ github.event_name != \u0026#39;pull_request\u0026#39; }} needs: build-image runs-on: example-runner steps: - uses: actions/checkout@v4 docker/metadata-action：先根据事件生成标签规则，再将多行输出交给 Action。\nyamlsteps: - id: metadata-tags run: | tags_file=$(mktemp) if [[ \u0026#34;$GITHUB_EVENT_NAME\u0026#34; == \u0026#34;pull_request\u0026#34; ]]; then cat \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; \u0026gt; \u0026#34;$tags_file\u0026#34; type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} EOF else cat \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; \u0026gt; \u0026#34;$tags_file\u0026#34; type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix={{branch}}- EOF fi echo \u0026#34;tags\u0026lt;\u0026lt;EOF\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; cat \u0026#34;$tags_file\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; echo \u0026#34;EOF\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; - id: meta uses: docker/metadata-action@v5 with: images: ${{ github.event_name != \u0026#39;pull_request\u0026#39; \u0026amp;\u0026amp; format(\u0026#39;{0}/{1}\u0026#39;, env.DOCKER_REGISTRY, env.IMAGE_NAME) || env.IMAGE_NAME }} tags: ${{ steps.metadata-tags.outputs.tags }} - uses: docker/build-push-action@v5 with: context: ./websites/example-site platforms: linux/amd64 push: ${{ github.event_name != \u0026#39;pull_request\u0026#39; }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} helm：先检查 Runner 中是否已有 Helm，没有时才安装。\nyamlsteps: - id: helm-installed run: | if command -v helm \u0026amp;\u0026gt; /dev/null; then echo \u0026#34;helm-version=$(helm version --client --template=\u0026#39;{{.Version}}\u0026#39;)\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_OUTPUT\u0026#34; fi - if: ${{ steps.helm-installed.outputs.helm-version == \u0026#39;\u0026#39; }} uses: azure/setup-helm@v4.2.0 helm upgrade：部署失败时区分“操作进行中”和普通失败；前者回滚后继续，后者等待后重试。\nbashfor retry in {1..10}; do res=$(helm -n \u0026#34;$NAMESPACE\u0026#34; upgrade \u0026#34;$name\u0026#34; \\ --install ./charts/$chart \\ --create-namespace \\ -f \u0026#34;$values_file\u0026#34; \\ -f \u0026#34;$amd_value_file\u0026#34; \\ --set image.repository=\u0026#34;$image\u0026#34; \\ --set image.tag=\u0026#34;$tag\u0026#34; 2\u0026gt;\u0026amp;1) if [[ $? -eq 0 ]]; then break fi if [[ \u0026#34;$res\u0026#34; = *\u0026#34;another operation (install/upgrade/rollback) is in progress\u0026#34;* ]]; then helm -n \u0026#34;$NAMESPACE\u0026#34; rollback \u0026#34;$name\u0026#34; continue fi sleep 10 done 部署辅助 mktemp、envsubst和yq：先生成临时 values 文件，再写入部署专属字段。\nbashvalue_file=$(mktemp).yaml yq eval \u0026#39;.gateway.hosts = []\u0026#39; websites/chart/values.yaml | tee \u0026#34;$value_file\u0026#34; cat \u0026lt;\u0026lt;EOF \u0026gt;\u0026gt; \u0026#34;$value_file\u0026#34; tolerations: - key: example.io/environment operator: Equal value: \u0026#34;$NAMESPACE\u0026#34; effect: NoSchedule EOF values_file=$(mktemp).yaml envsubst \u0026#39;$PUBLIC_VAR $REGISTRY\u0026#39; \u0026lt; deploy/values/template.yaml \u0026gt; \u0026#34;$values_file\u0026#34; GITHUB_STEP_SUMMARY：批处理任务逐项写入表格，最后统一判断失败。\nbashIFS=\u0026#39;,\u0026#39; read -ra ITEMS \u0026lt;\u0026lt;\u0026lt; \u0026#34;$INPUT\u0026#34; FAILED_COUNT=0 echo \u0026#34;| Item | Status |\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_STEP_SUMMARY\u0026#34; echo \u0026#34;|------|--------|\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_STEP_SUMMARY\u0026#34; for ITEM in \u0026#34;${ITEMS[@]}\u0026#34;; do if python main.py --domain \u0026#34;$ITEM\u0026#34; --nameservers \u0026#34;$NAMESERVERS\u0026#34;; then echo \u0026#34;| $ITEM | Success |\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_STEP_SUMMARY\u0026#34; else echo \u0026#34;| $ITEM | Failed |\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_STEP_SUMMARY\u0026#34; FAILED_COUNT=$((FAILED_COUNT + 1)) fi done if [ \u0026#34;$FAILED_COUNT\u0026#34; -gt 0 ]; then exit 1 fi persist-credentials：自动格式化前关闭检出步骤的凭据持久化，只在存在变更时提交。\nyamlsteps: - uses: actions/checkout@v4 with: persist-credentials: false - run: ./gradlew spotlessApply - run: | if ! git diff --name-only --exit-code; then git add . git commit -m \u0026#34;ci(bot): format\u0026#34; git push fi 自托管服务下的 CD 顺序 在 GitHub 或 GHES 负责仓库和调度、其余服务均为自托管的情况下，CD 主链如下：\nflowchart TD A[提交代码或手动触发] --\u0026gt; B[GitHub Actions 调度器] B --\u0026gt; C[ARC Controller] C --\u0026gt; D[Kubernetes Runner Pod] D -. 读取/保存缓存 .-\u0026gt; E[私有 Cache Server] E --\u0026gt; F[(PVC 或对象存储)] D --\u0026gt; G[Checkout、测试、构建镜像] G --\u0026gt; H[推送到私有镜像仓库] H --\u0026gt; I[azure/k8s-set-context] I --\u0026gt; J[Helm upgrade/install] J --\u0026gt; K[Kubernetes API Server] K --\u0026gt; L[创建或更新 Deployment] L --\u0026gt; M[应用 Pod] M -. imagePullSecret 拉取镜像 .-\u0026gt; H K --\u0026gt; N[post-install / post-upgrade Jobs] N --\u0026gt; O[init-database] O --\u0026gt; P[私有数据库] N --\u0026gt; Q[set-dns] Q --\u0026gt; R[私有 DNS 服务商] L --\u0026gt; S[rollout status / 健康检查] N --\u0026gt; S S --\u0026gt; T[通知结果]缓存服务器是 Runner 的旁路服务，不是 CD 主链中的必经步骤。如果镜像已经在 CI 阶段推送完成，CD 可以从azure/k8s-set-context开始。\nGitHub 与私有 Runner 的连接 GitHub 不需要访问 Kubernetes 的私网地址。ARC Listener 和 Runner 都从 Kubernetes 内部主动向 GitHub 建立出站连接：\nflowchart LR subgraph K8s[私有 Kubernetes 集群] C[ARC Controller] L[ARC Listener] R[Runner Pod] C --\u0026gt;|管理 Kubernetes 资源| L C --\u0026gt;|创建和销毁| R end L --\u0026gt;|HTTPS 出站连接| G[GitHub Actions 服务] R --\u0026gt;|接收任务、上传日志和结果| G W[Workflow: runs-on: example-runner] --\u0026gt; G G --\u0026gt;|匹配已注册的 Runner Scale Set| L githubConfigUrl决定 Runner 注册到哪个组织或仓库，githubConfigSecret提供 ARC 访问 GitHub API 所需的 GitHub App 凭据。 ARC Listener 注册 Runner Scale Set 后，GitHub 会记录它的名称和标签。Workflow 的runs-on只匹配这个名称，不匹配私网 IP。 任务进入队列后，Listener 获取任务并通知 Controller，Controller 再让 Kubernetes 创建 Runner Pod。 Runner Pod 完成任务后，仍然通过出站连接向 GitHub 上传日志和执行结果；GitHub 不需要主动进入私有网络。 因此私有集群至少需要能够解析并通过 HTTPS 访问 GitHub Actions 相关服务。没有公网出口时，需要配置 NAT、代理或其他出站网关。 ","permalink":"https://echo.ishian.me/2026/08/14/github-actions/","summary":"讲解GitHub Actions的核心概念、Workflow与Job的YAML配置及CI/CD实践","title":"GitHub Actions: 工作流与 CI/CD"},{"content":"Kubernetes 快速入门 简介 Kubernetes(简称 k8s)是一个开源的容器编排平台。相比于Docker，它能够在集群级别自动化地管理容器的部署、扩缩容和运维，但学习成本也相对更高 除了基础的容器调度与自愈能力，k8s原生支持服务发现、负载均衡、滚动更新与配置管理等服务治理，配合 Istio 等服务网格组件，可以替代传统的RPC框架，将流量治理、可观测性、安全等能力下沉至基础设施层 官方文档 工具介绍 对本地环境，minikube 提供了一种 k8s 的单节点集群方案 minikube start --driver=docker：用 docker 驱动启动一个 minikube 容器(集群)，让它接管 docker minikube status：查看集群状态 minikube stop：停止集群 minikube delete：删除集群 minikube dashboard：快速开启一个由 minikube 管理的 dashboard(管理容器的可视化网页) k3s 是一个轻量级的 k8s 发行版，单个二进制文件即包含全部组件，内存占用约 512MB，适合低配机器、边缘设备与 CI 环境，也可作为本地方案的另一选择 curl -sfL https://get.k3s.io | sh -：一条命令安装并启动，自带 containerd、Flannel 网络、本地存储与 Service LoadBalancer，开箱即用 k3s kubectl get node：内置了 kubectl，无需单独安装 凭证位于 /etc/rancher/k3s/k3s.yaml，让外部 kubectl/helm 使用需 export KUBECONFIG=/etc/rancher/k3s/k3s.yaml k3s-killall.sh / k3s-uninstall.sh：停止/彻底卸载 k3d 在 docker 容器里运行 k3s，可以像 minikube 一样快速创建和销毁多节点集群，兼顾轻量与隔离 kubectl 是和 k8s 集群交互的命令行工具 kubectl [command] [kind] [name] [flags] 是一般的命令格式，其中 kind 是 k8s API 定义的类型 kubectl get pod/service/node：获取 pod/service/node 等的简短状态 kubectl logs [podname]：查看日志 kubectl exec -it [podname] -- /bin/sh：进入容器(--interactive --tty) kubectl apply [-f x.yaml]...：应用配置 helm 是 k8s 的包管理器，替代了 apply 并提供更好的管理方式 Chart：一个完整应用所需的全部配置，在其目录下 Chart.yaml：一个 Chart 的定义 values-*.yaml：所有环境变量的定义，默认查找 values.yaml 文件 templates/：go tmpl 文件所在目录，定义应用的配置模板 Chart.lock：锁文件，由 helm dependency build 根据 Chart.yaml 所依赖的 chart 的版本生成 charts/：本 chart 依赖的子 charts Release：一个 Chart 在集群中的运行实例 Repo：类似 docker hub，每个仓库有一个 index.yaml 核心字段 apiVersion与kind k8s使用声明式管理，所有集群的状态通过yaml来定义\napiVersion：api版本定义，格式为GROUP/VERSION，一般来说v1是稳定版本，但存在特例\n核心组(v1)：基础资源，包含Pod、Service、ConfigMap、Secret、Namespace apps/v1：工作负载管理，包含Deployment、StatefulSet、DaemonSet、ReplicaSet batch/v1：批处理任务，包含Job、CronJob networking.k8s.io/v1：网络配置，包含Ingress、NetworkPolicy rbac.authorization.k8s.io/v1：RBAC权限控制，包含Role、RoleBinding、ClusterRole、ClusterRoleBinding storage.k8s.io/v1：存储管理，包含StorageClass、VolumeAttachment autoscaling/v2：自动扩缩容，包含HorizontalPodAutoscaler(v1已废弃，生产使用v2) apiextensions.k8s.io/v1：自定义资源，包含CustomResourceDefinition(CRD)，不常用 networking.istio.io/v1：Istio 网络配置，包含VirtualService、DestinationRule、Gateway、ServiceEntry security.istio.io/v1：Istio 安全配置，包含AuthorizationPolicy、PeerAuthentication telemetry.istio.io/v1：Istio 可观测性配置，包含Telemetry 使用kubectl explain [kind]查看资源对应的apiVersion和字段说明 使用kubectl api-resources查看所有资源及其所属API组 kind：资源类型，如Pod、Deployment、Service\nPod：最小部署单元，包含一个或多个容器，适用于临时调试、手动运行任务 Deployment：管理无状态应用，支持滚动更新和回滚，最常用的控制器 StatefulSet：管理有状态应用(如数据库)，提供稳定的网络标识和持久存储 DaemonSet：在每个节点上运行一个 Pod 副本，适用于日志收集、监控代理 ReplicaSet：维护 Pod 副本数量，Deployment 底层依赖，通常不直接使用 Job：一次性任务，执行完成后退出 CronJob：定时任务，按 Cron 表达式周期性执行 Service：为 Pod 提供稳定的访问入口，支持负载均衡 Ingress：七层负载均衡，基于域名和路径路由到不同 Service ConfigMap：存储非敏感配置数据，以键值对形式挂载到 Pod Secret：存储敏感信息(密码、密钥)，base64 编码存储，本身并不加密也不安全，只是因为敏感信息通常存在特殊字符甚至是二进制数据，而k8s传输数据使用json，因此通常转为base64，而Secret则是在ConfigMap的基础上添加一层base64编码校验的资源类型 StorageClass：存储模板，定义动态供给方式和存储参数 PVC(PersistentVolumeClaim)：命名空间级别的存储声明，由用户创建，引用 StorageClass 动态供给 PV(PersistentVolume)：集群级别的存储资源，云环境通常由 StorageClass 动态创建 NetworkPolicy：网络策略，限制 Pod 间的网络通信 LimitRange：资源限制范围，限制 Pod 的资源使用上下限 ResourceQuota：资源配额，限制命名空间的总资源使用量 HorizontalPodAutoscaler：自动扩缩容，根据 CPU/内存使用率调整 Pod 副本数 Namespace：资源隔离，不同命名空间的资源相互独立 示例：\nyamlapiVersion: v1 kind: Pod metadata: name: pod-1 metadata metadata.name：适用于所有资源，资源名称在命名空间内唯一，DNS 解析时会用到(如 Service 的 name.namespace.svc.cluster.local)\nmetadata.labels：适用于所有资源，键值对标签用于资源筛选和关联，Service 通过 labels 匹配 Pod，Deployment 通过 labels 管理 Pod\nmetadata.namespace：适用于所有资源，指定资源所属命名空间，默认为 default，系统组件在 kube-system\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod labels: app: nginx namespace: default spec: containers: - name: nginx image: nginx:1.21 spec spec.selector：适用于 Deployment、StatefulSet、DaemonSet、Job、Service，选择器匹配 Pod 的 labels，决定控制器管理哪些 Pod，必须与 spec.template.metadata.labels 匹配\nspec.template：适用于 Deployment、StatefulSet、DaemonSet、Job、CronJob，Pod 模板定义要创建的 Pod 的完整配置，包含 metadata.labels 和 spec.containers\nspec.replicas：适用于 Deployment、StatefulSet，期望的 Pod 副本数量，默认为 1\n示例：\nyamlapiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 containers 与 volumes spec.containers：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，容器列表，每个容器包含以下字段：\nname：容器名称 image：镜像名称和标签 ports.containerPort：容器暴露的端口 resources.requests：最小资源需求(CPU、内存)，调度依据 resources.limits：最大资源限制(CPU、内存)，防止资源争抢 volumeMounts：存储卷挂载路径 env：环境变量配置 livenessProbe：存活探针，检测容器是否存活，失败则重启容器 initialDelaySeconds：首次探测延迟 periodSeconds：探测间隔 failureThreshold：失败阈值，允许的最大连续探测失败次数，超出此阈值将重启pod successThreshold：成功阈值，将该pod视为健康的最小连续探测成功次数，存活探针的成功阈值必须是1 探测方式：httpGet(指定 path 和 port)、tcpSocket(指定 port)、exec(指定 command) readinessProbe：就绪探针，检测容器是否就绪，失败则从 Service 摘除。参数与 livenessProbe 相同，但失败后不会重启容器，只是暂时停止流量进入 spec.volumes：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，存储卷定义，常用类型：\nemptyDir：临时存储，Pod 删除时数据丢失，适用于缓存、临时文件 configMap：挂载 ConfigMap，需指定 name，可选择挂载特定 items 或全部键值对。支持 optional: true(ConfigMap 不存在时不阻塞启动)和 defaultMode(文件权限，默认 0644) secret：挂载 Secret，参数与 ConfigMap 类似，额外支持 secretName 指定 Secret 名称。常用于挂载 TLS 证书(secret.items 指定 tls.crt 和 tls.key)或数据库密码 persistentVolumeClaim：挂载 PVC，需指定 claimName，支持 readOnly: true 设置只读挂载。PVC 绑定的 PV 可以是静态供给(管理员预先创建)或动态供给(通过 StorageClass 自动创建) 示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: nginx image: nginx:1.21 ports: - containerPort: 80 resources: requests: memory: \u0026#34;64Mi\u0026#34; cpu: \u0026#34;250m\u0026#34; limits: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;500m\u0026#34; volumeMounts: - name: config mountPath: /etc/config readOnly: true - name: cache mountPath: /var/cache - name: tls mountPath: /etc/tls readOnly: true - name: data mountPath: /var/data livenessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: 80 initialDelaySeconds: 5 periodSeconds: 5 failureThreshold: 3 successThreshold: 1 volumes: - name: config configMap: name: my-config defaultMode: 0644 - name: cache emptyDir: {} - name: tls secret: secretName: my-tls-secret items: - key: tls.crt path: cert.pem - key: tls.key path: key.pem - name: data persistentVolumeClaim: claimName: my-pvc readOnly: false Service 独有字段 spec.type：服务类型，可选值：\nClusterIP：默认值，仅在集群内部可访问 NodePort：通过节点端口暴露，范围 30000-32767，但问题在于限制端口且不支持域名，用户不方便访问且会暴露服务IP，仅适用于开发测试 LoadBalancer：配合云厂商负载均衡器使用，适用于生产环境的对外提供服务 ExternalName：映射到外部域名，相当于DNS层面的代理，方便内部服务访问外部第三方服务 spec.externalTrafficPolicy：外部流量策略，仅 NodePort/LoadBalancer 有效\nCluster(默认)：流量经 kube-proxy 转发，可跨节点负载均衡，但会做一次 SNAT，丢失客户端真实 IP Local：流量只转发到本节点的 Pod，保留客户端真实 IP，但本节点无 Pod 时流量会被丢弃，需配合探针摘除不健康节点 spec.ports：端口配置列表，每个端口包含：\nport：Service 暴露的端口，供集群内部访问 targetPort：Pod 容器端口 nodePort：节点端口(仅 NodePort 类型，范围 30000-32767) protocol：协议，默认 TCP name：端口名称，多端口时必须指定 spec.clusterIP：虚拟 IP 地址，设置为 None 时为 Headless Service，无头服务是指，该Service不会做负载均衡，在DNS解析它管理的pod的地址时会直接指向其管理的pod IP，通常用于有状态服务，例如数据库服务等\n示例：\nyamlapiVersion: v1 kind: Service metadata: name: my-service spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080 Ingress 独有字段 spec.rules：路由规则列表，基于域名和路径转发到不同 Service\nhost：域名，如 api.example.com（可选，不指定则匹配所有域名） http.paths：路径规则列表 path：匹配路径，如 /api、/ pathType：路径匹配类型 Exact：精确匹配 Prefix：前缀匹配 ImplementationSpecific：由 Ingress Controller 决定 backend：后端服务配置 service.name：Service 名称 service.port.number：Service 端口号 spec.tls：TLS 证书配置，支持 HTTPS\nspec.ingressClassName：指定 Ingress Controller 的名称，对应 IngressClass 资源的名称，通常不需要自己创建\nnginx：Nginx Ingress Controller 安装时自动创建 istio：Istio Gateway 安装时自动创建 示例：\nyamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 80 tls: - hosts: - api.example.com secretName: tls-secret ConfigMap 与 Secret 独有字段 data：键值对配置数据，Secret 的值需 base64 编码\nstringData：仅 Secret 支持，明文写入自动转 base64\ntype：仅 Secret 支持，类型标识\nOpaque：默认 kubernetes.io/dockerconfigjson：镜像拉取凭证 kubernetes.io/tls：TLS 证书 示例：\nyamlapiVersion: v1 kind: ConfigMap metadata: name: my-config data: key1: value1 key2: value2 PV 与 PVC 独有字段 供给模式：\n静态供给：管理员手动创建 PV，用户创建 PVC 绑定已有 PV，适用于本地存储、NFS 等无动态供给的场景 动态供给：用户创建 PVC 并引用 StorageClass，Provisioner 自动创建 PV 并绑定，云环境推荐方式 spec.capacity.storage：仅 PV，存储大小，如 10Gi\nspec.accessModes：访问模式\nReadWriteOnce：单节点读写 ReadWriteMany：多节点读写 ReadOnlyMany：多节点只读 spec.storageClassName：存储类名称，用于 PV 和 PVC 绑定\nspec.resources.requests.storage：仅 PVC，请求的存储大小\nspec.volumeMode：卷模式\nFilesystem：默认值，文件系统模式 Block：块设备模式 spec.persistentVolumeReclaimPolicy：仅 PV，回收策略\nRetain：保留数据，需手动清理 Recycle：回收数据，已废弃 Delete：删除存储资源 示例(动态供给)：\nyamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: standard # 引用 StorageClass 动态创建 PV StorageClass 独有字段 provisioner：存储供给器，决定使用哪种存储后端 云厂商：kubernetes.io/aws-ebs、kubernetes.io/gce-pd、kubernetes.io/azure-disk 第三方：nfs-client、rbd.csi.ceph.com（Ceph） 本地存储：kubernetes.io/no-provisioner parameters：存储参数，传递给供给器的配置 AWS EBS：type: gp2（存储类型）、zone: us-east-1a（可用区）、fsType: ext4 GCE PD：type: pd-standard、replication-type: regional-pd Ceph RBD：pool: rbd、cluster: ceph reclaimPolicy：回收策略，PVC 删除时 PV 的处理方式 Delete：删除 PV 和底层存储（默认） Retain：保留 PV 和数据，需手动清理 Recycle：已废弃，执行 rm -rf /volume/* 后重用 volumeBindingMode：绑定模式，控制 PV 创建和绑定的时机 Immediate：立即创建并绑定（默认） WaitForFirstConsumer：延迟到第一个 Pod 调度后再创建，确保 PV 与 Pod 在同一可用区 allowVolumeExpansion：是否允许动态扩容（true/false） 需要存储后端支持，AWS EBS、GCE PD 支持，NFS 不支持 mountOptions：挂载选项，传递给 mount 命令 例如：[\u0026quot;hard\u0026quot;, \u0026quot;nfsvers=4.1\u0026quot;, \u0026quot;noatime\u0026quot;]（NFS 选项） allowedTopologies：限制 PV 创建的拓扑位置（如可用区） 配合 WaitForFirstConsumer 使用，确保存储与 Pod 在同一区域 示例： yamlapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type: gp2 zone: us-east-1a reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer allowVolumeExpansion: true mountOptions: - debug allowedTopologies: - matchLabelExpressions: - key: topology.kubernetes.io/zone values: - us-east-1a - us-east-1bDeployment 独有字段 spec.strategy：更新策略\nRollingUpdate：滚动更新，逐步替换 Pod Recreate：重建，先删除旧 Pod 再创建新 Pod spec.strategy.rollingUpdate.maxSurge：滚动更新时最多超出期望副本数的数量，控制更新过程中可以多创建多少个新 Pod，可以是数字或百分比\nspec.strategy.rollingUpdate.maxUnavailable：滚动更新时最多不可用的副本数，控制更新过程中最多有多少个旧 Pod 可以不可用，可以是数字或百分比，设置为0同时使maxSurge\u0026gt;0可保证不停机更新\nmaxSurge 与 maxUnavailable 配合使用场景：\nmaxSurge: 25% + maxUnavailable: 0：先创建新 Pod，确认就绪后再删除旧 Pod，零停机但资源占用高 maxSurge: 0 + maxUnavailable: 25%：先删除旧 Pod 再创建新 Pod，资源占用低但会有短暂不可用 maxSurge: 1 + maxUnavailable: 1：平衡更新速度和资源占用，最常用配置 示例：\nyamlapiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 StatefulSet 独有字段 spec.serviceName：关联的 Headless Service 名称，用于 Pod 网络标识\nspec.podManagementPolicy：Pod 管理策略\nOrderedReady：默认值，按顺序逐个启动或删除 Pod Parallel：并行启动或删除 Pod spec.updateStrategy：更新策略\nRollingUpdate：滚动更新 OnDelete：手动删除 Pod 后更新 示例：\nyamlapiVersion: apps/v1 kind: StatefulSet metadata: name: my-statefulset spec: serviceName: my-headless-service replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 Job 与 CronJob 独有字段 spec.completions：仅 Job，完成次数，默认为 1\nspec.parallelism：仅 Job，并行 Pod 数量，默认为 1\nspec.backoffLimit：仅 Job，失败重试次数，默认为 6\nspec.schedule：仅 CronJob，Cron 表达式，如 0 * * * *(每小时)\nspec.concurrencyPolicy：仅 CronJob，并发策略\nAllow：允许并发 Forbid：禁止并发 Replace：替换旧任务 spec.successfulJobsHistoryLimit：仅 CronJob，保留成功 Job 数量，默认为 3\nspec.failedJobsHistoryLimit：仅 CronJob，保留失败 Job 数量，默认为 1\n示例：\nyamlapiVersion: batch/v1 kind: CronJob metadata: name: my-cronjob spec: schedule: \u0026#34;0 * * * *\u0026#34; concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: containers: - name: job image: busybox command: [\u0026#34;echo\u0026#34;, \u0026#34;hello\u0026#34;] restartPolicy: OnFailure 健康检查字段 spec.containers[].livenessProbe：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，存活探针，检测容器是否存活，失败则重启容器\nspec.containers[].readinessProbe：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，就绪探针，检测容器是否就绪，失败则从 Service 摘除\nspec.containers[].startupProbe：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，启动探针，检测容器是否启动完成，用于慢启动应用\n探测方式(三种探针通用)：\nhttpGet：HTTP 请求探测，需指定 path 和 port tcpSocket：TCP 端口探测，需指定 port exec：执行命令探测，需指定 command 示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: nginx image: nginx:1.21 livenessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 10 periodSeconds: 5 readinessProbe: httpGet: path: /ready port: 80 initialDelaySeconds: 5 periodSeconds: 3 startupProbe: httpGet: path: /health port: 80 failureThreshold: 30 periodSeconds: 10 节点调度字段 spec.nodeSelector：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，简单的节点选择器，通过标签匹配节点\nspec.nodeName：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，直接指定节点名称，跳过调度器\nspec.tolerations：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，容忍度，允许 Pod 调度到有污点的节点\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: nodeSelector: disktype: ssd tolerations: - key: \u0026#34;key1\u0026#34; operator: \u0026#34;Equal\u0026#34; value: \u0026#34;value1\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; containers: - name: nginx image: nginx:1.21 镜像拉取字段 spec.containers[].imagePullPolicy：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，镜像拉取策略，可选值：\nAlways：每次都拉取镜像 IfNotPresent(默认)：本地不存在时才拉取 Never：从不拉取，只使用本地镜像 spec.imagePullSecrets：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，拉取私有镜像的凭证，存储在 Secret 中\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: imagePullSecrets: - name: my-registry-secret containers: - name: nginx image: nginx:1.21 imagePullPolicy: IfNotPresent 环境变量字段 spec.containers[].env：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，环境变量列表，支持直接赋值和引用 ConfigMap/Secret\nspec.containers[].envFrom：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，从 ConfigMap 或 Secret 批量导入环境变量\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: nginx image: nginx:1.21 env: - name: ENV_VAR_1 value: \u0026#34;value1\u0026#34; - name: DB_PASSWORD valueFrom: secretKeyRef: name: my-secret key: password envFrom: - configMapRef: name: my-config 重启策略字段 spec.restartPolicy：适用于 Pod，重启策略，可选值：\nAlways(默认)：容器退出后总是重启 OnFailure：容器异常退出时重启 Never：从不重启 示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: restartPolicy: OnFailure containers: - name: nginx image: nginx:1.21 服务账户字段 spec.serviceAccountName：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，指定 Pod 使用的 ServiceAccount，用于访问 API Server\nspec.automountServiceAccountToken：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，是否自动挂载 ServiceAccount Token，默认为 true\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: serviceAccountName: my-sa automountServiceAccountToken: false containers: - name: nginx image: nginx:1.21 初始化容器字段 spec.initContainers：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，初始化容器列表，在主容器启动前按顺序执行，必须全部成功退出后主容器才会启动\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: initContainers: - name: init-db image: busybox command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo Initializing...\u0026#34;] containers: - name: nginx image: nginx:1.21 优先级字段 spec.priorityClassName：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，优先级类名，高优先级 Pod 抢占低优先级 Pod 资源\nspec.priority：适用于 Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob，优先级数值，范围 1000000000 到 -2147483648\n示例：\nyamlapiVersion: v1 kind: Pod metadata: name: my-pod spec: priorityClassName: high-priority containers: - name: nginx image: nginx:1.21 Istio 简介 Istio 是最流行的服务网格(Service Mesh)实现，将流量治理、安全、可观测性等能力从业务代码中下沉到基础设施层，业务容器无感知 架构分为两层： 控制面(istiod)：负责配置分发(Pilot)、证书签发(CA)，是集群中的单一大脑 数据面(Envoy)：以 sidecar 形式注入到每个 Pod，通过 iptables 透明劫持出入流量，所有经过 Pod 的请求都会先经过 Envoy 代理 核心能力： 流量治理：按域名/路径/权重路由、灰度发布、重试、超时、熔断、会话保持 安全：服务间自动 mTLS、基于身份的访问控制(AuthorizationPolicy) 可观测性：自动生成访问日志、指标(Metrics)与调用链(Tracing) Sidecar 注入方式： 命名空间标签：kubectl label namespace [ns] istio-injection=enabled，该命名空间下所有新建 Pod 都会被注入 Pod 级别：在 spec.template.metadata.annotations 中设置 sidecar.istio.io/inject: \u0026quot;true\u0026quot;(或 \u0026quot;false\u0026quot; 排除) 注入发生在 Pod 创建时，已运行的 Pod 需要重建才能生效 安装 推荐使用 helm 安装，分为两个 chart，依赖顺序不能颠倒：\nbashhelm repo add istio https://istio-release.storage.googleapis.com/charts kubectl create namespace istio-system helm install istio-base istio/base -n istio-system helm install istiod istio/istiod -n istio-system --wait base chart 定义了 CRD 与集群级别的角色等前置资源，istiod chart 部署控制面本身\n常用配置(通过 istiod 的 values 覆盖 meshConfig)：\nyamlistiod: meshConfig: accessLogFile: /dev/stdout # 开启访问日志，输出到容器标准输出 outboundTrafficPolicy: mode: ALLOW_ANY # 未注册外部服务的出站流量默认放行(默认 REGISTRY_ONLY 会拒绝) global: proxy: resources: # sidecar(Envoy)的资源限制，避免占用过多业务资源 limits: cpu: 1000m memory: 1Gi istioctl 是 Istio 的命令行工具，常用命令：\nistioctl proxy-status：查看所有 sidecar 与 istiod 的同步状态 istioctl proxy-config route [pod]：查看指定 sidecar 的实际路由表，排查路由不生效问题 istioctl analyze：静态分析集群内 Istio 配置的潜在冲突 Gateway 入口网关，控制哪些流量可以进入网格，相当于传统架构的负载均衡器 + Nginx 的 server 块\nspec.selector：通过标签匹配承载流量的 ingress gateway Pod(通常部署在 istio-system)，一个网关 Pod 可被多个 Gateway 资源复用\nspec.servers：服务器列表，每个包含：\nport：监听端口(number、name、protocol) hosts：监听的域名列表 tls：TLS 配置，httpsRedirect: true 将 HTTP 重定向到 HTTPS；mode: SIMPLE 终止 TLS，证书通过 credentialName 引用 Secret 示例：\nyamlapiVersion: networking.istio.io/v1 kind: Gateway metadata: name: my-gateway spec: selector: istio: ingress # 匹配 ingress gateway Pod 上的标签 servers: - port: number: 80 name: http protocol: HTTP tls: httpsRedirect: false hosts: - api.example.com - port: number: 443 name: https protocol: HTTPS tls: mode: SIMPLE credentialName: example-tls # 引用同命名空间的 kubernetes.io/tls 类型 Secret hosts: - api.example.com VirtualService 路由规则，控制流量在网格内如何转发，相当于 Nginx 的 location 块，需同时被 Gateway(入口)或 Service(内部)引用\nspec.hosts：匹配的目标域名，网关场景为外部域名，内部场景为 Service 的 FQDN\nspec.gateways：绑定的 Gateway 名称，内部流量路由可省略(默认 mesh)\nspec.http：HTTP 路由规则列表，按顺序首次匹配生效：\nmatch：匹配条件(uri.prefix、headers、port 等) route：转发目标，destination.host 指向 Service，destination.port.number 指向端口 weight：多目标按权重分流，灰度发布的基础 rewrite、timeout、retries、fault(注入延迟/中止做故障演练) spec.tcp：TCP 路由，适用于非 HTTP 协议(如数据库)的端口转发\n示例：\nyamlapiVersion: networking.istio.io/v1 kind: VirtualService metadata: name: my-vs spec: hosts: - api.example.com gateways: - my-gateway http: - match: - uri: prefix: /v2 route: - destination: host: api-v2.default.svc.cluster.local port: number: 80 - route: # 兜底规则，其余流量走稳定版本 - destination: host: api-v1.default.svc.cluster.local port: number: 80 weight: 90 - destination: host: canary.default.svc.cluster.local port: number: 80 weight: 10 # 10% 灰度 DestinationRule 目标策略，定义流量到达目标 Service 后的转发策略，与 VirtualService 配合使用\nspec.host：作用的 Service(需与 VirtualService 的 route.destination.host 一致)\nspec.trafficPolicy：流量策略：\nloadBalancer：负载均衡算法，consistentHash(基于源 IP/指定 Header 的会话保持)、leastRequest、random connectionPool：连接池限制(tcp/http 的最大连接数、并发请求数) outlierDetection：熔断策略，连续错误次数(consecutive5xxErrors)达到阈值后摘除实例 tls：ISTIO_MUTUAL 开启双向 mTLS spec.subsets：将同一 Service 按 Pod 标签划分为不同子集(如 v1、v2)，供 VirtualService 按 subset 精确路由\n示例：\nyamlapiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: my-dr spec: host: api.default.svc.cluster.local trafficPolicy: loadBalancer: consistentHash: useSourceIp: true # 基于来源 IP 的会话保持 subsets: - name: stable labels: version: stable - name: canary labels: version: canary ServiceEntry 将网格外部的服务注册进网格，让外部服务也能享受 Istio 的路由、熔断与 mTLS 能力，是最能体现“替代自建代理”的资源\n典型场景：将外部 IP/域名伪装成集群内的 Service，内部服务直接通过 Service 域名访问外部数据库或第三方 API\nspec.hosts：对外暴露的虚拟域名，通常用 [name].[namespace].svc.cluster.local 让内部访问无感知\nspec.addresses：为该条目分配的虚拟 IP(可选)\nspec.ports：端口与协议(HTTP、TCP、MONGO 等)\nspec.resolution：地址解析方式，STATIC(端点 IP 固定)、DNS(按域名解析)\nspec.location：MESH_EXTERNAL(外部服务)或 MESH_INTERNAL\nspec.endpoints：实际后端端点列表(address + ports)\n示例(将外部 MongoDB 伪装成集群内 Service)：\nyamlapiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: external-mongo spec: hosts: - external-mongo.default.svc.cluster.local # 内部用该域名访问 ports: - number: 27017 name: mongo protocol: MONGO resolution: STATIC location: MESH_EXTERNAL endpoints: - address: 10.0.0.10 # 外部 MongoDB 实际地址 ports: mongo: 27017 AuthorizationPolicy 访问控制策略，基于工作负载身份(而非 IP)决定谁可以访问谁，默认全部放行，一旦定义则未匹配的请求全部拒绝\nspec.selector：策略作用的 Pod 标签\nspec.action：ALLOW(白名单)、DENY(黑名单)、CUSTOM\nspec.rules[].from.source：请求来源，ipBlocks/remoteIpBlocks(IP 黑白名单)、principals(对端服务身份)、namespaces\nspec.rules[].to.operation：请求目标，methods、paths、ports\n示例(网关层 IP 黑名单)：\nyamlapiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: ingress-policy namespace: istio-system spec: selector: matchLabels: istio: ingress action: DENY rules: - from: - source: remoteIpBlocks: - 1.2.3.4/32 证书管理 Gateway 的 HTTPS 证书通常配合 cert-manager 自动签发与续期，形成 ClusterIssuer → Certificate → Secret(kubernetes.io/tls) → Gateway.credentialName 的链路\nHTTP-01 校验需要真实公网入口，通配符域名(*.example.com)必须使用 DNS-01 校验，此时需要一个 DNS 服务商的 API 凭证 Secret(如 Cloudflare 的 Api Token)\n示例(DNS-01 通配符证书)：\nyamlapiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-dns spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: admin@example.com privateKeySecretRef: name: letsencrypt-key solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-token key: api-token Charts 实践 以上能力在生产实践中通常都会沉淀为一个 helm 仓库，并按“基础设施 → 应用模板 → 代理组件”分层组织，这也是大家的普遍做法\n总览 基础设施层： base：集群初始化，创建基础命名空间与镜像仓库凭证(registry secret)，并托管 cert-manager 依赖，作为其他所有 chart 的前置 istio：以 umbrella chart 封装官方 base + istiod 依赖，统一注入 meshConfig(访问日志格式、ALLOW_ANY 出站策略、sidecar 资源限制)，并创建带 istio-injection 标签的 istio-system 命名空间 ingress：封装官方 gateway chart(部署 ingress gateway Pod)，配套 ClusterIssuer、Cloudflare DNS 凭证、AuthorizationPolicy IP 黑名单等模板 monitoring：聚合 kube-prometheus-stack + loki + promtail + grafana + grafana-mcp 依赖，并模板化大量 Grafana 告警规则(证书过期、Pod 状态、CPU/内存、JVM、WebSocket 等)与数据源配置 应用模板层： common：最简无状态应用模板，仅 Deployment + Service + 镜像凭证 spring-app：功能最全的 Spring 应用模板，见下文 spring-job：一次性/定时任务模板，仅 Job + 密钥 + 存储，无 Service 与路由 代理组件层(纯 Istio 资源，不包含任何业务镜像)： simple-proxy：L7 反向代理，Gateway + VirtualService + ServiceEntry + DestinationRule 四件套，把外部域名流量转发到一组上游，可选 consistentHash 会话保持，替代自建 Nginx mongo-proxy：L4 代理，ServiceEntry 将外部 MongoDB IP 注册为集群内 Service，VirtualService 的 tcp 规则把网关 443 端口转发到 27017 mongodb：bitnami mongodb 依赖 + mongo-express 管理界面，通过 Istio 网关暴露 环境特定层： github-actions-cache-server：自托管 GitHub Actions 缓存服务(PVC + HPA + Ingress) aliyun-pod-ip：阿里云 ack-extend-network-controller 依赖，管理 Pod 弹性 IP 应用模板的设计要点 以 spring-app 为例，一个生产级应用 chart 通常包含：\n工作负载：Deployment(探针、资源限制)+ HPA(CPU 利用率自动扩缩)+ PDB(保证节点维护时的最小可用副本数) 流量入口：Service + Istio 的 Gateway/DestinationRule(可选会话保持)，证书由 cert-manager 自动签发 安全： ServiceAccount + 最小化 RBAC，关闭 automountServiceAccountToken NetworkPolicy 默认拒绝出站，仅放行 DNS，并显式屏蔽云厂商 metadata 网段(169.254.0.0/16、100.64.0.0/10)，防止应用被 SSRF 后窃取云凭证 数据库密钥、Cloudflare Token 等以 Secret 模板管理 多云适配：同一 chart 通过 values 切换对象存储挂载方案(oss/gcs/cos)与 StorageClass(按云厂商提供不同存储模板) 环境隔离：默认注入 nodeSelector: example.io/environment={{ .Release.Namespace }} 与对应容忍度，让命名空间与节点环境一一对应，用户可整体覆盖 生命周期钩子：post-install/post-upgrade Job 执行数据库迁移(schema 变更)等前置任务 沉淀的最佳实践 umbrella chart 封装上游依赖：不直接部署第三方 chart，而是以依赖(dependencies + condition 开关)的方式包装，统一版本与定制，升级时只需改一处版本号 版本统一为 0.0.0：由 CI 在发布时统一注入版本号，避免手工维护，可回溯到具体 commit 用 Istio 资源替代传统代理：外部 MySQL/MongoDB/第三方 API 的访问入口全部用 ServiceEntry + VirtualService 建模，业务方看到的是稳定的集群内域名，底层地址变更只需改 chart 的 values 可观测性即代码：Grafana 数据源、告警规则、通知渠道全部模板化在 monitoring chart 中，随应用一起评审与发布 ","permalink":"https://echo.ishian.me/2026/04/20/k8s/","summary":"介绍 Kubernetes 核心概念与 Pod、Service、Ingress 等资源的常用字段","title":"Kubernetes: 生产级的分布式集群部署"},{"content":"Go: 定时任务实现方案 K8s CronJob k8s的基础能力较强，通过k8s的CronJob，可以实现稳定可靠的定时任务，Go的实现也较为简单 使用k8s适用于大部分没有复杂依赖的定时任务，包括数据库定时更新、日志转储、消息定时推送等 可以在Go中进行简单的抽象，实现使用一个容器负责所有任务的定时执行，通过类似profile以及expr区分不同任务的执行时机 也可以不引入标签，把每个任务暴露为子命令，由CronJob直接覆盖容器的args来指定执行的任务 简单示例 Job抽象\ngopackage job type Job interface { Run() error Profile() string } var jobs []Job // RegisterJob register a job that will be detected once the app start func RegisterJob(j Job) { jobs = append(jobs, j) } Job执行\ngopackage job import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;github.com/expr-lang/expr\u0026#34; ) func Run() { var ( wg sync.WaitGroup profile = os.Getenv(\u0026#34;PROFILE\u0026#34;) env = map[string]bool{} ) for p := range strings.SplitSeq(profile, \u0026#34;,\u0026#34;) { env[strings.TrimSpace(p)] = true } for _, j := range jobs { wg.Add(1) go func(j Job) { defer wg.Done() program, err := expr.Compile(j.Profile(), expr.Env(env), expr.AllowUndefinedVariables()) if err != nil { fmt.Println(err.Error()) return } output, err := expr.Run(program, env) if err != nil { fmt.Println(err.Error()) return } if match, ok := output.(bool); ok \u0026amp;\u0026amp; match { if err := j.Run(); err != nil { fmt.Println(err.Error()) } } }(j) } wg.Wait() } Job实现\ngopackage jobs import ( \u0026#34;fmt\u0026#34; \u0026#34;example.com/m/job\u0026#34; ) func init() { job.RegisterJob(\u0026amp;UserDataUpdaterJob{}) } type UserDataUpdaterJob struct{} func (j *UserDataUpdaterJob) Run() error { fmt.Println(\u0026#34;aaa\u0026#34;) return nil } func (j *UserDataUpdaterJob) Profile() string { return \u0026#34;user_data_updater\u0026#34; } 容器入口：init()注册依赖包被导入，之后只需要执行一次job.Run()并退出，由k8s负责按周期创建Pod\ngopackage main import ( \u0026#34;example.com/m/job\u0026#34; _ \u0026#34;example.com/m/jobs\u0026#34; ) func main() { job.Run() } CronJob资源配置 上述容器对应一份CronJob资源，通过环境变量PROFILE指定本次触发的任务：\nyamlapiVersion: batch/v1 kind: CronJob metadata: name: example-jobs spec: schedule: \u0026#34;0 3 * * *\u0026#34; # 每天 3 点触发 timeZone: Asia/Shanghai # k8s 1.27+ 支持, 不设置时按 UTC 解释 concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: backoffLimit: 2 activeDeadlineSeconds: 600 template: spec: restartPolicy: Never containers: - name: jobs image: example.com/jobs:latest env: - name: PROFILE value: \u0026#34;user_data_updater,log_rotator\u0026#34; 常用字段：\nschedule：标准cron表达式，最小粒度为分钟；k8s 1.27+支持timeZone字段指定时区 concurrencyPolicy：上一次任务还未结束时又到了触发时机的策略，Allow（默认，允许并存）、Forbid（跳过本次）、Replace（终止旧任务并替换） startingDeadlineSeconds：错过了触发时机（如调度器不可用）时，允许延迟启动的最大秒数，超过则跳过 successfulJobsHistoryLimit与failedJobsHistoryLimit：保留的已完成任务数量，用于排查问题，避免Job与Pod无限堆积 jobTemplate.spec.backoffLimit：单次任务失败后的重试次数 jobTemplate.spec.activeDeadlineSeconds：单次任务的超时时间，防止任务挂死 cron表达式 schedule使用5字段的标准cron表达式，从左至右依次为：\ntext┌───────── 分钟 (0-59) │ ┌───────── 小时 (0-23) │ │ ┌───────── 日 (1-31) │ │ │ ┌───────── 月 (1-12) │ │ │ │ ┌───────── 星期 (0-7, 0 与 7 都是周日) │ │ │ │ │ * * * * * 月与星期也可以使用名称，如JAN-DEC、SUN-SAT\n特殊字符：\n*：任意值 ,：枚举多个值，如0,30 -：连续范围，如1-5 /：步进，如*/15表示每15分钟 常见示例：\n0 3 * * *：每天3:00 */30 * * * *：每30分钟 0 3 * * 1-5：工作日的3:00 0 0 1 * *：每月1号的0:00 30 8 * * 6：每周六的8:30 一个容易踩坑的语义：当日与星期都被限制（都不是*）时，只要其中一个匹配即触发，是OR的关系而非AND；只想限制其中一个时，另一个必须写成*\n最小粒度为1分钟，秒级调度只能由进程内cron（WithSeconds()）承担\nargs子命令方式 更贴近k8s习惯的方式是不引入标签，把每个任务暴露为子命令，入口按os.Args分发（实际项目通常用cobra注册子命令，还可以为不同任务声明独立的参数）：\ngopackage main import ( \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; \u0026#34;example.com/m/jobs\u0026#34; ) func main() { if len(os.Args) \u0026lt; 2 { log.Fatal(\u0026#34;missing job name\u0026#34;) } switch os.Args[1] { case \u0026#34;user-data-updater\u0026#34;: if err := (\u0026amp;jobs.UserDataUpdaterJob{}).Run(); err != nil { log.Fatal(err) } default: log.Fatalf(\u0026#34;unknown job %s\u0026#34;, os.Args[1]) } } 此时CronJob通过覆盖args指定本次执行的任务，一个镜像配合多份几乎相同的资源清单即可：\nyamlcontainers: - name: jobs image: example.com/jobs:latest args: [\u0026#34;user-data-updater\u0026#34;] 与PROFILE方式的对比：\n任务内容直接体现在资源清单中，kubectl get cronjob -o yaml即可看出该CronJob执行什么；PROFILE则需要事先了解标签约定 每个CronJob只跑一个任务，重试、超时与历史记录的粒度更清晰；PROFILE可以把多个小任务合并在一次触发中执行，节省冷启动开销 PROFILE配合expr可以表达组合条件（如nightly \u0026amp;\u0026amp; prod）；args是一对一的显式指定 两者不冲突，也可以用args选择任务、用PROFILE补充环境分组 多份相似的CronJob清单不需要手工维护，可以用Helm或kustomize模板化，或通过CI/CD的矩阵批量生成\n进程内定时任务 对于与服务同生命周期的任务，例如需要共享内存状态、连接池的任务，更适合在进程内调度，通常使用github.com/robfig/cron/v3\ngopackage main import ( \u0026#34;github.com/robfig/cron/v3\u0026#34; ) func main() { c := cron.New() // 默认 5 字段表达式, 与 k8s 一致 c.AddFunc(\u0026#34;0 3 * * *\u0026#34;, func() { /* ... */ }) c.AddFunc(\u0026#34;@every 10m\u0026#34;, func() { /* ... */ }) c.AddJob(\u0026#34;@daily\u0026#34;, \u0026amp;UserDataUpdaterJob{}) c.Start() defer c.Stop() select {} } cron.New(cron.WithSeconds())：使用6字段表达式，支持秒级精度，k8s做不到这一点\n预定义表达式：@hourly、@daily、@weekly、@monthly，以及@every \u0026lt;duration\u0026gt;（如@every 1h30m）\ncron.WithChain(cron.Recover(logger))：通过中间件链为任务注入panic恢复与日志\nc.Stop()返回一个context，等待正在执行的任务结束后才会取消，适合优雅退出：\ngoctx := c.Stop() \u0026lt;-ctx.Done() 多副本问题 CronJob每次触发只会创建一个Pod，天然单实例，不存在重复执行 进程内cron随Deployment水平扩容会为每个副本都调度一次，需要额外处理： 任务本身幂等，允许重复执行（例如覆盖式的缓存刷新）\n保持单副本并使用Recreate更新策略，避免滚动更新期间新旧副本并存\n使用分布式锁竞争执行权，例如redis的SET NX：\ngook, err := rdb.SetNX(ctx, \u0026#34;lock:user_data_updater\u0026#34;, hostID, 10*time.Minute).Result() if err != nil || !ok { return nil // 其他副本已在执行 } defer rdb.Del(ctx, \u0026#34;lock:user_data_updater\u0026#34;) 方案对比 维度 K8s CronJob 进程内cron 触发精度 分钟级 秒级 部署形态 独立Pod，与业务服务解耦 随服务部署，共享进程 资源开销 每次触发冷启动，需重建连接 常驻内存，复用连接与状态 多副本 天然单实例 需要分布式锁或单副本 失败处理 k8s负责重试并保留历史记录 需要自行实现重试与记录 总体而言：无状态、可独立重试的任务交给K8s CronJob，把调度、重试和可观测性都交给基础设施；强依赖服务内部状态的任务留在进程内，但要处理好多副本下的重复执行问题 ","permalink":"https://echo.ishian.me/2026/04/10/cronjob-go/","summary":"讲解基于k8s CronJob和Go自建调度器的定时任务实现方案与代码示例","title":"Go: Cron Job"},{"content":"sqlx 连接数据库 sqlx是标准库sql的扩展库，只要是sql支持的数据库，或者说只要是实现了driver.Driver接口的数据库都支持\n注册驱动：需要匿名导入对应的驱动（它们的init()里会调用sql.Register()），例如：\ngoimport _ \u0026#34;github.com/go-sql-driver/mysql\u0026#34; sqlx.Open(driverName, DSN string) (*DB, error)：打开一个数据库连接，其中driverName为注册时指定的名称，DSN为数据源，得到一个DB对象：\ngosqlx.Open(\u0026#34;mysql\u0026#34;, \u0026#34;root:pwd@tcp(127.0.0.1:3306)/test\u0026#34;) sqlx.Connect(dn, dsn string) (*DB, error)：与Open()类似，但是会主动通过ping来验证连接是否有效\n也可以通过*DB.Ping()手动验证\n*DB.Close()：关闭数据库连接\nDB API *DB.Query(query string, args ...any) (*sql.Rows, error)：基础查询 *DB.Queryx(query string, args ...any) (*sqlx.Rows, error)：增强查询，返回sqlx.Rows *DB.NamedQuery(query string, arg any) (*sqlx.Rows, error)：增强命名查询，返回sqlx.Rows *DB.Get(dest any, query string, args ...any) error：查询单行记录赋值给结构体，查询结果必须只有一行 *DB.Select(dest any, query string, args ...any) error：查询多行记录赋值给结构体切片 *DB.Exec(query string, args ...any) (sql.Result, error)：执行SQL语句，通常是非查询语句如INSERT等 *DB.NamedExec(query string, arg any) (sql.Result, error)：命名参数执行，即支持通过:xxx而不是?传递参数，arg可以传递map或是结构体 *DB.Preparex(query string) (*Stmt, error)：预编译语句，得到的Stmt可重复执行，需要Close() *DB.PrepareNamed(query string) (*NamedStmt, error)：预编译命名语句 sql.Result提供LastInsertId()与RowsAffected()方法获取主键id与影响的行数 *DB.XXXContext(...)：带上下文版本的方法，在开发中建议使用带上下文的查询以实现 Stmt与NamedStmt 它们需要Close() 它们是预编译的语句，可以重复使用同时效率比执行相同的Select()高效 它们的大多数方法的名字和*DB的相同，不过不需要query参数了 查询帮助函数 sqlx.In(query string, args ...any) (string, any, error)：将一个切片展开\ngoids := []int{1, 2, 3} sqlx.In(\u0026#34;SELECT * FROM x WHERE id IN (?)\u0026#34;, ids) // 返回 \u0026#34;SELECT * FROM x WHERE id IN (?, ?, ?)\u0026#34;, 1, 2, 3 *DB.Rebind(query string) string：将一个查询语句翻译成DB方言适配的查询\n事务管理 Begin()：返回*sql.Tx\nBeginx()：返回*sqlx.Tx\nBeginTx(ctx context.Context, opts *sql.TxOptions)：带上下文地开启事务，返回*sql.Tx\nBeginTxx(ctx context.Context, opts *sql.TxOptions)：带上下文地开启事务，返回*sqlx.Tx\nTx.Rollback()：回滚\ngodefer func() { // 发现有 panic if p := recover(); p != nil { tx.Rollback() panic(p) } } Tx.Commit() error：提交事务\n在事务内操作使用Tx而不是DB调用方法即可，其API与DB类似\n","permalink":"https://echo.ishian.me/2025/12/31/go-sqlx/","summary":"介绍 sqlx 库连接数据库、查询帮助函数、Stmt 与事务管理的用法","title":"Go: sqlx"},{"content":"Go日志管理 log标准库 配置设置 log.SetPrefix(stirng)：设置前缀 log.Prefix() string：获取前缀 记录日志 log的封装较为简单，包含以下三类常用方法 log.Print(\u0026quot;INFO\u0026quot;)：记录信息 log.Panic(\u0026quot;PANIC\u0026quot;)：记录信息后抛出panic log.Fatal(\u0026quot;FATAL)：记录信息后调用os.exit(1) 它们都有对应的ln版本 zap zap配置定义 zap.Config定义：\ngoimport \u0026#34;go.uber.org/zap/zapcore\u0026#34; type Config struct { // Level is the minimum enabled logging level. // 运行时修改会修改全部 logger 的 Level Level AtomicLevel `json:\u0026#34;level\u0026#34; yaml:\u0026#34;level\u0026#34;` // 开发模式, zap 在提供默认配置时会参考该值 // 调用对应的 NewProductionXxxConfig() / NewDevelopmentXxxConfig() Development bool `json:\u0026#34;development\u0026#34; yaml:\u0026#34;development\u0026#34;` // (默认即可) 默认记录 调用者 所有场景 DisableCaller bool `json:\u0026#34;disableCaller\u0026#34; yaml:\u0026#34;disableCaller\u0026#34;` // (默认即可) 默认记录 堆栈 WarnLevel-Development / ErrorLevel-Production DisableStacktrace bool `json:\u0026#34;disableStacktrace\u0026#34; yaml:\u0026#34;disableStacktrace\u0026#34;` // (默认即可) 生产环境下有采样, 用于限流高频日志 Sampling *SamplingConfig `json:\u0026#34;sampling\u0026#34; yaml:\u0026#34;sampling\u0026#34;` // 编码 \u0026#34;json\u0026#34; (json风格 建议生产环境) / \u0026#34;console\u0026#34; (控制台风格 建议开发环境) Encoding string `json:\u0026#34;encoding\u0026#34; yaml:\u0026#34;encoding\u0026#34;` // 编码器配置 EncoderConfig zapcore.EncoderConfig `json:\u0026#34;encoderConfig\u0026#34; yaml:\u0026#34;encoderConfig\u0026#34;` // 输出路径 \u0026#34;stdout\u0026#34; 表示标准输出 不会自动创建不存在的目录 OutputPaths []string `json:\u0026#34;outputPaths\u0026#34; yaml:\u0026#34;outputPaths\u0026#34;` // 错误日志输出路径 \u0026#34;stderr\u0026#34; 表示标准错误流 ErrorOutputPaths []string `json:\u0026#34;errorOutputPaths\u0026#34; yaml:\u0026#34;errorOutputPaths\u0026#34;` // 自定义的针对所有日志都默认追加的输出 InitialFields map[string]interface{} `json:\u0026#34;initialFields\u0026#34; yaml:\u0026#34;initialFields\u0026#34;` } zapcore.EncoderConfig定义：\ngoimport \u0026#34;io\u0026#34; type EncoderConfig struct { // Set the keys used for each log entry. If any key is empty, that portion // of the entry is omitted. // 信息 键 MessageKey string `json:\u0026#34;messageKey\u0026#34; yaml:\u0026#34;messageKey\u0026#34;` // 级别 键 LevelKey string `json:\u0026#34;levelKey\u0026#34; yaml:\u0026#34;levelKey\u0026#34;` // 时间戳 键 TimeKey string `json:\u0026#34;timeKey\u0026#34; yaml:\u0026#34;timeKey\u0026#34;` // NameKey string `json:\u0026#34;nameKey\u0026#34; yaml:\u0026#34;nameKey\u0026#34;` // 调用者 键 CallerKey string `json:\u0026#34;callerKey\u0026#34; yaml:\u0026#34;callerKey\u0026#34;` // FunctionKey string `json:\u0026#34;functionKey\u0026#34; yaml:\u0026#34;functionKey\u0026#34;` // 调用栈 键 StacktraceKey string `json:\u0026#34;stacktraceKey\u0026#34; yaml:\u0026#34;stacktraceKey\u0026#34;` // SkipLineEnding bool `json:\u0026#34;skipLineEnding\u0026#34; yaml:\u0026#34;skipLineEnding\u0026#34;` // LineEnding string `json:\u0026#34;lineEnding\u0026#34; yaml:\u0026#34;lineEnding\u0026#34;` // Configure the primitive representations of common complex types. For // example, some users may want all time.Times serialized as floating-point // seconds since epoch, while others may prefer ISO8601 strings. // 日志级别 编码器 EncodeLevel LevelEncoder `json:\u0026#34;levelEncoder\u0026#34; yaml:\u0026#34;levelEncoder\u0026#34;` // 时间戳 编码器 EncodeTime TimeEncoder `json:\u0026#34;timeEncoder\u0026#34; yaml:\u0026#34;timeEncoder\u0026#34;` // EncodeDuration DurationEncoder `json:\u0026#34;durationEncoder\u0026#34; yaml:\u0026#34;durationEncoder\u0026#34;` // 调用者 编码器 EncodeCaller CallerEncoder `json:\u0026#34;callerEncoder\u0026#34; yaml:\u0026#34;callerEncoder\u0026#34;` // Unlike the other primitive type encoders, EncodeName is optional. The // zero value falls back to FullNameEncoder. EncodeName NameEncoder `json:\u0026#34;nameEncoder\u0026#34; yaml:\u0026#34;nameEncoder\u0026#34;` // Configure the encoder for interface{} type objects. // If not provided, objects are encoded using json.Encoder NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:\u0026#34;-\u0026#34; yaml:\u0026#34;-\u0026#34;` // Configures the field separator used by the console encoder. Defaults to tab. // 控制台 不同字段的分隔符 默认为 tab ConsoleSeparator string `json:\u0026#34;consoleSeparator\u0026#34; yaml:\u0026#34;consoleSeparator\u0026#34;` } zap默认配置 zap.NewProduction() (*zap.Logger, error)：获取生产环境下的默认Logger实例 zap.NewDevelopment() (*zap.Logger, error)：获取开发环境下的默认Logger实例 zap.NewProductionConfig() zap.Config：获取生产环境的默认配置 zap.NewDevelopmentConfig() zap.Config：获取开发环境的默认配置 自定义配置(zapcore) zap的默认配置很多时候十分死板，而zap提供的获取Logger的方法不支持自定义编码器配置与自定义写接口，因此需要使用zapcore\n甚至zap提供的默认配置无法通过viper管理\nzapcore.NewProductionEncoderConfig() zapcore.EncoderConfig：获取生产环境的编码器配置，可在此基础上作修改\nzapcore.NewDevelopmentEncoderConfig() zapcore.EncoderConfig：获取开发环境的编码器配置\nzapcore.NewJSONEncoder(EncoderConfig) zapcore.Encoder：获取输出JSON格式的编码器\nzapcore.NewConsoleEncoder(EncoderConfig) zapcore.Encoder：获取输出控制台、更易读日志的编码器\nzapcore.Level：日志级别，实现了zapcore.LevelEnabler接口\nzap和zapcore提供了日志级别的快捷枚举：\nDebugLevel InfoLevel WarnLevel ErrorLevel DPanicLevel PanicLevel FatalLevel zapcore.AddSync(w io.Writer) zapcore.WriteSyncer：将一个io.Writer包装成WriteSyncer\nzapcore.NewMultiWriteSyncer(ws ...zapcore.WriteSyncer) zapcore.WriteSyncer：将多个WriteSyncer包装成一个WriteSyncer，在写入时会同步写入所有的ws，需要多路径输出时使用\nzapcore.NewCore(enc zapcore.Encoder, ws zapcore.WriteSyncer, enab zapcore.LevelEnabler) zapcore.Core：将编码器、写入器、级别限制包装成zapcore.Core，需要分级别输出时可以创建多个核心\nzapcore.NewTee(...zapcore.Core) zapcore.Core：将多个核心包装成一个核心，需要分级别输出时使用\nzap.New(zapcore.Core, ...zap.Option) *zap.Logger：使用给定的核心创建Logger对象，不会有错误抛出\nlumberjack lumberjack只有一个核心用法：以zap作为后端，提供日志轮转功能，包括日志过大分割、日志定时丢弃、日志压缩\n创建lumberjack.Logger：\ngologRotator := \u0026amp;lumberjack.Logger{ Filename: \u0026#34;/var/log/myapp/app.log\u0026#34;, // 日志文件路径 MaxSize: 100, // 单位: MB, 单个文件最大尺寸, 超过即分割 MaxBackups: 30, // 保留的旧日志文件最大数量 MaxAge: 90, // 单位: 天, 保留旧日志的最大天数 Compress: true, // 是否压缩归档的旧日志文件 LocalTime: true, // 使用本地时间命名旧日志文件 (默认使用 UTC) } 通过zapcore.AddSync(\u0026amp;lumberjack.Logger{...})将其转换为zapcore.WriteSyncer\n自定义编码器格式 EncodeTime：type TimeEncoder func(time.Time, zapcore.PrimitiveArrayEncoder) EncodeLevel：type LevelEncoder func(zapcore.Level, zapcore.PrimitiveArrayEncoder) EncodeDuration：type DurationEncoder func(time.Duration, zapcore.PrimitiveArrayEncoder) EncodeCaller：type CallerEncoder func(zapcore.EntryCaller, zapcore.PrimitiveArrayEncoder) EncodeName：type NameEncoder func(string, zapcore.PrimitiveArrayEncoder) 在自定义时，直接调用zapcore.PrimitiveArrayEncoder接口的方法，包含一系列AppendXxx()方法，其中Xxx可以是数值类型和字符串类型 zap.Option zap.AddCaller()：在日志中添加调用者的文件名和行号 zap.AddCallerSkip(skip int)：设置调用栈信息跳过的层数 zap.AddStacktrace(lvl zapcore.Level)：为指定级别及以上的日志记录堆栈跟踪 zap.Fields(fields ...Field)：向Logger添加固定的上下文字段 zap.Development()：启用开发模式（更易读的输出、禁用采样等） zap.ErrorOutput(w zapcore.WriteSyncer)：设置内部错误（如编码失败）的输出位置 zap.WrapCore(f func(zapcore.Core) zapcore.Core)：用于包装或替换底层的Core zap.Hooks(hooks ...func(zapcore.Entry) error)：为每条日志添加钩子函数 zap.IncreaseLevel(lvl zapcore.LevelEnabler)：提高日志记录的最低级别 zap.WithClock(clock zapcore.Clock)：自定义日志时间来源 zap.WithFatalHook(hook zapcore.CheckWriteHook)：为Fatal级别的日志设置钩子 zap.WithPanicHook(hook func(entry zapcore.Entry) error)：为Panic级别的日志设置钩子 Logger使用 Logger本身很简单，对应每种级别有对应的方法（七种级别），Logger的性能较好，方法签名如下：\nXxx(msg string, ...zap.Field)：本条日志的信息是msg，后面附带一系列字段（强类型）\nzap.Field：\ngoconst ( // UnknownType is the default field type. Attempting to add it to an encoder will panic. UnknownType FieldType = iota // ArrayMarshalerType indicates that the field carries an ArrayMarshaler. ArrayMarshalerType // ObjectMarshalerType indicates that the field carries an ObjectMarshaler. ObjectMarshalerType // BinaryType indicates that the field carries an opaque binary blob. BinaryType // BoolType indicates that the field carries a bool. BoolType // ByteStringType indicates that the field carries UTF-8 encoded bytes. ByteStringType // Complex128Type indicates that the field carries a complex128. Complex128Type // Complex64Type indicates that the field carries a complex64. Complex64Type // DurationType indicates that the field carries a time.Duration. DurationType // Float64Type indicates that the field carries a float64. Float64Type // Float32Type indicates that the field carries a float32. Float32Type // Int64Type indicates that the field carries an int64. Int64Type // Int32Type indicates that the field carries an int32. Int32Type // Int16Type indicates that the field carries an int16. Int16Type // Int8Type indicates that the field carries an int8. Int8Type // StringType indicates that the field carries a string. StringType // TimeType indicates that the field carries a time.Time that is // representable by a UnixNano() stored as an int64. TimeType // TimeFullType indicates that the field carries a time.Time stored as-is. TimeFullType // Uint64Type indicates that the field carries a uint64. Uint64Type // Uint32Type indicates that the field carries a uint32. Uint32Type // Uint16Type indicates that the field carries a uint16. Uint16Type // Uint8Type indicates that the field carries a uint8. Uint8Type // UintptrType indicates that the field carries a uintptr. UintptrType // ReflectType indicates that the field carries an interface{}, which should // be serialized using reflection. ReflectType // NamespaceType signals the beginning of an isolated namespace. All // subsequent fields should be added to the new namespace. NamespaceType // StringerType indicates that the field carries a fmt.Stringer. StringerType // ErrorType indicates that the field carries an error. ErrorType // SkipType indicates that the field is a no-op. SkipType // InlineMarshalerType indicates that the field carries an ObjectMarshaler // that should be inlined. InlineMarshalerType )每种类型都对应一个方法，Xxx(key string, value Type) zap.Field，需要提供键、对应的强类型的值\nLogger.Sugared() *zap.SugaredLogger：获取一个性能较差，但是支持log标准库风格的日志器，即SugaredLogger提供Xxx(string)和Xxxf(string, ...any)\n例子配置 viper配置映射及级别配置：\ngoimport \u0026#34;go.uber.org/zap/zapcore\u0026#34; const ( DebugLevel = \u0026#34;debug\u0026#34; InfoLevel = \u0026#34;info\u0026#34; FatalLevel = \u0026#34;fatal\u0026#34; WarnLevel = \u0026#34;warn\u0026#34; ErrorLevel = \u0026#34;error\u0026#34; PanicLevel = \u0026#34;panic\u0026#34; DefaultLevel = \u0026#34;debug\u0026#34; ) type ZapConfig struct { Level string `mapstruct:\u0026#34;level\u0026#34;` Development bool `mapstruct:\u0026#34;development\u0026#34;` Encoding string `mapstruct:\u0026#34;encoding\u0026#34;` OutputPaths []string `mapstruct:\u0026#34;outputPaths\u0026#34;` ErrorOutputPaths []string `mapstruct:\u0026#34;errorOutputPaths\u0026#34;` InitialFields map[string]any `mapstruct:\u0026#34;initialFields\u0026#34;` } func zapLevelEnabler(cfg *config.ZapConfig) zapcore.LevelEnabler { level, ok := levelMap[cfg.Level] if ok { return level } else { return levelMap[config.DefaultLevel] } } 配置编码器，这里使用的就是默认的\ngoimport ( \u0026#34;go.uber.org/zap\u0026#34; \u0026#34;go.uber.org/zap/zapcore\u0026#34; ) func zapEncoderConfig(cfg *config.ZapConfig) zapcore.EncoderConfig { var encoderCfg zapcore.EncoderConfig if cfg.Development { encoderCfg = zap.NewDevelopmentEncoderConfig() } else { encoderCfg = zap.NewProductionEncoderConfig() } return encoderCfg } func zapEncoder(cfg *config.ZapConfig) zapcore.Encoder { encoderCfg := zapEncoderConfig(cfg) switch cfg.Encoding { case \u0026#34;json\u0026#34;: return zapcore.NewJSONEncoder(encoderCfg) case \u0026#34;console\u0026#34;: return zapcore.NewConsoleEncoder(encoderCfg) default: return zapcore.NewConsoleEncoder(encoderCfg) } } WriteSyncer配置：\ngoimport ( \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;github.com/natefinch/lumberjack\u0026#34; \u0026#34;go.uber.org/zap/zapcore\u0026#34; ) func newWriterSyncer(paths []string) zapcore.WriteSyncer { syncers := make([]zapcore.WriteSyncer, 0, len(paths)) for _, path := range paths { var ws zapcore.WriteSyncer switch { case strings.ToLower(path) == \u0026#34;stdout\u0026#34;: ws = zapcore.AddSync(os.Stdout) case strings.ToLower(path) == \u0026#34;stderr\u0026#34;: ws = zapcore.AddSync(os.Stderr) default: lumberjackLogger := \u0026amp;lumberjack.Logger{ Filename: path, MaxSize: 100, MaxBackups: 30, MaxAge: 90, Compress: true, } ws = zapcore.AddSync(lumberjackLogger) } syncers = append(syncers, ws) } return zapcore.NewMultiWriteSyncer(syncers...) } func zapWriterSyncer(cfg *config.ZapConfig) (zapcore.WriteSyncer, zapcore.WriteSyncer) { return newWriterSyncer(cfg.OutputPaths), newWriterSyncer(cfg.ErrorOutputPaths) } ","permalink":"https://echo.ishian.me/2025/12/30/go-log/","summary":"介绍 Go log 标准库与 zap、lumberjack 的日志配置和使用","title":"Go: 日志管理"},{"content":"Gin会话管理 session 注册会话 Gin本身不支持会话管理，需要第三方框架github.com/gin-contrib/sessions\nsessions.Store表示一个存储引擎，sesssion的常用存储方式有三种：内存存储、cookie存储、redis存储（还支持MongoDB、GORM和PostgreSQL）\n这三种存储方式的实现在sessions/memstore、sessions/cookie、sessions/redis中，区别和gin没有关系，属于常识：\n内存存储和redis存储都是将会话信息存储在服务端然后使用cookie传输sessionId等会话的元数据，而cookie存储是将会话信息完全由cookie传输并存储在客户端浏览器上\ncookie引擎（通常附带密钥增加安全性）：\ngocookie.NewStore( []byte(\u0026#34;authentication-key-32-bytes-long\u0026#34;), []byte(\u0026#34;encryption-key-32-bytes-long\u0026#34;) )通常附带两个密钥，第一个会被用于认证密钥（使客户端无法写会话内容），第二个会被用于加密密钥（使客户端无法读会话内容）\n内存引擎（不支持集群）：memstore.NewStore()，也可以传递密钥，用于保护cookie的元数据\nredis存储（支持集群）：\ngofunc redis.NewStore( size int, network string, address string, username string, password string, keyPairs ...[]byte) (redis.Store, error)从上至下为连接池大小、传输协议、redis地址、redis用户名、redis密码、keyPairs与上述两种引擎一致，都是为了防止客户端修改和读取\n通过sessions.Sessions(name string, store sessions.Store) gin.HandlerFunc获取gin的中间件，name就是传输给浏览器的cookie的名字\nStore包含以下可配置项：\ngoimport ( \u0026#34;net/http\u0026#34; \u0026#34;github.com/gorilla/sessions\u0026#34; ) func A() { store.Options(sessions.Options{ Path: \u0026#34;/\u0026#34;, // cookie 生效的路径, 只有在请求响应该路径或子路径时才会发送 Domain: \u0026#34;\u0026#34;, // cookie 生效的域名, 空表示当前域名 // MaxAge=0 浏览器关闭即删除 // MaxAge\u0026lt;0 响应收到后即删除 // MaxAge\u0026gt;0 指定秒数后即删除 MaxAge: 0, Secure: false, // 是否仅允许 https HttpOnly: true, // 禁止 js 访问 cookie (与加密不同, 这是直接无法读取) // 跨站请求是否发送 cookie, 防 CSRF // 要么 NoneMode+secure=true 要么 LaxMode SameSite: http.SameSiteLaxMode, }) } 会话API sessions.Default(*gin.Context) sessions.Session：获取和当前请求绑定的session实例 Session.Get(key any) any：获取某个键对应的值 Session.Set(key, value any)：设置某个键值对 Session.Delete(key any)：删除某个键 Session.Save() error：执行保存当前session的修改 JWT ","permalink":"https://echo.ishian.me/2025/12/30/go-gin-session/","summary":"介绍 Gin 中基于 session 和 JWT 的会话管理实现与相关 API","title":"Gin: 会话管理"},{"content":"Go的序列化 特性 Go通过结构体标签来支持序列化和反序列化，内置的encoding标准库支持xml/json等配置文件的序列化和反序列化 gopls本身支持json结构体标签的快速生成 Go只会对一个结构体的公有字段进行序列化和反序列化 API 通过encoding下的子包encoding/xml或encoding/json的API Marshal(v any) ([]byte, error)：将任意值序列化，返回字节数组 MarshalIndent(v any) ([]byte, error)：类似Marshal()但是会提供缩进美化 Unmarshal(data []byte, v any) error：反序列化，v应当是指针 xml-etree beevik/etree是用于解析xml的第三方库 yaml 需要借助第三方库go-yaml/yaml 用法与encoding API一致，只不过yaml是严格缩进的，因此没有MarshalIndent() 切片和yaml数组对应 一个结构体会将yaml的子属性和公有字段对应，只收集匹配成功的，会忽略无法匹配的项，若没有配置则使用零值赋值 一个map会将所有该字段下的子字段全部收集起来，收集到和值类型相匹配的，如果存在和值类型不匹配的字段则报错 protobuf 需要借助第三方库github.com/golang/protobuf/proto ","permalink":"https://echo.ishian.me/2025/12/30/go-serializing/","summary":"介绍 Go 中 JSON、XML、YAML、Protobuf 等格式的序列化 API 与特性","title":"Go: 序列化与反序列化"},{"content":"Gin的中间件 中间件的原理 Gin的中间件提供的很少，但是提供了一个灵活的接口\n所有中间件只要封装成HandlerFunc，就可以注册为中间件，每个路由对应的处理器也算是一个HandlerFunc，只不过是局部中间件，更多时候是在最后被调用的\n*gin.Context.Next()：这个方法是最核心的方法\ngofunc (c *Context) Next() { c.index++ for c.index \u0026lt; int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }gin会把所有中间件统一到ctx对象中，封装为handlers([]HandlerFunc)和index，它们组成了处理器调用链\nindex的初值为-1，由路由匹配成功后直接调用Next()，一上来就自增使其指向第一个处理器\n一上来就自增的原因是处理器可以在内部调用Next()，因此需要使其主动指向下一个处理器\n因此一个处理器要么返回后执行for内部的自增，要么在内部调用Next()自增，为了安全，假设每个处理器都调用一次Next()，那么所有处理器的数量不能超过math.MaxInt8 \u0026gt;\u0026gt; 1 = 63，这个条件虽然很难达到，但也在注册函数内被assert检查了\n注册全局中间件：调用*gin.Engine.Use(...HandlerFunc)\n注册局部中间件：调用路由组*gin.RouterGroup(relativeName string, ...HandlerFunc)\n中间件执行顺序由左到右，由先加入到后加入\n此外全局中间件一定比局部中间件更先执行，所有中间件一定比定义路由时绑定的处理器先执行\n错误处理中间件 *gin.Context.Error(error) *gin.Error：在处理过程中添加自定义错误，返回*gin.Error\n*gin.Error可以继续链式地调用SetType(gin.ErrorType)和SetMeta(data any)\n可以在处理器中使用ctx.Errors获取所有的错误\ngoimport ( \u0026#34;reflect\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) var ( errorTypes = make(map[reflect.Type]string) errorHandlers = make(map[string]gin.HandlerFunc) ) func RegisterErrorType(errorCategory string, errorType ...error) { for _, errType := range errorType { errorTypes[reflect.TypeOf(errType)] = errorCategory } } func RegisterErrorHandler(errorCategory string, handler gin.HandlerFunc) { errorHandlers[errorCategory] = handler } func ErrorHandlerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Next() if len(c.Errors) == 0 { return } err := c.Errors[0].Err errType := reflect.TypeOf(err) var handler gin.HandlerFunc if t, exists := errorTypes[errType]; exists { if h, exists := errorHandlers[t]; exists { handler = h } } if handler != nil { handler(c) } else { c.JSON(500, gin.H{\u0026#34;error\u0026#34;: \u0026#34;Internal Error\u0026#34;}) } c.Abort() } } ","permalink":"https://echo.ishian.me/2025/12/28/go-gin-middleware/","summary":"讲解Gin中间件的实现原理、Next调用链机制及全局与局部中间件的注册方式","title":"Gin: 中间件"},{"content":"Gin的响应 响应方法 以下方法都是*gin.Context的方法： Status(code int)：设置状态码 Header(key, value string)：设置响应头 以下方法能够自动设置状态码和Content-Type响应头 String(code int, format string, values ...any)：字符串响应 JSON(code int, obj any)：JSON响应 XML(code int, obj any)：XML响应 YAML(code int, obj any)：YAML响应 ProtoBuf(code int, obj any)：Protobuf响应 HTML(code int, name string, obj any)：HTML模板渲染 File(filepath string)：文件响应（显示文件） FileFromFS(filepath string, fs http.FileSystem)：文件响应（从fs中获取） FileAttachment(filepath, filename string)：文件响应（请求浏览器下载文件） Data(code int, contentType string, data []byte)：原始数据 Stream(step func(w io.Writer) bool)：流式响应 Redirect(code int, location string)：重定向 code通常传递http.StatusXxx obj直接传递待发送的对象即可，针对模板文件它们是传入模板文件的数据，需要事先使用*gin.Engine.LoadHTMLFiles()或LoadHTMLGlob() 上传文件 FormFile(name string) (*multipart.FileHeader, error)：传入文件name，获取 MultipartFrom() (*multipart.Form, error)：获取完整的multipart表单 SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error：保存文件到本地磁盘 ","permalink":"https://echo.ishian.me/2025/12/28/go-gin-response/","summary":"讲解Gin的各种响应方法及JSON、文件上传下载等响应与表单处理","title":"Gin: 响应处理"},{"content":"validator 标签 语法介绍 gin框架依赖于validator，validator是基于结构体标签和反射进行数据校验的框架\nvalidator提供的结构体标签是validate：\ngotype A struct { Pub string `validate:\u0026#34;endwith=abc\u0026#34;` } validator提供的标签在baked_in.go中\n标签的不同规则通过,相与，通过|相或，标签的参数通过=传递，oneof标签的不同参数通过空格 分隔，-表示跳过验证，特殊字符(-、,、=、|)需要unicode转义\ngotype A struct { Pub string `validate:\u0026#34;requried,min=3,max=8\u0026#34;` Enum string `validate:\u0026#34;required,oneof=admin rdonly user\u0026#34;` Abc string `validate:\u0026#34;required,eq=abc0x3d\u0026#34;` Ignore int `validate:\u0026#34;-\u0026#34;` }=为0x3d，,为0x2c，|为0x7c，-为0x2d\n与和或不支持使用括号，只能从左到右校验\n通用校验 eq/lt/gt/ne/lte/gte=xxx：等于/小于/大于/不等于/小于等于/大于等于校验 required：强制要求 数值/布尔值：允许为零值 结构体/字符串：禁止为空结构体/空串 不允许为nil min/max：限制数值范围 omitempty：可选字段，即可以是nil或零值，如果不为nil则要经过后续的校验 omitnil：可选字段，可以是nil，但如果是零值则要经过后续校验 oneof=xxx1 xxx2：限制可选值范围，可用于枚举 len=xxx：限制长度 required_if=Field xxx：Field为xxx时强制要求 unique：不允许重复 常用格式校验 email：是否为邮箱格式 uuid/uuid4：uuid/uuid4校验 json：json格式校验 datetime=DateTimeFormat：日期时间验证 base64：base64格式校验 常用字符串校验 alphanum/alpha/numeric：是否只包含数字字母/字母/数字 contains=xxx：是否包含xxx子串 containsany=xxx：是否包含xxx字符集中的任何字符 excludes=xxx：是否不包含xxx子串 excludesall=xxx：是否不包含xxx字符集中的任何字符 startswith=xxx/startsnotwith=xxx：是否以/不以指定文本开始 endswith=xxx/endsnotwith=xxx：是否以/不以指定文本结束 uppercase/lowercase：是否全大写/小写 常用跨字段校验 validator比起java的bean validation，还支持跨字段验证 eqfield/nefield=OtherField：本字段和指定字段相等/不相等 gtfield/gtefield/ltfield/ltefield=OtherField：大于/大于等于/小于//小于等于指定字段 csfield系列可以跨结构体校验，但是不常用 常用网络字段校验 ip/ipv4/ipv6：通用ip/ipv4/ipv6地址格式校验 url/uri：url/uri格式校验 hostname：主机名校验 fqdn：完全限定域名校验 验证API 创建验证器 使用validator.New() *validator.Validate创建验证器 通常使验证器为单例模式 结构体校验 *Validate.Struct(any) error：验证传入的结构体的所有公开字段，即首字母大写的字段，默认进行递归校验\n应该传入结构体指针，否则返回InvalidValidationError\n如果验证出错，将返回ValidationErrors，被封装成error\n提取校验错误：\ngoerr := validate.Struct(a) for _, err := range err.(validator.ValidationErrors) { }其中err即validator.FieldError\nFieldError提供若干方法：\n其它数据结构的校验 虽然通常用于结构体，但也可以用于其它数据结构体的校验\nmap对象：使用一个和map对象结构相同的map对象，其中每个键对应的值是标签数据\n通过*Validate.ValidateMap(data, rules) map[string]any校验，返回错误map，每个键对应一个FieldError\n切片：通过*Validate.Var(any, tag string)校验，从左到右维度下降(切片本身到切片的每个元素)，通过dive标签划分不同的维度\n多维切片道理类似，通过dive标签划分不同维度\n一般变量：同样通过Var()进行验证\n自定义标签 自定义别名 *Validate.RegisterAlias(alias, tag string)：将一个tag命名为alias，则验证时会直接展开成tag 自定义验证函数 RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error：通过注册函数注册 Func是一个函数，签名为func(fl FieldLevel) bool，其中FieldLevel接口是一系列返回reflect.Value的快捷函数，最常用的是Filed()和Param()，表示获取当前字段的值和标签参数 fn返回true表示校验成功 与Gin的集成 在gin中，标签使用binding而不是validate 在gin中，不需要显式创建验证器和显式调用验证方法，所有校验错误会在解析数据、绑定数据失败时返回error ","permalink":"https://echo.ishian.me/2025/12/27/go-gin-validator/","summary":"介绍 validator 库的结构体标签语法、常用校验规则与验证 API","title":"Gin: 数据校验"},{"content":"Gin路由 httprouter 特点 Gin的路由基于julienschmidt/httprouter，是一个轻量级的路由组建（总共三个文件一千余行代码） httprouter只提供路由功能，标准库中的HandleFunc()依赖ServeMux来路由，而ServeMux针对前缀匹配采用遍历的方式匹配，效率较差 httprouter提供：请求方法和路径分离，路由算法优化（基于基数树）因此一次路由极快，路径模糊匹配，路径参数捕获 那么httprouter.Router就可以作为一个前端控制器直接传递给http.ListenAndServe(path, handler)了 API httprouter.New()：获取路由器对象 *httprouter.Router提供一系列方法绑定路由（仅仅处理路由） Handle(method, path string, handle Handle)：将方法、路径绑定到处理器，其中handle为httprouter.Handle GET/POST/PUT/DELETE/PATCH/OPTIONS/HEAD(path string, handle Handle)：Handle()的快捷方法 Handler()/HandlerFunc()：net/http的适配器 路径参数捕获：路径中使用:可以捕获，例如/users/:uid，将不包含路径分隔符的路径参数捕获并赋值给uid 使用httprouter.Handle：Handle方法的签名就是func(http.ResponseWriter, *http.Request, Params)，直接使用Params参数来访问 使用http的处理器API：则适配器会将参数挂到*http.Request.Context()中，通过httprouter.ParamsFromContext(Context)获取Params对象 还可以使用*捕获包含路径分隔符的路径参数，因此它们只能放在最后，例如/*userinfo 当模糊匹配和精确匹配发生冲突时，按精确匹配大于:大于*的顺序进行匹配 Params提供ByName(string)获取键对应的值 Gin的参数解析 gin.Default(...OptionFunc) *Engine：获取一个*gin.Engine，默认配置即可，可以通过选项模式修改配置\nGin支持路由参数，和httprouter的使用方法类似\n*gin.Engine对象提供和*httprouter.Router类似的方法：Handle()/GET()/...\n路径匹配规则类似：精确匹配、:匹配、*匹配\n区别在于处理器函数的类型是HandlerFunc：func(*gin.Context)\ngofunc(http.ResponseWriter, *http.Request, Params) func(*gin.Context)将参数替换成了上下文\n因此获取参数也变成了：*gin.Context.Param(string)\nGin还支持捕获URL参数：\n*gin.Context.Query(string)：获取GET参数的值，若无返回空 *gin.Context.DefaultQuery(string, string)：允许自定义默认值 Gin还支持捕获请求体参数\n*gin.Context.PostForm(string)：可以解析表单参数application/x-www-form-urlencoded与multipart/form-data *gin.Context.ShouldBind(any)：自动选择解析器，并赋值给传入的参数，因此传入的参数应该是指针，不会进行错误处理 *gin.Context.Bind(any)：是对ShouldBind()的封装，但如果参数解析错误则会自动返回400错误 *gin.Context.MustBindWith(any, binding.Binding)：指定解析器，和Bind()一样会自动返回400错误 BindJSON()/BindQuery()/BindYAML()/BindXML()等：MustBindWith()的快捷函数 对于JSON、XML、ProtoBuf等数据，ShouldBind()只能调用一次，需要使用ShouldBindBodyWith()方法\n分组路由 Gin提供*gin.Engine.Group(relativePath string, handlers ...HandlerFunc) *RouterGroup方法分组\nrelativePath的捕获语法与httprouter一致，相当于分组所表示的前缀\n可以表示相对路径（即开头不带/），则相对于Engine等价于相对于根路径，相对于RouterGroup等价于相对于路由组的路径\nhandlers会在子路由处理器执行之前执行，它们不是relativePath对应的处理器，如果需要则应该在分组内绑定一个\u0026quot;\u0026quot;路由\n*RouterGroup的方法和*Engine注册路由类似，此外它本身也可以注册新的路由组\n为了规范，在注册路由组下的路由时，通常用{}括住：\ngog := e.Group(\u0026#34;v1\u0026#34;) { g.GET(\u0026#34;/xxx\u0026#34;, func(c *gin.Context) { }) } 错误处理器 针对404路由响应（没有对应的路由），可以自定义处理器：*gin.Engine.NoRoute(HandlerFunc) 针对405路由响应（有对应的路由但是没有匹配的请求方法），可以使用*gin.Engine.NoMethod(HandlerFunc) gin本身没有十分完整的捕获错误、统一处理，不像spring-mvc的@ControllerAdvice，但是可以通过中间件实现 但是可以使用中间件，gin的中间件引入非常灵活，扩展性很高，完全可以实现类似统一错误处理器的效果 CORS中间件 官方提供了cors的中间件github.com/gin-contrib/cors\ngocors.New(cors.Config{ type Config struct { AllowAllOrigins bool // AllowOrigins is a list of origins a cross-domain request can be executed from. // If the special \u0026#34;*\u0026#34; value is present in the list, all origins will be allowed. // Default value is [] AllowOrigins []string // AllowOriginFunc is a custom function to validate the origin. It takes the origin // as an argument and returns true if allowed or false otherwise. If this option is // set, the content of AllowOrigins is ignored. AllowOriginFunc func(origin string) bool // Same as AllowOriginFunc except also receives the full request context. // This function should use the context as a read only source and not // have any side effects on the request, such as aborting or injecting // values on the request. AllowOriginWithContextFunc func(c *gin.Context, origin string) bool // AllowMethods is a list of methods the client is allowed to use with // cross-domain requests. Default value is simple methods (GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS) AllowMethods []string // AllowPrivateNetwork indicates whether the response should include allow private network header AllowPrivateNetwork bool // AllowHeaders is list of non simple headers the client is allowed to use with // cross-domain requests. AllowHeaders []string // AllowCredentials indicates whether the request can include user credentials like // cookies, HTTP authentication or client side SSL certificates. AllowCredentials bool // ExposeHeaders indicates which headers are safe to expose to the API of a CORS // API specification ExposeHeaders []string // MaxAge indicates how long (with second-precision) the results of a preflight request // can be cached MaxAge time.Duration // Allows to add origins like http://some-domain/*, https://api.* or http://some.*.subdomain.com AllowWildcard bool // Allows usage of popular browser extensions schemas AllowBrowserExtensions bool // Allows to add custom schema like tauri:// CustomSchemas []string // Allows usage of WebSocket protocol AllowWebSockets bool // Allows usage of file:// schema (dangerous!) use it only when you 100% sure it\u0026#39;s needed AllowFiles bool // Allows to pass custom OPTIONS response status code for old browsers / clients OptionsResponseStatusCode int } }) 常用的配置项和一般的网页应用一样：\ngoimport \u0026#34;github.com/gin-contrib/cors\u0026#34; func A() { cors.New(cors.Config{ AllowOrigins: []string{\u0026#34;http://localhost:8000\u0026#34;}, AllowMethods: []string{\u0026#34;*\u0026#34;}, // 允许客户端发送的请求头 AllowHeaders: []string{ \u0026#34;Origin\u0026#34;, \u0026#34;X-Requested-With\u0026#34;, \u0026#34;Content-Type\u0026#34;, \u0026#34;Accept\u0026#34;, \u0026#34;Authorization\u0026#34;, }, // 允许客户端读取的响应头 ExposeHeaders: []string{ \u0026#34;Content-Length\u0026#34;, \u0026#34;Access-Control-Allow-Origin\u0026#34;, \u0026#34;Access-Control-Allow-Headers\u0026#34;, \u0026#34;Cache-Control\u0026#34;, \u0026#34;Content-Language\u0026#34;, \u0026#34;Content-Type\u0026#34;, }, // 允许携带 cookie AllowCredentials: true, }) } 日志重定向 gin本身直接使用Stdout和Stderr，如果需要集成zap等，直接替换掉默认的日志记录器即可：\ngogin.DefaultWriter = sugar.Writer() gin.DefaultErrorWriter = sugar.Writer() 如果不想使用zap的sugar，可以手动实现一个适配器\ngotype zapWriter struct { logger *zap.Logger } func (w *zapWriter) Write(p []byte) (n int, err error) { w.logger.Info(string(p)) return len(p), nil } gin.DefaultWriter = \u0026amp;zapWriter{ logger:logger } ","permalink":"https://echo.ishian.me/2025/12/27/go-gin-router/","summary":"讲解Gin基于httprouter的路由原理、路径参数捕获及路由绑定与分组方法","title":"Gin: 路由"},{"content":"Viper配置管理 配置优先级 Viper.Set()设置的值 命令行中传入的参数 环境变量 配置文件 键值存储 Viper.SetDefault()设置的值 读取配置 从文件读取 goviper.SetConfigName(\u0026#34;config\u0026#34;) viper.SetConfigType(\u0026#34;yaml\u0026#34;) viper.AddConfigPath(\u0026#34;/etc/appname/\u0026#34;) viper.AddConfigPath(\u0026#34;$HOME/.appname\u0026#34;) viper.AddConfigPath(\u0026#34;.\u0026#34;) err := viper.ReadInConfig() if err != nil { panic(fmt.Errorf(\u0026#34;fatal error config file%w\u0026#34;, err)) } SetConfigName()：设置配置文件名，若不包含扩展名，需要设置SetConfigType()\nSetConfigType()：设置文件扩展名\nAddConfigPath()：设置配置文件搜索路径，先加入的优先级较低，支持使用环境变量\nReadInConfig() error：搜索并读取配置\n设置默认值 SetDefault(k, v) 访问配置 Get(key string) any\nGetBool(key string) bool\nGetFloat64(key string) float64\nGetInt(key string) int\nGetIntSlice(key string) []int\nGetString(key string) string\nGetStringMap(key string) map[string]any\nGetStringMapString(key string) map[string]string\nGetStringSlice(key string) []string\nGetTime(key string) time.Time\nGetDuration(key string) time.Duration\nIsSet(key string) bool\nAllSettings() map[string]any\n键的构造：viper对大小写敏感，通常使用.来表示嵌套的子配置，如果存在环境变量，则会被隐式地替换为大写字母和_分隔\n例如default.filepath会先检查DEFAULT_FILEPATH，然后检查配置文件中default字段的子字段filepath\n反序列化 Unmarshal(rawVal any) error：传入一个指针，将配置反序列化给该指针\nUnmarshalKey(key string, rawVal any) error：仅反序列化某个键的配置\n针对第一种反序列化，基本上都是传递一个匹配的结构体的指针，通常显式地指定mapstruct标签\ngotype Conf struct { FilePath string `mapstruct:\u0026#34;file_path\u0026#34;` } 监控配置变化与动态读取 goviper.OnConfigChane(func(e fsnotify.Event) { // doSth }) // 或 viper.WatchConfig() AutomaticEnv()：\n开发阶段常用\n","permalink":"https://echo.ishian.me/2025/12/27/go-viper/","summary":"介绍 Viper 的配置优先级、读取与访问配置及动态监控配置变化","title":"Go: Viper配置管理"},{"content":"net net/http 服务端 http.Server结构体的常用配置项：\nAddr：监听端口 Handler：请求处理器 XXXTimeout：一些超时控制 http.ListenAndServe(addr string, handler Handler) error：启动一个服务器，如传入(\u0026quot;:8000\u0026quot;, nil)\n或调用http.Server.ListenAndServe()方法\n处理器实现：Handler接口包含ServeHTTP(ResponseWriter, *Request)方法，可以针对请求进行不同的写入\nhttp.Handle(pattern string, handler Handler)：将处理器绑定到pattern路径上路由\nhttp.HandleFunc(pattern string, handler func(ResponseWriter, *Request))：可以直接传入一个匿名函数，内部通过适配器转换成一个Handler对象\n反向代理 客户端 http.Client结构体的可配置项： Transport RoundTripper：连接池配置 CheckRedirect func(req *Request, via []*Request) error：重定向控制函数 Jar CookieJar：cookie配置 Timeout time.Duration：超时配置 独立请求 Get(url string) (resp *Response, err error)：发起get请求 Post(url, contentType string, body io.Reader) (resp *Response, err error)：发起post请求 Response结构体的常用字段： StatusCode：状态码 Header：响应头，是map[string][]string Body：响应体，是io.ReadCloser，需要Close()，只能读一次 Response的常用方法： Cookies() []*Cookie：提取Header中的Cookie http.StatusXXX：状态码常量 ","permalink":"https://echo.ishian.me/2025/12/25/go-netprog/","summary":"介绍 Go net/http 包的服务端、客户端、反向代理等网络编程用法","title":"Go: 网络编程基础"},{"content":"并发 协程 特点 Go的并发支持是天生的，其中协程在Go中是十分简洁而高效的 其他语言如Rust、C#、Kotlin、Lua都支持协程，协程是用户级线程，上下文开销比OS级线程还要小，这种轻量级的线程却能完成十分复杂的任务 启动协程 启动一个coroutine十分简单，只需要使用go关键字后跟一个函数调用即可：\ngoimport \u0026#34;fmt\u0026#34; func A() { go func() { fmt.Print(\u0026#34;Hello world!\u0026#34;) }() } go协程允许函数调用带返回值，但不允许后跟有返回值的内置函数，如len()/make()等\n就像java的线程需要join()一样，单独声明goroutine是无效的，因为启动协程需要时间，而主协程在这之前已经退出了，因此需要通信来协调父子协程和同级协程\n管道 传统协程或线程通过共享内存来通信，而Go的声明是：通过通信来共享内存\n传统线程模型中，多个线程通过锁来防止对同一份共享内存的操作发生冲突\nGo的管道模型中，多个协程通过管道来通信，而管道本身不需要开发者通过锁手动管理\n这其实是一句俏皮话，在传统模型中通信是目的，共享内存与锁是实现方法，而Go将共享内存和加解锁的过程封装成了管道原语，用更容易理解的话说：使用通信(共享内存的封装)来代替共享内存这种手段\nchan类型用于在不同协程之间通信，可以通过make()创建：\ngomake(chanType, bufsize)如之前所说，分为chan/\u0026lt;-chan/chan\u0026lt;-三种管道类型，bufsize是管道的缓冲大小\n使用内置函数close(chan\u0026lt;-)关闭一个管道，也可以使用defer延迟\n写协程一方一定要记得(也应该由这一方)close()，否则读协程可能会阻塞地等待写协程写入数据，但写协程已经不再使用写管道或已经结束了\n管道关闭后，读协程可以继续读取管道，但如果不再有元素，也会立刻返回，不过返回的标志位是false\n管道操作：通过data, fin := \u0026lt;-chanObj读取管道，通过chanObj \u0026lt;- data写入管道\n管道阻塞：\n管道是一个阻塞队列，管道的操作是同步的，即内部封装决定了同一时刻只有一个协程能够读取或写入管道，且读和写互斥 无缓冲管道：缓冲区长度为零，一个协程向管道写入后必须由另一个协程读取这个管道，写协程才能继续运行，否则会阻塞 有缓冲管道：写协程在写入满管道会阻塞、读协程在读取空管道会阻塞，其它情况可以继续运行 nil管道：无论写入还是读取都会导致协程阻塞 管道panic：\n关闭一个nil管道 向已关闭管道写入 关闭已关闭的管道 管道传递的是值本身，因此针对大对象应该使用指针，实现类似共享内存的效果，针对引用类型的对象，本来就是传递指针似的\n编译器不会检查在写入后修改原变量的行为，如果写入指针或引用类型对象，则写协程不应该对原变量继续修改，否则会造成丢失修改\n一个协程最好只在写入一个变量之前修改变量，读取一个元素后所有权转移给读协程\nlen()与cap()可以作用于管道查询缓冲区的元素个数和空间大小\n单向管道通常用于函数签名和函数返回值的类型限制，双向管道最为常用，双向管道也可以隐式地转换成单向管道\n可读管道可以使用for range语法糖读取\n等待其它协程(sync.WaitGroup) WaitGroup用于等待一组协程 WaitGroup的实现是计数器加信号量，它是一个结构体所以零值是可用的，不需要自己构造，直接声明就能使用下面的方法 Add(delta int)：指定要等待的协程的个数，即初始化计数器 Done()：调用该方法的协程执行完毕，即计数器减一 Wait()：调用该方法的协程需要等待子协程执行完毕，即通过信号量等待计数器减为零 上下文(context.Context) Context是一个接口，有更强的协程控制能力 Deadline() (deadline time.Time, ok bool)：获取上下文应该取消的时间，若ok为false则没有ddl Done() \u0026lt;-chan struct{}：获取一个只读通道，若上下文取消时这个只读通道就会被关闭，部分实现中不支持它的返回值是nil Err() error：表示上下文取消的原因，上下文未关闭时返回nil Value(key any) any：返回上下文中key对应的值，不存在则返回nil 上下文是不可变对象，因此自然是并发安全的 context标准库提供了若干实现：emptyCtx/cancelCtx/timerCtx/valueCtx context.Background()：获取一个emptyCtx\ncontext.TODO()：获取另一个预先声明的emptyCtx\nemptyCtx的方法均返回零值，通常作为一个无法被取消的最顶层的上下文，由于不同上下文实例的内存理应不同(方便调试)，emptyCtx的底层类型是int\n使用Background()作为默认的根上下文，从这个空上下文开始派生子上下文\n使用TODO()作为占位符，当需要派生子上下文，但是不知道应该放置什么类型的上下文时使用TODO\ncontext.WithValue(parent Context, key, value any) Context：基于parent派生一个valueCtx，只额外实现了Value()方法，会向上查找键对应的值\ncontext.WithCancel(parent Context) (ctx Context, cancel CancelFunc)：可取消的上下文，执行cancel()后，其下的所有上下文（因为基于自己）都会被取消\n实现很简单，在创建时向上查找可取消的上下文，如果不存在则自己创建一个协程等待取消信号，就能实现当父上下文取消时所有子上下文都递归取消\n执行cancel()后，所有子上下文被关闭，\u0026lt;-Done()通道发出关闭事件，可以通过select检测\ncontext.WithTimeout(parent Context, time.Duration) (Context, CancelFunc)：与cancelCtx类似，但是额外地实现了Deadline()，即使不手动取消，超时后也会取消\ncontext.WithDeadline(Context, time.Time) (Context, CancelFunc)：与上面的区别是指定的是具体的时刻而不是时间间隔\n可取消的上下文可以通过context.WithXxxCause()获取一个能传递error的cancel()函数\nselect语句 select语句用于IO多路复用\nselect类似switch语句，包含case和default子句，但是case检测的是一个管道是否有返回值(不是是否关闭)：\ngoselect { case \u0026lt;-chA: // 可以配合 管道关闭 // doSth case _, ok := \u0026lt;-chB: // doSth default: // doSth } 当任何一个case都不可用时，选择default，若没有自定义default则阻塞直到有一个case可用；若同时有多个case可用则随机选择一个case执行\n之前说过管道在关闭后，即使没有元素也会立刻返回，因此\u0026lt;-ctx.Done()在上下文取消后，管道关闭会立刻返回，因此能被case选中\n锁 sync.Mutex 多个协程同时访问共享数据时，比起使用通道，使用锁也是一种控制方法 sync.Mutex是不可重入锁，即同一个协程不能重复获得同一个锁对象，否则会fatal 实现了sync.Locker接口： Lock()：加锁 Unlock()：解锁 TryLock()：非阻塞式加锁，sync.Mutex额外实现了它 sync.RWMutex sync.RWMutex是读写互斥锁，分离了读锁和写锁 像sync.Mutex那样的方法被分离成了加写锁，解写锁和非阻塞式加写锁 额外实现了RLock()，RUnlock()，TryRLock()表示对读锁的操作 和其它语言的读写分离一样，存在写锁时无法加读锁和写锁，存在读锁时无法加写锁但可以加读锁，实现上是内部封装了单个sync.Mutex sync.Cond sync.Cond条件锁，或者更准确的名称：条件变量，和java的Condition一样，建立在锁之上 条件变量允许一个锁拥有多个等待队列 sync.NewCond(l Locker) *Cond：获取一个针对l的条件变量指针 Wait()：短暂释放锁 Signal()：唤醒一个在等待队列的进程 Broadcast()：可以理解为SignalAll，唤醒全部在这个条件上等待的进程 sync的其它工具 sync.Pool sync.Once sync.Map atomic ","permalink":"https://echo.ishian.me/2025/12/25/go-concurrent/","summary":"讲解Go的协程、channel通信、sync包同步原语及select多路复用","title":"Go: 并发编程"},{"content":"迭代器 range 特定数据结构的迭代 在一开始，range只能用于数组、切片、字符串、map、可读chan的迭代\n数组/切片：\ngofor idx, val := range slice { } map：\ngofor key, val := range mapp { } 字符串：\ngofor idx, bt := range str { } 可读通道\ngofor elem := range channel { } 整型迭代 Go 1.22+引入了整型迭代语法糖\ngofor i := range n { } // 等价于 for i = 0; i \u0026lt; n; i++ { } 同时引入了不含索引变量的版本：\ngofor range n { } 迭代器函数 推送式迭代器 在Go 1.22，引入了Range over func，即迭代器函数，迭代器函数会降低一定的可读性\n推送式迭代器函数的形式：\ngofunc Func(...any) func(yield func(T) bool) { // 环境声明... var a T // 返回闭包 // 若干次调用 yield(a) ... a 即推送给 临时变量 v 的值 return func(yield func(T) bool) { // for 中的 break / return 将使 yield() 返回 false if !yield(a) { return // return 退出闭包函数, 外部 for 结束 } // 迭代 a a = DoSth(a) } } func A() { // 使用 for v := range Func(n) { // body } } 迭代器函数返回一个闭包函数，闭包函数返回空，接收一个回调函数yield\n回调函数yield由运行时自动注入，是外部开发者定义的for块，即：\ngofor v := range Func(n) { // body } // 等价于 iterator := Func(n) // 获取提供了指定环境的闭包函数 // 传递 for 块作为 yield 传递给闭包函数 // 其中 v 在闭包中由开发者主动推送 iterator(func(v T) bool { // body return true }) for体默认在结尾返回true，在遇到break、return等时，体现在yield()里则是返回false\n在一切正常时(for体没有break和return)，for执行多少次完全取决于闭包函数内yield()被调用了多少次\n简单的理解就是：range后跟自定义的迭代器函数，迭代器函数返回一个闭包函数\n闭包函数提供v的序列，通过yield()一轮一轮地推送\nyield()是for内的代码块，在结尾自动添加return true以及在特定关键字后添加return false\nyield()最多支持两个参数，允许只接收一个参数但没有意义\nGo 1.23引入了iter.Seq[T any]与iter.Seq2[T, S any]，它们简化了迭代器函数的返回值，推送式迭代器和原生的for range的性能相差不大\n拉取式迭代器 推送式迭代器的迭代逻辑全部位于闭包函数内，而很多时候迭代器需要由for块主动调用，这种迭代器称为拉取式迭代器\nGo 1.23引入了iter.Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())和iter.Pull2(...)...，用于将推送式迭代器转化为拉取式迭代器\n这个实现原理是：推送式作为生产者会不断调用yield，而Pull在中间添加了一个协程，用于伪装一个yield(并不是for体)，这个协程提供的yield收到推送式迭代器提供的参数后转发给next()，并阻塞式地等待返回值，从而实现阻塞式地迭代\n那么在调用next()时，推送式迭代器调用的yield就是协程提供的yield\n但是这种实现方式并不常用，因为先实现一个推送式迭代器再启用一个协程带来更大的复杂度和性能损耗，但和使用闭包并没有很大的区别\nstop()则是向生产者协程主动发送停止信号，让它停止阻塞式地等待消费者的信号\ngoimport \u0026#34;iter\u0026#34; func A() { next, stop := iter.Pull[int](seq) defer stop() for range n { v := next() } } 标准库的迭代器 slices All[Slice ~[]E, E any](s Slice) iter.Seq2[int, E]：将切片转换成切片迭代器，内部实际上就是for range s，一般用于数据流处理 Values[Slice ~[]E, E any](s Slice) iter.Seq[E]：转换成不返回索引的切片迭代器 Chunk[Slice ~[]E, E any](s Slice, n int) iter.Seq[Slice]：返回若干子切片，切片最多含有n个元素 Collect[E any](iter.Seq[E]) []E：将切片迭代器收集成切片 maps Keys()：返回键的迭代器 Values()：返回值的迭代器 All()：返回键值对的迭代器，和直接使用for range类似，一般用于数据流处理 Collect()：将映射表迭代器收集成映射表 流式处理 Go采用返回闭包并封装成标准库函数的形式实现迭代器函数，而不是声明一个流对象并以流对象作为接收者和返回值来实现(像Java那样) 因此Go的标准库并不提供链式调用的流式处理，因此调用链长度较长时可读性会下降 因此需要自己实现流式调用形式的，即自己定义一个流对象并封装一系列它的方法，在内部调用slices和maps提供的方法 Go目前还不支持匿名函数的简写，需要完整写完函数定义和函数体 ","permalink":"https://echo.ishian.me/2025/12/25/go-iterator/","summary":"介绍 Go 的 range 机制、推拉式迭代器函数与标准库迭代器及流式处理","title":"Go: 迭代器"},{"content":"Go I/O os os.File os.File是Go对文件描述符的抽象\nos.OpenFile(name string, flag int, perm os.FileMode) (*os.File, error)：用指定flag打开文件并返回文件指针，若发生错误则error不为nil\nflag包括\ngoimport \u0026#34;syscall\u0026#34; const ( // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified. O_RDONLY int = syscall.O_RDONLY // open the file read-only. O_WRONLY int = syscall.O_WRONLY // open the file write-only. O_RDWR int = syscall.O_RDWR // open the file read-write. // The remaining values may be or\u0026#39;ed in to control behavior. O_APPEND int = syscall.O_APPEND // append data to the file when writing. O_CREATE int = syscall.O_CREAT // create a new file if none exists. O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist. O_SYNC int = syscall.O_SYNC // open for synchronous I/O. O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened. )只读、只写、读写、追加、创建、不存在才创建、同步写、存在则清空，它们可以使用按位或运算同时存在\n必须三选一：指定只读、只写、读写\n其它标志可以使用按位或运算配合使用\n其中O_EXCL必须配合O_CREATE一起使用，此时如果文件已存在则会报错\nFileMode：表示文件类型和文件权限，Linux下用ls -l查询出的权限的分段二进制表示，比如0444\nFileMode只有在创建文件时有效，在其它情况下使用0即可\ngoimport \u0026#34;io/fs\u0026#34; // A FileMode represents a file\u0026#39;s mode and permission bits. // The bits have the same definition on all systems, so that // information about files can be moved from one system // to another portably. Not all bits apply to all systems. // The only required bit is [ModeDir] for directories. // os type FileMode = fs.FileMode // fs type FileMode uint32 Open(name string) (*File, error)：只读打开文件\nCreate(name string) (*File, error)：创建文件，若文件存在则截断，否则以0o666模式创建\nNewFile(fd uintptr, name string) *File：将fd封装成*File返回，*File可以使用Fd()获取底层的uintptr\n*File一定要记得Close()，例如在获取后立刻defer f.Close()\n文件I/O错误 os提供一系列方法帮助判断err： IsExist(error)/IsNotExist(error)：判断错误是否为文件存在/文件不存在 IsPermission(error)：判断错误是否为文件权限不足 IsTimeout(error)：判断错误是否为超时 读写文件 *File提供的方法： Read(b []byte) (n int, error)：读取文件内容存储到b，只会覆盖到len(b) ReadAt()：可额外传递偏移量（开始读的偏移量而不是字节切片的偏移量） Write(b []byte) (n int, error)：将b的内容写入文件，只会写入直到len(b)的内容 WriteAt()：可额外传递偏移量（开始写的偏移量而不是字节切片的偏移量），和os.O_APPEND互斥 WriteString(s string) (n int, error)：写入字符串 os.ReadFile(name string) ([]byte, error)：读取一个文件的全部内容并返回字节切片 os.WriteFile(name string, data []byte, FileMode) error：将字节切片的全部内容写入文件 文件元属性操作 os.Rename(oldpath, newpath string) error：重命名文件 os.Delete(name string) error：删除文件 os.DeleteAll(dir string) error：递归删除目录下所有文件和目录 (*File) Sync()：刷盘 目录操作 os.ReadDir(name string) ([]DirEntry, error)：读取一个目录，返回切片\ngoimport \u0026#34;os\u0026#34; func A() { dirs, _ := os.ReadDir(\u0026#34;.\u0026#34;) for _, dir := range dirs { dir.Name() // string dir.IsDir() // bool dir.Info() // (os.FileInfo, error) dir.Type() // os.FileMode } } 或者使用*File的ReadDir(n int)，最多读取n个DirEntry，n为负数时读取全部\nos.ReadDir()是对它的简单封装\nos.Mkdir(name string, perm FileMode) error：创建目录\nos.MkdirAll(path string, perm FileMode) error：类似mkdir -p地创建目录\nos.Getwd() (string, error)：获取工作目录\nos.Chdir(dir string) error：修改工作目录\n环境变量相关 os.Getenv(key string) string os.Setenv(key, value string) error os.LookupEnv(key string) (string bool) filepath标准库提供一系列处理文件路径的函数 io与io/fs I/O接口 io.Reader：只有Read([]byte) (n int, error)方法 io.Writer：只有Write([]byte) (n int, error)方法 io.Closer：只有Close() error方法 io.Seeker：只有Seek(offset int64, whence int) (int64, error)方法 io.ReadWriter：Reader与Writer并集 io.ReadCloser：Reader | Closer io.WriteCloser：Writer | Closer io.ReadWriteCloser：Writer | Reader | Closer io.ReadSeeker：Reader | Seeker io.ReadSeekCloser：Reader | Seeker | Closer 更多接口可查阅文档 *os.File实现了上述所有接口 io工具函数 io.Copy(dst Writer, src Reader) (written int64, err error)：复制文件，默认使用32K的缓冲区 io.CopyBuffer(Writer, Reader, []byte) (int64, error)：可以自己指定缓冲区 io.CopyN(Writer, Reader, int64)：指定最多读取的字节数 io.ReadAll(Reader) ([]byte, error)：读取所有字节并返回字节切片 io.ReadFull(Reader, []byte) (int, error)：调用Reader的Read，若长度不足则报错 io.MultiReader(...Reader) Reader：串联多个Reader顺序读取 io.MultiWriter(...Writer) Writer：并联多个Writer同时写入 fs.FS fs.FS将一系列文件资源包括网络资源都抽象成fs.FS，FS接口只有一个Open(name string) (File, error)方法\n可以通过os.DirFS(dir string) fs.FS将目录转化为FS对象\nfs.Sub(fsys FS, dir string) (FS, erorr)：将文件系统对象的一个子目录再转换为文件系统对象\nfs提供了很多查找和读取文件的方法：\nfs.ReadFile(FS, name string) ([]byte, error)：读取整个文件 fs.Open(name string) (File, error)：打开一个文件，根目录为文件系统对象的目录 fs.File.Read([]byte) (int, error)：读取一个文件读取到字节切片里 fs.File.Close() error：关闭文件 fs.Glob(FS, pattern string) ([]string, error)：模式匹配地查找文件名 fs.ValidPath(name string) bool：检查路径是否生效 以及很多关于目录的方法：\nfs.WalkDir(fsys FS, root string, WalkDirFunc) error：递归遍历目录，root相对于fsys的路径，WalkDirFunc回调用于自定义处理函数\nfs.ReadDir(FS, string) ([]DirEntry, error)：读取目录内容\nWalkDirFunc：\ngofunc(path string, d DirEntry, err error) error在回调中需要处理err，对d进行自定义的处理\n返回nil表示继续遍历，fs.SkipDir表示跳过当前的d，fs.SkipAll表示跳过所有的d\n在仅需要读取和遍历文件或其它资源的情况下，使用fs比使用os更好\n标准输入输出 os包内置声明了三个标准输入输出描述符：Stdin、Stdout、Stdout os包的这三个描述符本身就是文件，可以使用上述的os、io、fs提供的方法输出和输入 内置的print()和println()可以打印，但通常用于测试 fmt包提供许多用于打印的方法： Print()/Println()/Printf() fmt格式化： %%：百分号 %s/%d/%f/%b/%o/%x/%p：与其它语言类似 %v与%+v：输出值的原始形式，后者会附加值的字段 %q：输出带有双引号的字符串 bufio bufio接口 bufio标准库用于缓冲读写，适用于大文件，但是bufio是顺序访问，不像os.File那样是随机访问 bufio.NewReader(io.Reader) *bufio.Reader：将io.Reader转化为带缓冲的Reader bufio.NewWriter(...) ...：将io.Writer转化为带缓冲的Writer bufio.NewReadWriter(...) ...：将io.ReadWriter转化为带缓冲的ReadWriter bufio.NewScanner(io.Reader) *bufio.Scanner：将io.Reader转化为bufio.Scanner bufio.Reader Read(...)...：与*os.File.Read()类似，提供缓冲 ReadByte() (byte, error)：读取一个字节 ReadBytes(delim byte) ([]byte, error)：读取到分隔符delim ReadString(delim byte) (string, error)：读取到分隔符delim，返回string ReadLine() (line []byte, isPrefix bool, error)：读取一行 Peek(n int) ([]byte, error)：预读取n个字节但是不移动指针 Reset(io.Reader)：复用*bufio.Reader对象，绑定到新的io.Reader bufio.Writer 与bufio.Reader的方法相对应 Flush() error：刷盘 Reset(io.Writer)：复用*bufio.Writer对象，绑定到新的io.Writer bufio.Scanner Scan() bool：按行扫描，若指针没有到EOF则返回true Text() string：获取当行的文本 Err() error：检查扫描中的错误 Split(SplitFunc)：自定义分割函数，内置的分割函数如下 bufio.ScanLines(默认) bufio.ScanWords bufio.ScanRunes bufio.ScanBytes ","permalink":"https://echo.ishian.me/2025/12/25/go-io/","summary":"介绍 Go 标准库 os、io 与 io/fs 的文件操作和 I/O 接口","title":"Go: I/O"},{"content":"错误处理 级别分类 error：正常的运行时错误，需要处理，若忽略程序也不会崩溃 panic：恐慌，程序应该在处理完问题后立刻退出 fatal：致命错误，程序无法处理并立刻退出 错误处理的设计 Go没有try-catch语句，一切error(如果有)都作为返回值返回\n因此处理变得清晰：\ngores, err := ThrowSomeError() if err != nil { // process or: return err } 但有若干缺点：\n堆栈丢失：标准库的错误信息不包含堆栈，需要引入非标准库 代码冗余：有极多的if err != nil，不像try-catch那样有统一的结构化处理 但实际上也有一些优点：\n性能高：没有try-catch开销\n虽然错误信息不包含堆栈，但error本质就是返回值，可以一层一层向上调试找到\nerror作为值，更多时候作为返回值，能增强可读性，知道错误出自哪里\n编译时检查减轻处理负担：编译器或静态检查要求必须接收函数的返回值，当一个函数有可能抛出error时，编译器会提醒显示接收并处理(或return)\n而使用结构化处理的语言则总会遇到：当发生错误但没有显式处理时，直接使用try-catch块包围，带来性能损耗、难以定位\n它们更难受的问题在于，容易遗漏运行时错误而没有处理\n与另一个将错误作为返回值的语言rust对比，rust提供了语法糖来处理错误，因此同样作为返回值却没有引起很大的讨论\nerror 声明error error是预定义的类型\ngotype error interface { Error() string // 获取错误信息 } errors是错误处理的工具包\n创建错误：errors.New(msg string)\n也可以通过fmt创建：fmt.Errorf(msg string, ...any)传入格式化信息\n在没有链式调用的前提下，它们返回内置的errorString结构体\nerror理应是一个常量(哨兵错误)，但在设计上却是通过var声明的，是一个变量\n为了可维护性，一般将常用的error当作全局变量预先声明\n自定义error：error是一个接口，只要类型实现了Error()，就是一个error\n链式错误 链式错误是Go 1.13+提供的用于获取链式调用信息的错误\n通过fmt.Errorf(\u0026quot;%w\u0026quot;, err)可以包装一个错误并返回一个实现了Unwrap()的error，它返回的对象本质是内置的wrapErrors\n因为wrapErrors不是预定义的，它是包私有的，所以只能通过errors.Unwrap(error)解包，如果传入的error没有实现Unwrap()则返回nil\nerrors.Is(err, target error) bool：判断err的链式错误中是否包含target，即省去了自己写for的过程\nerrors.As(err error, target any) bool：判断err的链式错误中是否包含匹配target的类型错误，即在Is的基础上传递的target变为指针的指针并赋值，将第一个匹配成功的错误赋值给target并返回true\ntarget必须实现error target必须是指针的指针 gopackage main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { // Is: 判断是否包含 tgt := Myerr{\u0026#34;msg\u0026#34;} if errors.Is(err, tgt) { } // As: 判断是否有对应类型的错误并赋值 // 传入指针的指针，才可在函数内修改外部的错误变量 var tgt *Myerr if errors.As(err, \u0026amp;tgt) { fmt.Print(tgt.String()) } } 平级错误 通过errors.Join(...error) error创建一个封装了[]error的错误，它的返回值不是链式错误，而是joinError joinError实现了Unwrap() []error，errors.Is()与errors.As()都有单独处理它们的分支，所以可以像链式错误那样使用它们 但是errors.Unwrap()中不包含单独的判断，所以只会返回nil github.com/pkg/errors 这是另一个官方的errors包，但没有加入标准库 它会自动记录栈信息 但实际上，它的链式调用已被标准库替代，而堆栈记录已经被许多第三方依赖所吸收，早已处于归档状态 panic 创建 panic(string)内置函数用于自定义panic，但并不常用\n仅有在程序真的不会继续执行的时候，才使用panic()，或者使用日志框架\n例如数据库连接失败时，使用panic()\npanic会进行善后，比如执行事先定义的defer语句\ndefer语句按先进后出的顺序执行\n关于defer：defer后跟函数的参数是会被预计算的\n恢复 recover()可用于恢复panic并不退出程序 recover()必须在defer块中执行 panic向上传递，即向调用者方向传递，因此如果在defer块内的闭包函数调用recover()是无效的 ","permalink":"https://echo.ishian.me/2025/12/25/go-error/","summary":"讲解Go的error、panic、fatal错误级别划分及if err != nil错误处理设计","title":"Go: 错误处理"},{"content":"Go的泛型 通用接口 Go的泛型在1.18引入，同时为了服务泛型，更改了interface的定义为“一系列类型的集合”\n类型集用于约束或简化声明泛型参数T所能表示的类型\n示例：\ngotype SignedIntType interface { int8 | int16 | int | int32 | int64 } 并集：通过|获得两个类型集的并集\ngotype IntType interface { SignedIntType | UnsignedIntType } 交集：通过换行来分隔多个类型以获取交集\ngotype EmptyType interface { SignedIntType UnsignedIntType } 空集：空集不是空接口，空集只能通过交集运算获得，而空接口interface{}或any可以表示所有类型\n使用type声明的新类型：即使底层类型被包含在一个类型集中，只要这个新类型不在这个类型集中，就无法用作该泛型的参数\ngotype A interface { int8 } // TinyInt is not a A ! type TinyInt int8 底层类型声明：可以使用~解决上述问题\ngotype A interface { ~int8 } // TinyInt is a A type TinyInt int8~int8表示只要一个类型的底层类型是int8，那么这个类型就属于这个类型集\n需要记住：方法集是一个类型(一般接口)，类型集不是一个类型(通用接口)\n类型集不能通过类型断言(因为不是一个类型)，但是泛型参数T可以(因为T是类型) 类型集本身不能作为泛型实参，它本身不是一个类型而是一个类型集 方法集可以作为泛型实参，它本身是一个接口类型 不兼容类型之间不能相并(不能使用|)，它们只能通过换行获取交集\ncomparable不能和其它类型相并 方法集之间不能相并 声明泛型参数 泛型函数：\ngofunc Func[T any](a T, ...) (...) { } 新类型：\ngoimport \u0026#34;io\u0026#34; type ( NewType[T int | float64] []T NewType[T int | float64] struct { a T } NewType[T int | float64, S string | rune] struct { k T v S } NewType[T ATypeInterface] []T ) func Write[W io.Writer](w W, bs []byte) (int, error) { return w.Write(bs) } 当针对引用类型需要使用泛型时，推荐将T作为基本数据类型，然后使用[]T、map[T]T1、chan T\n声明泛型参数时不能使用交集运算，可以临时使用并集运算\n方法(有接收器的函数)不能定义泛型参数，但接收者可以携带泛型参数并供方法使用\n匿名结构体和匿名函数不支持临时定义泛型参数，但可以使用外部定义的泛型参数\n","permalink":"https://echo.ishian.me/2025/12/25/go-generics/","summary":"讲解Go 1.18引入的类型集、泛型参数约束及泛型函数与泛型类型的用法","title":"Go: 泛型"},{"content":"映射表 在Go中， map作为关键字而不是标准库提供，并且十分简洁，底层使用哈希的方式实现 除了通过map[keytype]valuetype{...}的方式创建，还可通过make(Type, cap)分配容量并创建 访问map元素与访问数组类似，obj[key]，不同的是返回两个值val, exist，如果不存在则返回值类型对应的零值 新增或覆盖：obj[key] = value 删除键值对：使用内置函数delete(map, key) 映射表是一个iterable，可以使用for range遍历 同切片一样可以使用clear()清空表(Go 1.21+) 和其它内置的非sync类型一样，都是非并发安全的 go没有内置的set类型，因为map[key]struct{}完全可以作为set`来使用，一个空结构体不会占用任何内存 maps：maps是标准库中的映射表工具库，但内部函数较少，许多和slices库和strings库的函数类似，也提供了迭代器函数 ","permalink":"https://echo.ishian.me/2025/12/24/go-map/","summary":"介绍 Go 映射表 map 的创建、增删改查、遍历及 maps 工具库","title":"Go: map"},{"content":"字符串 特性 string是一个值类型，意味着可以是空串但不是nil string是一个不可变的byte数组，是不可变类型 string对象本身可以被取地址，但它的元素不允许 并且由于本质是字节数组，通过数组访问符[]访问只能得到byte而不是rune并且也没有char类型 string与[]byte和[N]byte可以相互转换，要修改string需要先转换为字节数组，为了内存安全，转换这一行为会分配新的内存给字节数组 原始字符串：用反引号包围 字符串操作 由于字符串本质是字节数组，许多数组/切片的操作用于string是很容易理解的\n拼接：Go内置了+和+=字符串运算\n字符串可以直接通过切片符获取子串，但本质是获取字节数组的切片\nlen()：本质是求字节数组的长度，大部分情况字符是ASCII码因此只需要一个字节\ncopy()：拷贝操作\n按字符处理：先转换成[]rune，即int32序列再处理\n按字符遍历：range的最小单位就是int32，因此：\ngofor _, ch := range str { }这样的遍历是按rune遍历的\nstrings strings是内置的字符串工具包，用于处理utf8字符串 创建与拷贝 Repeat(s string, cnt int) string：按次数拷贝字符串 Clone(string) string：拷贝字符串，若不为空则分配内存并返回新的字符串对象 Builder结构体：用于通过消耗更小的内存来构建复杂字符串(相比使用+) 实现了io.Writer, io.ByteWriter, io.StringWriter接口：通过WriteXxx()方法构建字符串， Grow(n int)：预分配内存 Reset()：重置Builder String()执行构建 Builder不能作为参数传递 Join([]string, rep string) string：拼接字符串切片，分隔串为rep，比+性能更好 比较 Compare(a, b string) int：按字符比较两个字符串，返回1，0，-1 EqualFold(s, t string) bool：忽略大小写情况下是否相等 包含与匹配 Contains(s, substr string) bool：判断前者是否包含substr子串 ContainsAny(s, chs string) bool：判断前者的字符集是否包含chs字符集 Count(s, substr string) int：前者包含多少次后者 HasPrefix(s, prefix string) bool：判断前者是否有前缀prefix HasSuffix(s, suffix string) bool：判断前者是否有后缀suffix Index(s, substr string) int：返回子串第一次出现的位置 IndexAny(s, chs string) int：返回字符集第一次出现的位置 IndexRune(s, chs rune) int：返回指定unicode字符第一次出现的位置 IndexLastXxx：上述三者都有Last版本 分割 Fields(s string) []string：按空格分割字符串，类似split\nFieldsFunc(s string, f func(rune) bool) []string：按函数f的返回值决定是否分割\ngo// 等价于`Fileds(s)`: FiledsFunc(s, func(ch rune) bool { return ch == \u0026#39; \u0026#39; }) Split(s, sep string) []string：按子串sep分割\nSplitN(s, sep string, n int) []string：按子串sep分割，返回值最多n个元素\nSplitAfter(s, sep string)：与Split类似但是会保留分隔子串在每个元素的结尾\nSplitAfterN(...)\n删除与替换 Cut(s, sep string) (before, after string, found bool)：删除s中第一次出现的sep，before表示子串位置之前的串，after表示子串位置后的串\nMap(mapping func(rune) rune, s string) string：遍历替换unicode字符，若mapping返回-1则不进行替换而是直接删除该unicode字符\nReplace(s, old, new string, n int) string：将s中的old子串替换为new串，共能替换n次，n为负数时表示无限次\nReplaceAll(s, old, new string) string：等价于Replace(s, old, new, -1)\nToUpper()与ToLower()\nTrim(s, cutset string) string：删除两端只包含cutset字符集的子串\nTrimLeft()与TrimRight()\nTrimPrefix(s, substr string) string与TrimSuffix(...)：与上述四者不同，删除完全匹配substr子串的前缀或后缀\nTrimSpace(s string)\nTrimFunc(s string, f func(rune) bool)\n*Replacer：提前指定替换规则后复用，通过NewReplacer(oldnew ...string) *Replacer创建\ngoimport \u0026#34;strings\u0026#34; var rep = strings.NewReplacer(old1, new1, old2, new2) str = rep.Replace(str) strconv strconv提供基本数据类型与字符串之间转换的工具函数 Atoi(s string) (int, error)：字符串转整型 Itoa(x int) (string, error)：整型转字符串 ParseBool(string)与FormatBool(bool)：布尔值转换 ParseFloat(string, bitSize int)：字符串转浮点型，bitSize为位数 FormatFloat(float64, fmt byte, prec, bitSize int)：字符串转浮点型，bitSize为位数 fmt：格式化类型 prec：精度 bitzise：位数 ","permalink":"https://echo.ishian.me/2025/12/24/go-strings/","summary":"介绍 Go 字符串特性以及 strings、strconv 包的常用操作","title":"Go: 字符串"},{"content":"Slice 创建切片 数组与切片的区别 数组是值类型，编译时确定大小且拷贝时拷贝值，可以通过[...]type{val1, val2}编译器自动推断长度 切片是引用类型，切片必须基于一个数组获得，对切片的操作会影响数组本身，因此也会影响同样共享该数组的切片 切割 数组切割是一个语法糖，类似其它语言的切片语法，返回切片类型\n话虽如此，go的切片比较简单，只支持自然数索引：\ngons := [...]int{1, 2, 3, 4} ns[1:] // [1, 4) ns[:] // [0, 4) ns[:len(ns)-1] // [0, 3) 切片分割：与数组分割类似，数组和切片在默认时容量都是底层数组的长度减去start\n更常用的是完整的start:end:max表达式来防止切片影响原切片：\ngons := [...]int{1, 2, 3, 4} ns1 := ns[:] // 指定容量为 max-start, 一般指定 max=end 即可 ns2 := ns1[1:2:2] // 如果不指定容量则因没有爆容量而影响原切片 append(ns2, 1, 2) 使用make()预分配空间 直接使用[]type创建无法预先指定其底层数组的空间\n使用make()是常用的方法：\ngons := make([]int, len, cap)len表示已经确认存在的值，并初始化为零值，cap为底层数组的容量\n实际上len与cap是切片的基本组成属性\n自动扩容 slice本身会自动扩容，重新分配到新的内存并指向它\n因此当基于数组/切片分割获取切片时，需要注意，在没有重新分配时对切片进行修改会影响原数组，在拼接后如果超出容量则不会影响原数组\n最好的习惯是使用完整的start:end:cap\n切片转数组与解包 Go 1.17支持[LEN](s[:LEN])长度匹配的转换 Go 1.20支持[LEN](s)省略切片符，要求len(s)\u0026gt;=LEN 转换行为在内存上创建新的数组副本 切片能通过后跟...解包，通常也用于作为可变参数传递 切片常用操作 内置函数 len(slice) int：获取长度 cap(slice) int：获取容量 append(slice []Type, elems ... Type)：拼接切片，返回新切片对象并共用底层数组，不改变原切片 copy(dst []Type, src []Type) int：拷贝数组，返回实际复制的元素个数，即min(len(dts), len(src)) 衍生操作 删除指定元素：s = append(s[:i], s[i+1:]...) 删除连续元素：s = append(s[:start], s[end:]...) 插入指定元素：s = append(s[:i], append([]T{v}, s[i:]...)...) slices Go 1.21时推出了slices标准库，具有大量切片常用操作，且和strings内的大部分操作相似，也包括排序函数和迭代器函数等 ","permalink":"https://echo.ishian.me/2025/12/24/go-slice/","summary":"介绍 Go 切片的创建、切割、扩容机制、常用操作与 slices 标准库","title":"Go: 切片(Slice)"},{"content":"组合式编程 函数声明 Go是类型后置的，它的函数声明：\ngofunc (r Receiver) Func(a Type1, b Type2, ...) (ret1 Type1, ret2 Type2, ...) {} 其中，r Receiver是可选项，表示接收者，这个方法只能由Receiver类型的对象调用，在函数内可以通过r访问它的其它属性和其它方法\n函数签名：支持可变(变长)参数... Type\n返回值：支持多返回值以及返回值提前命名(命名可选)，可以直接在函数体使用且return可以省略返回值\n通常习惯使用r *Receiver指针接收者代替值接收者，对于复杂的类型也经常使用指针类型\nr本质是调用它的对象的副本，使用指针类型才能实现真正对内容进行修改，且不需要过多的拷贝\n但值接收者有它的好处，因为只修改副本所以天生并发同步\n无论是值接收者还是指针接收者，值和指针都可以直接通过.访问，go编译器会自动展开为(\u0026amp;val).与(*ptr).(编译器只会自动处理一层解引用与取地址)\n应该遵循一致性的声明，统一用值接收者或者统一用指针接收者\n函数签名相同而接收者不同时，不会发生冲突\nGo不支持函数重载，因此接收者相同，函数名相同，签名不同时，也会发生冲突\n值接收者方法一定无法修改原有的值，即使：\ngofunc (r Receiver) SetA(a int32) { r.a = a } var a = A{ a: 1 } (\u0026amp;a).SetA(2)会被自动解引用：(*(\u0026amp;a)).SetA(2)\n只能通过指针接收者方法修改内部值\n可见性 Go的可见性设计极简，因为其抛弃了继承，只有包内包外，公有私有几种情况\n命名首字母为大写的类型/字段/函数/接口为公有，所有包可访问\n命名首字母为小写的类型/字段/函数/接口为私有，仅包内可访问，包内指的是相同包名(因此也不包含子目录的文件)\n预声明类型没有可见性的问题，在任意地方可见，例如error等\n它们可以找到定义是因为那些只是方便查阅而留下的\n结构体 特点 Go不是一个传统OOP的语言，它更鼓励使用组合式编程，rust的traits系统也是类似 Go抛弃了类与继承，结构体不能包含任何方法，也不能继承自其它结构体，也没有一般意义上的构造方法 结构体更多通过组合的方式构造一个复杂的结构体，和其它传统OOP语言的组合类似，通过转发的方式调用成员属性的成员方法 可选参数实现 与Java一样，Go不支持可选参数，但Go还不支持函数重载，甚至不支持在声明结构体类型时提供默认值\n但通过一定的包装，如Java可以通过封装builder实现可选参数，Go则是通过可变参数实现可选参数\ngopackage main type A struct { a, b, c int32 d string } type AOption func(*A) // 接受*A返回空的函数 func WithA(aa int32) AOption { return func(a *A) { a.a = aa } } // ...(其它属性的With实现) // 构造函数, 接受可变参数 ...AOption func NewA(opts ...AOption) *A { // 提供默认值 ret := \u0026amp;A{ a: 1, b: 2, c: 3, d: \u0026#34;abc\u0026#34;, } for _, opt := range opts { opt(ret) } return ret } var a = NewA(WithA(3), WithD(\u0026#34;Hello World!\u0026#34;)) 第三方库go-optioner可以快速生成选项函数的实现\n与builder模式类似，不需要对每个类都使用复杂的选项模式\n更复杂的实现可以避免不同类型的WithXxx()方法因函数名相同而冲突的问题，例如引入builder模式声明类型XxxBuilder并作为接收者\n类型嵌入 Go通过在结构体声明内声明一个不含属性名的类型，可实现小程度的继承，但本质是组合：\ngotype A struct { B // 必须是通过`type`声明的命名类型 c int32 } 在实例化时，只需要指定：\ngoa := A{ B: B{}, c: 2, } 类型嵌入的特性：外部类型可以直接通过.访问嵌入类型的属性\n这能一定程度模拟继承的一些特性并减小重复代码，但也会带来继承的缺点：破坏封装\n但由于本质是组合，修改嵌入类型，不会过多地影响外部类型\n此外，嵌入类型作为接收者的方法都会自动提升为外部类型的方法，但是要注意函数签名冲突(因为不允许函数重载)与属性冲突\n函数签名相同时，外部类型的方法会覆盖嵌入类型的同名(签名)方法\n属性标签 属性标签是元信息，可用于反射、序列化等，详见反射章节 gopls提供了快速生成tags的codeActions 空结构体 空结构体不占用任何内存\n因此可以作为chan信号量或set的实现：\ngosign := chan struct{} done := make(chan struct{}) type Set map[string]struct{} set := make(Set) set[\u0026#34;key\u0026#34;] = struct{}{} 基本接口 声明语句 接口是Go实现多态、松耦合的基础\n在引入泛型之前，Go的接口的定义是一系列方法的集合\n在引入泛型的同时，Go的接口定义改为：一系列类型的集合\n这个更改是向后兼容的，即新版本的编译器完全可以编译并运行老版本的接口代码\n本节介绍基本接口，即旧定义的接口\n接口不能被实例化，但可以被声明为类型，这是多态的特性\n声明：\ngotype AInterface interface { A(int32) in32 B() } 接口实现 Go的接口实现采用非侵入式设计，即不需要像Java那样显式声明implements AInterface\n实现了接口的类型，意味着这个接口的方法集合是以这个类型作为接收者的方法集合的子集，一个类型如果没有完全实现一个接口的所有方法而被用作这个接口的一个实例，会被静态检查查出：\ngotype AInt interface { A() } type A struct { a int32 } func (a A) A() { // 非侵入式(隐式) A 实现了 AInt } func (a *A) A() { // *A 实现了 AInt } 需要注意的是，指针接收者和值接收者方法虽然都可在被调用时由编译器自动展开，但指针接收者实现并不意味着值可以作为这个接口的实例来使用，类型T的方法集只包含接收者为T的方法\ngotype AInt interface { A() } type A struct { a int32 } func (a *A) A() { } var ( a AInt = A{a: 1} // error b AInt = \u0026amp;A{a: 1} // ok )而值接收者的实现意味着指针可以作为这个接口的实例来使用，类型*T的方法集包含接收者为T和*T的方法\ngofunc (a A) A() { } var a AInt = \u0026amp;A{a: 1} // ok导致这种不对称设计的原因是iface的实现，接口的底层实现分离了类型信息和数据信息，即使自动取地址，其数据也是一份拷贝(如果实现者是T而不是*T)\n*T的方法集包含T和*T的，不允许T和*T同时实现同一个接口，但允许T实现接口然后*T只覆盖部分方法\n但正如之前所说，最好的风格是针对一个类型只使用值接收者、或者只使用指针接收者\n接口继承：通过包含另一个接口，表示本接口实现了被包含的接口\n接口的定义一般放在消费方，由于非侵入性，实现方所在文件不需要接口的定义\n包级别的早期检查：虽然通过静态检查可以检测在使用时一个类型是否实现了一个接口，在一些接口分离的大型项目中，可以添加：\ngovar _ SomeInterface = (*SomeType)(nil)提前检查一个类型是否实现了一个接口\n空接口(eface)：type any = interface{}\n","permalink":"https://echo.ishian.me/2025/12/24/go-composition/","summary":"讲解Go的函数声明、方法接收者、接口与结构体的组合及嵌入机制","title":"Go: 组合式编程"},{"content":"Go基本语法 包管理 package关键字表示当前文件属于哪个包，主函数文件想要当作入口文件，必须声明package main，包名设计和路径不同，不像java通过.分隔，而是单纯的一个字段表示包名，但仍然是一个目录对应一个包，不会进行递归扫描\n不强制目录名和包名相同，导入后使用的是package后声明的字段而不是目录名，但一般目录名和包名相同\nimport是导入其它包的关键字，这里引入的是用/分隔的包含包所在网址或本地项目路径的绝对包名\n因此如果导入多个不同路径的同时，它们包含一个相同的包名，会发生冲突，需要重命名\nimport可以通过()对导入进行分组，也可以重命名：\ngopackage main import ( \u0026#34;fmt\u0026#34; strs \u0026#34;strings\u0026#34; ) func main() { fmt.Print(\u0026#34;Hello world!\u0026#34;) strs.Split(\u0026#34;a,b,c\u0026#34;, \u0026#34;,\u0026#34;) } import起别名时可以用_表示匿名引入，只加载其中的init函数\ngoimport _ \u0026#34;mysql-driver-go\u0026#34; 内部包：Go约定，在命名为internal包内的函数无法被外部包导入\n可以导入内部包的文件，只有该内部包直接父目录的所有子文件\n数据类型 值类型 整型：\nuint8/16/32/64：无符号整k型 int8/16/32/64 uint和int：至少32位的整型 byte：等价于uint8 uintptr：保证足够大以至于能存储任何指针 浮点型：float32/64\n复数：complex64/128，本质为两个float32/64的封装\n字符型：\nbyte/uint8：可存储ASCII码\nrune：等价于int32，可存储utf8字符，用rune来在语义上区分整型和字符，也可表示一个Unicode\nstring：字符序列，可视为一个只读[]byte，它同样可以表示utf8(编码为byte序列后)字符串\nstring是一个不可变类型\n布尔型：bool\n数组类型：[n]Type，其中n必须是编译时可确定的\n数组也是一种值类型\n结构体：struct {}自定义类类型，是值类型\n指针类型：*Type，指针类型的对象可通过\u0026amp;作用于对象来创建，同时可以不使用解引用符访问值的属性和方法\n因此解引用符*更多作用于右值，此时解出来的是副本\n通过使用指针，可以实现类似引用类型的效果\n引用类型 切片类型：[]Type，是可变长的序列\n映射类型：map[keyType]valueType\n方法：func(T...) R自定义方法\n通道类型：Go擅于多线程场景，通道是Go提供的线程间通信手段\nchan Type：双向通道，chan表明它是一个通道，Type是通道传输的数据的类型 chan\u0026lt;- Type：只写通道 \u0026lt;-chan Type：只读通道 接口：interface {}自定义接口类型\n任意类型(别名)：any = interface{}\n可比较类型(别名)：comparable = interface{ comparable }\ncomparable应该作为函数参数的类型以限制形参，而不是用来定义一个变量\n所有基本数据类型、以及由可比较类型组成的数组、以及由可比较类型组成的struct都是comparable的\nGo的引用类型只有切片、映射、方法、通道、接口五种，它们像指针那样共享同一份堆中的数据，拷贝的是地址\n使用类型断言：obj.(type)获取obj的类型，obj.(Type)返回(value, ok)\n字面量 true和false\n整型可使用_分割加强可读性\niota：是预定义的字面量，只在const块下生效，表示自增的自然数序列，即0-idx\nconst组的一个特性是：后续没有显式赋值的常量会延用最近使用的表达式，因此只需要第一项指定为iota，后续可默认生成\ngoconst ( a = iota // 0 b // 延用 iota, 为1 c = iota * 2 // 2 * 2 d // 延用 iota * 2, 为3 * 2 e = 1 f = iota // 5 )实际上，iota并不算很好的命名，目前只有Cpp与APL以及Go在使用\n一种场景是：\ngoconst ( a = 1 \u0026lt;\u0026lt; iota b // 1 \u0026lt;\u0026lt; 1 c // 1 \u0026lt;\u0026lt; 2 ) nil：空值，它不是类型，而是用于初始化指针类型、内置引用类型的零值\n数组/切片字面量：一维数组如下，高维数组类似\ngoa := [5]int32{1, 2, 3, 4, 5} // 可以通过 idx:value 局部赋值 // 0-idx b := [5]int32{1: 2, 4: 5} 结构体字面量：结构体字面量有若干种写法\ngo// 一个类型的字面量是紧跟该类型后的大括号 var a AStruct = AStruct{ 1, // 如果不带字段名, 则按顺序赋值, 且必须全部手动赋值 2, // 所有属性值需要紧跟 \u0026#39;,\u0026#39; } // 可以指定字段名, 此时顺序无影响 // 此时会将所有未手动赋值的属性赋为零值 var b BStruct = BStruct{ a: 1, } // 匿名结构体习惯使用 := 赋值 a := struct { a int32 b int32 }{ 1, 2, } // 允许混合声明, 指定字段必须放在最后 // 但这是不好的代码习惯 b := struct { a int32 b int32 }{ 1, a: 2, } new()与make()：用于值类型和引用类型的内存分配\nnew(Type) *Type：new只能用于创建值类型，且使用零值初始化，返回指针\n不常用，更常用的是先创建一个值类型的值然后取地址\nmake(Type, ...int) Type：make专门用于为映射、切片、通道这三个内置引用类型分配内存，并返回它们的值引用\n这三种类型对应的make函数格式不同，根据第一个参数解析后续的参数\n声明语句 变量声明 Go是静态强类型语言，虽然可以使用var与const，但是最终需要编译时确定类型，即指定类型或指定初始值必须含一\n根据字面量动态推断类型：\ngoa := 1 const b = 1 // const变量只能进行一次初始化 c := 1 // 等价于 var c = 1短变量赋值语句只能在函数作用域内使用\n指定数据类型：Go是类型后置的，如果使用var声明变量不附带初始值，则必须指定类型\ngovar a int const b int = 1类型后置能带来一系列优点，首先是可以抛弃void而不影响观感，最重要的是方法签名和函数类型易于阅读\n变量分组：通过()对变量进行分组能提高可读性\ngovar ( a int32 b float32 ) const ( a = 1 b ) 多返回值：Go支持多返回值以及函数多返回值\ngoa, b := 1, 2 a, b := b, a // swap a, b = func() (int32, int32) { return 1, 2 }() 类型声明 类型声明(创建新类型)使用type TypeName OriginTypeName，基于其它类型创建新类型(两者类型的头信息不同)\n所有在之前数据类型小节讲述的类型以及自定义的类型都可以作为OriginTypeName\n与var和const类似，type关键字也可以使用type ()分组声明\n同一个type分组的类型声明，遵循先后顺序，且在前语句声明的类型可以在后语句立刻使用，需要注意循环引用\nGo的引用类型不像Java的引用类型，其更相似的是指针类型，使用指针类型可以实现循环引用\n示例-数组：type IntArr5 [5]int32\n示例-切片：type IntSlice []int32\n示例-结构体：结构体是一系列属性的集合\ngotype StructName struct { a int64 b int32 } 示例-接口：接口是一系列方法的集合\ngotype InterfaceName interface { A() int B(a int32) } 示例-通道：\ngotype ( DEIntChan chan int32 RIntChan \u0026lt;-chan int32 WIntChan chan\u0026lt;- int32 ) 示例-函数：\ngotype RetIntFunc func() int32 类型别名 类型别名使用type TypeName = OriginTypeName，使用=后，这个类型是类型别名，两者等价\n别名，意味着OriginTypeName必须已经声明，而不能是匿名的（如匿名结构体、匿名接口），但函数类型是允许的\n类型别名可以和类型声明放进同一个type分组里\ngotype ( RetIntFunc = func() int32 // ok StrcutA = struct { // error a int32 } StructB struct { a int32 } StructC = StructB ) 同一个type分组里，类型别名的位置没有任何影响，即使其引用的原类型在同一分组内的后面定义\ngotype ( A = B B int32 ) 运算与表达式 运算 大多数运算符和其它语言类似 不含前缀++或前缀--，且后缀++和后缀--都不具有返回值，即它们变成了原子操作 不支持三元表达式 条件表达式不需要小括号括住，且条件表达式必须返回布尔值，其它类型不会自动隐式地转换为布尔值 条件控制 if表达式与其它语言类似\nswitch表达式则有较大不同：\ngo// switch 后可跟简单的计算语句 // 特殊的是这些语句需要以 ; 结尾 switch t := nbr; { // case 语句后面跟条件表达式 case t == 1: stmt // 和其它语言不同, 默认情况下, // 每个条件块执行结束结束即退出(默认break) // 如果想继续执行下一分支则应使用 fallthrough case t == 2: stmt fallthrough default: default_stmt } 虽然支持label和goto语句，但是尽量不用goto，倒是可以结合break和continue使用标签\n循环控制 没有while表达式，全部使用for：\ngofor init_stmt; expr; post_stmt { for_stmt } for expr { while_stmt } for { // 不带表达式默认为 true while_true_stmt } for-range：\ngofor idx, val := range iterable { stmt } 不支持省略if、for语句的大括号\n不支持左大括号换行\n大部分风格可以由格式化工具如goimport和gofumpt决定，不需要过多注意\n","permalink":"https://echo.ishian.me/2025/12/23/go-beginner/","summary":"讲解Go的包管理、变量声明、控制流与基本数据类型等入门语法","title":"Go: 基本语法"},{"content":"语法分析 文法分类 0型文法：又称短语文法 1型文法：又称上下文有关文法 2型文法：又称上下文无关文法 3型文法：又称正则表达式 上下文无关文法 结构：(VT,VN,S,P)(V_T,V_N,S,P)(VT​,VN​,S,P)，其中VTV_TVT​是终结符集合，VNV_NVN​是非终结符集合，SSS是开始符号，PPP是产生式集合\n上下文无关文法允许产生式右部出现所有在产生式左部出现的非终结符\n推导：\n最左推导：每次展开都展开最左边的非终结符 最右推导(规范推导)：每次展开都展开最右边的非终结符 分析树：推导的树型表示\n二义性：存在多种推导的可能结果，例如产生式集合E→E+E∣E×EE\\rightarrow E+E|E\\times EE→E+E∣E×E\n消除二义性：通过引入中间非终结符解决：\nE→E+TT→T×T \\begin{align} \u0026amp;E\\rightarrow E+T \\\\\u0026amp;T\\rightarrow T\\times T \\end{align} ​E→E+TT→T×T​​ 一般，优先级高的运算，其产生式应该离根更远\n消除左递归：在递归下降分析程序里，由于程序无法看到后续的记号，因此如果存在左递归会存在一个函数递归调用自身而不继续解析的问题\n消除直接左递归：A→Aα∣βA\\rightarrow A\\alpha|\\betaA→Aα∣β\nA→βA′A′→αA′∣ϵ \\begin{align} \u0026amp;A\\rightarrow \\beta A\u0026#x27; \\\\\u0026amp;A\u0026#x27;\\rightarrow \\alpha A\u0026#x27;|\\epsilon \\end{align} ​A→βA′A′→αA′∣ϵ​​ 消除间接左递归：{A→Bα∣βB→Aγ∣ϵ\\begin{cases}A\\rightarrow B\\alpha|\\beta\\\\B\\rightarrow A\\gamma|\\epsilon\\end{cases}{A→Bα∣βB→Aγ∣ϵ​\n实际上可以先转化为直接左递归：\n{A→Bα∣βB→Bαγ∣βγ∣ϵ \\begin{cases}A\\rightarrow B\\alpha|\\beta\\\\B\\rightarrow B\\alpha\\gamma|\\beta\\gamma|\\epsilon\\end{cases} {A→Bα∣βB→Bαγ∣βγ∣ϵ​ 提取公共左因子：存在公共左因子会使程序不知道应该选择哪个，如果定死顺序则很可能出现错误分析\n提取公共左因子非常简单，只需要添加中间的非终结符即可：\nA→αA∣αBA→αCC→A∣B \\begin{align} \u0026amp;A\\rightarrow \\alpha A|\\alpha B \\\\\u0026amp;A\\rightarrow \\alpha C \\\\\u0026amp;C\\rightarrow A|B \\end{align} ​A→αA∣αBA→αCC→A∣B​​ 分析法 自上而下分析LL(1) LL(1)要求产生式集合没有二义性、没有左递归、没有公共左因子 求first集合： 终结符的first集合只有一个元素：它本身 非终结符的first集合： 先找到所有形如S→aα…S\\rightarrow a\\alpha\\dotsS→aα…的产生式，其右部的开始符号是终结符 再找到所有形如S→Aα…S\\rightarrow A\\alpha\\dotsS→Aα…的产生式，其右部的开始符号是非终结符，将first(A)−ϵfirst(A)-\\epsilonfirst(A)−ϵ加入first(S)first(S)first(S) 如果在第二步，ϵ∈first(A)\\epsilon\\in first(A)ϵ∈first(A)，那么使first(α)−ϵfirst(\\alpha)-\\epsilonfirst(α)−ϵ加入first(S)first(S)first(S)，继续本步骤 如果在第三步，遍历到最后的符号且最后的符号的first集包含ϵ\\epsilonϵ，那么将ϵ\\epsilonϵ加入first(S)first(S)first(S) 求follow集合：follow集合不包含ϵ\\epsilonϵ 将$加入开始符号的follow集合里 对所有A→αBβA\\rightarrow \\alpha B\\betaA→αBβ，将first(β)−ϵfirst(\\beta)-\\epsilonfirst(β)−ϵ加入follow(B)follow(B)follow(B) 对所有A→αBA\\rightarrow \\alpha BA→αB或A→αBβA\\rightarrow\\alpha B\\betaA→αBβ且ϵ∈first(β)\\epsilon\\in first(\\beta)ϵ∈first(β)，则将follow(A)follow(A)follow(A)加入follow(B)follow(B)follow(B) 非递归的下降分析：要求构建分析表，则程序分析时不递归而是查找分析表找到对应的产生式 行名为(栈顶的)非终结符，列名为输入符号，表格项为产生式 对每个A→αA\\rightarrow\\alphaA→α，对∀a∈first(α)\\forall a\\in first(\\alpha)∀a∈first(α)且a≠ϵa\\ne\\epsilona=ϵ，使M(A,a)M(A,a)M(A,a)为这个产生式 对每个A→αA\\rightarrow\\alphaA→α，若ϵ∈first(α)\\epsilon\\in first(\\alpha)ϵ∈first(α)，则对∀a∈follow(A)\\forall a\\in follow(A)∀a∈follow(A)使M(A,a)M(A,a)M(A,a)为这个产生式 若运行时遇到表格项为空，说明程序分析出错 若遇到一个表格项包含多个产生式，说明不是LL(1)文法 错误恢复： 词法错误 语法错误 语义错误 逻辑错误 同步选择集合： 自下而上分析 句柄：和某个产生式右部一致的串 移进归约分析： 移进：将下一个符号入栈 归约：将栈顶的整个句柄用对应的产生式归约，用产生式的左部替换这个句柄 接受：到达目标串的结尾且分析表未出错 错误：分析表到达了空表项 移进归约冲突：栈顶的句柄可以继续移进，也可以归约的时候 归约归约冲突：栈顶可以有多种归约可能情况 LR(0)(Left-to-right, Rightmost derivation, 0 lookahead)：非常容易产生移进归约冲突和归约归约冲突 项目：一个产生式的右部的符号中间添加·符号 项目集规范族： 引入增广文法（添加产生式E′→EE\u0026#x27;\\rightarrow EE′→E指向开始符号） 对扩展文法新增的产生式求E′→⋅EE\u0026#x27;\\rightarrow ·EE′→⋅E闭包，即只要·后是非终结符则要把这个非终结符为左部的所有产生式加入闭包，递归进行此步骤 对上述闭包可以移进的所有符号，进行移进得到新的项目集 重复第二步和第三步直到构建出DFA 从DFA构建LR(0)分析表： 对Ix-\u0026gt;Iy出边为终结符a的情况：ACTION[Ix, a]=sy 对Ix-\u0026gt;Iy出边为非终结符A的情况：GOTO[Ix, A]=y 对Ix能够归约的项目(产生式编号为y，产生式左部为A)：对所有终结符a，使ACTION[Ix, a]=ry 对归约项目为增广文法新增的产生式，使表项为acc 使其余所有表项为error 使用LR(0)分析表对输入进行语法分析： 初始状态栈只有一个元素：I0 设当前状态为Ix，输入符号为a，查询ACTION[Ix, a]： 若为sy，则状态变为Iy，输入后移，Iy入栈 若为ry，第y个产生式为A→αA\\rightarrow\\alphaA→α，则从状态栈出栈，设出栈后栈顶状态为Iz则状态变为GOTO[Iz, A] 若为error，则进入错误恢复程序 若为acc，表示接受输入，语法分析成功 SLR分析表(SLR(1))：与LR(0)对比，引入了follow集合，对Ix能够归约的项目(产生式编号为y，产生式左部为A)：对所有a属于follow(A)，使ACTION[x, a]=ry LR(1)分析表： SLR(1)分析仍然会出现移进归约冲突 LR(1)分析对项目进行修改：使其带搜索符，例如A→⋅α,aA\\rightarrow ·\\alpha, aA→⋅α,a 项目集规范族： 增广文法产生的项目为E′→⋅α,#E\u0026#x27;\\rightarrow·\\alpha, \\#E′→⋅α,# 同样对扩展文法新增的产生式求A′→⋅Bα,aA\u0026#x27;\\rightarrow ·B\\alpha, aA′→⋅Bα,a闭包，即只要·后是非终结符则要把这个非终结符为左部的所有产生式加入闭包，递归进行此步骤，其中它们的搜索符是first(αa)first(\\alpha a)first(αa) 生成分析表的过程和LR(0)的区别是：只对搜索符对应的表项设置为可归约项 LALR分析表：LALR在LR(1)的基础上，将略去搜索符后一致的项目族合并，因此和SLR拥有相似数量的项目族，但LALR可能出现归约归约冲突 ","permalink":"https://echo.ishian.me/2025/12/03/compilers-syntax/","summary":"讲解文法分类、上下文无关文法、二义性与左递归消除及语法分析方法","title":"编译原理: 语法分析"},{"content":"Hi there! I\u0026rsquo;m ishian Tech Stack\nLanguages: Java, Go, Typescript Editor: Neovim Shell: Zsh + Oh-My-Zsh Terminal: Kitty Dotfiles Projects\nnvim-mybatis: A neovim+mybatis plugin powered by tree-sitter. tree-sitter-mybatis: Mybatis mapper file CST parser generated by tree-sitter-cli. golem: An agent lib based on eino and eino-ext. mini.codex: Codex CLI integration in neovim with input/output and session management. Blog: https://echo.ishian.me/\n","permalink":"https://echo.ishian.me/about/","summary":"\u003ch2 id=\"hi-there-im-ishian\"\u003eHi there! I\u0026rsquo;m ishian\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"Profile Summary\" loading=\"lazy\" src=\"https://github-profile-summary-cards.vercel.app/api/cards/profile-details?username=ishianecho\u0026theme=default\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTech Stack\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eLanguages\u003c/strong\u003e: Java, Go, Typescript\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEditor\u003c/strong\u003e: \u003ca href=\"https://github.com/neovim/neovim\"\u003eNeovim\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eShell\u003c/strong\u003e: \u003ca href=\"https://github.com/zsh-users/zsh\"\u003eZsh\u003c/a\u003e + \u003ca href=\"https://github.com/ohmyzsh/ohmyzsh\"\u003eOh-My-Zsh\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTerminal\u003c/strong\u003e: \u003ca href=\"https://github.com/kovidgoyal/kitty\"\u003eKitty\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ca href=\"https://github.com/ishianecho/dotfiles\"\u003eDotfiles\u003c/a\u003e\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eProjects\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/ishianecho/nvim-mybatis\"\u003envim-mybatis\u003c/a\u003e: A neovim+mybatis plugin powered by tree-sitter.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/ishianecho/tree-sitter-mybatis\"\u003etree-sitter-mybatis\u003c/a\u003e: Mybatis mapper file CST parser generated by tree-sitter-cli.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/ishianecho/golem\"\u003egolem\u003c/a\u003e: An agent lib based on eino and eino-ext.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/ishianecho/mini.codex\"\u003emini.codex\u003c/a\u003e: Codex CLI integration in neovim with input/output and session management.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eBlog\u003c/strong\u003e: \u003ca href=\"https://echo.ishian.me/\"\u003ehttps://echo.ishian.me/\u003c/a\u003e\u003c/p\u003e","title":"About Me"},{"content":"词法分析 编译过程 编译器分为前端和后端，前端包括词法分析、语法分析、语义分析、生成中间代码，后端包括代码优化、最终代码生成，在过程中借助符号表管理器和错误管理器 通过中间代码划分前端和后端，不需要针对每一门语言都完整设计一个编译器 遍： 翻译器：将一门语言翻译成另一门语言的软件 编译器：将一门高级语言翻译成一门低级语言的软件 解释器： 即时编译： 词法分析过程 词法分析的目标是把源文件这一串符号流转换成记号流 词法单元：源文件这一串字符串 词法记号：满足一定规则的词法单元 字母表：符号的集合 串：符号组成的序列 语言：字母表上某些串的集合，包括空串、空集 句子：语言的一个元素 语言的运算及正则表达式：详见Regex 自动机 不确定的有限自动机(NFA) 允许出边的条件为空串 允许有具有相同条件的出边 由正则表达式生成NFA： 或运算x s|t y：在x后添加两条空串的出边，在y前添加两条空串的入边，分别指向s和t 与运算x st y：使s的出节点和t的入节点重合 闭包xs∗yx s^* yxs∗y：使x通过空串既指向s又指向y，使s的出节点通过空串指向入节点 由语言生成NFA：脑补所有可能结束的状态，由初始状态遍历字母表迭代计算 确定的有限自动机(DFA) 由NFA生成`：子集构造法 从初始状态开始，求出闭包（经过空串能得到的所有状态） 遍历每一个字母表的元素，计算上述闭包经过该元素能得到的集合，并求出新集合的闭包 重复第二个步骤，直到没有任何新增的闭包 化简DFA： 死状态：如果存在一个字母表中的元素，有一个状态没有对应的出边，那么需要使他指向死状态，死状态的所有出边都指向它自己，相当于出错时直接跳过当前词法单元 执行类似由NFA转化的步骤，但是将不可区分的状态合成为一个状态 词法分析器的生成器 lex和flex：使用嵌入了c/cpp的脚本语言，将脚本语言翻译成一般c程序 语法： 声明语句：\nlex{% int cnt = 0; %} 分隔符：%%\n入口：yylex()，每次调用都会解析直到在匹配规则里return或分析到字符串结尾，若中途返回则下次调用从返回处继续分析\nyytext：匹配成功，得到的完整的词法单元\n","permalink":"https://echo.ishian.me/2025/12/02/compilers-lexical/","summary":"介绍编译过程概览与词法分析，包括正则表达式、NFA与DFA自动机","title":"编译原理: 词法分析"},{"content":"无监督学习 主成分分析 主成分分析和主成分回归的第一步很像，使样本矩阵乘上载荷矩阵，载荷矩阵是p×Mp\\times Mp×M的矩阵，使共ppp个特征降维为MMM个特征，只不过无法通过交叉验证估计测试误差，也无法选择MMM 主成分分析必须对载荷矩阵进行对标准化，中心化可选 聚类分析 K均值聚类 PCA的本质是降维，保留使得方差大的方向(数据投影到该方向上，能占据较宽的范围)，证明这个方向很接近数据分布的方向，从而实现去噪声 而K均值聚类则是在PCA的基础上做聚类任务，K均值聚类事先规定了K，而系统聚类通过人工分析得到最终的类别数 K均值聚类希望类内差异尽可能小，度量类内差异通常有： 平方欧氏距离：\\frac1{n_{k}}\\sum_{i,j\\in C_k}||\\boldsymbol x_{i}\\boldsymbol x_j||^2 K均值聚类的步骤：事先定义K个类中心，每次迭代使一个样本分类到距离其最近的类中心所代表的类里，然后重新根据类内差异计算类中心，直到所有点所属的类都不再变化 系统聚类 凝聚法(自下而上)：每次迭代选出两类合并为一类(初始每个样本独自为一类) 选择合并双方的评判方法有： 最长距离法：选取最大差异度最小的两类 最短距离法：选取最小差异度最小的两类 重心法：选取两类的重心(所有样本点各个特征的平均值为重心的各个特征值)距离最小的两类(会发生颠倒现象) 类平均法：选取平均差异度最小的两类 差异度的评判方法： 欧氏距离 基于相关性的距离 ","permalink":"https://echo.ishian.me/2025/11/28/stat-unsupervise-learning/","summary":"介绍无监督学习方法：主成分分析降维及K均值与系统聚类","title":"统计学习: 无监督学习"},{"content":"支持向量机 超平面 超平面就是一个式子，β0+β1x1+⋯+βpxp=0\\beta_0+\\beta_1x_1+\\dots+\\beta_px_p=0β0​+β1​x1​+⋯+βp​xp​=0 分割超平面：如果超平面两边(\u0026gt;0或\u0026lt;0\u0026gt;0或\u0026lt;0\u0026gt;0或\u0026lt;0)刚好划分了两类样本，则为分割超平面 最大间隔分类器： 间隔：所有样本点到分割超平面的距离的最小值 最大间隔分类器：使所有样本点的间隔最大的分割超平面 支持向量分类器 支持向量分类器：又称软间隔分类器，它和分割超平面不同，允许有间隔错误和超平面错误 间隔错误：在间隔内部以及分类错误的样本点 超平面错误：分类错误的样本点，是间隔错误的子集 支持向量：落在间隔上或者间隔错误的样本点个数就是支持向量的个数 核函数和支持向量机 事实上，超平面可以改写成f(x)=β0+∑i∈Sα^i\u0026lt;x,xi\u0026gt;f(x)=\\beta_0+\\sum_{i\\in S}\\hat\\alpha_i\u0026lt;x,x_i\u0026gt;f(x)=β0​+∑i∈S​α^i​\u0026lt;x,xi​\u0026gt;，其中\u0026lt;x,xi\u0026gt;\u0026lt;x,x_i\u0026gt;\u0026lt;x,xi​\u0026gt;为它们的内积 而这个内积可以改为其它函数，称为核函数，基于不同核函数的分类器统称为支持向量机 线性核函数：支持向量分类器所用的核函数 多项式核函数：可以生成光滑决策边界 径向基核函数： 当类别可分时，支持向量机和LDA做的比逻辑斯蒂回归更好 ","permalink":"https://echo.ishian.me/2025/11/27/stat-svm/","summary":"讲解支持向量机的最大间隔分类器、软间隔与核函数原理","title":"统计学习: SVM（支持向量机）"},{"content":"数据集集成框架 数据的流向：先将数据交给自定义的数据集类(其任务为对源数据预处理，包装成数据加载器能够访问的数据集)，通常转化为张量 通过数据加载类进行采样并送给模型计算并优化(其任务为根据默认或自定义的采样器Sampler进行真正的采样)\nmap-style和iterable-style：索引式数据集和迭代式数据集\n索引式数据集的做法是将整个数据集读取到内存，采样的时候通过[]访问即可 适用于批量读取、小数据集，由于数据均在内存中，往往更快 迭代式数据集的做法是事先定义迭代的行为，采样的时候从硬盘中取出数据并作为迭代器返回 适用于各种数据无法事先全部读入内存的情况(例如训练过程中会产生新样本、或内存过小)，当然是比较慢的 torch.utils.data.Dataset(简称Dataset)是所有数据集类型的基类，用户如果需要构建自己的索引式数据集，必须直接继承该类并重载以下方法(其实就是封装成一个索引式数据集)：\n__len__()：应返回数据集的长度，以便可以用len(obj)访问数据集长度 __getitem__(idx)：应返回该索引指向的数据，以便可以用[]访问数据 torch.utils.data.IterableDataset(简称IterableDataset)是所有迭代式数据集的基类，继承该类后必须重载：\n__iter__()：yield一个迭代器，一般是在方法内临时读取文件并返回一条数据 torch.utils.data.DataLoader(简称DataLoader)是数据集对象的迭代器，相比诸多语言的内置迭代器封装了很多功能，不需要手动实现乱序、分批读取等，加载数据集十分方便，其构造方法__init__()参数用法为：\ndataset：Dataset(或子类)对象，表示要遍历的数据集 batch_size：批数据的大小，即通过该迭代器遍历时返回的样本个数；将其设置为None以关闭自动批处理 sampler：可以指定采样器进行更多样的采样行为 batch_sampler：类似sampler，但要求用户提供的采样器必须返回一批张量的索引而不是一个张量的索引，这一批张量均用用户提供的Sampler对象来采样，它和sampler、batch_size是冲突的(因为认为用户已经在sampler里定义了批量大小) shuffle：布尔值，决定是否乱序采样，和sampler、batch_sampler冲突(因为要使用默认的RandomSampler)，没有更高需求的采样行为而懒得自定义采样器时，使shuffle=True就足够了 只提供dataset，其它参数保持默认下，迭代器会顺序地进行批量为一的自动批处理采样 其它不常用的参数：\nnum_workers：将数据分给多个子进程加载，默认只用主进程加载 collate_fn：是一个可调用对象，表示对数据集采样后，返回给用户前进行的行为 默认的collate_fn常在一个开启了自动批处理的索引式数据集中用到，会将采样的数据合并为一批 因为迭代式数据集的批量采样等行为通常是由用户在__iter__()中实现的，所以对迭代式数据集不会提供默认的collate_fn 用户可以自定义collate_fn pin_memory：由于GPU中的内存全是锁页内存，但CPU中一般只是不锁页内存(即会被交换到虚存中)，设置pin_memory=True，将使读入的样本变成锁页内存，通过消耗CPU内存的方式加快其载入GPU的过程 drop_last：布尔值，表示是否丢弃最后一个不完整的批次 timeout：表示数据加载的时限 torch.utils.data.Sampler(简称Sampler)是采样器类，表示采样的策略，具体为提供一个索引序列 内置的Sampler有：\nSequentialSampler：顺序采样，每次返回一个索引，是未提供sampler且shuffle=False时的默认采样器 RandomSampler：全集的随机采样，是未提供sampler且shuffle=True时的默认采样器 WeightedRandomSampler：根据权重的随机采样 SubsetRandomSampler：对数据集子集的随机采样 BatchSampler：对其它Sampler产生的索引序列分批，顺序地提供的各个批次的索引序列(一次返回整个批次的所有索引) 开启自动批处理时，最终会经过这个采样器进行分批 用户可以继承Sampler或以上这些特化的采样器类，来自定义一个Sampler 不过大多数情况下，上述采样器已经足够了\n","permalink":"https://echo.ishian.me/2025/11/26/pytorch-dataprocess/","summary":"讲解Dataset、IterableDataset与DataLoader的封装方式及采样批处理参数","title":"Pytorch: 使用torch.utils.data封装数据"},{"content":"统计学习 线性回归 stats包 c(...)：使参数拼接为一个向量 sample(tot, sz)：生成一组索引，从tot个样本中随机采样sz个样本 poly(x, k, raw, ...)：生成关于x的k阶多项式，raw布尔值表示TRUE(正交多项式)、FALSE(原始多项式)，正交多项式的每一项是原始多项式(xix^ixi)的线性组合 lm()： formula：模型公式，例如y ~ x1 + x2 +：线性相加 .：通配符，包括所有变量的+ -：去除变量，通常配合. :：交互项 *：表示包含两者以及两者的交互项，例如x1*x2等价于x1+x2+x1:x2 0或-1：表示去除截距项 I(x^k)：多项式项(k次幂) data：所用的数据，必须包含formula中使用的变量 subset：逻辑表达式或者索引，在训练前先作用于data glm()：广义线性模型，相比lm()添加了family参数 gaussian：默认值，进行线性拟合 binomial：逻辑回归 summary(model)：获取模型的总结，包括系数、标准差、ttt值、ttt假设检验的p值、RSERSERSE、R2R^2R2(adj表示加了惩罚项)、FFF统计量、FFF假设检验的p值 逻辑回归的总结还包括零模型偏差和完整模型偏差，以及AIC估计 coef(model)：返回更少的内容，只返回系数的估计值 anova(models...)：通过方差分析，判断更复杂的模型新增的变量是否有显著影响 predict(model, data, interval, level, type)：使用模型进行预测 data：待预测的数据 interval：指定预测区间还是置信区间，可选confidence、prediction，默认为none(点估计) level：指定置信水平，默认0.95 type：指定预测类型，可选response、link，指定为response来获得响应值，因为默认的link难以解释 which.min(vec)：求出最小值对应的下标，max()类似 可视化 plot(x, y)：绘制散点图 abline(model)：绘制模型的最小二乘线 MASS(现代应用统计学) lda()：线性判别分析 qda()：二次判别分析 boot cv.glm(data, glmfit, K, cost)：对glm()的模型进行k折交叉验证，返回值的delta列的第一项是标准的交叉验证估计值(res$delta[1]) boot(data, statistic, R, ...)：自助法估计指定statistic统计量的某个指标 data：原始数据集\nstatistic(data, indices)：自定义函数，在里面指定模型、返回感兴趣的统计指标，例如：\nRfunction(data, indices) d \u0026lt;- data[, indices] # indices是boot()提供的索引组 model \u0026lt;- lm(y ~ x, data=d) pred \u0026lt;- predict(model, d, type=\u0026#34;response\u0026#34;) return mean(pred == d$Y) leaps regsubsets(y ~ ., data, method, nvmax, ...)：默认为最优子集法，method可选forward和backward summary()可返回rsq, rss, bic, adjr2, cp统计量 glmnet glmnet()相比glm()新增了alpha和lambda参数，alpha=0表示岭回归(默认值)，alpha=1表示Lasso回归，lambda为惩罚系数，可以通过交叉验证求得 一般通过predict()使type=\u0026quot;coeficcients\u0026quot;获取系数，因为使用压缩估计的目的是变量选择，所以不使用summary() 使用cv.glmnet()来选择超参数lambda，它会在内部自动调用glmnet() pls pcr()：scale参数 validationplot() plsr()：偏最小二乘回归 scale=TRUE参数表示标准化，validation=\u0026quot;cv\u0026quot;表示使用交叉验证来估计测试误差 非线性模型 通过cut(x, sz)划分变量范围，来拟合阶梯函数 splines bs()表示样条函数，默认为三次样条 smooth.spline()：光滑样条 loess()：局部回归 gam gam()：广义可加模型 ","permalink":"https://echo.ishian.me/2025/11/26/rlang-beginner/","summary":"记录R语言统计学习常用函数：线性回归、广义线性模型与预测分析","title":"Rlang: 虽然不知道为什么要学"},{"content":"树模型 决策树 树方法的特点 解释性强，方法简单 准确性不如之前介绍的方法 回归树 回归树的损失函数是将RSSRSSRSS划分为多个区域RjR_jRj​，形式为：\nRSS=∑j=1J∑i∈Rj(yi−y^Rj)2 RSS=\\sum_{j=1}^J\\sum_{i\\in R_j}(y_i-\\hat y_{R_j})^2 RSS=j=1∑J​i∈Rj​∑​(yi​−y^​Rj​​)2 建树方法：递归二叉分裂：\n自上而下：从顶端开始，选择预测变量和分割点(每一个预测变量都可以分段，所有分割点就是所有变量的分割点的集合) 贪婪：选择能使树的RSSRSSRSS最小的那个分割点sss 剪枝：递归二叉分裂的过拟合较为严重，需要剪枝防止预测结果进一步分裂\n代价复杂性剪枝(最弱联系剪枝)：对RSSRSSRSS添加惩罚项：\nRSS=∑j=1J∑i∈Rj(yi−y^Rj)2+α∣T∣ RSS=\\sum_{j=1}^J\\sum_{i\\in R_j}(y_i-\\hat y_{R_j})^2+\\alpha|T| RSS=j=1∑J​i∈Rj​∑​(yi​−y^​Rj​​)2+α∣T∣其中α\\alphaα对应一棵子树，是非负的超参数，∣T∣|T|∣T∣为该子树的叶子节点数，α\\alphaα同样通过交叉验证选择\n即叶子结点数越多，惩罚越大\n分类树 分类树和回归树类似，只不过损失函数需要变化\n效果较差的分类错误率：\nE=1−max⁡k(p^mk) E=1-\\max_k(\\hat p_{mk}) E=1−kmax​(p^​mk​)p^mk\\hat p_{mk}p^​mk​是在第mmm个区域第kkk类所占比例，即所有非最常见类的预测比例\n基尼系数：\nG=∑k=1Kp^mk(1−p^mk) G=\\sum_{k=1}^K\\hat p_{mk}(1-\\hat p_{mk}) G=k=1∑K​p^​mk​(1−p^​mk​) 交叉熵：\nD=−∑k=1Kp^mklog⁡p^mk D=-\\sum_{k=1}^K\\hat p_{mk}\\log \\hat p_{mk} D=−k=1∑K​p^​mk​logp^​mk​ 装袋法 装袋法又称自助法聚集，学习自助法可以知道，通过有放回重复采样可以平均方差，使预测结果的方差减小 装袋法通过生成BBB棵树，分别进行预测，求平均得到最终的预测结果 针对分类树，采用多数投票的方法，取最多投票的分类作为最终的预测结果 由自助法，每个自助样本包含原始样本大约23\\frac2332​的样本点，那么一颗自助树的袋外样本就有13\\frac1331​，那么不使用这13\\frac1331​袋外样本作为训练集的自助树有B3\\frac B33B​棵，通过这B3\\frac B33B​个袋外预测值就能估计测试误差了 它的估计过程和自助法估计不同，自助法针对每个模型计算袋外样本的测试误差，然后平均(共模型数个)，装袋法针对每个袋外样本先算测试误差，然后平均(共袋外样本数个) 随机森林 随机森林本质是变量数更少的装袋法，在每个分裂点都选出m≤pm\\le pm≤p个变量来训练，通常mmm取p\\sqrt pp​ 提升法 提升法适用于多个回归方法，针对回归树，它先训练多个弱学习器，不过是顺序训练的，后面的学习器会采用前面学习器的残差作为yyy 提升法的目标是降低偏差 ","permalink":"https://echo.ishian.me/2025/11/25/stat-tree-models/","summary":"介绍回归树与分类树的建树剪枝方法及装袋、随机森林、提升等集成模型","title":"统计学习: 树型模型"},{"content":"非线性模型 基函数 多项式回归 多项式回归通过添加变量的幂来实现非线性的拟合 阶梯函数 阶梯函数通过将预测变量的取值划分为KKK个区间，来得到K+1K+1K+1个新的定性的变量 基函数推广 基函数本质是针对一个预测变量XXX做许多变换，来增加模型的复杂度，其基本形式是：\ny=β0+β1b1(X)+⋯+βkbk(X)+ϵ y=\\beta_0+\\beta_1b_1(X)+\\dots+\\beta_kb_k(X)+\\epsilon y=β0​+β1​b1​(X)+⋯+βk​bk​(X)+ϵ 多项式回归和阶梯函数都是基函数的特例\n回归样条 回归样条在分段多项式回归的基础上添加限制条件，使不同区间在分段点上连接，称为spline(样条) d阶样条是d阶多项式在每个分段点的零阶到d-1阶的导数都是连续的 样条基函数：通过将d阶多项式改写为y=β0+β1b1(x)+⋯+βkbk(x)+ϵy=\\beta_0+\\beta_1b_1(x)+\\dots+\\beta_kb_k(x)+\\epsilony=β0​+β1​b1​(x)+⋯+βk​bk​(x)+ϵ的基函数的形式，能减小使其k阶导数连续的难度 线性样条： 三次样条：有K个节点的三次样条的自由度为K+4 自然样条：有K个节点的自然样条的自由度为K 结点选择：通过交叉验证选择 光滑样条 光滑样条不同于回归样条，通过在最小二乘损失函数RSSRSSRSS的基础上添加光滑惩罚项(波动性惩罚) λ∫g′′(t)2dt\\begin{align}\\lambda\\int g\u0026#x27;\u0026#x27;(t)^2\\mathrm dt\\end{align}λ∫g′′(t)2dt​​ 这个对二阶导求和的惩罚项要求模型在节点处限制其粗糙程度，样条越光滑二阶导的积分越小 最终结果是自然三次样条的收缩版本，λ\\lambdaλ是收缩程度，λ\\lambdaλ同样通过交叉验证选择 其自由度随λ\\lambdaλ增大从nnn降低到222 局部回归 局部回归在训练时只使用一个点附近的数据来拟合 广义可加模型 广义可加模型在多项式回归函数的基础上，将βjxj\\beta_jx_jβj​xj​改为fj(xj)f_j(x_j)fj​(xj​)非线性函数(例如上面的阶梯函数和样条函数)，称为可加模型 ","permalink":"https://echo.ishian.me/2025/11/24/stat-nlinear-models/","summary":"介绍非线性回归方法：多项式回归、回归样条、光滑样条与GAM","title":"统计学习: 非线性模型"},{"content":"估计方法 简介 此前我们所说的RSE、置信区间等指标都需要测试误差，而测试误差是不可知的，因为没有标签 因此需要用一定的方法估计测试误差，除了测试误差，各种统计量的不确定性也需要估计，这有利于我们分析估计出来的统计量的浮动程度 在真正处于没有标签的环境之前，可以通过切出小部分数据集不让模型训练，从而预估模型的效果，分割出来给模型训练的是训练集，其余的为测试集，有时希望人工调整一些参数，会继续分割为验证集和测试集，验证集用于中期人工调整、测试集用于最终效果评估和展示(不再调整) 验证集的思路是好的，但是验证集通常是远小于训练集的数据量，且具有偶然性，测试误差被高估(有偏的)(不同的分割方式对误差影响大) 交叉验证法 K折交叉验证 K折交叉验证就是用于估计测试误差的被广泛使用的方法 其原理是将数据集分为K折大小一致的组，进行K次训练-测试，其中第i次的测试集为第i组，其余组为训练集，交叉验证最终的MSE为K次测试MSE的均值 通过划分来进行交叉验证，解决了验证集方法因训练次数只有一次而因划分方案导致的随机性，K折交叉验证本身也会因K的变大而变得更加稳定 当K不那么小时，可将交叉验证估计出来的误差为对某nk\\frac nkkn​个未知样本的误差的渐进无偏估计，当K变大其训练集的样本数自然变少，从而和验证集方法一样变得有偏 留一法 LOOCV(留一法)是特殊的K折交叉验证，它把数据集分为n(样本数)组，即验证集只有一个样本，因此会进行完整n次训练-测试拟合\nLOOCV是对某一个未知样本的误差的渐进无偏估计，渐进指N十分大时，估计出的误差和真实的泛化误差差不多\n由于留一法测试时选取的是一个样本，所以说是对某一个未知样本的无偏估计，又因为训练集几乎等同于整个数据集，留一法的偏差会更小\n留一法的划分是固定的，因为每一折只有一个样本，划分方案只有一种，对同一个数据集，留一法的估计结果是确定的\n在采用最小二乘法拟合线性模型时，LOOCV估计的误差可以近似于只进行一次拟合：\nCV(n)=1n∑i=1n(yi−y^i1−hi) CV_(n)=\\frac1n\\sum_{i=1}^n\\left(\\frac{y_i-\\hat y_i}{1-h_i}\\right) CV(​n)=n1​i=1∑n​(1−hi​yi​−y^​i​​) 自助法 自助法专注于估计一个统计量的不确定性，比如泛化误差的标准差和置信区间，而不是估计泛化误差\n估计不确定性需要大量的数据和大量的重复试验来估计统计量的总体分布，因此交叉验证不适合\n自助法的原理是对数据集进行多次有放回重复简单随机抽样，从而获得近似总体的若干数据集，一次简单随机抽样获得的数据集和原始数据集大小一致，称为一个自助样本\n然后，对于每个自助样本，可以证明当n较大时有大概三分之二的重复的样本点，自助法使用自助样本来训练，使用袋外样本来测试，得到对应的统计量，这个统计量(例如泛化误差)一般是被低估的，虽然测试集和训练集没有重叠，但是训练集本身因为有重复样本已经和总体分布偏离了\n虽然统计量的偏差较大，但是其方差、置信区间等却能很好地代表总体的方差\n三分之二重复的证明：抽样符合P(x未被抽中)=(1−1n)nP(x未被抽中)=\\left(1-\\frac{1}{n}\\right)^nP(x未被抽中)=(1−n1​)n，n取极大后取极限为1e\\frac1ee1​约等于三分之一\n蒙特卡洛模拟对该方法进行了压力测试，即使统计量的估计分布和其总体分布偏差较大，但统计量的估计分布的不确定性却十分准确\n间接估计测试误差 CpC_pCp​ 交叉验证是用于直接估计测试误差的方法，而有些方法通过对训练误差进行数学调整来间接地估计测试误差\nCpC_pCp​统计量：其中ppp为预测变量个数、σ^2\\hat\\sigma^2σ^2为测试误差的方差的估计值\nCp=1n(RSS+2pσ^2) C_p=\\frac1n(RSS+2p\\hat\\sigma^2) Cp​=n1​(RSS+2pσ^2) 因为训练误差通常小于测试误差，因此需要加上惩罚项来估计被低估的测试误差，其中参数个数越大惩罚越大\n直观上来看，CpC_pCp​统计量肯定越小越好，如果σ^2\\hat\\sigma^2σ^2是σ2\\sigma^2σ2的无偏估计，那么CpC_pCp​也就是测试误差的无偏估计了\nσ^2\\hat\\sigma^2σ^2一般用包含全部变量的模型通过自助法估计\nBICBICBIC BIC(贝叶斯信息准则)统计量，对于最小二乘法拟合得到的模型，其形式为：\nBIC=1nRSS+log⁡(n)pσ^2 BIC=\\frac1nRSS+\\log(n)p\\hat\\sigma^2 BIC=n1​RSS+log(n)pσ^2 相比CpC_pCp​，在n\u0026gt;100n\u0026gt;100n\u0026gt;100时其惩罚更大\nAICAICAIC AIC(赤池信息准则)统计量不仅适用于最小二乘法，适用于大部分极大似然法拟合得到的模型(其中LLL为似然函数的极大值)：\nAIC=−2log⁡L+2p AIC=-2\\logL+2p AIC=−2logL+2p AIC针对最小二乘法的形式为：\nAIC=1nσ^2(RSS+2pσ^2) AIC=\\frac1{n\\hat\\sigma^2}(RSS+2p\\hat\\sigma^2) AIC=nσ^21​(RSS+2pσ^2) 调整R2R^2R2 调整R2R^2R2肯定是让它变小：\nR2=1−RSS(n−1)TSS(n−p−1) R^2=1-\\frac{RSS(n-1)}{TSS(n-p-1)} R2=1−TSS(n−p−1)RSS(n−1)​ 调整R2R^2R2没有理论证明，但是广泛适用，是对多余变量数ppp的惩罚\n","permalink":"https://echo.ishian.me/2025/11/20/stat-estimate/","summary":"讲解测试误差的估计方法：交叉验证、留一法与自助法及自举推断","title":"统计学习: 估计方法"},{"content":"变量选择 子集选择法 最优子集选择法：对p个预测变量，进行总共∑i=1pCpi=2p−1\\sum_{i=1}^pC_{p}^i=2^p-1∑i=1p​Cpi​=2p−1次拟合，即会拟合所有可能的模型\n具体做法是先对每一个i，求出包含i个变量的最优模型(训练误差最小)，然后在剩下的p个最优模型里再选择最优(真正进行交叉验证，选择泛化误差最小)\n但效率极低，当p较大时2p2^p2p级别的复杂度就会使拟合无法完成\n逐步选择：\n逐步向前选择：从不包含任何变量的模型开始，遍历没有加入模型的预测变量，选择使得下一模型的训练误差最小的变量加入，直到所有变量全部加入\n模型仍然能获得p个“最优”模型，在其中估计泛化误差选择最小者\n其拟合次数为∑i=1p=p(p+1)2\\sum_{i=1}^p=\\frac{p(p+1)}{2}∑i=1p​=2p(p+1)​，约为p2p^2p2\n逐步向后选择：从包含全部变量的模型开始，遍历所有已经加入模型的预测变量，选择使得下一模型的训练误差最小的变量删除，直到所有变量全部删除\n模型仍然能获得p个“最优”模型，在其中估计泛化误差选择最小者\n其拟合次数为∑i=1p=p(p+1)2\\sum_{i=1}^p=\\frac{p(p+1)}{2}∑i=1p​=2p(p+1)​，约为p2p^2p2\n向后选择在p\u0026gt;np\u0026gt;np\u0026gt;n时无法进行，因为方程组无解(方程数小于未知变量数)\n最优模型的选择依据\n模型选择依赖于交叉验证估计的较为准确的泛化误差 压缩估计 压缩估计是一种针对训练过程的变量选择方法，通过影响损失函数来间接影响拟合函数，让某些多余变量的系数接近零或变成零，因此也可作为变量选择的一节 压缩估计不是通过对训练误差直接增加惩罚项或直接估计测试误差，而是试图在训练时就对最小化函数增加惩罚项，一般是针对系数加惩罚，来减小系数的方差以测试误差的方差最终有效地防止过拟合 前面介绍的方法都没有影响基于最小二乘法的训练过程，而是通过各种手段估计测试误差，以其作为依据选择最优模型，而压缩估计则是直接修改了损失函数的形式，从而在选择最优模型之前的那一步减弱模型的过拟合 Lasso回归(L1回归) Lasso回归是在最小二乘法的基础上，将最小化函数RSSRSSRSS改造为：\nLasso=∑i=1n(yi−y^i)2+λ∑j=1p∣βj∣ Lasso=\\sum_{i=1}^n\\left(y_i-\\hat y_i\\right)^2+\\lambda\\sum_{j=1}^p|\\beta_j| Lasso=i=1∑n​(yi​−y^​i​)2+λj=1∑p​∣βj​∣ 最小化|\\beta_j|，它的解在解空间里将有“尖角”特性，其和RSSRSSRSS最小化的解的交集将有很大概率落在坐标轴上，因此βj\\beta_jβj​系数更容易被压缩到零，从而更能实现变量选择的效果\n与第一节的权衡一致，增加惩罚项使偏差增大，方差减小，能减小模型的过拟合现象\n它的另一个形式是作为一个约束(其中sss为无穷大时等价于最小二乘法)：\n∑j=1p∣βj∣≤s \\sum_{j=1}^p|\\beta_j|\\le s j=1∑p​∣βj​∣≤s Ridge回归(L2回归) 岭回归是在最小二乘法的基础上添加惩罚项λ∑j=1pβj2\\lambda\\sum_{j=1}^p\\beta_j^2λ∑j=1p​βj2​，它的效果并没有Lasso回归那么好，因为其解是圆滑的，随着系数会接近零但不会为零，这减小了可解释性\n与Lasso回归类似，它的另一个形式是作为一个约束：\n∑j=1pβj2≤s \\sum_{j=1}^p\\beta_j^2\\le s j=1∑p​βj2​≤s 选择λ\\lamdbaλ或sss 和子集选择法类似，最终仍然通过交叉验证来估计测试误差，只不过针对的是不同λ\\lambdaλ下的最优模型，即不是直接比较不同变量组合的模型，而是比较不同正则化强度的模型 降维选择法 此前的子集选择法会完全不使用部分变量，而压缩估计则是通过压缩系数来间接地选择变量，而降维选择法则是通过将原始ppp个变量变换为更少的MMM个变量(并不是将一些变量合为一组来进行训练) 降维选择法直接影响拟合函数的形式，也是一种针对训练过程的变量选择 变量个数MMM也通过交叉验证来选择 主成分回归 PCR回归通过投影方法，把ppp个变量投影到MMM个变量，通过乘投影矩阵得到主成分 偏最小二乘 PLS偏最小二乘可以视作有指导的主成分回归，响应变量YYY会参与降维的过程 ","permalink":"https://echo.ishian.me/2025/11/20/stat-var-selection/","summary":"讲解变量选择方法：子集选择、逐步回归及Lasso与Ridge压缩估计","title":"统计学习: 变量选择"},{"content":"分类任务 逻辑回归 二分类逻辑回归 针对分类任务，通常可用概率值估计类别，例如二分类求出一个0~1之间的概率，若概率大于某个阈值则取其中一类\n而一般的线性回归会求出超出0~1范围的数，这是一种不合理\n如同之前分析的线性回归问题“数据非线性”的解决方法那样，逻辑回归通过对预测变量作变换来解决分类任务\n一元逻辑回归的形式是：\np(X)=eβ0+β1X1+eβ0+β1X=11+e−β0−β1X p(X)=\\frac{e^{\\beta_0+\\beta_1X}}{1+e^{\\beta_0+\\beta_1X}}=\\frac{1}{1+e^{-\\beta_0-\\beta_1X}} p(X)=1+eβ0​+β1​Xeβ0​+β1​X​=1+e−β0​−β1​X1​这在人工智能里称为激活函数Sigmoid，其中可将β0+β1X\\beta_0+\\beta_1Xβ0​+β1​X替换为多元线性回归的形式来拟合多元分类任务\n逻辑回归的想法就是将分类任务转换为回归任务\n计算估计参数的方法自然是极大似然估计法，处理定性变量和一般线性回归相同，通过设计哑变量来表示\n预测变量和响应变量的相关性通过Z统计量及其假设检验计算\n和线性回归一样，如果预测变量之间高相关，那么容易产生混淆现象\n多分类逻辑回归 和线性回归不同，针对逻辑回归，“多元”一般指多分类，即输出是多个类别\n使用的激活函数是Softmax，Sigmoid是Softmax的特例：假设输出类别数为K，那么第i个输出是\nSoftmax(Xi)=ezi∑k=1Kezk Softmax(X_i)=\\frac{e^{z_i}}{\\sum_{k=1}^Ke^{z_k}} Softmax(Xi​)=∑k=1K​ezk​ezi​​ 线性判别分析 LDA（线性判别分析）是另一种间接估计Pr(Y=k∣X=x)Pr(Y=k|X=x)Pr(Y=k∣X=x)的方法，通过建立预测变量的分布模型，根据贝叶斯定理估计后验概率，最后分类\n贝叶斯定理：\nPr(Y=y∣X=x)=πyfy(x)∑(l=1)Kπlfl(x)其中πy=Pr(Y=y),fy(x)=Pr(X=x∣Y=y) Pr(Y=y|X=x)=\\frac{\\pi_yf_y(x)}{\\sum_(l=1)^K\\pi_lf_l(x)} 其中\\pi_y=Pr(Y=y),f_y(x)=Pr(X=x|Y=y) Pr(Y=y∣X=x)=∑(​l=1)Kπl​fl​(x)πy​fy​(x)​其中πy​=Pr(Y=y),fy​(x)=Pr(X=x∣Y=y) 估计Pr(X=x∣Y=y)Pr(X=x|Y=y)Pr(X=x∣Y=y)是难题，假设它服从正态分布，那么代入可得贝叶斯分类器，经过推导得到观测的最终分类将是使下式得到最大值的第kkk类：\nδy(x)=xμyσ2−μy22σ2+log⁡(πy) \\delta_y(x)=x\\frac{\\mu_y}{\\sigma^2}-\\frac{\\mu_y^2}{2\\sigma^2}+\\log(\\pi_y) δy​(x)=xσ2μy​​−2σ2μy2​​+log(πy​) 线性判别分析和逻辑回归的区别：\n逻辑回归是参数模型，假设这个条件分布是变换后的线性模型，通过拟合Pr(Y=y∣X=x)Pr(Y=y|X=x)Pr(Y=y∣X=x)的模型并计算参数得到计算式子 LDA是非参数模型，假设这个条件分布服从正态分布，通过贝叶斯估计直接推导得出的计算式子，然后求解决定性因子δy(x)\\delta_y(x)δy​(x)得到yyy 何时使用LDA\n不同类别区分度较大时 样本量较少无法计算参数时 响应分类类别数较大 通用：假设服从正态分布时和逻辑回归类似，但可以假设服从其它各种分布 决策边界：LDA会出现对于不同的y其\\delta_y(x)相同的情况，即：\nδy1(x)=μy1σ2−μy122σ2+log⁡(πy1)=δy2(x)=μy2σ2−μy222σ2+log⁡(πy2) \\delta_{y_1}(x)=\\frac{\\mu_{y_1}}{\\sigma^2}-\\frac{\\mu_{y_1}^2}{2\\sigma^2}+\\log(\\pi_{y_1})=\\delta_{y_2}(x)=\\frac{\\mu_{y_2}}{\\sigma^2}-\\frac{\\mu_{y_2}^2}{2\\sigma^2}+\\log(\\pi_{y_2}) δy1​​(x)=σ2μy1​​​−2σ2μy1​2​​+log(πy1​​)=δy2​​(x)=σ2μy2​​​−2σ2μy2​2​​+log(πy2​​)由等式计算得到的xxx，就是决策边界\n先验概率对决策边界的影响：\n根据等式，先验概率πy\\pi_yπy​越大的类别，其覆盖的x范围越大，即决策边界会“远离”该类别\np=1p=1p=1时LDA的假设：不同类别的x服从方差σy2\\sigma^2_yσy2​相同的正态分布\np\u0026gt;1p\u0026gt;1p\u0026gt;1时LDA的假设：不同类别的x服从协方差矩阵相同的多元正态分布，允许不同预测变量之间有相关性（协方差可非零）\n混淆矩阵 横坐标为预测分类、纵坐标为真实分类\n(真∣假)(阳∣阴)性率=预测成功∣预测失败×预测阳性∣预测阴性真实阳性∣真实阴性真阳性率=预测阳且真实阳真实阳预测(阳∣阴)性率=预测成功(阳∣阴)预测(阳∣阴) \\begin{align}\u0026amp;(真|假)(阳|阴)性率=\\frac{预测成功|预测失败\\times预测阳性|预测阴性}{真实阳性|真实阴性}\\\\\u0026amp;真阳性率=\\frac{预测阳且真实阳}{真实阳}\\\\\u0026amp;预测(阳|阴)性率=\\frac{预测成功(阳|阴)}{预测(阳|阴)}\\end{align} ​(真∣假)(阳∣阴)性率=真实阳性∣真实阴性预测成功∣预测失败×预测阳性∣预测阴性​真阳性率=真实阳预测阳且真实阳​预测(阳∣阴)性率=预测(阳∣阴)预测成功(阳∣阴)​​​ 二次判别分析 QDA(二次判别分析)假设每一类别的预测变量仍服从多元高斯分布，但允许不同类的预测变量的协方差矩阵不同，在这样的假设下估计出来的∑k\\sum_k∑k​不同，代入后得到：\nδy(x)=−12(x−μy)T∑y−1(x−μy)+log⁡πy−12log⁡∣∑y∣ \\delta_y(x)=-\\frac12(x-\\mu_y)^T\\sum_y^{-1}(x-\\mu_y)+\\log\\pi_y-\\frac12\\log|\\sum_y| δy​(x)=−21​(x−μy​)Ty∑−1​(x−μy​)+logπy​−21​log∣y∑​∣ QDA要求计算等同于类别数个的协方差矩阵，带给他更高的光滑度\n由于判别函数是二次的，QDA的光滑度更高，因此偏差更小方差更大，当数据量非常大时，LDA的假设十分远离实际，因此偏差异常的大，而QDA带来的方差变大并不那么重要，反而能减小偏差，此时QDA更适合\n","permalink":"https://echo.ishian.me/2025/11/18/stat-classification/","summary":"整理统计学习分类方法：逻辑回归、判别分析与K近邻等分类模型","title":"统计学习: 分类任务"},{"content":"线性回归 简单线性回归 最小二乘法 线性回归是监督学习模型，假设所有预测变量和响应变量具有线性关系 最小二乘回归(OLS)是最常用的简单线性回归方法，它采用的损失函数是残差平方和，其中残差是预测值与真实值的差值，残差平方和(RSS)为 ∑i=1n(y^i−yi)2\\begin{align}\\sum_{i=1}^n(\\hat y_i-y_i)^2\\end{align}i=1∑n​(y^​i​−yi​)2​​ 最小化损失函数是几乎所有方法使用的估计系数方法，对于OLS而言则是最小化RSS，即求导使导数为零然后解方程 模型参数估计值指标 随机误差：随机误差是线性回归本就假设进去的一项，它的方差$\\sigma^2$是不可约误差，是未知的常数 标准差SE：表示参数估计结果偏离真实值的平均值，用到了随机误差的方差以及RSS 残差标准差(RSE)：虽然不可知，但可以估计，一般用t(n-2)统计量来估计 置信区间：xx%置信区间通过参数的估计值加上一定比例的标准差来得到，例如95%置信区间的范围是参数估计值±2SE(参数估计值)参数估计值\\pm 2SE(参数估计值)参数估计值±2SE(参数估计值) 预测区间 参数估计值是否为零：通过对t统计量进行假设检验，若p值接近零，认为有显著影响，因此否定估计值为零的原假设 模型预测值评价指标 残差标准差表示模型预测值偏离真实值的平均值\n$R^2$统计量通过总平方和以及残差平方和来计算，总平方和是真实值与平均值的差的平方和，该统计量越接近1，说明模型能更好的解释变异\n偏差-方差权衡：模型的光滑度代表了它的复杂程度，光滑度越高，模型的非线性拟合能力越强，方差越小，但偏差也会越大\n随光滑度增高，训练误差单调递减，但测试误差随之呈U型变化，左侧呈现出欠拟合问题(模型复杂程度太低)，右侧呈现出过拟合问题(训练误差小于不可约误差，但测试误差较大)\n多元线性回归 多元线性回归假设各个预测变量不相关 多元线性回归同样使用最小二乘法估计系数 多元线性回归同样通过t统计量的p值判断某个特定系数是否为零，此外还通过F统计量的p值判断是否所有预测变量的系数均为零 处理定性的预测变量 二值变量使用一个哑变量即可，即是某类时赋值为1，否则为零 n值变量使用n-1个哑变量，所有哑变量均为零时表示剩下那个状态 处理线性回归的假设 多元线性回归假设各个预测变量不相关，但现实中几乎不可能，为了减小相关性影响，引入交互项(乘积项)，和一般的预测变量相同地参与模型评价 实验分层原则：若交互项存在，那么参与交互项的预测变量(称为主效应项)都应在模型中 线性回归的问题 数据的非线性：画出残差-拟合值图像，对于每一个拟合值，求出的残差的均值应是零，若存在若干点残差均值远离零，那么很有可能有数据的非线性问题\n解决办法是观察图像并猜测有可能的线性关系，添加对预测变量作一定变换的非线性项\n随机误差项自相关：原本假设是nnn个ε\\varepsilonε之间互不相关(且假设服从正态分布)，如果相关会导致估计标准差低于真实标准差，链式导致置信区间变窄\n此问题常出现在时间序列数据上，解决办法是增加采样时间或注重随机采样\n随机误差项方差非恒定：画出残差-拟合值图像，随拟合值变化，残差的均值虽然接近零，但上下范围随之变化\n解决办法是对响应变量作一定变换\n离群点：响应值标签是异常的，针对一元或二元线性回归，可以画出真实值-输入值图像或学生化残差图来发现离群点\n学生化残差指残差和残差标准差的比值，提供一个易于分析的评估范围(绝对值大于3视为离群点)\n离群点不会过多影响最小二乘计算出的参数估计值，但会影响评价指标(因为RSE会被影响)，如p值、置信区间\n高杠杆点：输入数据(预测变量)的值是异常的(简单理解为附近没有其它样本点)，可以通过计算杠杆统计量值量化寻找\n高杠杆点和离群点不同，它会极大影响最小二乘计算出的参数估计值，必须找出它\n数据的共线性：预测变量之间高度相关，难以猜测哪些变量是真正相关的，若两个高度相关的变量放在一起，参数的标准差增大、t统计量值减小、假设检验的p值增大，导致无法看出哪个变量是真正和响应变量相关的，且难以解释系数\n可通过计算协方差矩阵或计算方差膨胀因子(VIF)发现共线性数据\n可通过其它回归方法或去除某些变量解决该问题\n","permalink":"https://echo.ishian.me/2025/11/17/stat-linear-regression/","summary":"讲解简单与多元线性回归的最小二乘估计、显著性检验及模型评价指标","title":"统计学习: 线性回归"},{"content":"Linux内核 Linux内核主要负责四个功能：系统内存管理、软件程序管理、硬件设备管理、文件系统管理\n系统内存管理 Linux内核可以管理硬盘上的交换区域，这片区域被称为虚拟内存，它扩大了系统可使用的内存\n内核会自动把长时间未访问的资源从物理内存换出到交换区域里，需要访问时又把它换入到物理内存中\n软件程序管理 运行中的程序称为进程，第一个进程称为init进程，\n","permalink":"https://echo.ishian.me/2025/08/20/linux-kernel/","summary":"介绍 Linux 内核的系统内存管理、软件程序管理等核心功能","title":"Linux: 内核"},{"content":"卷积层 卷积层(Convolutional Layer)专门用于处理网格状数据或具有多维度特征信息的数据，例如时间序列(一维)、图像(二维)等\n在全连接层中图像数据的预处理方法：将所有像素均视为特征，一个图像的所有像素的RGB值等排成一列作为一条样本\n因此，全连接层极其不适合处理网格数据，原因是：\n网格数据的特征量极多，导致使用全连接层处理时，参数量爆炸导致计算效率低下 网格数据具有局部相关性，相邻数据相关程度远大于不相邻数据的相关程度，而全连接层使结点和全局数据关联起来，忽略了网格数据的“形状” 平移不变性：网格数据的部分目标平移时，应该将平移前后的数据看作类似的输入，但显然全连接层做不到 卷积层解决了上述问题，并总结为三个特性：\n稀疏交互/稀疏连接/稀疏权重：使权重远小于输入，使模型只需要检测一些小的有意义的局部特征，减少存储需求的同时提高统计效率\n参数共享：在同一层卷积层中，计算不同结点的输出所用的参数(权重、偏置)是一致的\n平移等变：假设平移函数是使原输入的部分数据向某个方向平移的函数，那么原输入在经过平移函数后的输出和原输出将是等价的\n卷积层中的卷积运算对平移变换函数是天然等变的，对于放缩、旋转等变换，则需要额外的处理\n卷积层一般作为特征提取的核心，靠近输入层的卷积层能够提取低级特征、越深的卷积层越能理解更高级别的语义特征\n卷积层在不同深度学习框架中的命名较为统一，均为ConvXd的形式，表示X维卷积层\n具体结构：\n卷积层的维度：卷积层允许接受维度不同的输入数据，卷积层的维度和输入数据的维度一致\n无论数据维度如何，卷积层始终进行局部特征提取，下面以二维卷积层Conv2d为例，一维或三维的卷积层可举一反三地理解\n输入、输出均为三维的特征图X(c,h,w)X(c,h,w)X(c,h,w)，其中ccc为通道数、hhh为特征高度、www为特征宽度\n通道(Channel)数是描述特征种类数的整数，例如RGB图像就是三通道的特征图\n卷积层不是通过展平，而是将数据的结构用通道数、高度、宽度表示出来，强调了数据的“形状”特性\n权重为四维的滤波器集合W(cout,cin,hW,wW)W(c_{out},c_{in},h_W,w_W)W(cout​,cin​,hW​,wW​)，在每个输出通道上的三维张量称为滤波器，又称卷积核，其中cout/inc_{out/in}cout/in​为输出/输入通道数、hWh_WhW​为核高度、wWw_WwW​为核宽度\n某一层卷积核的数量，等同于输出通道数，和输入通道数无关\n一般来说，hW=wWh_W=w_WhW​=wW​且为奇数，此后统一用KKK表示\n偏置为向量b(cout)\\boldsymbol b(c_{out})b(cout​)，同一输出通道的所有元素共享一个偏置值\n输入与权重的运算为卷积运算，实际上并非数学上的卷积，而是互相关运算，用∗\\ast∗表示\n在数学上的卷积运算中，核是进行了翻转的，即运算的双方在一方索引增大的同时另一方索引减小\n这种翻转的唯一目的是为卷积运算赋予可交换性，但对于神经网络而言它并不重要\n因为传统原因，许多实现把这种互相关运算称为卷积，所以本文也将其称为卷积，并默认其是不带翻转的卷积运算\n卷积层的卷积运算定义为(设输入特征图为XXX，过滤器为WWW)： (X∗W)cout,i,j=∑k=1cin∑m=1K∑n=1KXk,m+i−1,n+j−1⋅Wcout,k,m,n \\begin{align}\u0026amp;(X\\ast W)_{c_{out},i,j}=\\sum_{k=1}^{c_{in}}\\sum_{m=1}^{K}\\sum_{n=1}^{K}X_{k,m+i-1,n+j-1}·W_{c_{out},k,m,n}\\end{align} ​(X∗W)cout​,i,j​=k=1∑cin​​m=1∑K​n=1∑K​Xk,m+i−1,n+j−1​⋅Wcout​,k,m,n​​​ 简单来说，输出特征图在通道为c1c_1c1​上的第iii行第jjj列元素的初步加权求和值，等同于计算：\n对输入特征图的每个输入通道c2c_2c2​，取出以第iii行第jjj列个元素为左上角的、和卷积核结构相同的子矩阵，求出这个子矩阵和输出通道为c1c_1c1​、输入通道为c2c_2c2​的卷积核的逐元素相乘之和 将上述值求和，得到结果 因此在这种情况下，hout=hin−K+1h_{out}=h_{in}-K+1hout​=hin​−K+1，woutw_{out}wout​同理，即输入特征图的高度、宽度必须不小于卷积核的高度、宽度\n卷积层相比于全连接层，有一些独有的超参数：\n卷积核的输出通道数、宽高：宽高一般相等，且为奇数\n步长(Stride)：步长指卷积核移动的步长，上述公式中步长为s=1s=1s=1，通过增大步长，可达成压缩数据、增大感受野的效果，有时正需要损失部分信息而快速地减少数据规模，例如使s=2s=2s=2\n添加步长这个概念后，上述公式可表示为：\n(X∗W)cout,i,j=∑k=1cin∑m=1K∑n=1KXk, m+(i−1)s, n+(j−1)s⋅Wcout,k,m,nhout=⌊hin−Ks⌋+1 \\begin{align}\u0026amp;(X\\ast W)_{c_{out},i,j}=\\sum_{k=1}^{c_{in}}\\sum_{m=1}^{K}\\sum_{n=1}^{K}X_{k,\\ m+(i-1)s,\\ n+(j-1)s}·W_{c_{out},k,m,n}\\\\\u0026amp;h_{out}=\\left\\lfloor\\frac{h_{in}-K}s\\right\\rfloor+1\\end{align} ​(X∗W)cout​,i,j​=k=1∑cin​​m=1∑K​n=1∑K​Xk, m+(i−1)s, n+(j−1)s​⋅Wcout​,k,m,n​hout​=⌊shin​−K​⌋+1​​ 卷积层的填充(Padding)：如上述所示，经过卷积核运算后，输出特征图的高度、宽度必然缩小，在后续的卷积层中会无法继续运算，上述这种方式也称为有效填充(Valid)，即对原输入不进行任何的填充\n这种缩小本质是一种数据丢失，边缘的信息从来无法位于子矩阵的中心，导致丢失边缘信息\n为了保留信息、扩大输出特征图的结构，会在原输入的四周填充预设值，最常见的两种是：若s=1s=1s=1则使输出和原输入结构相同、若s\u0026gt;1s\u0026gt;1s\u0026gt;1则使输出为原输入的一半\n有如下经典的填充方式：\n全零填充(Same)：对四周填充零 反射填充(Reflection)：以四个边缘为对称轴，对称地填充四周的数值，顺序是左右上下 填充后新增的圈数称为填充量，记为ppp，可以这样计算： ∵hout=⌊hin−K+2ps⌋+1∴⌊(hout−1)s+K−hin2⌋≤p\u0026lt;⌊houts+K−hin2⌋ \\begin{align}\u0026amp;\\because h_{out}=\\left\\lfloor\\frac{h_{in}-K+2p}s\\right\\rfloor+1\\\\\u0026amp;\\therefore\\left\\lfloor\\frac{(h_{out}-1)s+K-h_{in}}2\\right\\rfloor\\le p\\lt\\left\\lfloor\\frac{h_{out}s+K-h_{in}}2\\right\\rfloor\\end{align} ​∵hout​=⌊shin​−K+2p​⌋+1∴⌊2(hout​−1)s+K−hin​​⌋≤p\u0026lt;⌊2hout​s+K−hin​​⌋​​ 因此，取\np=⌊(hout−1)s+K−hin2⌋\\begin{align}p=\\left\\lfloor\\frac{(h_{out}-1)s+K-h_{in}}2\\right\\rfloor\\end{align}p=⌊2(hout​−1)s+K−hin​​⌋​​即可\n当输出结构与原输入一致且步长s=1s=1s=1时，\np=⌊K−12⌋\\begin{align}p=\\left\\lfloor\\frac{K-1}2\\right\\rfloor\\end{align}p=⌊2K−1​⌋​​ 感受野(Receptive Field，或译为接受域)：指在某一层特征图上的某一个元素，对应原始数据输入的区域大小\n输入层的每一个神经元结点的感受野RFRFRF都是111，表示该层的每一个结点都只能看到1×11\\cross11×1的区域\n卷积程度越深，感受野将越大，直到最后某层的卷积结点的感受野等于全局感受野时，意味着一个结点掌握了完整输入的全局特征\n感受野可以迭代地计算：\nRFl+1=RFl+(Kl+1−1)∏i=1lsi\\begin{align}RF_{l+1}=RF_{l}+(K_{l+1}-1)\\prod_{i=1}^{l}s_i\\end{align}RFl+1​=RFl​+(Kl+1​−1)i=1∏l​si​​​可以通过计算感受野大小，帮助我们调整上述超参数，但感受野实际上与填充无关\n卷积层的前向传播： Xcin(l+1)=Zcout(l)=g(l)((X(l)∗W(l))cout+bcout(l)) \\begin{align}\u0026amp;X_{c_{in}}^{(l+1)}=Z^{(l)}_{c_{out}}=g^{(l)}\\left((X^{(l)}\\ast W^{(l)})_{c_{out}}+b^{(l)}_{c_{out}}\\right)\\end{align} ​Xcin​(l+1)​=Zcout​(l)​=g(l)((X(l)∗W(l))cout​​+bcout​(l)​)​​ 卷积层的反向传播梯度计算，以计算输出通道为coutc_{out}cout​的参数梯度为例：\n为了便于理解，先从s=1s=1s=1、Same填充开始推导： ∂Ycout(l)∂Xcin,i,j(l)=∑m=1K∑n=1KWcout,cin,m,n,(和i,j无关,是标量,记为SW)δcout(l)={∂L∂y^∂g(l)∂Y(l),l为输出层δcout(l+1)SW∂g(l)∂Y(l),l为隐藏层∂Y(l)∂Wcout,cin,i,j(l)=∑m=1K∑n=1KXcin, i+(m−1)s, j+(n−1)s∂L∂Wcout,cin,i,j(l)=δcout,i,j(l)∂Y(l)∂Wcout,cin,i,j(l) \\begin{align}\u0026amp;\\frac{\\part Y^{(l)}_{c_{out}}}{\\part X^{(l)}_{c_{in},i,j}}=\\sum_{m=1}^K\\sum_{n=1}^KW_{c_{out},c_{in},m,n},\u0026amp;(和i,j无关,是标量,记为SW)\\\\\u0026amp;\\boldsymbol\\delta^{(l)}_{c_{out}}=\\begin{cases}{\\Large\\frac{\\part L}{\\part\\hat y}\\frac{\\part g^{(l)}}{\\part Y^{(l)}}},\u0026amp;l为输出层\\\\\\boldsymbol\\delta^{(l+1)}_{c_{out}}SW{\\Large\\frac{\\part g^{(l)}}{\\part Y^{(l)}}},\u0026amp;l为隐藏层\\end{cases}\\\\\u0026amp;\\frac{\\part Y^{(l)}}{\\part W^{(l)}_{c_{out},c_{in},i,j}}=\\sum_{m=1}^{K}\\sum_{n=1}^KX_{c_{in},\\ i+(m-1)s,\\ j+(n-1)s}\\\\\u0026amp;\\frac{\\part L}{\\part W^{(l)}_{c_{out},c_{in},i,j}}=\\boldsymbol\\delta^{(l)}_{c_{out},i,j}\\frac{\\part Y^{(l)}}{\\part W^{(l)}_{c_{out},c_{in},i,j}}\\end{align} ​∂Xcin​,i,j(l)​∂Ycout​(l)​​=m=1∑K​n=1∑K​Wcout​,cin​,m,n​,δcout​(l)​=⎩⎨⎧​∂y^​∂L​∂Y(l)∂g(l)​,δcout​(l+1)​SW∂Y(l)∂g(l)​,​l为输出层l为隐藏层​∂Wcout​,cin​,i,j(l)​∂Y(l)​=m=1∑K​n=1∑K​Xcin​, i+(m−1)s, j+(n−1)s​∂Wcout​,cin​,i,j(l)​∂L​=δcout​,i,j(l)​∂Wcout​,cin​,i,j(l)​∂Y(l)​​(和i,j无关,是标量,记为SW)​ 池化层 ","permalink":"https://echo.ishian.me/2025/07/18/deeplearning-convlayer/","summary":"讲解卷积层的稀疏交互、参数共享、平移等变特性及卷积运算的具体结构","title":"机器学习: 卷积层"},{"content":"MyBatis SqlSessionFactory SqlSessionFactory通常是单例模式，它的实例用于获取SqlSession，并与其他事务控制 SqlSession接口 SqlSession包含所有执行Sql的方法，类似JDBC的Connection，但它是后者的进一步封装，它将一些繁琐的错误处理，结果处理，参数绑定简化了 ","permalink":"https://echo.ishian.me/2025/07/16/java-mybatis/","summary":"介绍 MyBatis 的 SqlSessionFactory 与 SqlSession 接口的使用","title":"Java: Mybatis"},{"content":"全连接层 全连接层指本层的每一个结点都和上一层的所有结点相连接\n全连接层适合处理特征数量较少的全局特征数据，要求各条样本之间不含空间或时间上的相关性\n全连接层通常作为任务头，用于生成输出而不是中间计算，例如作为输出层\n全连接层在不同深度学习框架中的命名有所不同，例如：\nAffine层(仿射变换层)：根据其数学本质(线性变换与偏移)命名 Linear层(线性层) Dense层(密集层) 具体结构：\n全连接层的输入和输出均为特征展平的列向量x\\boldsymbol xx，本层输入的元素个数和上一层结点数相同、本层输出的元素个数和本层结点数相同\n权重为二维矩阵WWW，Wi,j(l)W_{i,j}^{(l)}Wi,j(l)​表示第l−1l-1l−1层第iii个结点到第lll层的第jjj个结点的权重\n因此若第kkk层结点数为c(k)c^{(k)}c(k)，则W(l)W^{(l)}W(l)是一个c(l−1)c^{(l-1)}c(l−1)行c(l)c^{(l)}c(l)列的矩阵\n偏置则为列向量b\\boldsymbol bb，它的元素个数与所在层的结点数相同\n全连接层的FP(此后假设同一层的激活函数相同)： xj(l+1)=gj(l)((W:,j(l))Tx(l)+b(l))x(l+1)=g(l)((W(l))Tx(l)+b(l)) \\begin{align}\u0026amp;x_{j}^{(l+1)}=g_j^{(l)}\\left(\\left(W_{:,j}^{(l)}\\right)^T\\boldsymbol x^{(l)}+\\boldsymbol b^{(l)}\\right)\\\\\u0026amp;\\boldsymbol x^{(l+1)}=g^{(l)}\\left(\\left(W^{(l)}\\right)^T\\boldsymbol x^{(l)}+\\boldsymbol b^{(l)}\\right)\\end{align} ​xj(l+1)​=gj(l)​((W:,j(l)​)Tx(l)+b(l))x(l+1)=g(l)((W(l))Tx(l)+b(l))​​ 全连接层的BP梯度计算： δ(l)={∂L(y^)∂y^∂g(l)(Y(l))∂Y(l),l为输出层W(l+1)δ(l+1)∂g(l)(Y(l))∂Y(l),l为隐藏层∂L∂W(l)=x(l)(δ(l))T∂L∂b(l)=δ(l) \\begin{align}\u0026amp;\\boldsymbol\\delta^{(l)}=\\begin{cases}{\\Large\\frac{\\part L(\\hat y)}{\\part\\hat y}\\frac{\\part g^{(l)}(Y^{(l)})}{\\part Y^{(l)}}},\u0026amp;l为输出层\\\\W^{(l+1)}\\boldsymbol\\delta^{(l+1)}{\\Large\\frac{\\part g^{(l)}(Y^{(l)})}{\\part Y^{(l)}}},\u0026amp;l为隐藏层\\end{cases}\\\\\u0026amp;\\frac{\\part L}{\\part W^{(l)}}=\\boldsymbol x^{(l)}(\\boldsymbol\\delta^{(l)})^T\\\\\u0026amp;\\frac{\\part L}{\\part\\boldsymbol b^{(l)}}=\\boldsymbol\\delta^{(l)}\\end{align} ​δ(l)=⎩⎨⎧​∂y^​∂L(y^​)​∂Y(l)∂g(l)(Y(l))​,W(l+1)δ(l+1)∂Y(l)∂g(l)(Y(l))​,​l为输出层l为隐藏层​∂W(l)∂L​=x(l)(δ(l))T∂b(l)∂L​=δ(l)​​ ","permalink":"https://echo.ishian.me/2025/07/15/deeplearning-fclayer/","summary":"讲解全连接层的结构、前向传播与反向传播的梯度计算公式","title":"机器学习: 全连接层"},{"content":"","permalink":"https://echo.ishian.me/2025/07/14/spring-security/","summary":"介绍Spring Security的用法","title":"Spring framework: Security"},{"content":"Spring Validation org.springframework.validation 介绍 Spring Validation是原生的API，不依赖任何外部框架或规范 org.springframework.validation.Validator接口 这里的Validator不是jakarta.validation.Validator 仅包含两个方法： boolean supports(Class\u0026lt;?\u0026gt;)：检验该验证器是否支持指定的类 void validate(T obj, Errors)：检验obj，将错误信息交给Errors引用 对比jakarta.validation.Validator的validate()方法，它将错误信息交给返回值Set\u0026lt;ConstraintViolation\u0026lt;T\u0026gt;\u0026gt; Errors接口 Errors接口包含一系列方法用于获取错误信息\nreject(String errorCode)：注册一个全局的错误，errorCode若要分层，风格可使用.符号分隔不同层\nreject(String errorCode, String message)：使用自定义错误信息\nrejectValue(String field, String errorCode, String message)：对特定字段field注册一个错误\nhasErrors()：判断是否有错误\nhasGlobalErrors()：判断是否有全局错误\nhasFieldErrors(String Field)：判断是否有特定字段错误\nList\u0026lt;ObjectError\u0026gt; getGlobalErrors()：获取所有全局错误\nFieldError getFieldError(String field)：获取指定的字段错误\nList\u0026lt;FieldError\u0026gt; getFieldErrors()：获取所有字段错误\nBindingResult接口 BindingResult继承了Errors接口 Object getTarget()：获取发生错误的目标对象 集成Bean Validation API Spring使用适配器模式将Spring Validation与Bean Validation API集成起来\nLocalValidatorFactoryBean是一个org.springframework.validation.Validator，内部使用一个jakarta.validation.Validator的实现(一般是Hibernate)从而支持注解形式的验证API，它同时也是一个FactoryBean，因此可以通过IoC容器管理\n@Validated是一个Spring额外提供的注解，比起@Valid，它提供分组功能，分组使用方法和Bean Validation API里的groups属性类似\n和Spring MVC的联动：在方法参数中使用@Valid或@Validated或其它的注解约束，由容器会自动进行校验，并将抛出的异常赋给BindingResult对象自动注入\n也可定义@ExceptionHandler方法处理\n","permalink":"https://echo.ishian.me/2025/07/14/spring-validation/","summary":"介绍Spring原生Validator接口、Errors错误处理及Bean Validation集成用法","title":"Spring framework: Validation API"},{"content":"前馈神经网络基本原理 基本结构 神经网络是深度学习最主要的实现手段，它包含多个神经网络层，每一层包含若干神经元\n神经网络根据数据流动的特点不同，分为前馈神经网络(FeedForward NN，FNN)、反馈神经网络(FeedBack NN，但不简称为FNN)、图神经网络(GNN)\n前馈神经网络更简单，这种模型的每次计算结果是独立的、数据单向流通，其典型如全连接神经网络FCN、卷积神经网络CNN\n反馈神经网络的结点不仅接受来自前方结点的数据，还接受来自本身或后方的结点的数据，数据流动存在环的形式，因此其每次计算会受到过去的计算结果的影响，其典型如循环神经网络RNN\n图神经网络则没有“前后”的概念，它的结构是树或者图，可以处理结构更复杂的数据，例如一句话\n以下我们介绍FNN的基本原理\n神经网络层根据位置不同分为输入层、隐藏层(除首层和尾层的所有层)、输出层\n虽然神经网络层种类众多，但输入经过每层，基本地需要进行加权求和、添加偏置、激活函数三步得到输出，同时作为下一层的输入\n加权求和：每个输入对应一组权重，分别记为XXX和WWW\n到某个结点的加权求和是一种运算，记为W∘XW\\circ XW∘X，这个中间结果记为YYY\n添加偏置(bias)：在经过激活函数前加上偏置，可以控制激活神经元的阈值，加速模型的拟合\n偏置表示为bbb\n你可以想象它为本层的一个隐藏结点，该结点的输出固定为111，偏置则是其到下一层结点的权重\n激活函数记为g(x)g(x)g(x)，同一层的激活函数一般相同，激活后的非线性输出为本层的最终输出，记为ZZZ\n激活函数有时候会被单独作为一层来说明，称为探测层\n前向传播过程(FP)：向前递推计算所有层的输入，直至得到输出层的输出即为模型的预测值\nX(l+1)=Z(l)=g(l)(Y(l))=g(l)(W(l)∘X(l)+b(l))\\begin{align}\u0026amp;X^{(l+1)}=Z^{(l)}=g^{(l)}(Y^{(l)})=g^{(l)}\\left(W^{(l)}\\circ X^{(l)}+b^{(l)}\\right)\\end{align}​X(l+1)=Z(l)=g(l)(Y(l))=g(l)(W(l)∘X(l)+b(l))​​ 为了方便，称参数、激活函数属于第lll层，为在第lll层“向前看”的参数、激活函数 反向传播过程(BP)：反向递推计算所有参数(指所有隐藏层及输入层的权重、偏置)的误差项\n通常通过梯度下降法计算，其一般结论为：\n参数新值=参数旧值−η∂L∂参数\\begin{align}参数新值=参数旧值-\\eta\\frac{\\part L}{\\part{参数}}\\end{align}参数新值=参数旧值−η∂参数∂L​​​，其中：\nη\\etaη为学习率，用于控制更新参数的步长，不至于使每次训练过度调整导致在最优点周围徘徊\nLLL为损失函数，\n∂L∂参数\\begin{align}\\frac{\\part L}{\\part 参数}\\end{align}∂参数∂L​​​为参数梯度，其基本原理为使参数向梯度的反方向调整，即可使模型的损失值沿最小值方向变化\n每层的参数梯度可通过迭代的方式计算(YYY为未经过激活函数的中间输出)：\n∂L∂参数(l)=∂L∂Y(l)∂Y(l)∂参数(l)∂L∂Y(l)=∂L∂Y(l+1)∂Y(l+1)∂Y(l)=∂L∂Y(l+1)∂Y(l+1)∂X(l+1)\\begin{align}\u0026amp;\\frac{\\part L}{\\part 参数^{(l)}}=\\frac{\\part L}{\\part Y^{(l)}}\\frac{\\part Y^{(l)}}{\\part 参数^{(l)}}\\\\\u0026amp;\\frac{\\part L}{\\part Y^{(l)}}=\\frac{\\part L}{\\part Y^{(l+1)}}\\frac{\\part Y^{(l+1)}}{\\part Y^{(l)}}=\\frac{\\part L}{\\part Y^{(l+1)}}\\frac{\\part Y^{(l+1)}}{\\part X^{(l+1)}}\\end{align}​∂参数(l)∂L​=∂Y(l)∂L​∂参数(l)∂Y(l)​∂Y(l)∂L​=∂Y(l+1)∂L​∂Y(l)∂Y(l+1)​=∂Y(l+1)∂L​∂X(l+1)∂Y(l+1)​​​记\n∂L∂Y(l)\\begin{align}\\frac{\\part L}{\\part Y^{(l)}}\\end{align}∂Y(l)∂L​​​为δ(l)\\delta^{(l)}δ(l)，其结构与第lll层输出一致，对于具体的神经网络层，我们只需要计算\n∂Y(l)∂参数(l)\\begin{align}\\frac{\\part Y^{(l)}}{\\part 参数^{(l)}}\\end{align}∂参数(l)∂Y(l)​​​以及\n∂Y(l)∂X(l)\\begin{align}\\frac{\\part Y^{(l)}}{\\part X^{(l)}}\\end{align}∂X(l)∂Y(l)​​​即可得出梯度的具体数学表示\n由于参数、XXX、YYY一般是向量或矩阵形式，需要学习矩阵求导相关知识才能理解其数学形式推导，有时也有这种形式：求标量分量的偏导、组合出对整个向量或矩阵的偏导\n计算图：为了用正式的图形语言表示上述通过一系列运算将输入变为输出的过程，使用计算图语言，它是一个有向无环图，描述了不同层参数、输入之间的计算关系，称为计算图\n梯度下降法的优化 梯度下降法(Gradient Descent，GD)是神经网络训练中最基本的优化算法\n为了达成损失最小化，需要使损失函数对所有参数的偏导为零(达到极值)\n为了使该极值为极小值，需要向函数值减小的方向调整\n根据梯度的数学意义，沿梯度的反方向调整即可满足导数下降、函数值下降、调整效果最大化三个要求，故称梯度下降\n参数更新：使原本的参数减去算出的梯度值即可(即沿梯度的反方向更新)\n为了控制每次更新的步长不至于过大而导致模型永远在最优点四周徘徊，一般会给每一层添加学习率η\\etaη(一般为0.0010.0010.001)，即： Pnew(l)=Pold(l)−η(l)∂L∂Pold(l) \\begin{align}P_{new}^{(l)}=P_{old}^{(l)}-\\eta^{(l)}\\frac{\\part L}{\\part P_{old}^{(l)}}\\end{align} Pnew(l)​=Pold(l)​−η(l)∂Pold(l)​∂L​​​ 衍生算法：\n随机梯度下降法(Stochastic GD)/增量梯度下降法(Increment GD)：随机选择一个样本点训练，每处理一个样本点，立刻根据梯度更新参数 训练很快，能确保在局部最优解附近 像一个醉鬼一样下山，走到哪算哪，可能无法走到全局最优解(损失函数有多个极小值点，可能只能走到较大的极小值点而无法走到最小值点) 批量梯度下降法(Batch GD)：批量即每次求损失值时，计算所有样本的梯度并存储(暂时不更新) 在处理完整个样本集后，对梯度求平均后更新 每次迭代时能稳定地指向最准确的极值方向，结果是全局最优的 当一个数据集的样本特别多时，一次的训练时间过长 小批量梯度下降(Mini-Batch GD)：小批量如其名，将样本集再划分为多个小样本集，一次训练对小样本集进行上述操作 是一种折中的方法，兼顾训练效率、模型准确度 ","permalink":"https://echo.ishian.me/2025/07/14/deeplearning-simplenn/","summary":"讲解前馈神经网络的基本结构、前向传播与反向传播原理及梯度下降优化","title":"机器学习: 简单神经网络（前馈神经网络）的原理"},{"content":"Spring Web MVC MVC架构模式与前端控制器设计模式 MVC(Model-View-Controller，模型-视图-控制器)是网页应用中最常见的架构 模型用于存储数据与负责业务逻辑 视图用于显示与渲染数据 控制器是模型与视图的中介，用于接收并过滤来自视图的输入、格式化并输出来自模型的数据 在传统的应用中，视图渲染同时也在服务器端进行，即是一个前后端不分离的应用，此时控制器处理请求的返回值是一个视图，在Spring中由ViewResolver解析器保证开发者只需要返回String而不是具体的View 在前后端分离的现代Web应用中，前端交给一个前端框架开发，也许你已经听说过，此时后端应该提供RESTful的API，控制器返回的不再是View，而是数据 前端控制器设计模式旨在将Controller部分再次拆分，用一个单一的处理程序(前端控制器)来处理所有的请求，包括过滤、认证与授权、处理、记录日志等 这个前端控制器可以根据请求的类型不同，通过一个调度器/分发器，将请求分发到下属的不同的处理程序 不同的处理程序会有对应的响应数据，视图用于呈现这些响应数据 传统的Servlet开发有如下问题： Servlet的单个doXxx()处理多个不同URL的请求，管理映射困难，需要用分支判断 而如果对每一个URL都写一个单独的Servlet服务，虽然能解决上述问题，但相似业务逻辑的代码就会被分散在不同的文件中 需要手动地通过分支判断决定重定向以及内部转发，视图和服务紧耦合，难以扩展 需要通过getParameter()获取参数以及手动类型转换和校验 Spring Web MVC是基于Jakarta EE Servlet API的Web框架，通常简称Spring MVC 配置相关 DispatcherServlet的配置 Spring MVC提供了大部分实现，以使开发者只需要专心于编写控制器或处理器程序，然而稍微了解其内部构造仍是有用的\nDispatcherServlet是一个前端控制器，它实现了jakarta.servlet.http.HttpServlet 它的作用就是识别项目里被@Controller等注解的处理器以及内部的方法映射，将来自不同URL的请求分发给它们\n这个前端控制器同时应该作为一个Bean被纳入IoC容器管理，而作为一个Servlet应用，应用启动的流程是：由Servlet容器创建并注册所有的Servlet服务类，然后启动，最后创建ApplicationContext实例，因此无法使用自动注入\n为了解决这个问题，DispatcherServlet的构造方法有两种：默认构造方法会查询ServletConfig的InitParameter，使用contextConfigLocation配置；另一个构造方法要求一个WebApplicationContext参数，从而引导Servlet容器去创建IoC容器的上下文 这两种方法对应两种配置： 使用web.xml以及IoC容器所需的XML文件注册，部署在外部Servlet容器中 Servlet容器在创建DispatcherServlet时，检测到\u0026lt;init-param/\u0026gt;标签，转而读取对应的IoC容器的XML文件，在创建DispatcherServlet之前创建ClassPathXmlWebApplication实例\nxml\u0026lt;!-- web.xml --\u0026gt; \u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;org.springframework.web.servlet.DispatcherServlet\u0026lt;/servlet-class\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;!-- 参数名需要记 --\u0026gt; \u0026lt;param-name\u0026gt;contextConfigLocation\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;/WEB-INF/applicationContext.xml\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcher\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/ctx_path\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; \u0026lt;!-- applicationContext.xml --\u0026gt; \u0026lt;!-- IoC容器的XML配置 --\u0026gt; \u0026lt;beans ...\u0026gt; \u0026lt;/beans\u0026gt; 使用WebApplicationInitializer接口，覆写onStartup(ServletContext)方法来编程式地创建DispatcherServlet，实际上等价于用Java代码替代XML配置\njava// 基于注解 public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) { WebApplicationContext ctx = new AnnotationConfigWebApplicationContext(...); DispatcherServlet dispatcher = new DispatcherServlet(ctx); var registration = servletContext.addServlet(\u0026#34;dispatcher\u0026#34;, dispatcher); registration.addMapping(\u0026#34;/api/*\u0026#34;); } } 如果使用基于Java的注解，更建议继承AbstractAnnotationConfigDispatcherServletInitializer：\njavapublic class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class\u0026lt;?\u0026gt;[] getRootConfigClasses() { return new Class\u0026lt;?\u0026gt;[] { RootConfig.class }; // 返回null可能不是最佳实践 } @Override protected Class\u0026lt;?\u0026gt;[] getServletConfigClasses() { return new Class\u0026lt;?\u0026gt;[] { MyWebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { \u0026#34;/api/**\u0026#34; }; } } DispatcherServlet默认有若干内置的对象，重要的接口如下：\nHandlerMapping接口：将处理器对应的类映射到某个具体的URL上 不需要调用或实现它，它的默认实现是RequestMappingHandlerMapping，它会自动解析@RequestMapping、@GetMapping等注解映射 HandlerAdapter接口：(适配器)将处理器的不同方法统一为一个方法，使前端控制器不需要关注其细节 不需要调用或实现它，它的默认实现是ResquestMappingHandlerAdapter，它替代前端控制器来执行HandlerMapping找到的方法 HandlerExceptionResolver接口：将不同地方的各种异常映射到统一的异常处理器类上 不需要调用或实现它，它有若干实现类用于处理不同类别的异常源 ViewResolver接口：负责解析后端处理器返回的字符串，变为View对象，如JSP或Thymeleaf模板 不需要调用或实现它，它的默认实现是InternalResourceViewResolver 因为现代Web应用一般使用前后端分离，所以ViewResolver也不常用了 LocaleResolver接口：负责i18n 不需要调用或实现它，它有若干实现类 ThemeResolver接口：负责解析UI主题，即前端的工作 前端的工作一般不用Java来做，所以一般不需要使用它 MultipartResolver接口：负责解析multipart类型的数据 没有默认配置，需要自己配置，有现成的实现类 拦截链 就像Jakarta EE的Filter那样，DispatcherServlet在找到对应的处理器后，调用之前会先调用拦截器链\n拦截器需要实现HandlerInterceptor接口，包含三个方法，它们会作为回调函数被观察者调用\npreHandle(HttpServletRequest req, HttpServletResponse resp, Object handler)：调用处理器之前的拦截点，通常进行日志记录、身份验证 postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView modelAndView)：成功调用处理器之后，视图渲染之前的拦截点，通常用于添加视图的全局配置 afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex))：完成整个请求后的回调，通常用于资源关闭、性能统计等，就像finally块那样，即使之前的处理链有异常发生，也会调用该方法 注册拦截器：\n基于Java的配置：\njava// 需实现WebMvcConfigurer接口(全部都有default实现, 因此可按需实现) @Configuration @EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { private MyHandlerInterceptor interceptor; public WebMvcConfig(MyHandlerInterceptor interceptor) { this.interceptor = interceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { // 注册interceptor, 作用于/api/**, 忽略/api/noIntercept // 执行顺序为最高(0为最高优先级) // addInterceptor()返回InterceptorRegistration, 然后允许链式调用配置方法 registry.addInterceptor(interceptor) .addPathPatterns(\u0026#34;/api/**\u0026#34;) .exludePathPatterns(\u0026#34;/api/noIntercept\u0026#34;) .order(0); } } 基于XML配置：待补\nCORS配置：Spring MVC的默认CORS策略是同源策略，即默认情况下，不允许被不同域访问(包括localhost的不同端口)，浏览器确认的具体做法是先发送预请求获取对方的CORS配置，然后限定只能发送或不能发送特定的请求\n@CrossOrigin类/方法级注解就是用于配置CORS的，针对一个类，它会作用于该类的所有方法\norigin属性包括它允许的来源域 originPatterns属性包括它允许被获取的URI methods属性包括它允许的请求方法 allowedHeaders和exposedHeaders：允许/不允许请求所包含的请求头 maxAge：秒为单位的CORS配置有效时间 除了注解配置，还可以基于Java配置，仍然是WebMvcConfigurer的方法：\njava@Configuration @EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.allowedOrigins(\u0026#34;localhost\u0026#34;) .addMappings(\u0026#34;/api/**\u0026#34;) .allowedMethods(\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;) .allowedHeaders(HttpHeaders.LOCATION) .exposedHeaders(HttpHeaders.ACCEPT) .maxAge(3600); } } 模型与视图 Model接口 Model类似ServletRequest的属性在Servlet应用中的作用，用于管理单个请求范围内的由后端程序设置的属性 Model提供addAttribute(String, Object)和getAttribute(String, Object)和containsAttribute(String)三个常用的属性相关方法 它不像ServletRequest那样提供删除属性的方法，因为Model的生命周期较短，设计者认为不需要删除属性 Model实例通常不需要开发者自己创建，而是由Spring容器自动创建并注入为参数 ModelAndView类 ModelAndView类通常作为一个返回视图的方法的返回值，需要开发者自己创建，是Model结合返回值String的一个替代选择，它包揽了Model以及返回视图的功能，算是一种风格上的不同 即使在前后端结合的应用中，它也不常用，因为它不止返回视图，还返回其中添加的属性 它提供addObject(String, Object)、setViewName(String) 注解式声明 Controller声明 @ResponseBody类/方法级注解，注解类时表示该类的所有方法的返回值不应经过ViewResolver，而是作为响应体返回；注解方法时只表示该特定方法的返回值不经过ViewResolver @Controller类级注解：标识该类是传统的控制器类，通常作为页面控制器而存在，虽然返回值允许多个类型，但期望返回String，表示视图类型，然后ViewResolver会解析它为视图 @RestController类级注解：标识该类是现代的前后端分离的控制器类，通常作为数据接口，即API控制器而存在，期望返回ResponseEntity\u0026lt;T\u0026gt;或其它类型，表示数据 @RestController本质是@Controller加上@ResponseBody注解，后者表示该类的方法返回的是数据本身而不是一个视图，不应经过ViewResolver解析 @ControllerAdvice类级注解：表示该类中定义的ExceptionHandler方法会应用于全局的Controller类 @RestControllerAdvice类级注解：等价于@ControllerAdvice加上@ResponseBody注解 URL映射 @RequestMapping：通常作为类级注解，value表示映射到的URL路径，method表示请求方法\n针对方法，在@RequestMapping的基础上有若干的扩展注解，即@GetMapping、@PostMapping、@PutMapping等，它们规定了@RequestMapping的method属性，会继承来自类级@RequestMapping的映射路径，例如：\njava@Controller @RequestMapping(\u0026#34;/api\u0026#34;) public class ApiController { // 访问/api/obj时会重定向到此 @GetMapping(\u0026#34;/obj\u0026#34;) public String getObj() { return \u0026#34;index\u0026#34;; } } @RequestMapping除value(URL路径)以外，还有若干属性用于缩小一个方法对应的URL范围\nconsumes属性：指定该方法能处理的请求的数据类型，例如consumes={\u0026quot;application/json\u0026quot;}，它会自动解析Content-Type请求头 produces属性：指定该方法响应的数据类型，例如produces={\u0026quot;application/json\u0026quot;}，它会自动解析Accept请求头 params属性：限定该方法接受请求所带的参数，例如params={\u0026quot;a\u0026quot;, \u0026quot;!a\u0026quot;, \u0026quot;a=b\u0026quot;}，\u0026quot;a\u0026quot;表示检测请求是否带a参数，!a表示检测请求是否不带a参数，a=b表示检测请求里的a参数是否等于b headers属性：限定该方法接受请求所带的请求头，格式和params类似 除了*以及**通配符以外，Spring提供?以及{}捕获功能\n?匹配任意单个字符 {variable}：将该部分路径信息捕获，赋给variable模板参数，可以通过@PathVariable获取 {variable:regex}：仅将能成功匹配regex正则表达式的URL交给该方法处理，并将该部分路径赋给variable 因为在java中，使用regex与PCRE不同，例如\\需要\\\\转义 类型转换由Spring提供，需要目标参数的类型能通过单个String对象构造 允许自动注入的方法参数 虽然允许直接注入ServletRequest、ServletResponse等Servlet API，但通常没有必要 InputStream、Reader、OutputStream、Writer：从Servlet API获取的流对象 Model：获取当前请求的整个模型实例 Spring核心提供的类型转换十分强大，以下注解获取的数据只要能进行转换(包括json数据)，就能自动注入为各种非String类型的参数值 甚至可以自定义Converter并注册，以实现自定义转换的功能 @PathVariable：获取对应的从URL中捕获的参数 @MatrixVariable：获取从URL最后由;分割的名值对 例如/api/a;name1=value1,value2;name2=value3，不同名值对由;分割、同一名值对的多个值由,分割 @RequestParam(\u0026quot;paramName\u0026quot;)：获取对应的请求参数，若为POST则只能获取表单或multipart参数 @RequestHeader(\u0026quot;headerName\u0026quot;)：获取对应的请求头的值 @CookieValue(\u0026quot;cookieName\u0026quot;)：获取对应的Cookie值 @RequestBody：获取对应的请求体 @RequestPart：获取multipart请求体的一个part @ModelAttribute(\u0026quot;attr\u0026quot;)：获取模型中，属性名为attr的值 @SessionAttribute(\u0026quot;attr\u0026quot;)：获取当前会话的属性attr的值 @RequestAttribute(\u0026quot;attr\u0026quot;)：获取当前请求中存的属性attr的值 其它方法参数，若不带任意注解且不是上述的任意类型，若为简单类型则解析为@RequestParam，否则解析为@ModelAttribute 返回值类型 ResponseEntity\u0026lt;T\u0026gt;：完整的由开发者决定的响应体 调用一个指定了状态码的静态方法，创建一个ResponseEntity.BodyBuilder或ResponseEntity.HeadersBuilder创建者： status(HttpStatusCode)：指定任意状态码，创建一个响应报文 ok()：等价于status(200)，服务器成功返回网页 accept()：等价于status(202)，服务器接受但尚未处理 basRequest()：等价于status(400) notFound()：创建一个仅含响应头的响应报文Builder internalServerError()：等价于status(500) HeadersBuilder接口包含以下方法： build()：创建ResponseEntity header(String, String)以及headers(HttpHeaders)：添加响应头 BodyBuilder继承HeaderBuilder接口，此外包含以下方法： contentType(String)：设置响应体数据类型 contentLength(long)：设置响应体大小 body(T)：设置响应体，会自动由Converter序列化 void：方法参数必须包含输出流或ServletResponse，此时表示Spring认为请求在该方法内部完成了处理，返回值为void @ModelAttribute：它在修饰参数时表示获取，而在修饰方法时表示方法的返回值会添加到该请求的Model里而不是返回 String、View、ModelAndView：返回字符串表示的视图、自己创建的视图、绑定了一些属性的视图 异常处理 由@ExceptionHandler注解修饰的方法用于处理来自Controller类的异常，它不需要@RequestMapping 其接受的方法参数在大多数情况应该是某一个异常类型 其方法的返回值类型规则与一般的@RequestMapping方法一致 @ExceptionHandler允许设置一系列异常类的class属性，表示只会接受这些类型的异常 在@Controller类、@RestController类中定义的异常处理器只作用于其所在类的其它方法抛出的异常 在@ControllerAdvice类、@RestControllerAdvice类中定义的异常处理器作用于所有的Controller类 其它工具类 org.springframework.http.HttpStatus：包含一系列状态码的枚举 org.springframework.http.MediaType：包含一系列数据类型常量，包括可以用parseMediaType(String)将其中没有提供的数据类型显式转换为MediaType org.springframework.http.HttpHeaders：包含一系列请求头、响应头的常量以及设置方法 ","permalink":"https://echo.ishian.me/2025/07/12/spring-mvc/","summary":"讲解Spring MVC的前端控制器模式、DispatcherServlet配置与请求处理流程","title":"Spring framework: MVC"},{"content":"Jakarta EE Validation API 简介 Bean Validation有多套标准，最早是JSR 303的Bean Validation 1.0，现今流行的是Java EE 8的Bean Validation 2.0，即JSR 380标准\nBean Validation通过注解作用于属性字段上，在运行时进行验证\n需要说明的是，JPA规范的@Id、@UniqueConstraint等是在生成SQL时额外添加约束，这个约束是数据库层面上的，因此只有在保存、更新、删除时会检查值的有效性\n而Bean Validation作用于Java内存中，给标识符赋值的时候，例如@NotNull注解规定该标识符不能被赋予null，这种约束的检查是在JVM内部进行的，没有用到外部的软件\n添加Bean Validation，有助于在更早的时候发现问题，而对于没有问题的操作，Validation的开销并不大，对于Validation无法发现的问题，数据库的约束则是最后一层保障\n接口依赖：\nxml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;jakarta.validation\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jakarta.validation-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.0.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; hibernate实现(包含接口依赖)：\nxml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.hibernate\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;hibernate-validator\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.0.1.Final\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 可能使用到el表达式\n验证注解 验证的通用属性 message：验证失败时，将自动抛出ConstraintViolationException或MethodArgumentNotValidException异常，它们会包含该message\nmessage支持有限的EL表达式，默认会是{jakarta.validation.constraints.ConstrantName.message}这个EL表达式，是配置文件里设置的\ngroups：指定Class\u0026lt;?\u0026gt;[]，表示这个验证注解所属的分组类(可有多组)\n在验证时，使用validate(bean, Class...)，只会检验bean中属于Class的验证注解的字段\n默认为jakarta.validation.groups.Default，validate()若不指定组则默认检验属于Default组的验证\npayload：指定Class\u0026lt;? extends Payload\u0026gt;[]，定义payload后，发生异常时可从异常中通过getPayload()获取payload信息\n通常用于定义一些元数据，例如便于分类地处理异常，例如按严重程度、按业务逻辑\n@Xxx.List子注解：指定@Xxx(同类型验证注解)数组，便于对同一类注解但不同的验证条件赋予不同的message\n空值安全 只有以下注解会针对null，其它注解将始终视null为合法的 @NotNull/@Null：针对任意Object，不允许/必须为null @NotBlank：针对任意CharSequence，必须不为null且必须包含非空白字符，即不为空、不含Unicode空白字符(空格、制表符、换行符、回车符、换页符) @NotEmpty：针对任意CharSequence、Collection、Map、Array，必须不为null且不为空 数值检查 @AssertTrue/@AssertFalse：针对boolean或Boolean，必须为True/False\n@Min(value)/@Max(value)：针对BigDecimal，BigInteger，byte、short、int、long以及它们的包装类，检查其最小值/最大值，value值是long\ndouble和float因为取整误差，不被支持设置@Min和@Max\n@DecimalMin(value)/@DecimalMax(value)：类似@Min(value)/@Max(value)，但是value是String，会被转化为BigDecimal表示\n此外，额外含有一个inclusive布尔属性，表示是否包含边界\n@Negative/@NegativeOrZero/@Positive/@PositiveOrZero：针对BigDecimal，BigInteger，byte、short、int、long、float、double以及它们的包装类，检查其是否为负/非正/正/非负数，因为只需要判零以及符号，所以支持float和double\n@Digits(integer, fraction)：针对BigDecimal，BigInteger，byte、short、int、long以及它们的包装类，以及任意的CharSequence，检查整数位数\u0026lt;= integer，小数位数\u0026lt;= fraction\n@Size(min = 0, max = Integer.MAX_VALUE)：针对任意CharSequence、Collection、Map、Array，其元素个数必须在[min, max]内，允许为null\n特殊类型 @Pattern(regexp, flags = {})：针对任意CharSequence，检查其是否符合java.util.regex.Pattern的检查\nregexp与java.util.regex.Pattern的规则一致，flags则是@Flag[]，表示检查模式，与java.util.regex.Pattern内部定义的标志位一致，以注解形式成为元数据的一部分\n@Email(regexp = \u0026quot;.*\u0026quot;, flags = {})：提供一个默认的邮箱正则表达式规则，可以使用自定义的regexp覆盖原有表达式\n@URL(protocal=\u0026quot;\u0026quot;, host=\u0026quot;\u0026quot;, port=-1, regexp=\u0026quot;.*\u0026quot;, flags={})：针对任意CharSequence，检查其是否符合java.net.URL的构造方法的验证，可自定义协议名称、主机名称、端口号，以及可以覆盖@Pattern提供的regexp和flags来自定义匹配规则\n@Future/@FutureOrPresent/@Past/@PastOrPresent：针对任意java.time以及java.time.chrono的日期/时间类对象，检查是否是未来/未来或现在/过去/过去或现在的时间，Now这个对象基于JVM以及现在的时区来获取，具体到时间\n这里的Present的含义会因为针对的类型而变化，例如针对Year对象，Present视整一年为合法的“现在”，而不需要完全和Now一致\n@Valid：空注解，针对任意类型的对象，可以注解一个方法、一个方法的参数、一个类的属性，表示对返回值、参数值、属性值进行级联验证，即检查到这个对象时，会同时检查该对象的所有属性，但只会级联一层，不会深入到属性的属性(除非又有@Valid)，例如@Valid List\u0026lt;@Valid ElementType\u0026gt;，这样才会级联到ElementType的属性\n验证器 所有注解在针对方法参数以及方法时，必须依赖Spring的IoC容器或其它CDI框架的实现，才是有用的(能自动校验)\n否则，在一般的Hibernate或其它实现中，必须依靠验证器，由开发者在代码中显式地校验\nValidator接口：\nvalidate(T obj, Class\u0026lt;?\u0026gt;... groups)：验证obj的所有属性 validateProperty(T obj, String pName, Class\u0026lt;?\u0026gt;...)：验证其特定属性 validateValue(T obj, String pName, Object value, Class\u0026lt;?\u0026gt;...)：验证value是否符合其某个字段的约束 ConstraintViolation\u0026lt;T\u0026gt;接口：所有上述验证方法的返回值是Set\u0026lt;ConstraintViolation\u0026gt;，表示出现冲突的约束信息\ngetMessage()：获取错误信息 getRootBean()：获取根对象 getLeafBean()：获取出错的对象 getConstraintDescriptor()：获取约束描述符 ConstraintDescriptor：约束描述符\ngetPayload()：获取payload Hibernate实现下的扩展 ","permalink":"https://echo.ishian.me/2025/07/11/java-bean-validation/","summary":"介绍 Jakarta Bean Validation 的验证注解、验证器与 Hibernate 扩展","title":"Java EE: Bean Validation"},{"content":"Jakarta Persistence API 介绍 JPA是一套API规范，从EJB中分离出来，是持久层的流行API\n单纯引入JPA是没用的，其著名实现有Hibernate等，\nSpring Data JPA不是JPA的实现，而是基于JPA的一套Repository接口\nJPA定义了一套ORM框架，使POJO得以和关系联系起来\n接口依赖：\nxml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;jakarta.persistence\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jakarta.persistence-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.2.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; hibernate实现依赖：\nxml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.hibernate\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;hibernate-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;6.1.4.Final\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 核心组件 Persistence Persistence用于获取EntityManagerFactory 配置： EntityManager与EntityManagerFactory EntityManagerFactory全局单例且线程安全，用于获取EntityManager EntityManager接口管理对象的持久化操作，定义了四种实体状态： Transient：新建状态 Managed：托管状态 Detached：分离状态 Removed：移除状态 EntityManager实例包含以下方法： persist(T)：准备保存实体，会生成实体的主键，但可能直到flush才保存 find(Class\u0026lt;T\u0026gt;, Object pk)：立即加载获取指定主键(第二个参数)的对象 getReference(Class\u0026lt;T\u0026gt;, Object pk)：获取一个实体代理，只有在访问时才查询 merge(T)：合并分离出去的实体，返回托管实体(传进去的实体仍是分离状态) remove(T)：删除实体 EntityTransaction 用于事务管理 基于注解的ORM配置 单表注解 @Entity注解一个POJO类是实体类\n@Table注解一个实体类所对应的关系，若不使用，则@Entity注解类的默认表名为类名的全大写\n在该注解内定义约束等类似SQL中在属性外的表内定义约束\nname：指定表名 schema：指定模式 catalog：指定表所属的目录 uniqueConstraints：指定表的唯一性约束 indexes：指定表的索引 check：指定表的检查约束 @UniqueConstraint注解表示一个唯一性约束，在@Table的uniqueConstraints中起作用\nname：约束名 columns：受约束的属性组 @Index注解表示一个索引，在@Table的indexes中起作用\nname：索引名 columnList：属性组，是一个字符串，不同属性由,连接 @CheckConstraint注解表示一个检查约束，在@Table的check中起作用\nname：约束名 constraint：一个本地SQL约束 @Id注解作用于属性，标明一个主键\n@GeneratedValue：作用于被@Id注解的属性，标明如何初始化主键值，strategy属性有以下选择\nGenerationType.AUTO(默认)策略：根据具体的数据库，自适应策略 GenerationType.IDENTITY(常用)策略：依赖数据库的AUTO_INCREMENT(或其他)自增 GenerationType.SEQUENCE(可能高性能)策略：依赖数据库的序列，需要配置@GeneratedValue的generator属性 GenerationType.UUID(特殊场景)策略：依赖UUID生成器 定义@GeneratedValue后，允许使用不带主键参数的构造方法\n@Transient注解作用于属性，标明这个属性永远不会被持久化\n@Column注解作用于属性，是数据库的列映射\nname：指定属性映射的列名，默认为属性名 unique：布尔值，标识是否唯一 nullable：布尔值，标识是否允许为null columnDefinition：指定本地SQL为该列的定义，而不是使用JPA生成 length：指定长度 check：同@Table的check @Version：表示该对象应使用乐观锁来查询/更新，若在事务中发现冲突则会抛出OptimisticLockException\n属性组主键注解 大部分约束，例如唯一性约束、检查约束、非空约束，由之前介绍的注解可以覆盖大部分场景(包括属性组的约束)\n但依靠上述注解无法使一个属性组作为主键\n@Embeddable注解标明一个类是可嵌入的\n该类的所有属性不需要@Column注解，且该类不需要@Table以及@Entity注解，应该作为一个普通的POJO，只是多了一个@Embeddable\n为了方便作为主键，需要覆写equals()与hashCode()方法，且实现Serializable空接口\n@EmbeddedId注解标明一个属性是主键，且类型是一个@Embeddable类，可用于属性组为主键的场景\n默认不支持AUTO、IDENTITY的策略生成嵌入主键，可以使用SEQUENCE策略\n@Embedded注解标明一个属性的类型是一个@Embeddable类\nJPA转化为DDL语句时，会检测所有的Embedded属性，将该类的每个属性就像一般属性那样写成SQL\n默认情况下，@Embeddable类中的所有属性对应的列名就是属性名，就像没有添加@Column注解的一般属性那样\n在作为实体类的属性时，建议用@AttributeOverride注解清晰地决定映射：\njava@Entity @Table(name = \u0026#34;embed_demo\u0026#34;, schema = \u0026#34;demo\u0026#34;) public class EmbedEntity { @Embeddable public static class EmbedId implements Serializable { private Long uid; private String username; // 重写 equals() hashCode() ... } @EmbeddedId @AttributeOverrides({ @AttributeOverride(name = \u0026#34;uid\u0026#34;, column = @Column(name = \u0026#34;uid\u0026#34; /** 其它约束 */ )), @AttributeOverride(name = \u0026#34;username\u0026#34;, column = @Column(name = \u0026#34;username\u0026#34;)) }) private EmbedId id; @Embedded @AttributeOverrides({ @AttributeOverride(name = \u0026#34;uid\u0026#34;, column = @Column(name = \u0026#34;parent_uid\u0026#34; /** 其它约束 */ )), @AttributeOverride(name = \u0026#34;username\u0026#34;, column = @Column(name = \u0026#34;parent_username\u0026#34;)) }) private EmbedId pid; } 多表关联注解 @JoinColumns注解包含多个@JoinColumn，表示一个属性有多个外键依赖\n@JoinColumn注解表示一个属性是外键，这样的属性其类型应是一个@Entity类(或是一个列表包含@Entity类)而不应该是引用的属性的类型，JPA会将这个实体类视为外表，在其中查找外键\nname：该属性在本表的列名 referencedColumnName：该属性所引用的其它表的列名 foreignKey：具体的外键细节 unique：一般来说，外键是和其它表的主键相关联的，因此不需要设置unique属性，只需要知道遇到其它情况时记得设置unique=true即可 @ForeignKey注解在@JoinColumn的foreignKey中起作用\nname：外键约束名 foreignKeyDefinition：使用本地DDL定义外键 @JoinTable是用于生成中间表的注解，通常配合@ManyToMany一起使用，它包括@Table的所有属性，同时多出若干属性用于拆解多对多关系\njoinColumns：指向己方实体的外键列，由若干@JoinColumn组成@JoinColumn里的name定义中间表的列名，referencedColumnName指向己方表的列名 inverseJoinColumns：指向对方实体的外键列，类似joinColumns，只不过referencedColumnName指向对方表的列名 foreignKey与inverseForeignKey：类似@JoinColumn的foreignKey @MapsId、@PrimaryKeyJoinColumns、@PrimaryKeyJoinColumn：\n有时，一个实体的外键不仅是被引用表的主键，而且是引用表的主键(例如为了满足范式要求而拆表)，即它们共享主键\n@MapsId支持使用共享主键，value属性默认是被引用表的主键列名，使用@MapsId后，本实体类的@Id属性自动关联到被引用表的主键上，因此不需要@GeneratedValue注解也能使用不含主键参数的构造方法\n我们知道@JoinColumn等注解是会体现在JPA生成的DDL中的，但在共享主键的场景下，@Id属性就是外键，此时如果希望通过对象引用导航到主实体的实例上，就可以使用@PrimaryKeyJoinColumn和@PrimaryKeyJoinColumns注解，它们和一般的@JoinColumn注解的唯一区别是：DDL不会带有多余的外键列\n数据库的映射基数有四种：一对一、一对多、多对一、多对多，其中只有多对多需要使用中间表，其它在由ER图转化为关系模式时都可变作“多”那方的外键引用“一”那方的主键\n在JPA中，有点反直觉的是，外键是整个被引用实体的引用而不是这个实体的主键类型\n特别是在双向关联的场景下，如果仅靠@JoinColumn或@JoinTable注解，JPA将只知道有关联，但不知道返回值的数量，无法使JPA知道对方是多方还是一方，己方是多方还是一方，那么对象导航(绑定)就无法进展下去(无法知道返回的是List\u0026lt;T\u0026gt;还是单纯作为一个实体类来解析)\n综上，联系类型的注解是很有必要的，且多对多@ManyToMany需要配合@JoinTable，而@ManyToOne、@OneToOne需要配合@JoinColumn，@OneToMany通常不和任何列映射注解配合使用\n@OneToOne注解：表示一对一注解\n两个@OneToOne可配对实现双向关联，其中一方为@JoinColumn+@OneToOne(optional=...)、另一方为@OneToOne(mappedBy=...)\ncascade：设置级联的操作\nfetch：设置级联操作的时机\noptional：设置参与度约束，默认为true，即允许为null\n在数据库中，设置参与度约束是通过将外键设置为NOT NULL限制的\norphanRemoval：设置关系断开的级联操作，即在主控方断开关联后后是否级联地删除关系另一方的相应记录\nmappedBy：通常用于双向关联的场景，主控方若想要维护一个反向的对象导航，不需要(也不应该)像外键持有方那样使用@JoinColumn，而是使用mappedBy属性\n在这种场景下，反向导航不会被数据库存储，即这个属性不是一个@Column或@JoinColumn属性，而是一个仅由JPA根据mappedBy获取的反向引用\nmappedBy属性的值是对方实体的属性名，而不是对方表的列名\n@OneToOne注解包含所有映射基数注解的所有属性，因为外键方可以是任意一方，所以有optional属性，又因为被引用的那方可以是任意一方，所以有mappedBy属性\n在一对多或多对一的映射基数上，多方一般是持有外键的那方，所以@OneToMany只有mappedBy(因为一方不持有外键)、@ManyToOne只有optional(因为多方持有外键，外键可选)\n此外，@OneToMany注解属性的类型应该是一个集合，例如List\n@OneToMany(mappedBy=...)和@JoinColumn+@ManyToOne(optional=...)配对实现双向关联\n@ManyToMany是一个特殊的映射基数，它只有mappedBy属性，因为在数据库中，多对多的映射基数需要借助中间表，这个中间表持有两个外键，从而将@ManyToMany拆成两个@OneToMany\n单向关联：如果不使用@JoinTable也会加上一个默认的@JoinTable，所以建议显式定义@JoinTable便于管理\n双向关联：像@OneToOne那样，需要一方使用mappedBy属性，避免生成两个中间表\n即@JoinTable+@ManyToMany和@ManyToManay(mappedBy=...)配对实现多对多的双向关联\nCascadeType：作用于映射基数注解里的cascade属性，JPA在实体中的一大优点是实体含有整个的引用而不只是其它实体的主键，因此也可以实现对实体进行持久化操作的同时对其含有的外键进行相同的操作\nPERSIST：主体保存时，同时保存其关联的实体\nMERGE：主体更新时，同时更新其关联的实体\nREMOVE：主体删除时，同时删除其关联的实体\norphanRemoval在关系断开时就会级联删除(包括将关联属性替换、设null)，而REMOVE策略只是在主体删除时才进行级联删除\nREFRESH：主体刷新时，同时获取其关联的实体\nDETACH：实例被分离出持久层管理时，同时将其关联的实例也分离出去\nALL：包括以上所有策略\nFetchType：作用于映射基数注解里的fetch属性，表示加载的策略\nLAZY：懒加载，只在真正访问时加载\nLAZY是一对多、多对多注解的默认值，因为是List通常需要很长的时间加载，许多时候是没必要的\nEAGER：急加载，查询到主实体后立刻加载该引用\nEAGER是一对一、多对一注解的默认值，因为仅加载一个关联实体的实例并不花多久\n继承注解 例子 自关联的例子：\njava@Entity @Table(\u0026#34;employee\u0026#34;) public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = \u0026#34;eid\u0026#34;) private Integer eid; @JoinColumn(name = \u0026#34;manager\u0026#34;, referencedColumnName = \u0026#34;eid\u0026#34;, foreignKey = @ForeignKey(name = \u0026#34;fk_EmpManager_EmpEid\u0026#34;)) @ManyToOne(optional = true) private Employee manager; @OneToMany(mappedBy = \u0026#34;manager\u0026#34;) private List\u0026lt;Employee\u0026gt; emps; } 共享主键例子：\njava@Entity @Table(\u0026#34;user\u0026#34;) public class User { @Id @Column(name = \u0026#34;uid\u0026#34;) private Long uid; @Column(name = \u0026#34;username\u0026#34;, nullable = false, length = 50) private String username; @OneToOne(mappedBy = \u0026#34;user\u0026#34;) private UserDetail userDetail; } @Entity @Table(\u0026#34;user_detail\u0026#34;) public class UserDetail { @Id @Column(name = \u0026#34;uid\u0026#34;) private Long uid; @PrimaryKeyJoinColumn(referencedColumnName = \u0026#34;uid\u0026#34;) @MapsId @OneToOne(optional = false) private User user; // 其它信息 @Column(name = \u0026#34;password\u0026#34;, check = @CheckConstraint(\u0026#34;password ~ ^[0-9a-zA-Z]{8,20}$\u0026#34;)) private String password; } JPQL 参数化查询 为了防止SQL注入，参数化查询提供了类型检查，避免了SQL语句的拼接 命名参数：使用方法中形参的名字引用值，如:paramName表示取paramName这个形参的值 位置参数：使用1-idx的位置来索引形参，如?1表示取第一个参数的值 查询细节 JPQL(Java Persistence Query Language)，是JPA规范提供的查询语言，它和SQL的语法类似，但是操作对象是实体类，而不是关系\n也因此，JPQL是编译时检查的，类型检查能防止很多查询错误\n由于有映射基数的注解，在FROM子句使用JOIN时，可以不用ON关键字，除非需要复杂的查询条件\n推荐使用面向对象的思想编写JPQL，即要多表查询时，使用A JOIN A.B而不是A JOIN B，用.运算符使用对象导航\n对于列表类型，允许使用[0-idx]访问其中的实体实例\n一对多的关系(列表类型)的懒加载会导致N+1查询问题，如果大部分场景需要懒加载，但在特定场景需要获取多方的所有实体时，可以使用JOIN FETCH，能优化为两次查询\n多态查询：\n在不使用Spring Data JPA的场景下，查询结果无法自动转化成返回类型的对象，需要使用NEW ObjClass(...)\n面向对象的便捷函数 TYPE(obj)：获取其类型 TREAT(obj AS Type)：向下转型 SIZE(collection)：获取集合的大小，是COUNT()加子查询的便捷版 obj MEMBER OF collection：类似IN collection IS EMPTY：等价于SIZE(collection) = 0 Criteria API JPA生命周期 ","permalink":"https://echo.ishian.me/2025/07/09/java-jpa/","summary":"介绍 Jakarta Persistence API 的核心组件与基于注解的 ORM 配置","title":"Java EE: JPA"},{"content":"激活函数 基本概念 激活函数是神经网络中十分重要的一部分，用于对线性叠加后的输出添加非线性因素，增大模型容量，实现对非线性问题的拟合 通常激活函数会限制输出的值域，控制均值 如果一个结点的激活值为零(或接近零)，则它对下一层所有结点的贡献均为零(或很小)，形象地称该神经元死亡 激活函数应该满足：非线性、连续可导、部署在隐藏层和输出层中 常见激活函数 Sigmoid(又称Logistic)： σ(x)=11+e−x、σ′(x)=e−x(1+e−x)2=σ(x)(1−σ(x))\\begin{align}\\sigma(x)=\\frac1{1+e^{-x}}、\\sigma\u0026#x27;(x)=\\frac{e^{-x}}{(1+e^{-x})^2}=\\sigma(x)(1-\\sigma(x))\\end{align}σ(x)=1+e−x1​、σ′(x)=(1+e−x)2e−x​=σ(x)(1−σ(x))​​ 定义域为(−∞,+∞)(-\\infty,+\\infty)(−∞,+∞)，值域为(0,1)(0,1)(0,1)，是一种二分类函数 性质：严格单调递增、输出范围有界且狭小 缺点： 输入的绝对值较大时，输出达到饱和，对输入变化不敏感，容易丢失这部分的梯度信息 输出恒大于零，意味着均值不为零，将导致后续神经元的输入也是非零均值的 Tanh(双曲正切)： tanh⁡(x)=ex−e−xex+e−x=2σ(x)−1\\begin{align}\\tanh(x)=\\frac{e^x-e^{-x}}{e^x+e^{-x}}=2\\sigma(x)-1\\end{align}tanh(x)=ex+e−xex−e−x​=2σ(x)−1​​ 定义域为(−∞,+∞)(-\\infty,+\\infty)(−∞,+∞)，值域为(−1,1)(-1,1)(−1,1) 解决了Sigmoid函数非零均值的问题 ReLU(Rectified linear unit，整流线性单元)：f(x)=max⁡(0,x)、f′(x)={1,x\u0026gt;00,x≤0f(x)=\\max(0,x)、f\u0026#x27;(x)=\\begin{cases}1,\u0026amp;x\u0026gt;0\\\\0,\u0026amp;x\\le0\\end{cases}f(x)=max(0,x)、f′(x)={1,0,​x\u0026gt;0x≤0​ 是最常用的激活函数 性质：计算简单、求导简单、正输入的输出不会饱和 缺点： 非零均值 Dead ReLU(神经元坏死)：负输入的输出为零，容易导致死一大片神经元 Leaky ReLU： f(x)=max⁡(αx,x),α为十分小的固定正数\\begin{align}\u0026amp;f(x)=\\max(\\alpha x,x),\u0026amp;\\alpha\\text{为十分小的固定正数}\\end{align}​f(x)=max(αx,x),​α为十分小的固定正数​​ PReLU： f(x)=max⁡(αx,x),α为十分小的正数,可在学习过程中改变\\begin{align}\u0026amp;f(x)=\\max(\\alpha x,x),\u0026amp;\\alpha\\text{为十分小的正数,可在学习过程中改变}\\end{align}​f(x)=max(αx,x),​α为十分小的正数,可在学习过程中改变​​ 负输入的输出将向下渗漏，不恒为零 前者的参数是固定的，使函数近似线性，后者的α\\alphaα则为超参数 ELU：f(x)={x,x\u0026gt;0α(ex−1),x≤0f(x)=\\begin{cases}x,\u0026amp;x\u0026gt;0\\\\\\alpha(e^x-1),\u0026amp;x\\le0\\end{cases}f(x)={x,α(ex−1),​x\u0026gt;0x≤0​ Softmax： ","permalink":"https://echo.ishian.me/2025/07/09/deeplearning-activefuncs/","summary":"讲解激活函数的作用与要求，介绍Sigmoid、Tanh、ReLU及其变体等常见激活函数","title":"机器学习: 激活函数"},{"content":"Tomcat 依赖 xml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.tomcat.embed\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;tomcat-embed-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;11.0.10\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 若用到jsp技术 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.tomcat.embed\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;tomcat-embed-jasper\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;11.0.10\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt;结构介绍 Tomcat是Apache开发的开源Servlet容器\n其中，WEB-INF目录是受保护的，客户端无法访问这个目录下的资源，保存应用的信息\n这个目录下面还有classes/、lib/以及web.xml\nclasses/存放Java程序的编译结果、lib/存放一系列第三方jar包\n其余应是一系列静态资源，例如js/、css/、views/等常见的命名\n使用maven管理项目的情况下，lib/不需要手动寻找、配置\n传统部署：指Tomcat服务器包含开发者开发的.war包这样的Web应用，Servlet应用开发好后打包成.war包并复制到Tomcat服务器中，然后启动服务器\n传统部署中，classes/与lib/由Tomcat解包.war文件得到\n内嵌tomcat：指Tomcat API作为java程序的一部分而存在，若使用maven的情况下调用Tomcat实例的addWebapp()方法，实例会从指定的路径中找到WEB-INF/web.xml文件并解析，然后生成classes/与lib/等在target/下\nweb.xml映射 这是一种比较传统的映射方式，仅了解即可\nweb.xml应位于webapp/WEB-INF目录下，其中webapp为自定义的网页应用的名字，web.xml里的所有的配置会覆盖注解形式的映射\n声明为webapp：\nxml\u0026lt;web-app xmlns=\u0026#34;http://xmlns.jcp.org/xml/ns/javaee\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\u0026#34; id=\u0026#34;WebApp_ID\u0026#34; version=\u0026#34;4.0\u0026#34;\u0026gt; \u0026lt;/web-app\u0026gt; 声明一个Servlet：\nxml\u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;Servlet_name\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;类路径\u0026lt;/servlet-class\u0026gt; \u0026lt;/servlet\u0026gt; 将Servlet映射到某个路径上：\nxml\u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;Servlet_name\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/路径\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; 初始化某个Servlet或某个Filter的参数(ServletConfig)：\nxml\u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;param_name\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;xml文件、字符串等\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; 设置ServletContext的配置：\nxml\u0026lt;context-param\u0026gt; \u0026lt;param-name\u0026gt;name\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;value\u0026lt;/param-value\u0026gt; \u0026lt;/context-param\u0026gt; 设置欢迎文件：\nxml\u0026lt;welcome-file-list\u0026gt; \u0026lt;welcome-file\u0026gt;index.html\u0026lt;/welcome-file\u0026gt; \u0026lt;/welcome-file-list\u0026gt; 设置session的生效时间\nxml\u0026lt;session-config\u0026gt; \u0026lt;session-timeout\u0026gt;30\u0026lt;/session-timeout\u0026gt; \u0026lt;/session-config\u0026gt; 注解映射 @WebServlet注解：在Servlet 3.0前，需要在web.xml中配置大量信息，在WebServlet注解出现后，所有的配置被分散到各个服务类上，最重要且必要的属性是urlPatterns，是String集合，表示这些URI均映射到这个Servlet类上\n在传统部署时，它们的解析是全自动的\n内嵌Tomcat 内嵌部署Tomcat显得更加灵活，由开发者完全控制Tomcat的配置，但因此注解映射、静态资源需要由开发者亲自解析、引入，因此在不借用Spring boot框架的情况下，直接配置web.xml并调用addWebapp()或手动通过Tomcat对象注册服务以及配置映射是更常用的\n可以理解为：通过web.xml配置的底层实现就是让addWebapp()解析该xml文件，并间接地在内部调用addServlet()、addMapping()等方法\n由new Tomcat()直接构造一个Tomcat实例，调用start()启动该服务器(会抛出LifecycleException受检异常)，调用getServer().await()使该服务器不断地等待请求\nTomcat实例方法：\nsetHostname(String)：设置服务器地址\nsetPort(int)：设置服务器端口\nsetBaseDir(String)：设置基准目录，表示Tomcat实例工作时，临时文件等存放的根目录，这里的临时文件不是classes/与lib/等，而是运行时生成的文件，例如jsp翻译后生成的文件\n推荐设置为target/下的某个目录\ngetConnector()：获取其绑定的连接器，这个方法在首次调用时会进行一系列的初始化操作，包括应用在此前设置的baseDir等参数\n这个方法应尽量晚些调用\nContext addWebapp(String contextPath, String docBase)：添加Web应用并获取它的上下文实例\ncontextPath指上下文的根所映射的URL路径，例如传入\u0026quot;\u0026quot;则该上下文会映射到\u0026quot;/\u0026quot;\ndocBase指要引入的资源的本地路径，提供的路径下必须含有WEB-INF目录及web.xml文件\n等价于将整个Web应用加载到服务器上，使用addWebapp()会自动生成并注册一个默认的Servlet服务，当客户端访问那些在用户自定义Servlet服务中找不到的服务时，会转向这个默认服务\n默认服务会试图在docBase下的静态资源中查询是否有对应的资源，若找到则返回，否则返回404响应\nContext addContext(String contextPath, String docBase)：添加一个空白的上下文实例，参数意义同上\naddServlet(String contextPath, String servletName, Servlet serv)：登记一个名为servletName的微服务，对应到路径为contextPath的上下文\n有静态方法的版本，其中contextPath应替换为Context对象\n同时需要使用Context实例的addServletMappingDecoded(String pattern, String name)将微服务映射到URL的某个路径上\n这两步等价于在web.xml中声明Servlet服务和映射\n该方法返回的Wrapper对象可链式调用addMapping(String pattern)，它等价于使用Context实例的addServletMappingDecoded()方法\n上下文类Context抽象的是整个Web容器，和ServletContext不同，一般作用于容器启动前，而后者作用于Web应用运行中处理业务，Context常用的实例方法有：\naddWelcomeFile(String)：设置当客户端访问根路径时，返回的资源文件\n即将String指向的资源文件映射到\u0026quot;/\u0026quot;\naddServletMappingDecoded(String pattern, String name)：将微服务映射到某个URL上\naddFilterDef(FilterDef)：登记一个Filter，由FilterDef封装其信息\nFilterDef常用方法：setFilter(Filter)、setFilterName(String)、addInitParameter(String, Object)\n分别为登记Filter对象、登记Filter服务名、初始化Filter参数\naddFilterMap(FilterMap)：登记从Filter到URL的映射，由FilterMap封装映射信息\nFilterMap常用方法：addURLPatternDecoded(String)、addServletName(String)、setFilterName(String)\n分别为登记其应用的URL、登记其应用的Servlet、设置其绑定的Filter服务名\n例子 基本配置：\njavaTomcat tomcat = new Tomcat(); tomcat.setHostname(\u0026#34;localhost\u0026#34;); tomcat.setPort(8080); tomcat.setBaseDir(\u0026#34;/target/tomcat/\u0026#34;); // // ...其它配置... // tomcat.getConnector(); tomcat.init(); tomcat.start(); tomcat.getServer().await(); Servlet等配置：\njavaString contextPath = \u0026#34;/api\u0026#34;; // String docBase = new File(\u0026#34;/src/main/webapp\u0026#34;).getAbsolutePath(); String docBase = Path.of(\u0026#34;/src/main/webapp\u0026#34;).toAbsolutePath().toString(); Context context = tomcat.addContext(contextPath, docBase); String servletName = \u0026#34;ServletDemo\u0026#34;; tomcat.addServlet(contextPath, servletName, new ServletDemo()).addMapping(\u0026#34;/*\u0026#34;); // context.addServletMappingDecoded(\u0026#34;/*\u0026#34;, servletName); FilterDef filterDef = new FilterDef(); filterDef.setFilter((req, resp, chain) -\u0026gt; { // 过滤 ... // 链式处理 chain.doFilter(req, resp); }); filterDef.setFilterName(\u0026#34;FilterDemo\u0026#34;); FilterMap filterMap = new FilterMap(); filterMap.addURLPatternDecoded(\u0026#34;/*\u0026#34;); filterMap.addServletName(servletName); filterMap.setFilterName(\u0026#34;FilterDemo\u0026#34;); context.addFilterDef(filterDef); context.addFilterMap(filterMap); 上述配置与以下配置是近乎等价的，只是以下配置会有多出一个默认的Servlet服务，用于检索静态资源\njavatomcat.addWebapp(\u0026#34;/api\u0026#34;, \u0026#34;src/main/webapp/\u0026#34;);xml\u0026lt;!-- web.xml --\u0026gt; \u0026lt;web-app xmlns=\u0026#34;http://xmlns.jcp.org/xml/ns/javaee\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd\u0026#34; id=\u0026#34;WebApp_ID\u0026#34; version=\u0026#34;4.0\u0026#34;\u0026gt; \u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;ServletDemo\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;demo.servlet.ServletDemo\u0026lt;/servlet-class\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;ServletDemo\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/*\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; \u0026lt;filter\u0026gt; \u0026lt;filter-name\u0026gt;FilterDemo\u0026lt;/filter-name\u0026gt; \u0026lt;filter-class\u0026gt;无法用lambda表达式声明的接口实现\u0026lt;/filter-class\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter-mapping\u0026gt; \u0026lt;filter-name\u0026gt;FilterDemo\u0026lt;/filter-name\u0026gt; \u0026lt;!-- 针对/*这个URL路径设置Filter --\u0026gt; \u0026lt;url-pattern\u0026gt;/*\u0026lt;/url-pattern\u0026gt; \u0026lt;!-- 针对ServletDemo这个服务单独设置Filter --\u0026gt; \u0026lt;servlet-name\u0026gt;ServletDemo\u0026lt;/servlet-name\u0026gt; \u0026lt;/filter-mapping\u0026gt; \u0026lt;/web-app\u0026gt; ","permalink":"https://echo.ishian.me/2025/07/08/java-tomcat/","summary":"介绍 Tomcat 结构、web.xml 与注解映射及内嵌 Tomcat 的用法","title":"Java EE: Tomcat"},{"content":"","permalink":"https://echo.ishian.me/2025/07/06/spring-data-jpa/","summary":"介绍Spring Data JPA的用法","title":"Spring Data: JPA"},{"content":"损失函数及正则化 损失函数概念及约定 损失：表示模型预测值与真实值之间差距的实数，越接近零表示误差越小\n代价：是整个数据集上所有样本的损失的平均值，即损失的期望，但事实上代价通常也被称作损失，大多数教材并不明确地区分它们\n实际训练中，求和并除以nnn使损失变为代价，使其可比较样本数量不同的数据集，并根据整个样本集的代价进行更优秀的调整\n损失函数：输入为模型的预测值，输出为损失的一种函数\n在一次训练以后，模型会通过损失函数估计出损失，并通过某种提前设定好的优化方法调整参数(如权重、偏置等)\n无论使用的优化方法是否为梯度下降法，损失函数都是机器学习算法中很重要的一部分\n约定yyy为真实值、y^\\hat yy^​为模型的预测值\n一个损失函数适用于某种问题总是可以由最大似然估计推导\n回归任务 回归任务：输出为一系列确切的数值，目标是预测某个连续型变量的分布\n均方误差(Mean Squared Error，MSE)，又称L2 Loss：\nLMSE=1n∑i=1n(yi−y^i)2\\begin{align}L_{MSE}=\\frac1n\\sum_{i=1}^n(y_i-\\hat y_i)^2\\end{align}LMSE​=n1​i=1∑n​(yi​−y^​i​)2​​ 将真实值和预测值的差表示出来：z=y−y^z=y-\\hat yz=y−y^​，且假设误差z∼N(0,σ2)z\\sim N(0,\\sigma^2)z∼N(0,σ2)\n则真实值在已知输入的条件下，y ∣ X∼N(y^,σ2)y\\ |\\ X\\sim N(\\hat y,\\sigma^2)y ∣ X∼N(y^​,σ2)\n根据极大似然估计法，多条样本的极大似然函数为\nL=∏i=1n12πσ2e−(yi−y^i)22σ2\\begin{align}L=\\prod_{i=1}^n\\frac1{\\sqrt{2\\pi\\sigma^2}}e^{\\Large-\\frac{(y_i-\\hat y_i)^2}{2\\sigma^2}}\\end{align}L=i=1∏n​2πσ2​1​e−2σ2(yi​−y^​i​)2​​​取对数后为\n−n2ln⁡(2πσ2)−12σ2∑i=1n(yi−y^i)2\\begin{align}-\\frac n2\\ln(2\\pi\\sigma^2)-\\frac1{2\\sigma^2}\\sum_{i=1}^n(y_i-\\hat y_i)^2\\end{align}−2n​ln(2πσ2)−2σ21​i=1∑n​(yi​−y^​i​)2​​，而极大化似然函数等价于极小化均方误差\n均方误差的性质：连续光滑、处处可导，是凸函数可最小化，梯度简单且稳定(与误差线性相关)，因此能够快速收敛但对离群点较敏感\n平均绝对误差(Mean Absolute Error，MAE)，又称L1 Loss：\nLMSE=1n∑i=1n∣yi−y^i∣\\begin{align}L_{MSE}=\\frac1n\\sum_{i=1}^n|y_i-\\hat y_i|\\end{align}LMSE​=n1​i=1∑n​∣yi​−y^​i​∣​​ 平均绝对误差的性质：连续、仅在y=0y=0y=0处不可导，所有点的梯度相同，因此收敛速度较稳定，对离群点具有更强的鲁棒性 均方根误差(Root MSE，RMSE)：LRMSE=LMSEL_{RMSE}=\\sqrt{L_{MSE}}LRMSE​=LMSE​​\n分类任务 分类任务：输出为一系列离散的值(表示不同的类别，可编号)，目标是预测某个离散型变量的分布\n二分类任务：将输入分为两个类别，一个输入仅属于一个类别\n真实值标签为非零即一的值\n模型输出一个(0,1)(0,1)(0,1)的实数，若大于一定程度则认为属于正类，否则属于反类\n单标签多分类任务：将输入分为kkk个类别，一个输入仅属于一个类别\n真实值标签为一次性向量(one-hot向量)，仅有一个分量为111，其余均为000\n模型输出多个(0,1)(0,1)(0,1)的实数，取最大者为预测的类别\n多标签多分类任务：将输入分为kkk个类别，一个输入可属于多个类别\n真实值标签为多热向量，允许多个分量为111\n模型输出多个(0,1)(0,1)(0,1)的实数，根据不同策略预测出多个类别\n交叉熵损失(Cross-Entropy Loss)：基础为信息论中的交叉熵，在不同任务中由于真实值所遵循的分布不尽相同，其具体式子有所不同\n信息论中的交叉熵：\n−∑iP(yi)ln⁡Q(yi)\\begin{align}-\\sum_iP(y_i)\\ln Q(y_i)\\end{align}−i∑​P(yi​)lnQ(yi​)​​信息熵表示某个概率分布的不确定性，越小其分布越稳定，离散随机变量的真实分布PPP的熵为\nH(P)=−∑iP(yi)ln⁡P(yi)\\begin{align}H(P)=-\\sum_iP(y_i)\\ln P(y_i)\\end{align}H(P)=−i∑​P(yi​)lnP(yi​)​​交叉熵则是估计分布QQQ在近似真实分布PPP时的平均信息量，\nH(P,Q)=−∑iP(yi)ln⁡Q(yi)\\begin{align}H(P,Q)=-\\sum_iP(y_i)\\ln Q(y_i)\\end{align}H(P,Q)=−i∑​P(yi​)lnQ(yi​)​​交叉熵总大于信息熵，因为QQQ总是不能完美近似PPP，交叉熵和信息熵的差值被称为相对熵，也称KLKLKL散度，记为\nDKL(P ∣∣ Q)=∑iP(yi)ln⁡(P(yi)Q(yi))\\begin{align}D_{KL}(P\\ ||\\ Q)=\\sum_iP(y_i)\\ln(\\frac{P(y_i)}{Q(y_i)})\\end{align}DKL​(P ∣∣ Q)=i∑​P(yi​)ln(Q(yi​)P(yi​)​)​​在机器学习中将PPP视为固定分布，即H(P)H(P)H(P)为常量，因此最小化DKLD_{KL}DKL​等价于最小化交叉熵\n二分类任务：真实分布PPP服从010101分布\n因此最大化似然函数等价于最小化\nLCE=−1n∑i=1n[yiln⁡y^i+(1−yi)ln⁡(1−y^i)]\\begin{align}L_{CE}=-\\frac1n\\sum_{i=1}^n[y_i\\ln\\hat y_i+(1-y_i)\\ln(1-\\hat y_i)]\\end{align}LCE​=−n1​i=1∑n​[yi​lny^​i​+(1−yi​)ln(1−y^​i​)]​​此交叉熵又称二进制交叉熵，即BCE损失函数\n单标签多分类任务：真实分布PPP服从分类分布\n最大化似然函数等价于最小化\nLCE=−1n∑i=1n∑kyikln⁡y^ik\\begin{align}L_{CE}=-\\frac1n\\sum_{i=1}^n\\sum_ky_{ik}\\ln\\hat y_{ik}\\end{align}LCE​=−n1​i=1∑n​k∑​yik​lny^​ik​​​若已知真实值为第jjj项，则\nLCE=−1n∑i=1nln⁡y^ij\\begin{align}L_{CE}=-\\frac1n\\sum_{i=1}^n\\ln\\hat y_{ij}\\end{align}LCE​=−n1​i=1∑n​lny^​ij​​​ 多标签多分类任务：真实分布PPP服从二项分布，等价于对每个类别进行二分类交叉熵的求和\nLCE=−1n∑i=1n∑k[yikln⁡y^ik+(1−yik)ln⁡(1−y^ik)]\\begin{align}L_{CE}=-\\frac1n\\sum_{i=1}^n\\sum_k[y_{ik}\\ln\\hat y_{ik}+(1-y_{ik})\\ln(1-\\hat y_{ik})]\\end{align}LCE​=−n1​i=1∑n​k∑​[yik​lny^​ik​+(1−yik​)ln(1−y^​ik​)]​​ Hinge损失函数：\n余弦相似度损失函数：\n指数损失函数：\n聚类任务 聚类任务：与分类任务相似，但聚类任务依据分析数据本身特点而非标签来分组，实际是将整个样本集分为多个具有相似特征的样本的集合 SSE损失函数： 其它损失函数 一些更复杂的任务：\n结构化输出： 转录任务：输入为一系列非结构化的数据，输出为离散的有结构的文本标签 机器翻译任务：输入和输出均为有结构的符号序列，两种序列使用的语言不同 异常检测：从输入中标记出异常、非典型的个体 缺失值填补：补充样本中的已知缺失，其输出通常作为某种机器学习算法的输入 去噪：去除样本中损坏的部分，这些损坏通常是非预期发生的 KL散度损失函数：\n正则化技术 正则化(Regularization)：是为了防止过拟合的一系列技术，其原理是主动限制模型的复杂度\n传统的正则化通过对损失值添加正则项的方式限制模型的复杂度，其原理是最大后验估计\n限制模型复杂度，一般是故意地让某些神经元结点“死亡”，即它们的权重为零\n常见的正则化：\nL1正则化(例如Lasso回归)：正则项为\nλ∑i∣wi∣\\begin{align}\\lambda\\sum_i|w_i|\\end{align}λi∑​∣wi​∣​​，能使得部分过小的权重为零，只有少数的权重显著不为零\nL2正则化(例如Ridge回归)：正则项为\nλ∑iwi2\\begin{align}\\lambda\\sum_iw_i^2\\end{align}λi∑​wi2​​​，使权重均匀减小，但不至于减小至零\nDropout：在训练过程中dropout会随机关闭部分神经元结点，这被实践证明是一种有效的trick\n直观的理解是：这种dropout使每次训练涉及的部分是在所有的可能子网络中的一个，相当于每次训练的神经网络结构均不同，使得模型不会依赖某个局部特征\n其具体做法是：在一次训练中，每次经过一个神经元前，以ppp概率使该神经元临时死亡(由伯努利分布提供采样)，为了使输出期望与原来一致，使有效输出乘以\n11−p\\begin{align}\\frac{1}{1-p}\\end{align}1−p1​​​因此，反向传播时，只会更新那些未死亡神经元的参数\n测试时，保留所有神经元，正常地进行前向传播\n在实现中，通常会把Dropout视为和一般神经网络层同等级别的“层”，称为Dropout层\n早停(Early Stopping)：在验证集性能无法提升时，提前结束训练\n模型如果在一定次数内(称为耐心值)，验证指标改进幅度均小于最小改进阈值，则提前结束训练，并恢复到效果最优的版本输出最终模型\n","permalink":"https://echo.ishian.me/2025/07/06/deeplearning-lossaregular/","summary":"讲解回归与分类任务的常用损失函数及其极大似然推导，以及正则化方法","title":"机器学习: 损失函数与正则化"},{"content":"javax.servlet Java EE介绍 在Java EE 8及之前，这个企业版是由Oracle开发的闭源项目，包括一系列大型或中小型的商业软件包，但由于Spring等框架的流行，Java EE开始力不从心\n于是在之后，Oracle将整个Java EE移交给Eclipse开源基金会维护，并强制要求Eclipse不能继续使用Java EE的项目名以及javax的命名空间，因此在Eclipse接手这个项目后迫不得已将javax改为jakarta，并将大版本号提升至5，表示不再向下兼容\n总而言之，javax命名空间适用于4.0及之前的企业版项目，jakarta适用于5.0及之后的企业版\nServlet源于Server Applet，即在服务端上运行的小程序\n在SE的网络编程中对于Http的处理只介绍了HttpClient，即客户端的编写，是因为服务端需要考虑的东西过多\n而Servlet项目致力于高效而安全地开发服务器\nServlet本质可用于任意通信协议，但主要用于HTTP\n它内部封装了线程池，通过多线程的方式处理请求，因此比起此前的方案性能更高\nServlet需要部署到专用的Web服务器上，称作Servlet容器，例如Apache的Tomcat项目、Eclipse的Jetty项目\n因为Servlet本质是一个Web应用，是Servlet容器向开发者暴露的规范接口，Servlet容器是更复杂的应用\nservlet依赖 接口依赖：\nxml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;javax.servlet-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.0.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;jakarta.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jakarta.servlet-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;6.1.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Servlet接口的实现由Web容器提供，可以使用provided表示仅编译时需要\nServlet接口与HttpServlet实现类 Servlet有三个重要方法：init()、service()、destroy()，由Servlet容器调用进行生命周期管理\nservice()含有两个参数HttpServletRequest与HttpServletResponse\nHttpServlet是最常用的实现类，有四个重要方法：doGet()、doPost()、doPut()、doDelete()\n它们也均含有两个参数：HttpServletRequest与HttpServletResponse\n重写它们来自定义处理逻辑\n一般不需要重写service()，由HttpServlet已经实现好了，他会自动解析请求方法并调用doXxx()\n除非需要使用HttpServlet处理其它通信协议\nHttpServletRequest接口表示一个HTTP请求报文，常用方法如下：\nsetCharsetEncoding(String)：设置解析的编码集\nString getParameter(String name)：获取某个请求参数\n这里的参数已经消除了GET、POST等不同请求方法的差异，但对于POST方法而言，只会解析类型为application/x-www-form-urlencoded或multipart/form-data，其它类型如application/json需要通过输入流获取\nString getHeader(String name)：获取某个请求头\ngetRequestDispatcher(String location)：获取一个请求分发器，指向内部的另一个URI，通常会继续调用RequestDispatcher接口的forward(req, resp)内部转发出去或include(req, resp)转发后回来\ngetAttribute()和setAttribute()：可以主动设置属性，方便请求处理链条中数据的传递\nHttpServletResponse接口表示一个HTTP响应报文，常用方法如下：\nsetCharsetEncoding(String)：设置解析的编码集 setContentType(String type)：设置响应体的类型 PrintWriter getWriter()：获取绑定的字符输出流 ServletOutputStream getOutputStream()：获取绑定的字节输出流 setStatus(int)：设置状态码 setHeader(String name, String value)：设置响应头 sendRedirect(String)：302临时重定向到另一个URI 一个服务器的所有Servlet实现类只会有一个实例，因此遇到多个请求时会用同一个实例来处理\n因此实现类的实例属性的所有访问需要保证线程安全\nSession与Cookie Session用于存储客户端的某种状态，能保证每个Session唯一，内部类似于一个Map\u0026lt;String, Object\u0026gt;\nHttpServletRequest对象的getSession()方法会(若不存在)创建并返回一个HttpSession对象，(若存在)直接返回HttpSession对象\ngetSession(false)则不会创建，而只是返回已有的对象，若无则返回null\nHttpSession实例的常用方法为setAttribute(String, Object)和Object getAttribute(Stirng)以及removeAttribute(String)\n分别为设置、获取、删除其存储的对象数据\nSession通常是通过Cookie技术实现的，它会让客户端在发送请求时同时发送一个Session\n自定义Cookie需直接通过构造方法new Cookie(String name, String value)创建\nCookie可设置最大时间setMaxAge(seconds)\n通过HttpServletResponse实例的addCookie(Cookie)方法传递给客户端\n通过HttpServletRequest实例的getCookies()获取Cookie[]数组\n通过Cookie实例的getName()和getValue()获取键值对\nServletContext ServletContext是Web应用上下文的抽象，可以用它获取整个Web应用的信息，最常用的方法如下： 获取Web应用的属性：Object getAttribute(String) 设置Web应用的属性：void setAttribute(String, Object) 移除Web应用的属性：void removeAttribute(String) 记录日志：log(String) 使用上下文需注意，因为处于多线程环境，虽然ServletContext本身是线程安全的，但传入的属性值需要自行确保线程安全 ServletConfig ServletConfig是某个Servlet服务的配置的抽象，可以用它获取某个Servlet服务独有的配置项，最常用的方法如下： 获取初始参数：String getInitParameter()，这个初始参数可以用XML或注解的形式设置，也可由内嵌Tomcat的Wrapper配置 JSP技术 JSP技术比较落后，了解即可\n由于Servlet本质是内嵌HTML的Java微服务程序，大量的HTML文本由一行行的输出流打印比较麻烦，因此JSP技术出现\nJSP本质也是一个Servlet应用，由Web服务器在启动时自动将其编译成Servlet程序并在服务器上运行\nJSP作为静态资源可由Web容器自动部署，但也可以自定义部署URL路径\nJSP语法：\nJsp脚本：\u0026lt;% Java代码 %\u0026gt;，只执行代码，没有返回值 Jsp表达式：\u0026lt;%= Expr %\u0026gt;，返回表达式的值 Jsp声明：\u0026lt;%! 成员变量、方法 %\u0026gt; Jsp指令：\u0026lt;%@ 指令 %\u0026gt; Jsp动作：\u0026lt;jsp:action /\u0026gt; Jsp注释：\u0026lt;%-- 注释 --%\u0026gt; EL表达式：$ { expr } JSP隐含对象：在Jsp脚本中不用声明就可使用的对象，但不能重复定义\nHttpServletRequest request与HttpServletResponse response：因为JSP本身就是一个Servlet，自然有request和response参数 HttpSession session：等价于request.getSession() ServletConfig config：JSP这个Servlet的配置 JspWriter out：不常用 PageContext pageContext：是整个页面上下文的抽象 page：等价于this，是这个Jsp翻译得到的Servlet程序实例，因为this是可省略的所以page不会被直接使用 Exception exception：用于异常处理 JSP指令：\n声明页面的属性：\u0026lt;%@ page import=\u0026quot;java.util.*, java.net.*\u0026quot; %\u0026gt; 声明页面包含其它页面：\u0026lt;%@ include other.jsp %\u0026gt; 声明Tag的来源以及前缀：\u0026lt;%@ taglib uri=\u0026quot;\u0026quot; prefix=\u0026quot;\u0026quot; %\u0026gt; EL表达式的隐含对象\nPageContext pageContext：除它之外，以下所有都是Map param：对应request的参数表 paramValues：对应request的参数表，只不过值是数组用于存储含多个值的参数 header：对应request的请求头表 headerValues：对应request的请求头表，只不过值是数组用于存储含多个值的请求头 cookie：对应cookie表 initParam：对应ServletConfig的初始参数表 pageScope：页面作用域，可通过它访问所有在页面内定义的标识符 requestScope：请求作用域，可通过它访问所有在request中定义的attribute sessionScope：会话作用域，可通过它访问所有在session中定义的attribute applicationScope：应用作用域 EL表达式可以不指定隐含对象地输出字段，此时会从小到大(pageScope-\u0026gt;requestScope-\u0026gt;sessionScope-\u0026gt;applicationScope)地查找该字段 EL表达式的关键字：not、le、lt、ge、gt、eq、ne、div等\nJsp动作：\n\u0026lt;jsp:useBean id=\u0026quot;a\u0026quot; class=\u0026quot;com.Clazz\u0026quot; scope=\u0026quot;page|request|session|application\u0026quot;/\u0026gt;\n创建一个标识为a、类型为com.Clazz、所属作用域为所有作用域的对象\n\u0026lt;jsp:getProperty name=\u0026quot;a\u0026quot; property=\u0026quot;age\u0026quot;/\u0026gt;\n获取标识为a的对象的age属性\n\u0026lt;jsp:setProperty name=\u0026quot;a\u0026quot; property=\u0026quot;age\u0026quot; value=\u0026quot;value\u0026quot;/\u0026gt;\n设置标识为a的对象的age属性为字面量\u0026quot;value\u0026quot;\n\u0026lt;jsp:setProperty name=\u0026quot;a\u0026quot; property=\u0026quot;age\u0026quot; param=\u0026quot;age\u0026quot;/\u0026gt;\n设置标识为a的对象的age属性为引用变量age的值\nFilter Filter接口作用于收到请求、初始化响应之后，Servlet服务处理请求之前，可用于身份校验、初始化配置等 实现Filter接口需要实现doFilter(ServletRequest, ServletResponse, FilterChain)方法，如果需要拦截则直接return，如果放行则需要调用FilterChain实例的doFilter(ServletRequest, ServletResponse)方法 Listener Listener采用观察者模式，当某种实例创建、更改、销毁时，自定义的Listener组件会随之调用对应的方法 Listener本身并不存在，只存在一系列的XxxListener接口 ","permalink":"https://echo.ishian.me/2025/07/05/java-servlet/","summary":"介绍 Servlet 接口体系、Session/Cookie、Filter 与 Listener 等 Web 组件","title":"Java EE: Servlet API"},{"content":"Spring资源管理 Resource接口 Resource类似File、Path、URL，是Spring提供的对一类资源的抽象 Resource会增加应用与Spring框架的耦合，但实际上它只是一个对于URL等传统资源类的有力替代品和实用API，用其它资源API也是可以的 Resource的好处是：方便，例如能够使用classpath*等路径前缀查找而不需要写完整的文件或URL的路径信息 Resource接口包含的日常常用方法： boolean exists()：判断资源是否存在 boolean isReadable()：判断资源是否可读 InputStream getInputStream()：获取该资源的输入流(继承InputStreamSource接口) ReadableByteChannel readableChannel()：获取nio接口 String getDescription()：获取资源的全路径名，通常用于错误日志输出 Resource的常见实现类： UrlResource：是java.net.URL的包装 ClassPathResource：表示所有可以从classpath中获取的资源 FileSystemResource：是java.io.File以及java.nio.file.Files的包装 ServletContextResource：表示所有可以从Web应用中获取的资源 ByteArrayResource：表示一个byte数组资源 ResourceLoader与ResourcePatternResolver接口 在平时，一般不会直接使用上述的实现类来显式地用new创建Resource实例，而是用ResourceLoader或ResourcePatternResolver帮助我们自动地选择实现类 ResourceLoader包含两个方法，最常用的是Resource getResource(String loc)，用于加载loc指向的资源并自动选择合适的实现类，然后返回Resource实例 ResourcePatternResolver继承ResourceLoader接口，此外还包含一个方法Resource[] getResources(String locPattern)，用于加载匹配locPattern模式串的所有资源，然后返回Resource[]数组 此前所说的ApplicationContext接口继承了ResourcePatternResolver接口，因此我们不需要额外地创建一个ResourceLoader或ResourcePatternResolver实例，而是使用ctx.getResource(loc)或ctx.getResources(locPattern)即可 String转化为Resource格式： classpath:bean.xml：从classpath(例如target/classes)中加载，使用ClassPathResource实现类 file:///src/main/config.xml：从文件系统中加载，使用UrlResource实现类 https://www.baidu.com/：从URL中加载，使用UrlResource实现类 classpath*:WEB-INF/**/*.xml：从classpath中查找，使用Ant风格的通配符，**表示零或多个目录、*表示零或多个字符(不能跨越目录)，会查找所有包含该路径的资源 无前缀：视具体的ApplicationContext实现类不同，可能是ClassPathResource或ServletContextResource 例子 javapublic class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext(\u0026#34;a.xml\u0026#34;); Resource res = resourceLoader.getResource(\u0026#34;https://www.baidu.com/\u0026#34;); if (res.exists() \u0026amp;\u0026amp; res.isReadable()) { try (InputStream is = res.getInputStream(); ReadableByteChannel bc = res.readableChannel();) { // use bio or nio } catch (IOException e) { e.printStackTrace(); } } } }ResourceLoaderAware接口(可选) 当一个bean需要在内部使用ResourceLoader或ResourcePatternResolver来方便地在类内部加载资源时，采用@Autowired才是更推荐的方式 如果需要隐式注入而非显式声明，可以使bean实现ResourceLoaderAware接口 ResourceLoaderAware接口有一个方法setResourceLoader(ResourceLoader rl)，当这个bean被ApplicationContext实例纳入管理时，ApplicationContext会把自己当作参数传递给这个set方法 ","permalink":"https://echo.ishian.me/2025/07/04/spring-core-resource/","summary":"介绍Spring Resource资源抽象接口、实现类与ResourceLoader加载机制","title":"Spring framework: Resource API"},{"content":"IoC容器 简介 IoC(Inversion of Control，控制反转)容器的原则是使原本有相互依赖的各个部分解耦，实现松耦合地运作 在传统的组合中，上层的模块在内部定义某些对象的类型，控制权在上层的模块中，如果需要修改内部的业务逻辑则会违反开闭原则 而IoC的思想则是使上层的模块将控制权“反转”给一个第三方，例如开发者自己或者一个IoC容器，由外部来控制并决定向上层模块提供什么样的实现类\nIoC容器的常见的实现方法是DI(Dependency Injection，依赖注入)，即上层模块所依赖的其它对象仅通过构造方法或工厂方法或**setter**方法暴露注入入口，容器在创建对象时控制注入对象的实现类\n当我们用到IoC容器时，说明一个类的实例往往是全局单例的，只要明白这个点，就能理解为什么会发生注入冲突 但同样可以实现非单例的实例化\n例子：\njavaclass Person { private Eye e = new BlackEye(); // 在后续改动时违反开闭原则 } class Person { private Eye e; public Person(Eye e) { this.e = e; } } Person = new Person(new BlackEye()); // 在外部注入 而在Spring框架中，这个“外部注入”不需要由开发者手动地注入，而是通过声明式注解由Spring内置的IoC容器自动注入\norg.springframework.beans与org.springframework.context是IoC容器的核心\n依赖：\nxml\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-context\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;7.0.0-M9\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 元数据配置 上下文 Spring的IoC容器通过解析元数据来管理Bean\n元数据通常包括以下内容：\nBean的类型 Bean的id Bean的依赖注入 Bean的作用域 Bean的生命周期行为(以回调形式存在) Bean的懒加载、协作者等其它元数据 ApplicationContext：应用上下文接口的实例表示一个IoC容器，它会自动创建、配置、组装所有的Bean\n继承BeanFactory接口：该接口提供一系列和Bean有关的方法 T getBean(String name, Class\u0026lt;T\u0026gt; clazz)：获取它管理的类型为T、名字为name的Bean 继承MessageSource接口：该接口提供国际化处理信息的能力 String getMessage(String msg, Object[] args, Locale locale)：根据locale以及可选的选项args解析msg 创建一个ApplicationContext实例： 创建ApplicationContext实例同时意味着许多配置，在不依靠外部框架(如Spring Boot和Tomcat)的情况下，ApplicationContext只能通过new创建 Spring提供了两种构造ApplicationContext实例的方式，分别为基于XML的配置、基于Java的配置 基于XML的配置：实现类为ClassPathXmlApplicationContext，构造方法需要的参数是String...，即一系列**xml配置文件的路径** 基于Java的配置：实现类为AnnotationConfigApplicationContext，构造方法需要的参数是Class\u0026lt;?\u0026gt;...，即一系列由@Configuration注解的配置类\n基于XML、基于注解、基于Java的关系\n三种方式都由Spring控制实例的注入 XML是最早的Spring提供的配置方式，它是外部的、支持非侵入式地修改元数据配置 Spring 2.0开始逐步地提供一系列注解来方便分散化、简洁化配置，提供了基于注解的配置，位于org.springframework.stereotype和org.springframework.beans.factory.annotations包中 在基于注解的配置里，开发者对实例的创建和注入的控制方式和XML形式类似，只是停留在声明Bean上，目的是简化配置代码书写并提供编译时的类型检查 例如stereotype包中的@Service、@Repository、@Component，它们的修饰表示声明这个类属于某种Bean，即配置是什么的元数据 例如beans.factory.annotations包中的@Autowired和@Qualifier，它们修饰表示指示如何创建Bean 这种方式的配置需要在XML或Java中启用组件扫描来纳入管理 Spring 3.0开始，提供org.springframework.context.annotations来替代整个XML形式的配置，并提供了新的ApplicationContext的实现类，例如@Configuration与@Bean 尽管注入仍由Spring框架管理，但这种方式允许开发者十分灵活地决定如何创建一个实例，允许使用Java代码进行带有分支判断地创建实例 第二种方式和另外两种方式并不是互斥的，第一种方式和第三种方式对应着两种不同的实现类，而第二种方式则是作为一种方便的声明式API存在，适用于结构简单的Bean的声明 现代应用中常使用第二种与第三种方式混合的配置方案，但并不意味着XML配置完全无用，它的外部、非侵入性正是它的特点 Bean的XML配置 \u0026lt;bean/\u0026gt;的id、name属性或\u0026lt;alias/\u0026gt;：它们都是可选项，默认由Spring生成一个唯一的id\nid是唯一标识，若自定义，则必须是唯一的，不允许使用特殊字符，通常使用驼峰命名法 name是别名集合(字符串表示)，不同别名用,或;隔开，允许重复、允许使用类似/的特殊字符 若使用引用，则查找id是否含有匹配项、然后查找name是否含有匹配项、然后查找类型是否匹配，若有二义性则报错 \u0026lt;alias name=\u0026quot;\u0026quot; alias=\u0026quot;\u0026quot;/\u0026gt;：在定义bean标签以外的地方定义它的别名 选择bean的构造器：\nclass属性：用于指定该bean所使用的构造器的来源，并不一定是class类型的 若不提供任何构造方法，则自动根据提供的参数匹配合适的构造方法 使用静态工厂方法：指定factory-method属性，会调用指定class属性这个类内部的factory-method静态方法 使用工厂类实例的工厂方法：不能指定class属性，而是指定factory-bean属性，它是另一个类型为某个工厂类的bean primary布尔属性：标识该\u0026lt;bean/\u0026gt;对应的构造器为默认的创建方法 提供构造参数(依赖注入)来消除同名构造器的二义性：\n基于构造器的注入：\u0026lt;constructor-arg/\u0026gt;：作为\u0026lt;bean/\u0026gt;的子标签，表示传递给构造器的参数，为了消除二义性，它有若干属性，不必全部指定而只需要保证不存在歧义即可： name属性表示参数的名称 index属性表示参数的索引(0-idx) type属性表示参数的类型 value属性表示参数的字面值 ref属性表示使用其它bean作为参数 基于setter的注入：要求该bean对应的class含有指定属性的setter方法，则可使用\u0026lt;property/\u0026gt;传递用setter设置的参数 其属性与\u0026lt;constructor-arg/\u0026gt;一致 一般来说，\u0026lt;constructor-arg/\u0026gt;会严格匹配构造器，因此会更安全(不会出现null) \u0026lt;property/\u0026gt;则是在构造后通过调用setter方法进行配置的方案，可能导致某些属性没被注意而为null 前者若想达到可选参数的效果，也可使构造器参数为Optional\u0026lt;T\u0026gt;或使用@Nullable注解 启用组件扫描：\nxml\u0026lt;beans\u0026gt; \u0026lt;context:component-scan base-package=\u0026#34;包名\u0026#34; /\u0026gt; \u0026lt;/beans\u0026gt; 例子：\nxml\u0026lt;!-- primary标识首选 --\u0026gt; \u0026lt;bean id=\u0026#34;userBean\u0026#34; class=\u0026#34;demo.beans.UserBean\u0026#34; primary=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;!-- 根据构造参数唯一确定构造器 --\u0026gt; \u0026lt;constructor-arg index=\u0026#34;0\u0026#34; type=\u0026#34;int\u0026#34; value=\u0026#34;10\u0026#34;/\u0026gt; \u0026lt;constructor-arg index=\u0026#34;1\u0026#34; name=\u0026#34;otherBean\u0026#34; ref=\u0026#34;otherBean\u0026#34;/\u0026gt; \u0026lt;constructor-arg index=\u0026#34;2\u0026#34; type=\u0026#34;java.lang.String\u0026#34; value=\u0026#34;str\u0026#34;/\u0026gt; \u0026lt;!-- 后续的setter配置 --\u0026gt; \u0026lt;property name=\u0026#34;optionalArg\u0026#34; value=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;!-- 不带任何参数表示选择默认构造方法, 但这是在primary之后的选择 --\u0026gt; \u0026lt;bean id=\u0026#34;userBean\u0026#34; class=\u0026#34;demo.beans.UserBean\u0026#34;\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;!-- 在外部起别名 --\u0026gt; \u0026lt;alias name=\u0026#34;userBean\u0026#34; alias=\u0026#34;uB\u0026#34;/\u0026gt; Bean的Java配置 声明为具有某种功能的bean(类级修饰)：\n@Component(value=\u0026quot;\u0026quot;)：声明为一般的bean，value可选，表示该bean的名称，默认为类名的驼峰命名 @Service：声明为服务类，@Component的一种 @Repository：声明为持久化类，@Component的一种 @Controller：声明为控制器类，@Component的一种 需要用@ComponentScan(basePackages=\u0026quot;\u0026quot;)启动扫描 依赖注入：\n@Autowired(required=true)：修饰方法或属性，表示自动注入，修饰方法时若修饰构造器或setter则对应构造器注入或setter注入、修饰一般方法则会使bean在初始化完成后调用该方法，(不推荐)修饰属性时表示直接注入 默认强制要求注入，可使required=false或使用Optional\u0026lt;T\u0026gt;或使用@Nullable表示可选 如果含有多个构造方法，基于注解的配置要求至少有一个@Autowired @Qualifier(value=\u0026quot;\u0026quot;)：修饰类或@Bean方法时给该bean起限定符，修饰属性或形参时表示指定使用value对应的bean注入该属性 @Value(value=\u0026quot;\u0026quot;)：注入简单字面值，可以修饰形参 @Primary：标识某个类时表示当某个接口或某个抽象类存在多个实现类时，选择该类作为首选的实现 标识某个@Bean方法时表示当存在多个返回同一类型的方法时首选该方法 基于Java的配置：\n@Configuration：标识某个类为配置类bean，它也是一个@Component @Bean：标识某个方法为创建某个类型bean的方法，配合@Primary决定首选方法 其name属性表示这个bean的名字，它可以同@Qualifier的value属性一样被绑定然后用@Qualifier指定注入 因为一般name的命名有一定风格(通常是类名的驼峰命名)，而如果需要用含特殊语义的命名，@Qualifier的value属性是一种选择 例子：\njavapackage demo.config; @Configuration @ComponentScan(\u0026#34;demo.service\u0026#34;) public class BeansConfiguration { // 默认用方法名作为@Bean的name @Bean public User user() { return new User(); } @Bean @Primary public User user(int type) { return new User(type); } } package demo.service; @Service public class UserService { private final UserRepository userRepo; public UserService() { } @Autowired public UserService(UserRepository userRepo, @Value(\u0026#34;0\u0026#34;) int type) { this.userRepo = userRepo; } } package demo; public class Main { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(demo.config.BeansConfiguration); } } ","permalink":"https://echo.ishian.me/2025/07/03/spring-core-ioc/","summary":"讲解Spring IoC容器的控制反转与依赖注入思想及常用注解配置","title":"Spring framework: IoC容器"},{"content":"期末总复习 基本概念 文件系统的弊端 数据冗余、数据不一致：在多个文件中，某些数据会出现冗余，导致修改时涉及很多文件，此时发生异常将导致数据不一致问题；这里的一致性指的是不同文件中数据本该相同，但实际上不同的情况 访问困难 数据孤立 数据完整性：指文件中数据不符合现实逻辑的情况，对某个数据添加约束时，很难约束所有涉及到该文件的程序 操作的原子性：对文件的修改难以做到要么全部完成、要么完全没有修改 多用户并发访问：当多个用户同时访问文件时，容易导致数据不一致问题 安全性问题：难以灵活调整不同用户对文件的访问权限 DBMS基本概念 数据是描述现实事物的符号记录\n数据库是数据的集合\nDBMS(数据库管理系统)，是用于管理数据库的基础软件系统，也是通常意义上的数据库 数据库系统则是由DBMS、数据库、管理员、应用进程组成的一套系统\n数据库的三级模式：分为外部层(视图层、外模式)、概念层(逻辑层、模式)、内部层(物理层、内模式)\n先考虑模式的概念：相当于一种数据类型，一个模式包含多个属性名及对应的属性类型和完整性约束 平常接触的表就是一个模式的实例 平常一个模式只会有一个实例，所以经常将“关系”、“关系模式”混着说 视图层：对于不同的用户、根据权限和需求展现不同的数据 例如平时使用查询语句以及创建视图时，查出来的结果，用户可以修改视图的列名，由系统完成从视图层到概念层的映射，只不过这些视图是 逻辑层：包含由创建该模式的用户起的名称及类型，例如平常接触的表就是一个概念层的实例 物理层：包含具体来说在数据库管理系统中真正的由系统起名、由系统管理的名称；以及其对应的存储大小、存储位置等 数据库的两级映射：使数据库具有独立性，在一层发生变动时，其它层不需要由用户维护而仍保持不变\n视图层和逻辑层：使数据库具有逻辑独立性 逻辑层和物理层：使数据库具有物理独立性 数据模型：是数据、数据联系、数据语义、数据一致性约束的集合\n概念数据模型：实体-联系模型 也就是ER图 逻辑数据模型：关系模型、网状模型、层次模型、基于对象数据模型、半结构化数据模型 物理数据模型：B树等 关系数据模型 关系数据模型是一种逻辑数据模型 其中数据结构、数据完整性约束体现在用户设计的模式上 数据操作和数据结构、数据完整性约束共同构成关系数据模型 在关系数据模型中： 关系模式的实例称为关系，它们通常是一对一的，所以会混着用 关系也称为表 属性相当于表的一列 元组相当于表的一行，元组是一个关系的最小单位 一个属性的域是所有可以作为该属性的值的集合 域的原子性是第一范式，即域的所有值都是不可再分的 空值NULL是一个特殊的值，表示未知、不确定，其参与的所有表达式都返回false(除is null等特殊表达式) 关系完整性约束： 超码：是一个属性组，若这个属性组的值确定，则可以唯一确定关系中所有属性的值 候选码：是最小的超码，其任何真子集都不是超码 所有在候选码中出现过的属性称作主属性，其余是非主属性 主码：被设计者选中来作为关系的唯一标识的那个候选码 实体完整性约束：对于一个主码，它会受到实体完整性约束，用户插入的主码的值必须满足唯一、非空 参照完整性约束和外码：一个关系R1R_1R1​的属性组T1T_1T1​参照另一个关系R2R_2R2​的主码T2T_2T2​，称为T1T_1T1​是参照R2R_2R2​的外码 其中，T1T_1T1​不要求是主码，但有参照完整性约束，T1T_1T1​的值要么为空、要么是现在R2R_2R2​中存在的T2T_2T2​的值 T2T_2T2​要求是R2R_2R2​的主码 用户自定义完整性约束：唯一性约束、非空约束、检查约束(约束值的范围) 关系代数 概述 过程式查询语言\n基本、附加、扩展\n基本运算：σ、Π、\\sigma、\\Pi、σ、Π、\nΠ投影的若干列(σ选择条件(数据源))\\Pi_{投影的若干列}\\left(\\begin{aligned}\\sigma_{选择条件}\\left(数据源\\right)\\end{aligned}\\right)Π投影的若干列​(σ选择条件​(数据源)​)\nσ\\sigmaσ：选择(限制)运算\n选择条件可用的逻辑运算符：∧、∨、¬\\wedge、\\vee、\\neg∧、∨、¬\nΠ\\PiΠ：狭义的投影运算，只能选择属性进行投影\nρ新名(原名)\\rho_{新名}(原名)ρ新名​(原名)：重命名运算\n×\\cross×：笛卡尔积运算\n集合运算：∪、−\\cup、-∪、−\n⋈\\bowtie⋈：自然连接\n⟕、⟖、⟗⟕、⟖、⟗⟕、⟖、⟗：左/右/全外连接，在自然连接的基础上保留左端/右端/两端的所有行\n⋈θ\\bowtie_\\theta⋈θ​：θ\\thetaθ连接，其中θ\\thetaθ是自定义的连接条件\n关系除：r(R)r(R)r(R)\n聚集函数：分组依据G聚集函数值_{分组依据}\\mathcal G_{聚集函数值}分组依据​G聚集函数值​\nΠename,sal(σdept.loc=′New York′∧emp.deptno=dept.deptno(emp×dept))\\begin{align}\\Large\\rm\\Pi_{ename,sal}(\\sigma_{dept.loc=\u0026#x27;New\\ York\u0026#x27;\\wedge emp.deptno=dept.deptno}(emp\\cross dept))\\end{align}Πename,sal​(σdept.loc=′New York′∧emp.deptno=dept.deptno​(emp×dept))​​数据库设计 基本概念 在进行需求分析后，数据库设计分为三步： 设计概念数据模型 设计逻辑数据模型 设计物理数据模型 ER模型设计 ER模型是最常见的概念数据模型，全称Entity-Relationship模型，即实体-联系模型\nER模型由以下部分组成：\n实体集 联系集 属性和它的域 属性的类型：\n简单类型\n复合类型：由多个属性组成的属性，在ER模型中表示为：\ntextcomposite_attribute component_1 ...即由缩进表示从属关系\n多值类型：可能同时含有多个值的类型，在ER模型中表示为：\ntext{multivalue_attribute} 派生属性(或导出属性)：可以由其它属性求出的属性，在ER模型中表示为：\ntextderived_attribute() 作为码的属性：在ER模型中用下划线指出\n实体由属性和具体的值表示，由所有可能的实体构成的集合就是实体集 实体集在ER模型中表示为方框，上面是实体集名称，下面是属性集合\n联系是若干实体之间的关联，也就是一个联系允许关联多个实体(但本文仅考虑二元联系)，但不允许有关联联系的联系 由相同类型的联系构成的集合就是联系集，在ER模型中表示为棱形 一个联系集允许有不同于相关联实体集属性的属性，称为描述性属性，在ER模型中表示为由虚线引出的方框中的属性\n参与度约束：\n称一个实体参与一个联系集，是一个实体至少对应一个另一端的实体，即限制对应关系的下限 称一个实体集完全参与一个联系集，是这个实体集的所有实体都参与这个联系集 否则称这个实体集部分参与这个联系集 完全参与和部分参与在ER模型中表示为双线、单线 映射基数约束：限制对应关系的上限，分为一对一基数、一对多基数、多对多基数\n一对一基数：双方实体集都满足“所有实体至多对应一个另一端的实体” 一对多(或多对一)基数：对于多方实体集，没有上述限制 多对多基数：对于双方实体集都没有上限的限制 码约束：\n对于实体集来说，用户定义的码属性就是该实体集的码，它们的某个值必须能够唯一标识一个实体 对于联系集来说，多方实体集的码属性就是联系集的码；如果两边都是多方，则使用双方的码作为联系集的码；如果双方都是“一”方，则使用任意一方的码作为联系集的码 ER模型中不允许出现起外码功能的属性，如果实体集中存在这样的属性则需要删去 如果删去后剩余的码属性无法唯一标识一个实体，但依赖于另一方的码属性后又可以唯一标识时，则称该实体集为弱实体集，关联该弱实体集和其依赖的强实体集的联系集用双棱形表示 弱实体集的“码属性”没有码的职责，因此它们的集合称为分辨符，用虚线下划线表示 关系型数据模型设计 关系型数据模型是一种逻辑数据模型，将ER模型转化为关系型数据模型 对于所有一对一的联系集，联系集不需要单独作为一个关系，两个实体集各作为一个关系，联系集的属性可作为任意一个实体集的属性 ","permalink":"https://echo.ishian.me/2025/06/18/db-review/","summary":"数据库课程期末复习笔记，涵盖三级模式、SQL、范式、事务与并发控制等知识点","title":"数据库: 期末复习"},{"content":"Docker-Compose docker-compose.yml docker-compose.yml是唯一的必需文件 version：docker-compose版本声明 services：服务定义 networks：网络定义（可选） volumes：卷定义（可选） configs：配置定义（可选） secrets：密钥定义（可选） services配置 子字段均是自定义的服务名称\n自定义的服务名称下是主要需要配置的\nimage：镜像名+标签（默认为latest），也可使用私有仓库的镜像\nalpine标签通常是及其轻便的，适用于容器化部署\n默认则是latest标签\nbuild：除了image还可以使用build，即从Dockerfile构建：\ncontext：Dockerfile所在目录 dockerfile：Dockerfile文件名 args：构建参数 通用配置：\ncontainer_name：容器名 hostname：主机名 ports：端口映射，是一个数组（宿主机port:容器port） working_dir：工作目录 depends_on：依赖其它服务，是一个数组 networks：自定义网络引用，是一个数组 volumes：自定义卷引用，是一个数组，每个元素是卷引用:数据地址 command：覆盖默认的启动命令，是一个数组 environment：注入环境变量 env_file：从文件注入环境变量 restart：重启策略，包括\u0026quot;no\u0026quot;、always、on-failure、unless-stopped user：指定运行的用户和用户组 group_add：附加额外的用户组，是一个数组 logging：日志分割配置时，需要主动覆盖，设置logging.driver为json-file并设置logging.options.max-size（每个文件最大大小）和max-file（最多保留文件个数） networks配置 子字段均是自定义的网络名称 只写网络名称是最常用的，默认driver为bridge volumes配置 子字段均是自定义的卷名称 只写卷名称是最常用的，默认driver为local 可以在hub.docker.com/_/mysql查询数据存放的目录 docker inspect container_name:latest --format='{{json .Config.Volumes}}' | python -m json.tool也适用于大多数容器的数据卷映射建议 常用docker-compose命令 docker-compose up -d [services]：启动，-d为--daemon，可指定services只启动部分服务 docker-compose ps [-a] [services]：查看运行状态，-a为查看详细信息，可指定services只查看部分服务 docker-compose logs [services]：查看日志 docker-compose down：关闭并删除所有容器和服务，可附加-v把卷同时删除 docker-compose stop [services]：关闭服务，保留容器 docker-compose restart [services]：配合stop使用，重启服务 docker-compose exec service_name sh_name：指定sh和服务名称进入该容器 docker compose：这是Docker Compose v2的命令，上述均为v1的 ","permalink":"https://echo.ishian.me/2025/06/17/docker/","summary":"讲解docker-compose.yml的常用配置项及使用Compose进行多容器部署的方法","title":"Docker-Compose: 容器部署"},{"content":"操作系统复习 概述 计算机启动(Boot) 分为四个阶段，第一阶段为BIOS(Basic Input/Output System) 硬件自检(Power-On Self-Test，即POST) BIOS将控制权交给下一个启动程序，启动的顺序被存储在外存中 第二阶段：主引导记录(Master Boot Record，即MBR)，是某个硬盘第零柱面的第一扇区 将控制权转让，即BIOS读取MBR并将其放入内存，并通过MBR得知操作系统的具体分区 如果此时拥有控制权的设备不能用于启动(在MBR中有特殊标注)，则按顺序继续转让控制权，继续读取下一个设备的MBR 当计算机有启动管理器这个程序时，计算机在转让控制权时会由用户决定 第三阶段：硬盘启动 当找到可以用于启动的硬盘时，也就知道了操作系统的具体分区，计算机会读取这个分区的卷引导记录(Volumn Boot Record，即VBR)，即这个分区的第一个扇区 计算机就可以精确地找到操作系统的位置并加载 第四阶段：操作系统 系统内核先被加载，然后运行init进程，直到运行login进程让用户登录 OS的目的和发展 在最初没有OS，输入、输出完全由手工操作时，每个任务由操作人员准备和放入，效率低下 OS出现的目的是提高系统资源利用率，增加系统吞吐量，以至于到后来提供方便性(对用户方便)、可扩充性(对扩充新模块方便)、开放性(对不同计算机的程序的移植方便) 单道批处理：用户事先放入多个同类作业，由监控程序整理为一批作业串行执行 但当作业无法继续运行时，它不会放弃CPU，导致仍存在系统资源的浪费 中断和通道技术的出现(进入集成电路时代)，推动了现代意义上操作系统的出现 发展为多道程序批处理系统，主存中可同时存在多个作业，CPU在其中切换 实现了一定程度的并发，即不同进程交替占用CPU运行，实现了微观上串行、宏观上并行的效果 但人机脱离，没有交互性 分时操作系统：相比多道程序批处理系统，分时操作系统是联机的、多用户的、交互的操作系统，具有交互性、多用户同时性、独立性、及时性 Unix是典型的分时系统 实时操作系统：强调可靠性和实时性，通用性差，专用于有实时控制需求的场景 分为硬实时和软实时，硬实时保证在限定的时间内完成任务，软实时并不保证这一点但也不允许过多地超过规定时间 操作系统服务 用户接口：CLI、GUI等 进程管理：对进程进行调度 存储管理：针对内存进行存储分配、存储共享、存储保护(越界发生中断)、存储扩张(虚存) 设备管理：管理I/O设备 文件管理：文件存储管理、目录管理、文件操作管理、文件保护 通信服务：同一机器上不同进程的IPC和不同计算机上的IPC 资源分配 错误检测 统计 保护和安全 操作系统的结构 系统调用 操作系统分为两种模式：内核模式和用户模式，通过CPU状态位帮助OS判断现在CPU处于管态(内核态)或目态(用户态)，目态相比于管态，无法执行一些特定的指令 若发生中断或错误，系统会自动切换到管态来执行中断处理程序 系统调用是内核暴露给用户的原始接口，用户可通过系统调用申请内核的服务，内核调用的实现是通过软中断或**syscall指令使用户态切换到内核态执行内核态的特殊指令和内核函数，执行完毕后再切换回用户态** API是应用程序接口，也即在内核之上的库例程，是用高级语言编写的更高级的封装接口，可能完全在用户空间运行，也可能封装了系统调用 用户通常用API编写程序，因为API类似一个中间件，使程序具有更强的可移植性、更直观 系统调用的传参方式：寄存器传参(限制参数个数)、内存块传参、堆栈传参 POSIX API POSIX全称Portable Operating System Interface of UNIX，即针对所有类UNIX系统的可移植操作系统接口，它是一套标准 pid_t fork()：头文件unistd.h，用于创建一个子进程 fork()执行一次，创建一个子进程，向父进程和子进程均返回pid_t值 对父进程的返回值一定不等于零，若小于零说明创建失败，若大于零则是子进程的pid 对子进程的返回值一定等于零，子进程会采用写时拷贝的方式复制父进程的堆栈和数据段，只共享代码段且PC均指向fork()的下一条指令 在不使用wait()的情况下，父进程和子进程的执行顺序是未定的，如果父进程提前结束，子进程将挂靠在父进程的父进程上 pid_t wait(int* stat_loc)：头文件sys/types.h和sys/wait.h，使父进程阻塞，直至所有子进程执行完毕后唤醒 stat_loc若不为空指针，wait()会将子进程的结束状态赋给stat_loc指向的整型变量 一般用法不关心子进程的结束状态，会使用wait(NULL) void exit(int status)：头文件stdlib.h，用于以状态码status结束当前进程 简单bash shell chmod 进程和线程 进程的概念 进程和程序的本质区别是动态性，进程相当于程序、PCB、数据的组合体 此外，进程还具有并发性、独立性、异步性\n并发性：多个进程在宏观上并行执行的现象 独立性：进程是资源分配、调度的基本单位 异步性：不同进程的执行通常不需要按相同的节奏推进 具体来说，进程包含代码段、数据段、堆、栈、PC 其中，全局变量存放在数据段中，局部变量存放在栈中，动态分配内存的变量存放在堆中\n进程状态模型：\ngraph LR Ready(就绪) --\u0026gt;|调度| Run(运行) --\u0026gt;|时间片结束或被抢占式调度算法中断| Ready Run --\u0026gt;|处理I/O或等待事件发生| Wait(阻塞) --\u0026gt;|I/O完成或事件发生| Readygraph LR Ready(就绪) --\u0026gt;|调度| Run(运行) --\u0026gt;|时间片结束或被抢占式调度算法中断| Ready Run --\u0026gt;|处理I/O或等待事件发生| Wait(阻塞) --\u0026gt;|I/O完成或事件发生| Ready Wait \u0026lt;--\u0026gt; SuspendWait(挂起阻塞) Ready \u0026lt;--\u0026gt; SuspendReady(挂起就绪) Run --\u0026gt; SuspendReady SuspendWait --\u0026gt; SuspendReady 进程上下文切换\n指CPU切换到另一个进程时，保存原来进程状态、并加载新进程的状态的过程 涉及到用户级上下文(用户代码段、用户数据段、用户堆栈)、寄存器上下文(PSW程序状态寄存器、栈指针、通用寄存器)、系统级上下文(PCB、内存区表、内核栈) CPU调度层次：\n长期调度、作业调度：选择作业载入内存的就绪队列 中期调度、平衡负载调度：通过交换技术，将内存中的进程换出或将挂起的进程换入，缓解内存空间压力 短期调度、进程调度：选择一个处于就绪队列的进程占有CPU执行 引起进程阻塞的原因：\n请求系统服务 无新工作可做(例如无工作可做的系统进程) 调度是一个系统服务，其本质是执行一个系统调用schedule()，正如之前所说执行系统调用是通过软中断或syscall指令进行，执行系统调用相当于执行一个中断处理程序，所以进程执行系统调用和陷入内核态不属于进程的调度 而通常在实现上，从内核态返回用户态时，会发生一次调度，原因大概是为了减少上下文切换的开销，在执行完系统调用后返回用户态前顺便进行一次schedule()，能减少因“返回用户态后立刻因为时间片结束或新进程到达导致再次被中断进行调度”造成的开销\nIPC IPC：Inter-Process Communication，进程间通信 低级通信：用于传递控制消息，用互斥锁、信号量实现 高级通信：用于传输数据，用管道、消息队列、共享内存实现 线程概念 为了减少因进程调度产生的上下文切换开销，将调度的基本单位设置为进程内的若干执行序列，并将其称为线程 线程和进程的区别： 在引入线程后，进程仍是资源分配的基本单位，线程是系统调度的基本单位 同一进程下的不同线程共享代码段、数据段，每个线程有自己的堆栈、PC、寄存器集 线程切换的开销小于进程切换的开销 线程间共享比进程间共享更简单 线程的引入提高了并发性，使得一个进程的不同计算任务也可以并发推进 用户级线程：操作系统内核不支持线程，进程通过线程库来实现多线程编程的情况 若进程的一个线程进入阻塞，则所有其它对等线程也进入阻塞 同一进程的不同线程的切换不需要经过内核，切换时间短 一个进程含有多个线程不会增加整个进程的执行时间 内核级线程：操作系统内核支持线程 一个线程的阻塞不会影响其它线程的状态 线程切换时需要中断并执行系统调用，切换开销大 多线程编程下，一个进程的执行时间更长 多线程模型： 多对一：多个用户级线程映射到一个内核级线程 一对一：每个用户级线程都对应一个内核级线程 多对多：多个用户级线程对应相同个或更少的内核级线程 二级模型：允许多对多和一对一映射 进程调度 进程调度即短期调度，指从就绪队列中选择一个进程，将CPU控制权分配给它 调度时机： 进程从运行态到阻塞态 进程结束 均为非抢占的，进程自愿放弃CPU 进程从运行态到就绪态 进程从阻塞态到就绪态 均为抢占的，进程被迫放弃CPU、进程根据先到先得的方式获得非CPU资源 进程调度算法： 先来先服务：优先考虑等待时间 最短作业优先(SJF)：非抢占、存在饥饿 最短剩余时间优先(SRTF)：抢占、存在饥饿 最高响应比优先(HRF)： R=W+TT\\begin{align}R=\\frac{W+T}{T}\\end{align}R=TW+T​​​，周转时间(等待时间加运行时间)除以运行时间，同时考虑等待时间和运行时间 轮转法(RR) 最高优先级调度：静态优先级存在饥饿现象 多级反馈轮转法：现代操作系统采用的方式，分为多个优先级的队列，每个队列内部采用轮转法 进程互斥与同步 临界资源：多个进程共享但同一时间只允许一个进程访问的资源\n互斥：一组并发进程由于共享某些资源，导致它们的临界区不允许交替执行\n同步：一个进程的输出作为另一个进程的输入，导致它们有执行的先后顺序\n临界区三大要求：互斥、空闲让进、有限等待\n互斥软件解法(面包店算法)：进程先取号，在已经取号的进程里面，根据(取得的号，进程id)元组从小到大的顺序访问临界资源\n互斥硬件解法(原子操作TestAndSet())\n信号量的两种实现：\n自旋锁：进程会循环申请锁，出现忙等现象，这在多处理器环境或只需要短时间占有锁的情况下是有益的，不会导致频繁的切换上下文导致巨大开销 非自旋锁：进程如果无法申请锁，则会进入对应的阻塞队列并阻塞，在单处理器情况下是常用的 互斥信号量的P操作必须紧贴临界区，V操作无所谓\n读者写者问题：可同时读-读、不可同时读-写、写-写 定义读信号量S、写信号量X、共享变量r_cnt表示读者个数\nc// 读者 while (1) { P(S); ++r_cnt; if (r_cnt == 1) {\t// 此前没有读者, 需要保证当前没有写者 P(X); } V(S); /* 读取操作 */ P(S); --r_cnt; if (r_cnt == 0) {\t// 现在没有读者了, 释放写锁 V(X); } V(S); } // 写者 while (1) { P(X); /* 写操作 */ V(X); }类似这种A类进程间有互斥关系、A和B类进程间有互斥关系、B类进程间没有互斥关系，需要用共享变量统计B类进程的个数并用信号量维护 同理，如果A类进程间也没有互斥关系，也要用一个共享变量和信号量统计并维护A类进程个数 然后用一个互斥信号量维护资源的个数\n进程死锁 四大方式：死锁预防、死锁避免、死锁检测、死锁恢复 死锁的根本原因：资源不足 死锁预防是预防发生死锁的四大必要条件：互斥、占有并等待、不可抢占、循环等待 解决占有并等待：进程必须一次性获取所有资源才可分配，即静态资源分配 解决不可抢占：如果进程申请资源失败，则要释放所有已占有的资源 解决循环等待：对不同类资源进行编号，进程按照资源编号递增的顺序申请资源，即有序分配资源 死锁避免：进程申请资源分配时，动态执行银行家算法判断分配后是否仍属于安全状态，若属于则允许分配 进程需要事先说明最大所需资源数 现代操作系统的做法是：允许进入死锁，及时进行死锁检测并恢复 死锁检测：画出资源分配图看进程之间是否存在环，实际上就是执行银行家算法 死锁恢复：终止引起死锁的进程或剥夺其占有的所有资源 内存管理 绑定内存地址时机 重定位：将逻辑地址转化为物理地址的过程 编译时期绑定：内存位置已知，可执行文件中存放的地址就是内存中的地址 装入时期绑定：内存位置未知，可执行文件中存放的地址是逻辑地址，加载到内存时才进行重定位，转化为物理地址 运行时期绑定(动态地址重定位)：内存位置未知，可执行文件中、装入内存后的地址均为逻辑地址，只在执行指令时通过基地址寄存器BR和程序虚拟地址寄存器VR(文件中存储的逻辑地址)算出 存储管理方案 单一连续分配：出现在单道程序操作系统中，不需要考虑多个进程同时装入内存 分区管理方案 固定分区：存在内部碎片和外部碎片 动态分区：存在内部碎片 页式管理方案：将内存分帧、进程空间分页，页和帧大小一致且固定 只在进程的最后一页可能存在内部碎片 不利于进程共享代码 对用户来说，进程的空间是一维线性空间，只需要提供一个逻辑地址就可以由硬件自动完成映射 优化页表访问：TLB，即Translation Lookaside Buffer，地址转换后备缓冲 段式管理方案：支持由用户观点划分程序段，相当于将大进程分为多个小进程进行动态分区管理 存在外部碎片 对用户来说，进程的空间是二维线性空间，用户需要同时提供段号、段内偏移 优化段表访问：快表 段页式管理方案：在段式的基础上，将段内偏移分为了页号、页内偏移 先通过段表地址寄存器访问段表，获取段号对应的页表位置；再访问页表获取页号对应的页面号；再加上页内偏移计算出物理地址 动态存储空间分配 最先适配法：按可用空间的起始地址从小到大匹配 最好适配法：按可用空间的大小从小到大匹配，容易出现极小的外部碎片，每次都要重新调整可用表 最坏适配法：按可用空间的大小从大到小匹配，每次都要重新调整可用表 虚拟内存 实现方式：按需调页和按需调段 页表每个页添加有效位表示是否有对应的物理页面，访问无效页时，发生缺页中断 访问无效页时的缺页中断处理时，如果在内存中不存在空闲的页面，则进行页面替换，页面替换算法有： FIFO：淘汰最先进入内存的页 存在Belady现象，即内存页面数增加时反而导致缺页次数增加的现象 OPT：淘汰在未来，最久不会被使用的页 这个算法是最优的，但无法实现 LRU：最近最少使用，即淘汰内存中最久没被使用过的页，实现为每次命中时将页移至队尾，淘汰时替换掉队头的页 利用局部性原理，根据调用历史模拟OPT算法 LRU近似算法——二次机会算法(Clock)：所有页添加引用位(初始为1)，淘汰时循环检查队列，若引用位为1则忽略并置0(给予二次机会) LRU近似算法——增强型二次机会算法(E-Clock)：添加脏位，循环检查队列，先找vis=0,dirt=0的页 若找不到，则找vis=0,dirt=1的页，将所有遇到的页vis置0 若仍找不到，则再找vis=0,dirt=0的页，由于第二部将vis置0，故一定能找到一页 用户进程内存分配： 固定分配(平均或比例分配)、优先级分配 颠簸现象：进程没有足够的帧时，页可能会频繁地在内存、外存之间调度 解决方法：增加物理内存页面数，修改页面替换算法，降低多道程序数量 内核进程内存分配： Buddy系统：固定分区分配，分区大小一定为2的幂，最小分区是一页 Slab系统：以slab为最小单位 文件系统 文件系统层次 用户接口 目录系统 虚拟文件系统(VFS)：统一的文件系统访问接口 磁盘调度系统 文件概念 文件系统功能：按名存取、文件共享 逻辑结构：无结构文件(流式文件)、有结构文件(记录式文件) 文件访问方法：顺序存取(磁带)、随机存取(磁盘)、按键存取(索引文件) 目录系统 单级目录、双级目录(主文件目录、用户文件目录)、树状结构目录、无环图结构目录 树状：支持文件分组、快速检索 空间分配方法(文件的物理结构) 连续空间分配、链接式分配、索引分配(在链接式分配的基础上，将所有指针存放在一个物理块中，称为索引表) 文件存储空间通过空闲文件目录或空闲块链或位示图管理 磁盘调度系统 FCFS SSTF：最短寻道时间优先，类似OPT，最好但难以实现 SCAN：又称电梯算法，按固定方向扫描并处理沿途请求，到达另一端后反向扫描，以此循环 C-SCAN：按固定方向扫描并处理沿途请求，到达一端后立刻返回另一端(时间忽略不计)，继续沿原有方向扫描，等待时间更均匀 LOOK和C-LOOK：记录请求柱面号的最小值和最大值，与SCAN和C-SCAN的区别是不会移动到磁盘的尽头而是移动到最小值或最大值 I/O设备管理 中断分类 硬中断： 外部中断(中断)：DMA申请的中断、用户发起的中断等 内部中断： 陷阱：有意的中断，如系统调用 故障：可恢复的错误引起的中断，如缺页中断 终止：不可恢复的错误引起的中断，如除以零 软中断 ","permalink":"https://echo.ishian.me/2025/06/11/os-review/","summary":"操作系统期末复习提纲，梳理启动流程、进程内存文件设备管理与同步等核心考点","title":"操作系统: 期末复习"},{"content":"概述 点分治适用于处理类似问题：需要维护/查询树中任意两点的路径长度(不含平行边) 其输出往往不是给出两点求距离，而是给出距离查询是否存在这样的路径长度/小于该距离的路径数量等等 因为所有路径都可通过经过/不经过某点来分类，点分治是比一般暴力更优的借助分治思想的另类暴力 (朴素暴力法)：对每个结点都作为起点进行遍历，每次遍历O(n)O(n)O(n)，因此总共O(n2)O(n^2)O(n2) 算法结构 考虑一棵带权无根树，即任意结点均可作为根结点且不影响答案，任选一根结点rrr 则任意两点的路径可分为经过rrr、不经过rrr的两类路径 经过rrr的路径分为以rrr为起点、不以rrr为起点(也不为终点)两类路径；所有不以rrr为起点的路径可由前者排列组合求得(并不是前者所有路径的排列组合，两条路径途径结点必须没有交集才可合并为新的经过rrr的路径) 所有不经过rrr的路径可由递归算法(其新的根结点为原根结点的孩子，且计算时删去原根结点)求得 因此仅需要考虑经过rrr的路径如何计算\n上述给出了分治的雏形(递归求解)，为了使分治不退化至O(n)O(n)O(n)的深度，一般分治需要使分割点尽量在序列中间；点分治也同理，需要使分治中心：根结点为树的重心，以尽量达到O(log⁡n)O(\\log n)O(logn)的递归深度 寻找重心过程：详见6.6_树的重心，由于需要多次查询重心、且每次查询的总结点数将变化，需要改动：\n添加visited数组，标识“某结点被删除” 求子树的重心时，总结点数变为size[son] 合并过程：关于所有经过rrr但不以rrr为起点的路径，先记录所有向下结点至根结点的距离(同时记录所属的子树)，合并时如若所属子树不同直接相加即可\n一般可用桶/哈希表来记录路径，并支持在线查询(预处理后常数时间得到答案) 也可以用离线方式处理一批查询(对每批查询都使用一次点分治) 先对这批查询排序，然后使用双指针遍历一批路径(指计算完经过rrr的所有路径后遍历)，更新布尔数组即可 为了方便排序+查询其所属子树，不宜对深搜时得到的路径数组进行改动，需要额外的数组再存储一遍路径(通过存结点编号来存路径，排序时修改排序函数即可)\nc\u0026#43;\u0026#43;// 采用链式前向星存树(边编号为0表示空) // 以\u0026#34;查询是否存在长为K的路径\u0026#34;为例 bool visited[VN], queryAns[QN]; // 已访问结点、查询结果Ans int size[VN], w[VN], center, // 树大小、树大小最大值、重心 n, qn, queryK[QN], // 现在遍历子树的总结点数、查询个数、查询值K dis[VN], tmp[VN], cntTmp, // 一批路径个数不多于总结点数、tmp作为将路径排成连续序列的副本(存储编号来代表路径) from[VN]; // 所属子树 // 寻找当前树重心(赋值给center), O(n) void getCenter(int rt, int fa, int curN) { size[rt] = 1; w[rt] = 0; for (int i = head[rt]; i; i = edge[i].next) { if (edge[i].to != fa \u0026amp;\u0026amp; !visited[edge[i].to]) { getCenter(edge[i].to, rt, curN); size[rt] += size[edge[i].to]; w[rt] = max(w[rt], size[edge[i].to]); } } if (!center) { w[rt] = max(w[rt], curN - size[rt]); if (w[rt] \u0026lt;= (curN \u0026gt;\u0026gt; 1)) { center = rt; } } } // 获取所有向下结点离center的距离, O(n) void getDistance(int rt, int fa) { for (int i = head[rt]; i; i = edge[i].next) { if (edge[i].to != fa \u0026amp;\u0026amp; !visited[edge[i].to]) { dis[edge[i].to] = dis[fa] + edge[i].w; tmp[cntTmp++] = edge[i].to; // 也可用tmp[0]代替cntTmp, 大差不差 from[edge[i].to] = from[rt] ? from[rt] : edge[i].to; getDistance(edge[i].to, rt); } } } // 对离center距离进行合并+处理查询 void calc() { int left, right; cntTmp = 0; // 部分题解在遍历子树时调用getDis(方便确定所属子树), 其实对getDis稍作改动即可简化 dis[center] = 0; // 用来表示以center为起点的路径 tmp[cntTmp++] = center; getDistance(center, 0); sort(tmp, tmp + cntTmp, [](const int l, const int r) -\u0026gt; bool { return dis[l] \u0026lt; dis[r]; }); for (int i = 0; i \u0026lt; qn; ++i) { l = 0; r = cntTmp - 1; if (!queryAns[i]) { // 已有查询结果的不用查 while (l \u0026lt; r) { // 双指针遍历 // 前两个分支查以center为起点(和过大则右指针左移) if (dis[tmp[l]] + dis[tmp[r]] \u0026gt; queryK[i]) { --r; } else if (dis[tmp[l]] + dis[tmp[r]] \u0026lt; queryK[i]) { ++l; } // 和相等又分两种, 可以合并/不可合并 else if (from[tmp[l]] == from[tmp[r]]) { // 不可合并 if (dis[tmp[l]] == dis[tmp[l + 1]]) ++l; // 两分支顺序无要求 else --r; } else { // 可合并 queryAns[i] = true; break; } } } } } // 递归进行所有子树的calc() void solve(int rt, int curN) { int tmpN; calc(); // 计算经过center的路径 visited[rt] = 1; // 表示center已计算 for (int i = head[rt]; i; i = edge[i].next) { // 子树比重心更大, 说明在原来的有根树中子树是重心的向上子树, 因此用树大小减去rt大小 tmpN = size[edge[i].to] \u0026gt; size[rt] ? curN - size[rt] : size[edge[i].to]; getCenter(edge[i].to, 0, tmpN); solve(center, tmpN); } } int main() { cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m \u0026gt;\u0026gt; q; // 输入略 getCenter(1, 0, n); // 计算整棵树重心 solve(center, n); // 输出略 } ","permalink":"https://echo.ishian.me/2025/06/11/algo-tree-point-dac/","summary":"讲解点分治处理树上路径统计问题的分治结构、找重心与合并过程","title":"树的算法: 点分治"},{"content":" 文件是操作系统在外部持久性存储设备上存储信息的基本单位 文件的逻辑结构： 流式文件(无结构)：在OS中看作字节流或字符流，通常用于OS支持的文件系统中 记录式文件(有结构)：由固定格式的记录组成，通常用于DBMS中 记录的存储方式有： 顺序逻辑结构：文件中的连续记录即为文件的内容 索引逻辑结构：用一个顺序文件作为索引文件，对记录进行索引 索引顺序逻辑结构：用一个顺序文件作为索引文件，对记录组进行索引；每个记录组内顺序查找 文件的访问模式： 顺序访问：如编译器访问代码文件，磁带、磁盘均支持 直接访问(随机访问)：可按地址直接访问文件内数据，磁盘支持 索引访问： 磁盘分区结构： MBR(Master Boost Record，主引导记录) GPT(GUID Partition Table，全局唯一标识分区表) 文件系统： FAT NTFS(New Technology FS) EXT 文件的目录结构：文件/目录是按名访问的 一级目录结构：仅用一个线性表存储文件的属性，多个文件容易重名 二级目录结构：第一级为主文件目录(每个目录项为第二级目录的索引)，第二级为用户的文件目录，用户仅能查看自己的文件目录 解决多个用户协作时文件的私有性问题和文件名混乱问题 但单个用户的文件仍容易重名、过多 树形目录结构：每个目录可包含多个子目录 非循环图目录结构：不同目录的目录项允许指向同一文件/目录，但整体目录结构无环 文件系统设计： 分区/目录/文件的存储(核心问题)： 磁盘数据结构：保证文件系统数据在断点后仍完整保存 BCB(Boot Control Block，启动控制块)：包含加载FS的代码 分区控制块(例如Superblock超级块)：包含分区信息、文件系统的元信息 FCB(文件控制块)：存储文件/目录的元信息 文件的具体数据 内存数据结构：为了提速 文件系统加载表(fstab)：将超级块信息读入内存 目录缓存：将常用目录项读入内存，加快路径解析速度 全系统打开文件表 进程打开文件表 目录实现： ","permalink":"https://echo.ishian.me/2025/06/10/os-filesystem/","summary":"介绍操作系统文件的结构、访问模式、目录结构与文件系统设计","title":"操作系统: 文件系统"},{"content":"主存管理 地址绑定：将逻辑地址和物理地址绑定\n编译时绑定： 装入时绑定(静态重定位)： 运行时绑定(动态重定位)： 内存分配：为进程分配内存空间(以下管理方式指的是逻辑地址的管理方式)\n内存碎片问题：\n内部碎片： 外部碎片： 连续内存分配：通过分配连续的物理地址表示连续的逻辑地址\n固定分区分配 动态分区分配： First-Fit(首次适配)分配算法： Best-Fit(最佳适配)分配算法： Worst-Fit(最差适配)分配算法： 页式管理及其分配：\n定义：逻辑地址分页(一页大小固定，系统确定)，物理地址分块(与页大小一致)，连续的逻辑页对应的物理地址块不一定连续 并通过页表映射逻辑/物理地址 MMU(Memory Management Unit，内存管理单元)：为每个进程都分配较大逻辑地址空间(但实际物理地址较小)，将虚拟地址通过页表转换为物理地址 TLB(Translation Lookaside Buffer，地址转换后援缓冲器)：因引入分页机制导致访存效率降低，引入TLB缓存加快翻译速度 多级页表：相比单级页表，可降低页表所占空间 哈希页表：当任务占用较少的虚存时较为合适，但可能造成哈希冲突引起额外处理 反置页表：当逻辑地址空间远大于物理空间时，通过增加翻译花销，降低页表所占空间 段式管理及其分配：\n定义：逻辑地址根据程序的逻辑意义(人为管理)分段，物理地址根据段大小分配，连续的逻辑段对应的物理地址段不一定连续 段页式管理：\n内存保护：保证一个进程只能访问自己的内存空间、以及对共享内存的保护\n内存扩充方式：\n覆盖： 交换：例如进程任务的挂起和取消挂起 虚存管理 程序的局部性原理 按需分配 页的置换： FIFO OPT LRU 惰性加载 ","permalink":"https://echo.ishian.me/2025/06/03/os-memory/","summary":"整理操作系统内存管理：地址绑定、连续分配与页式、段式、段页式管理及虚存保护","title":"操作系统: 内存管理"},{"content":"Java数据库编程 传统JDBC编程 Driver和DriverManager和Connection JDBC由各种数据库支持，提供各种包下的Driver类(例如com.mysql.cj.jdbc.Driver)，它们实现了java.sql.Driver接口\nDriver接口是驱动类，所有实现了这个接口的驱动类会在静态加载时将其本身注册到DriverManager类中(用一个写时拷贝的CopyOnWriteArrayList管理)\nDriverManager接口是驱动的管理器，用于创建一个连接\nConection是用于管理一个数据库连接的\njavaClass.forName(\u0026#34;数据库驱动主类名\u0026#34;); Connectoin conn = DriverManager.getConnection(\u0026#34;连接url\u0026#34;); PreparedStatement PreparedStatement是预编译的语句 JPA Spring JDBC Spring Data JPA Spring Boot与MyBatis Spring Data JDBC ","permalink":"https://echo.ishian.me/2025/06/02/java-jdbc/","summary":"介绍传统 JDBC 及 JPA、Spring JDBC、MyBatis 等数据库访问方式","title":"Java: JDBC编程"},{"content":"笛卡尔树 笛卡尔树中的笛卡尔指的是二元组，笛卡尔树针对第一元素是一棵二叉搜索树、针对第二元素是一个最大/最小堆；由二叉搜索树的性质，当第一、第二元素都不重复时，笛卡尔树是唯一的 根据定义，笛卡尔树其实就是一个树堆的特例；平常使用中，通常第一元素是数组下标、而第二元素是数组值，对应到树堆就是数组下标是键，数组值是优先级 也就是说，数据中给出的数组值需要是比较随机的，否则笛卡尔树就退化成链表了\n笛卡尔树的用处：比较小众，适用于维护连续区间值的问题，例如：\n需要快速知道每一个元素aia_iai​扩展出去的连续区间[l,r][l,r][l,r]，区间满足i∈[l,r]i\\in[l,r]i∈[l,r]且aj≤ai,j∈[l,r]a_j\\le a_i,j\\in[l,r]aj​≤ai​,j∈[l,r] 这样的需求可以用两次单调栈得到每个元素的l、rl、rl、r，也可以建一个最大堆笛卡尔树来维护 需要维护区间最值，当然可以用线段树维护，但也可以用笛卡尔树(转化为LCA问题，[l,r][l,r][l,r]的最值就是alca(l,r)a_{lca(l,r)}alca(l,r)​) 树的构造：和树堆类似，以最小堆笛卡尔树举例，维护一个栈顶元素始终最大的单调栈，从栈底到栈顶表示一条由根结点到最右结点的链，即栈顶元素表示当前树的最右结点 由于第一元素通常是数组下标，所以从左到右插入元素即可 每插入一个元素aia_iai​，需要在单调栈中找到第一个比aia_iai​小的结点，然后使aia_iai​成为该结点的右儿子，如果它本身就有右儿子，则让整棵右子树成为aia_iai​的左子树即可 此外，如果栈为空，则当前结点将成为根结点 由于每个元素最多进出栈一次，所以建树是O(n)O(n)O(n)的 实际上对应于树堆的建树过程，这就相当于先对数组值排序再O(n)O(n)O(n)建树，而笛卡尔树中，数组下标恰好是自然有序的\nc\u0026#43;\u0026#43;// p为原数组, 范围[1, n]; tr[][2]为树, rt为根结点编号 stack\u0026lt;int\u0026gt; stk; rt = 1; stk.push(1); // 插入首元素 for (int i = 2; i \u0026lt;= n; ++i) { while (!stk.empty() \u0026amp;\u0026amp; p[stk.top()] \u0026gt; p[i]) { stk.pop(); } if (stk.empty()) { // 说明该结点是根结点 tr[i][0] = rt; // 原来的根结点作为左儿子 rt = i; } else { tr[i][0] = tr[stk.top()][1]; // 使原本的右儿子成为当前结点的左儿子 tr[stk.top()][1] = i; // 使当前结点成为栈顶结点的右儿子 } stk.push(i); // 当前结点成为栈顶 } 例题 [TJOI2011] 树的序 本题题意言简意赅，就是要你根据它给的顺序建BST，然后输出其前序遍历；然而一般的BST建树时极其容易退化为O(n2)O(n^2)O(n2)，所以考虑用构造笛卡尔树的方式建树 考虑建成之后，结点的键值kkk是构成BST的，且所有父亲结点的插入时间都比其儿子结点的插入时间早，构成最小堆；因此考虑二元组\u0026lt;k,t\u0026gt;\u0026lt;k,t\u0026gt;\u0026lt;k,t\u0026gt;建笛卡尔树，因为k∈[1,n]k\\in[1,n]k∈[1,n]且不重复，故将其看作数组下标即可 ","permalink":"https://echo.ishian.me/2025/05/30/algo-descartes-tree/","summary":"讲解笛卡尔树的定义、性质、单调栈O(n)建树方法及其在连续区间问题中的应用","title":"数据结构: 笛卡尔树"},{"content":"","permalink":"https://echo.ishian.me/2025/05/24/java-reflection/","summary":"介绍 Java 反射 API 的学习笔记","title":"Java: 反射API"},{"content":"WSL2 基本介绍 WSL全称Windows Subsystem for Linux，是2016年发布的适用于Windows的Linux子系统 通常在一个电脑上，通过装虚拟机软件会比装两个系统更方便，例如安装VMWare或VirtualBox，它们能在更广泛的系统上安装其它子系统，例如在MacOS里安装Windows子系统(通过提供一个镜像文件)，而WSL只适用于在Windows里安装WSL支持的Windows子系统，且不提供GUI 但WSL有它独有的优势，它的速度更快、配置更简单，且启动极快 实现原理： wsl本身不带Linux内核，仅仅是作为表示层/翻译层，将Linux命令翻译成Windows兼容的内核命令 性能极高(因为只进行翻译)，但仅兼容ELF可执行文件，不支持Docker等 wsl2具有完整内核，是通过Hyper-V实现的轻量级虚拟机，同样与Windows深度集成，在Linux内部使用虚拟硬盘性能较高，但访问Windows文件系统(/mnt/)时性能较低 兼容所有的Linux软件 VMWare等重量级虚拟机软件：完全模拟计算机硬件，需要完整启动BIOS、完整的上下文切换和硬件模拟，因此性能低，但在网络配置上自由度高 WSL配置 安装wsl：一键式安装\nsh# 管理员powershell(会将wsl2作为默认版本) # 安装后需要重启电脑 wsl --install # 重启后会安装默认的Ubuntu20发行版并要求创建新的用户名和密码 安装wsl：手动配置\nsh# 管理员powershell # 启用wsl dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart # 启用虚拟机平台 dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart然后安装WSL2 Linux kernel update package for x64 machines 最后在powershell执行启用wsl2\nshwsl --set-default-version 2 安装Linux发行版：在Microsoft Store中搜索即可 或通过命令行安装：\nsh# 查看Microsoft Store里的所有发行版 wsl --list --online # wsl -l -o # 安装对应发行版(实例名称为默认的发行版名称) wsl --install -d \u0026lt;Distro-name\u0026gt; WSL常用命令 wsl -l -v/wsl --list --verbose：查看已安装的虚拟机实例的状态 wsl --set-default \u0026lt;Distro-name\u0026gt;：设置指定的虚拟机实例为默认虚拟机 wsl：启动并进入默认虚拟机实例 wsl --shutdown：关闭所有虚拟机 wsl --unregister \u0026lt;Distro-name\u0026gt;：删除虚拟机实例 wsl -d \u0026lt;Distro-name\u0026gt;：启动并进入指定虚拟机实例 wsl \u0026lt;command\u0026gt; [-d \u0026lt;Distro-name\u0026gt;]：在虚拟机实例中执行指定的命令(执行结束后退出) wsl --export \u0026lt;Distro-name\u0026gt; \u0026lt;Filename\u0026gt;：导出指定发行版为指定的文件 wsl --import \u0026lt;Distro-name\u0026gt; \u0026lt;InstallDir\u0026gt; \u0026lt;Filename\u0026gt;：导入指定的归档文件为指定名称的虚拟机实例，其中InstallDir为该虚拟机所挂载的目录 ","permalink":"https://echo.ishian.me/2025/05/19/wsl/","summary":"介绍WSL2的原理、与虚拟机软件的对比及安装配置方法","title":"WSL2: Windows下极其轻量的Linux虚拟机"},{"content":"Java多线程编程 Runnable接口 所有类，如果希望让它单独作为一个线程运行，可以继承Thread类并重写void run()方法 所有类，如果希望让它单独作为一个线程运行，更常用的方法是实现Runnable接口以便用多态特性被调用 Runnable接口要求实现void run()方法，当这个类作为线程运行时，运行的就是这个run()方法 使用这种方法，实现了Runnable接口的类称为线程任务对象，传递给Thread运行，创建的Thread对象称为线程对象 Thread类 Thread的常用构造器： Thread()：创建空线程 Thread(Runnable task, String name)：将实现了Runnable接口的方法引用传递给它，并命名为name java线程包括七个状态： New：新建的线程，尚未执行 Runnable：正在执行run()的线程 Blocked：因阻塞而被挂起的线程 Waiting：因某些原因在等待的线程 Timed Waiting：因主动调用Thread.sleep()而计时等待的线程 Terminated：终止的线程，run()因各种原因而结束 start()：启动一个线程，使其开始以另一个线程的形式执行run() 在主线程直接调用某个线程类的run()无法达到多线程的效果，必须通过start()在JVM中登记 join()：使当前线程等待调用该方法的线程实例结束，再继续运行 join(long)：使当前线程仅等待有限时间，其它同上 interrupt()：使该线程的中断标志位置1，可以通过isInterrupted()循环检查，实现中断线程的效果 但interrupt()并不立刻生效，仅仅是发出一个中断请求 而且当外部线程调用该线程的interrupt()方法时，若该线程处于等待状态(例如调用join()或sleep())，则join()或sleep()等会抛出InterruptedException异常 volatile关键字：由于JVM的内存模型，在线程修改共享变量时不会立刻写回主内存 而volatile修饰的变量则会使JVM在读取时总是读取最新值、写入时总是立刻写入 但volatile并不保证原子性，在读写含有多个字段的volatile变量时可能会有问题 setDaemon(true)：一个线程默认是非守护线程，JVM进程会等待所有的非守护线程结束后再结束 但一些线程是无限循环的，可以调用setDaemon(true)将它们设置为守护线程，JVM进程结束时不会关心它们是否结束 守护线程不能占有任何需要显式关闭的资源，守护线程本身无法保证这些资源能在JVM进程结束时关闭 传统线程同步 不同线程在读写同一份资源、或需要相互协作时，就需要考虑线程同步问题 除非资源是只读的，例如不可变类型，则不需要考虑线程同步 大部分标准库中的类为了提高性能，都是非线程安全的，涉及到非线程安全的读写操作时，必须手动添加线程同步代码以保证线程安全\nsynchronized关键字：\n作用于对象时，会对该对象加锁 修饰实例方法时，等价于作用于this 修饰静态方法时，等价于作用于所在类的class实例 一般不用该关键字修饰方法，因为会导致加锁混乱、不明确且在很多情况会使两个本不冲突的方法变为冲突 使用synchronized的代码块无法并发执行，且加锁解锁有额外开销 对某对象加锁，不代表其它线程就无法访问该对象，如果一个线程对该对象加锁而另一个线程并不这样做，则仍存在线程同步问题 JVM的基础原子操作：除long、double以外的任意变量的赋值操作 涉及多行的赋值操作时，仍需要用synchronized修饰代码块\n可重入锁：JVM允许同一个线程重复获取同一个锁，其本质是一个信号量，进入/退出synchronized代码块使信号量加/减1\n死锁的必要条件：互斥、不可抢占、占有并等待、循环等待，只要破坏其中一种条件即可避免死锁\n破坏循环等待：所有线程获取同一组锁的顺序保持一致，这是最简单的一种方案 互斥不可破坏，破坏不可抢占可能导致混乱，破坏占有并等待必须一次性分配需要的资源、资源利用率低且可能导致饥饿 继承自Object的wait()：可使该线程暂时放弃调用wait()的对象锁，进入等待状态直至被唤醒，唤醒后立刻尝试重新请求这个对象锁 需要注意这不是作用于线程类对象的，而是作用于被加锁的资源\n继承自Object的notify()和notifyAll()：可随机唤醒某一个等待该资源的线程/唤醒全部等待该资源的线程 同上，这两者作用于被加锁的资源，通常后者更安全\nnotify()可能会唤醒同类线程，导致活锁或死锁，例如生产者消费者问题： 两个消费者阻塞→\\rightarrow→生产者P1唤醒消费者C1→\\rightarrow→两个生产者阻塞→\\rightarrow→消费者C1消耗资源，但唤醒同类消费者C2→\\rightarrow→两个消费者阻塞，至此所有线程均阻塞，造成死锁 使用notifyAll()，至少能唤醒一个非同类线程，而其它同类线程应该继续等待，所以**wait()应该在循环**里而非if语句块中 JVM线程同步原理是对象监视器，Object的wait()和notify()相当于每个变量都可以作为信号量的封装，由于**wait()使线程可以短暂释放已获得的锁**，使其不需要像操作系统课程上讲的那般麻烦(需要互斥信号量与同步信号量且互斥信号量的PV操作紧贴临界区)，而是使同步信号量围绕着互斥资源通过wait()和notify()进行同步 例如在生产者消费者问题中，互斥资源的空/满可以化作while中的条件，充当同步信号量\njava// 一个线程同步的生产者消费者队列类 private Queue\u0026lt;Integer\u0026gt; q; private int size; private static final MAX_SIZE = 10; // Consumer synchronized (this) { while (size == 0) { // Condition: Empty wait(); } e = q.poll(); // consume --size; notifyAll(); } // Provider synchronized (this) { while (size == MAX_SIZE) { // Condition: Full wait(); } q.offer(e); // provide ++size; notifyAll(); } java.util.concurrent.locks与Semaphore synchronized的加锁机制是悲观锁、重量级锁、阻塞的，这种锁适合竞争激烈的多线程同步场景，线程先必须获得锁(进入或退出Monitor)才能读写对象\njava.util.concurrent是Java 5开始提供的高级并发包，以下是需要提前了解的一些概念\n乐观锁：线程无需获取锁地尝试修改，在修改时检查是否冲突 理念是估计在读过程中不会有其它线程在写 乐观读锁在读多写少的场景中好处显而易见：一是乐观读锁不排它，减少写锁饥饿的情况；二是因为写少，乐观读大概率成功而减少了悲观锁的阻塞开销 CAS(Compare And Swap或Compare And Set)机制是实现乐观锁的一种方式：线程查询内存中的值和此前读取的值是否相同，若相同则更新，否则失败 若失败则读取内存中的值，循环地进行CAS直至更新成功 CAS机制存在ABA问题，即线程T1尝试通过CAS读写时，虽然内存值和此前读取的值一致，但这个内存值A可能被另一个线程先改为B再改为A，在T1看来没有改变过的资源实际上被其它线程更改过 因此实现上对资源的更改会添加时间戳/版本号 CAS机制有时会配合自旋的机制： 自旋锁优点在于可避免不必要的上下文切换开销，缺点在于循环导致的CPU忙等 自旋锁仅在CPU多核的并行处理场景中，线程能在忙等中获取其它线程释放的资源时才有效 接下来从完全悲观到完全乐观地介绍java.util.concurrent提供的线程同步机制 Lock接口：可以替代synchronized，是一种显式锁，由代码层面而非语法层面实现加锁和解锁，其实现类是对synchronized的封装 Lock是悲观锁\nlock()：显式加锁 unlock()：需要在finally块中解锁 tryLock()和tryLock(long, TimeUnit)：Lock支持非阻塞获取锁或有限忙等地获取锁，前者仅尝试一次、后者在有限时间内循环尝试 返回true表示获取成功 newCondition()：返回一个Condition对象，Lock支持多条件锁，与synchronized单锁相区别 ReentrantLock是Lock的实现类之一，译为“可重入锁” 支持公平锁，在构造时传递true即可 公平锁即每次向等待队列加入新的线程时，它无法插队，保证每次获取锁的线程是队列中等待时间最长的线程 Condition接口：表示某条件的等待队列，如上文传统的线程同步代码中，由于空/满这两种条件使不同类线程在同一等待队列中，因此会出现死锁现象而必须用notifyAll()来唤醒非同类线程 而Condition和Lock允许在一个锁对象上分配多个不同条件的等待队列，能保证每次唤醒都能唤醒非同类线程\nawait()：使线程在该条件上短暂地释放锁，进入等待状态，被唤醒后重新尝试获取 await(long, TimeUnit)：可以在有限时间内地等待 signal()：唤醒在该条件上等待的某个线程 javaprivate Queue\u0026lt;Integer\u0026gt; q; private int size; private static int MAX_SIZE = 10; private Lock lock = new ReentrantLock(); private final Condition notempty = lock.newCondition(), notfull = lock.newCondition(); // Consumer lock.lock(); try { while (size == 0) { notempty.await(); } e = q.poll(); --size; notfull.signal(); } finally { lock.unlock(); } // Provider lock.lock(); try { while (size == MAX_SIZE) { notfull.await(); } q.offer(e); ++size; notempty.signal(); } finally { lock.unlock(); } ReadWriteLock接口：有时排它锁过于重量，在多读少写的场景下，希望允许多个线程同时读，这个接口就用于这种场景\nreadLock()：获取读锁 writeLock()：获取读锁 类的内部会维护，若有线程占有读锁，则不会有线程能占有写锁，反之亦然；若没有线程占有写锁，则允许多个线程占有读锁 虽然它实现了读写分离，但它仍是悲观锁，因此也可能导致需要写锁的线程饥饿 ReentrantReadWriteLock是ReadWriteLock的实现类之一，是可重入锁 AQS框架：\nStampedLock类：Java 8开始提供的乐观读锁和读写分离的悲观锁两者的封装锁，在互斥上可替代ReadWriteLock，但要注意它不是可重入锁、不支持公平锁\nStampedLock和ReadWriteLock一样有readLock()和writeLock()方法，用于获取读锁和写锁 但它们的实现有所区别：它们不返回Lock对象而是在内部就调用了锁的lock()方法，然后返回long类型的版本号stamp 因此需要使用unlockRead(stamp)和unlockWrite(stamp)释放掉这个版本号的读写锁 StampedLock还支持乐观读锁，通过tryOptimisticRead()尝试获取乐观读锁的版本号stamp，然后通过validate(stamp)检验，乐观锁不需要解锁，因为本质上并没有加锁操作 如前文所说，乐观锁不排它，乐观读过程中允许其它线程写，如果检验成功，说明加乐观读锁途中没有线程占有死锁 如果检验不成功则有两种选择：自旋地重复乐观读、悲观读 同Lock一样支持非阻塞或有限阻塞地获取悲观读锁和悲观写锁 支持锁转换： tryConvertToReadLock(stamp)：若stamp有效，原子地，悲观读锁则返回其本身、悲观写锁则释放它并返回悲观读锁、乐观读锁则非阻塞地请求一个悲观读锁 tryConvertToWriteLock(stamp)：若stamp有效，原子地，悲观读锁且写锁空闲则返回写锁、悲观写锁则返回其本身、乐观读锁则非阻塞地请求一个悲观写锁 tryConvertToOptimisticRead(stamp)：若stamp有效，原子地，悲观锁则释放它们并返回乐观读锁、有效的乐观锁则返回其本身 但StampedLock不支持基于条件的线程间协作，因此只适用于单个资源的互斥读写场景 Semaphore：信号量可以被最多N个线程获取，但不支持基于条件的线程间协作，用于复数个资源的互斥获取，它也支持公平锁\nacquire()：阻塞地获取该信号量 release()：需要在finally块中释放 tryAcquire()和tryAcquire(long, TimeUnit)：非阻塞地或有限阻塞地获取信号量 Callable\u0026lt;T\u0026gt;接口与异步线程池 Callable\u0026lt;T\u0026gt;接口：由于Runnable的run()没有返回值且不允许抛出异常，因此Callable\u0026lt;T\u0026gt;诞生了，是单方法接口，包含方法T call() throws Exception java线程池提供了多线程异步的功能，能更好地利用多核资源 所谓同步，即各个任务有一定的执行顺序，一些任务必须等待其它任务完成后，利用其计算结果才能继续运行；异步是各个任务没有执行顺序，任务通过回调函数或其它手段获取其它任务的计算结果，在这段时间内可以执行其它计算 同步异步与线程个数没有必然联系，单线程异步可以通过检测事件循环实现 Future\u0026lt;T\u0026gt;接口是实现多线程异步的核心，表示“能在未来得到计算结果的对象”，包括以下核心方法： get()：获取结果，如果其对应的计算任务尚未完成则会使调用get()的线程进入阻塞 get(long, TimeUnit)：仅等待有限时间地获取结果 cancel(true)：true表示通过发出中断请求来取消任务，若任务尚未开始或线程响应中断请求后则成功取消，返回true，否则返回false cancel(false)：若任务尚未完成则一定能取消成功，但任务可以继续执行直至结束 成功调用cancel()后，isCancelled()返回true，此后get()会抛出CancellationException异常 ExecutorService接口： 任务提交：把任务提交给线程池，异步地执行 Future\u0026lt;T\u0026gt; submit(Callable\u0026lt;T\u0026gt; task)：提交一个计算任务给线程池执行，立刻获得一个Future\u0026lt;T\u0026gt;对象 任务执行：把多个任务提交给线程池，当前线程阻塞，即同步地执行 List\u0026lt;Future\u0026lt;T\u0026gt;\u0026gt; invokeAll(List\u0026lt;Callable\u0026lt;T\u0026gt;\u0026gt;)：执行多个计算任务，当前线程阻塞直至它们全部执行完成，返回值顺序和提交的任务顺序一致 invokeAll()还允许传递时间参数，有限地阻塞 shutdown()：停止接受所有新线程，执行完已提交的线程，然后关闭线程池 shutdownNow()：停止接受所有新线程，尝试中断地取消所有已提交线程，返回所有未开始的任务 创建ExecutorService对象： Executors ThreadPoolExecutor Fork/Join线程池： 其它API java.util.concurrent.atomic中的类基于CAS机制实现无锁的线程同步，使用乐观锁、非阻塞地封装资源 例如AtomicReferencte\u0026lt;T\u0026gt;、AtomicReferenceArray\u0026lt;T\u0026gt;，除此之外还对三个基本数据类型有更全面的封装：AtomicInteger、AtomicLong、AtomicBoolean 如果ABA问题会影响业务逻辑，则应使用AtomicStampedReference或AtomicMarkableReference 以下是AtomicReference一系列常用的原子方法： get()：获取当前值 set(T)：volatile地写入新值 compareAndSet(T ept, T upd)：CAS地更新值，可能失败 getAndSet(T)：获取旧值，写入新值 getAndUpdate()或updateAndGet()：提供单元运算函数更新值，返回更新前/更新后的值 getAndAccumulate()或accumulateAndGet()：提供二元运算函数和加值，返回更新前/更新后的值 线程安全的集合： List的实现类CopyOnWriteArrayList Map的实现类ConcurrentHashMap Set的实现类CopyOnWriteArraySet Queue的实现类ArrayBlockingQueue与LinkedBlockingQueue Deque的实现类LinkedBlockingDeque CompletableFuture ThreadLocal 虚拟线程： ","permalink":"https://echo.ishian.me/2025/05/17/java-multithreading/","summary":"介绍 Java 多线程编程的 Thread、Runnable、线程同步与线程池","title":"Java: 多线程编程"},{"content":"网络编程基础 TCP客户端 Java在客户端提供的TCP套接字的类为Socket Socket构造方法：Socket(String, int)，和提供的服务器IP和端口号绑定 Socket常用实例方法： 如果不用构造方法，也可使用connect()发起TCP连接 getInputStream()和getOutputStream()：获取对应的输入流和输出流对象 其中OutputStream对象应该及时flush() 通常使用InputStreamReader和OutputStreamWriter将字节流转化为字符流并传递指定的字符集处理 TCP服务端 Java在服务端提供的TCP套接字的类为ServerSocket ServerSocket构造方法：ServerSocket(int)，和提供的端口号绑定 ServerSocket常用实例方法： accept()：阻塞式地等待请求，收到请求后返回Socket对象，它和客户端的Socket对象相关联 建议使用try-with-resources语法块构造ServerSocket对象避免调用close() getPort()和getInetAddress()：返回服务器绑定的本地端口号、本地IP地址 getLocalSocketAddress()：返回服务器绑定的IP地址与端口号组成的SocketAddress对象 UDP客户端与服务端 Java在客户端和服务端提供的UDP套接字的类为DatagramSocket DatagramSocket的构造方法同ServerSocket，但是注意，由于TCP和UDP是两种不同的协议，不同协议使用的端口号是互不干扰的，它们的相同点在于使用16位的端口号 DatagramPacket：这个类对象作为数据报的抽象，是数据传输的中介 构造方法：DatagramPacket(byte[], int, InetAddress, int) 和一个byte[]数组绑定，后两个参数可选，和待发送的IP与端口绑定 getData()、getOffset()、getLength()：获取数据、偏移、长度 通常用new String(packet.getData(), packet.getOffset(), packet.getLength(), Charset)解析收到的数据报 setData(byte[])：设置存储的数据 DatagramSocket常用实例方法： receive(DatagramPacket)：收取一个UDP数据报赋值给提供的引用 接收后，这个DatagramPacket对象的目标IP和端口会被替换为发送方的IP与端口 send(DatagramPacket)：发送该数据报到提供的数据报对象指向的IP与端口 connect(InetAddress, int)：使当前的套接字对象绑定一个默认的目标IP及端口，它并不像TCP那样会发起连接，而只是在本地进行记录 连接后，send()发送的DatagramPacket不需要有绑定的目标IP，且接收时会过滤掉这个IP及端口以外的数据报 为了方便，通常在客户端使用connect()过滤掉不需要的包 而服务端自然不需要也不应该设置默认的目标IP，由receive()自动设置即可 disconnect()：删除本地的默认连接 HTTP客户端 直接使用TCP和UDP套接字灵活性很强，但应用层协议需要自己制定，在安全性和性能上没有设计好的协议简易而安全，例如要开发一个网页客户端，使用HTTP协议是最好不过的 但是处理HTTP报文需要检查和解析响应行、响应头、响应体，十分麻烦 早期JDK通过HttpURLConnection访问HTTP，但是不好用，Java 11后提供java.net.http简化开发 HttpClient构造方法：建议通过静态方法创建 HttpClient.newHttpClient()：使用默认配置构建\nHttpClient.newBuilder().build()：使用HttpClient.Builder构建器构建 这种方法是最常用的，可以在build()前链式调用一系列方法自定义配置，例如：\njavaHttpClient client = HttpClient.newBuilder() .version(Version.HTTP_2) // HTTP版本 .followRedirects(Redirect.NORMAL) // 重定向策略 .connectTimeout(Duration.ofSeconds(20)) // 超时时长 .proxy(ProxySelector.of(new InetSocketAddress(\u0026#34;proxy.example.com\u0026#34;, 80))) // 正向代理 .authenticator(Authenticator.getDefault()) // HTTP身份认证 .build(); // 返回由该Builder构造的HttpClient对象其中Redirect与Authenticator是HttpClient的内部枚举，包含一系列重定向策略与身份认证策略\n创建HTTP请求：一个HttpRequest对象代表一个请求报文，通过静态方法HttpRequest.newBuilder()创建构建器，这个构建器包含若干配置方法： uri(URI)：设置目标网络资源 header(String, String)：设置某个请求头的键值 也可以通过headers()一次性设置多个请求头 GET()与POST(BodyPublisher)：设置请求方式，默认是GET()，因此前者通常会被省略 BodyPublishers是工厂类，用于创建BodyPublisher对象，常用方法有ofString()等 处理HTTP响应：一个HttpResponse对象代表一个响应报文，通过HttpClient对象的send()发送请求报文后阻塞式地(同步式地)获取最终响应报文 该类是泛型类，泛型类型表示响应体被解析而成的类型，由send()时提供的BodyHandler参数决定解析类型，例如： HttpResponse\u0026lt;String\u0026gt; resp = client.send(req, BodyHandlers.ofString()) statusCode()获取响应码 headers()：获取只读的响应头集合HttpHeaders previousResponse()：获取前一次响应，用于回溯重定向链 version()、uri()、request()：更多获取信息的方法 异步网络编程 了解RMI RMI(Remote Method Invotation，远程方法调用)是java.rmi提供的远程调用API，仅需了解即可 远程调用十分常用，这使得客户端代码无需有接口的实现类，而只需要声明接口即可正常调用，实现类由服务端提供\n远程方法调用的服务接口(即服务器端)需要继承Remote空接口且所有方法需要抛出受检异常RemoteException，创建实例后，通过UnicastRemoteObject转化为RMI服务，通过Registry注册到本地的端口上 而客户端需要通过Registry的getRegistry(host, port)和lookup(serv_name)获取服务以及服务接口\njava// 服务端: 包含RemoteDemo接口, RemoteDemoImpl实现类 // 在本地1999号端口上启动一个RMI注册表 Registry registry = LocalRegistry.createRegistry(1999); // 将普通接口转化为RMI服务接口 RemoteDemo serv = new RemoteDemoImpl(); RemoteDemo serv_obj = UnicastRemoteObject.exportObject(serv, 0); // 将该RMI服务注册到注册表中 registry.rebind(\u0026#34;Serv_name\u0026#34;, serv_obj); // 客户端: 只包含RemoteDemo接口 // 获取目标IP及端口的注册表 Registry registry = LocalRegistry.getRegistry(\u0026#34;localhost\u0026#34;, 1999); // 获取服务, 需要强制转换 RemoteDemo remoteDemo = (RemoteDemo) registry.lookup(\u0026#34;Serv_name\u0026#34;); ","permalink":"https://echo.ishian.me/2025/05/10/java-net/","summary":"介绍 Java TCP/UDP 套接字、HTTP 客户端与异步网络编程及 RMI","title":"Java: 网络编程基础"},{"content":"常见随机变量分布及其数学特征 离散型随机变量 二项分布X∼B(n,p)X\\sim B(n,p)X∼B(n,p)：可看作nnn个独立同分布的0−10-10−1分布 P(X=k)=(nk)pk(1−p)n−k\\begin{align}P(X=k)=\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)p^k(1-p)^{n-k}\\end{align}P(X=k)=(nk​)pk(1−p)n−k​​，k∈[0,n]k\\in [0,n]k∈[0,n] EX=npEX=npEX=np： ∑k=0nkP(X=k)=nEXi=np\\begin{align}\\sum_{k=0}^nkP(X=k)=nEX_i=np\\end{align}k=0∑n​kP(X=k)=nEXi​=np​​，前者用二项式定理或生成函数证明、后者为nnn个0−10-10−1分布叠加 前者的二项式定理证明： 原式=np∑k=1n(n−1)!(k−1)!(n−k)!pk−1(1−p)n−k=np∑k=0n−1(n−1k)pk(1−p)n−k−1=np\\begin{align}\u0026amp;原式=np\\sum_{k=1}^n\\frac{(n-1)!}{(k-1)!(n-k)!}p^{k-1}(1-p)^{n-k}=np\\sum_{k=0}^{n-1}\\left(\\begin{matrix}n-1\\\\k\\end{matrix}\\right)p^k(1-p)^{n-k-1}=np\\end{align}​原式=npk=1∑n​(k−1)!(n−k)!(n−1)!​pk−1(1−p)n−k=npk=0∑n−1​(n−1k​)pk(1−p)n−k−1=np​​ DX=np(1−p)DX=np(1-p)DX=np(1−p)：由于为nnn个独立的0−10-10−1分布，可以由DXiDX_iDXi​叠加得出 定义证法： EX2=∑k=0nk2P(X=k)=∑k=1nkn!(k−1)!(n−k)!pk(1−p)n−k=∑k=0n−1(k+1)⋅n!k!(n−k−1)!pk+1(1−p)n−k−1=∑k=1n−1kn!k!(n−k−1)!pk+1(1−p)n−k−1+np∑k=0n−1(n−1)!k!(n−k−1)!pk(1−p)n−k−1=n(n−1)∑k=0n−2(n−2)!k!(n−k−2)!pk+2(1−p)n−k+np=n(n−1)p2+np=n2p2−np2+npDX=EX2−(EX)2=np(1−p)\\begin{align}\u0026amp;EX^2=\\sum_{k=0}^nk^2P(X=k)=\\sum_{k=1}^nk\\frac{n!}{(k-1)!(n-k)!}p^k(1-p)^{n-k}\\\\\u0026amp;=\\sum_{k=0}^{n-1}\\frac{(k+1)·n!}{k!(n-k-1)!}p^{k+1}(1-p)^{n-k-1}\\\\\u0026amp;=\\sum_{k=1}^{n-1}k\\frac{n!}{k!(n-k-1)!}p^{k+1}(1-p)^{n-k-1}+np\\sum_{k=0}^{n-1}\\frac{(n-1)!}{k!(n-k-1)!}p^{k}(1-p)^{n-k-1}\\\\\u0026amp;=n(n-1)\\sum_{k=0}^{n-2}\\frac{(n-2)!}{k!(n-k-2)!}p^{k+2}(1-p)^{n-k}+np\\\\\u0026amp;=n(n-1)p^2+np=n^2p^2-np^2+np\\\\\u0026amp;DX=EX^2-(EX)^2=np(1-p)\\end{align}​EX2=k=0∑n​k2P(X=k)=k=1∑n​k(k−1)!(n−k)!n!​pk(1−p)n−k=k=0∑n−1​k!(n−k−1)!(k+1)⋅n!​pk+1(1−p)n−k−1=k=1∑n−1​kk!(n−k−1)!n!​pk+1(1−p)n−k−1+npk=0∑n−1​k!(n−k−1)!(n−1)!​pk(1−p)n−k−1=n(n−1)k=0∑n−2​k!(n−k−2)!(n−2)!​pk+2(1−p)n−k+np=n(n−1)p2+np=n2p2−np2+npDX=EX2−(EX)2=np(1−p)​​ EXk=nEXik=npk\\begin{align}EX^k=nEX^k_i=np^k\\end{align}EXk=nEXik​=npk​​ 泊淞分布X∼P(λ)X\\sim P(\\lambda)X∼P(λ) P(X=k)=λkk!e−λ\\begin{align}P(X=k)=\\frac{\\lambda^k}{k!}e^{-\\lambda}\\end{align}P(X=k)=k!λk​e−λ​​，k∈N+k\\in N^+k∈N+ EX=λ\\begin{align}EX=\\lambda\\end{align}EX=λ​​：经典级数 ∑k=1+∞kλkk!e−λ=λe−λeλ=λ\\begin{align}\\sum_{k=1}^{+\\infty}k\\frac{\\lambda^k}{k!}e^{-\\lambda}=\\lambda e^{-\\lambda}e^{\\lambda}=\\lambda\\end{align}k=1∑+∞​kk!λk​e−λ=λe−λeλ=λ​​ DX=λDX=\\lambdaDX=λ EX2=∑k=1+∞kλk(k−1)!e−λ=λe−λS(λ)∫Sdλ=∫∑k=1+∞kλk−1(k−1)!dλ=∑k=1+∞λk(k−1)!=λeλS=(λ+1)eλ⇒EX2=λ2+λDX=EX2−(EX)2=λ\\begin{align}\u0026amp;EX^2=\\sum_{k=1}^{+\\infty}k\\frac{\\lambda^k}{(k-1)!}e^{-\\lambda}=\\lambda e^{-\\lambda}S(\\lambda)\\\\\u0026amp;\\int S\\mathrm d\\lambda=\\int\\sum_{k=1}^{+\\infty}\\frac{k\\lambda^{k-1}}{(k-1)!}\\mathrm d\\lambda=\\sum_{k=1}^{+\\infty}\\frac{\\lambda^{k}}{(k-1)!}=\\lambda e^{\\lambda}\\\\\u0026amp;S=(\\lambda+1) e^{\\lambda}\\Rightarrow EX^2=\\lambda^2+\\lambda\\\\\u0026amp;DX=EX^2-(EX)^2=\\lambda\\end{align}​EX2=k=1∑+∞​k(k−1)!λk​e−λ=λe−λS(λ)∫Sdλ=∫k=1∑+∞​(k−1)!kλk−1​dλ=k=1∑+∞​(k−1)!λk​=λeλS=(λ+1)eλ⇒EX2=λ2+λDX=EX2−(EX)2=λ​​ 或者： EX2=∑k=1+∞(k−1+1)λk(k−1)!e−λ=e−λ∑k=1+∞(k−1)λ⋅λk−1(k−1)!+λ=λ+e−λλ∑k=0+∞kλkk!=λ+λ2\\begin{align}\u0026amp;EX^2=\\sum_{k=1}^{+\\infty}(k-1+1)\\frac{\\lambda^{k}}{(k-1)!}e^{-\\lambda}\\\\\u0026amp;=e^{-\\lambda}\\sum_{k=1}^{+\\infty}(k-1)\\frac{\\lambda·\\lambda^{k-1}}{(k-1)!}+\\lambda=\\lambda+e^{-\\lambda}\\lambda\\sum_{k=0}^{+\\infty}\\frac{k\\lambda^{k}}{k!}\\\\\u0026amp;=\\lambda+\\lambda^2\\end{align}​EX2=k=1∑+∞​(k−1+1)(k−1)!λk​e−λ=e−λk=1∑+∞​(k−1)(k−1)!λ⋅λk−1​+λ=λ+e−λλk=0∑+∞​k!kλk​=λ+λ2​​ EXn=∑k=1+∞knλkk!e−λ\\begin{align}\u0026amp;EX^n=\\sum_{k=1}^{+\\infty}k^n\\frac{\\lambda^k}{k!}e^{-\\lambda}\\end{align}​EXn=k=1∑+∞​knk!λk​e−λ​​，可以将knk^nkn替换为系数为第二类斯特林数的kkk的下阶幂的和 可递归表示为第二类斯特林数系数结合λ\\lambdaλ的幂、或二项式系数结合低阶期望的形式： EXn=∑k=0n[nk]λk=λ∑k=0n−1(n−1k)EXk\\begin{align}EX^n=\\sum_{k=0}^n\\left[\\begin{matrix}n\\\\k\\end{matrix}\\right]\\lambda^k=\\lambda\\sum_{k=0}^{n-1}\\left(\\begin{matrix}n-1\\\\k\\end{matrix}\\right)EX^k\\end{align}EXn=k=0∑n​[nk​]λk=λk=0∑n−1​(n−1k​)EXk​​ 连续型随机变量 均匀分布X∼U(a,b)X\\sim U(a,b)X∼U(a,b)： f(x)={1b−a,a≤x≤b0,otherwisef(x)=\\begin{cases}{\\Large\\frac1{b-a}},\u0026amp;a\\le x\\le b\\\\0,\u0026amp;\\rm otherwise\\end{cases}f(x)={b−a1​,0,​a≤x≤botherwise​ F(x)={0,x\u0026lt;ax−ab−a,a≤x≤b1,x\u0026gt;bF(x)=\\begin{cases}0,\u0026amp;x\u0026lt;a\\\\{\\Large\\frac{x-a}{b-a}},\u0026amp;a\\le x\\le b\\\\1,\u0026amp;x\u0026gt;b\\end{cases}F(x)=⎩⎨⎧​0,b−ax−a​,1,​x\u0026lt;aa≤x≤bx\u0026gt;b​ EX=∫abxb−adx=b+a2\\begin{align}EX=\\int_{a}^b\\frac{x}{b-a}\\mathrm dx=\\frac{b+a}{2}\\end{align}EX=∫ab​b−ax​dx=2b+a​​​ EXk=∫abxkb−adx=bk+1−ak+1(k+1)(b−a)\\begin{align}EX^k=\\int_{a}^b\\frac{x^k}{b-a}\\mathrm dx=\\frac{b^{k+1}-a^{k+1}}{(k+1)(b-a)}\\end{align}EXk=∫ab​b−axk​dx=(k+1)(b−a)bk+1−ak+1​​​ DX=EX2−(EX)2=(b−a)(b2+ab+a2)3(b−a)−(b+a)24=(b−a)212\\begin{align}DX=EX^2-(EX)^2=\\frac{(b-a)(b^2+ab+a^2)}{3(b-a)}-\\frac{(b+a)^2}4=\\frac{(b-a)^2}{12}\\end{align}DX=EX2−(EX)2=3(b−a)(b−a)(b2+ab+a2)​−4(b+a)2​=12(b−a)2​​​ 指数分布X∼e(λ)X\\sim e(\\lambda)X∼e(λ)： f(x)={λe−λx,x\u0026gt;00,otherwisef(x)=\\begin{cases}\\lambda e^{-\\lambda x},\u0026amp;x\u0026gt;0\\\\0,\u0026amp;\\rm otherwise\\end{cases}f(x)={λe−λx,0,​x\u0026gt;0otherwise​ F(x)={0,x≤01−e−λx,x\u0026gt;0F(x)=\\begin{cases}0,\u0026amp;x\\le0\\\\1-e^{-\\lambda x},\u0026amp;x\u0026gt;0\\end{cases}F(x)={0,1−e−λx,​x≤0x\u0026gt;0​，可以记住 ∫x+∞λe−λxdx=e−λx\\begin{align}\\int_x^{+\\infty}\\lambda e^{-\\lambda x}\\mathrm dx=e^{-\\lambda x}\\end{align}∫x+∞​λe−λxdx=e−λx​​ EX=∫0+∞λxe−λxdx=−∫0+∞xd(e−λx)=−xe−λx∣0+∞+∫0+∞e−λxdx=1λ\\begin{align}EX=\\int_0^{+\\infty}\\lambda xe^{-\\lambda x}\\mathrm dx=-\\int_{0}^{+\\infty}x\\mathrm d\\left(e^{-\\lambda x}\\right)=-xe^{-\\lambda x}\\Bigg|_{0}^{+\\infty}+\\int_0^{+\\infty}e^{-\\lambda x}\\mathrm dx=\\frac1\\lambda\\end{align}EX=∫0+∞​λxe−λxdx=−∫0+∞​xd(e−λx)=−xe−λx​0+∞​+∫0+∞​e−λxdx=λ1​​​ EXk=∫0+∞λxke−λxdx=−∫0+∞xkd(e−λx)=−xke−λx∣0+∞+k∫0+∞xk−1e−λxdx=kλEXk−1由EX=1λ,得EXk=k!λk\\begin{align}\u0026amp;EX^k=\\int_0^{+\\infty}\\lambda x^ke^{-\\lambda x}\\mathrm dx=-\\int_{0}^{+\\infty}x^k\\mathrm d\\left(e^{-\\lambda x}\\right)\\\\\u0026amp;=-x^ke^{-\\lambda x}\\Bigg|_0^{+\\infty}+k\\int_0^{+\\infty}x^{k-1}e^{-\\lambda x}\\mathrm dx=\\frac{k}{\\lambda}EX^{k-1}\\\\\u0026amp;由EX=\\frac1\\lambda,得EX^k=\\frac{k!}{\\lambda^k}\\end{align}​EXk=∫0+∞​λxke−λxdx=−∫0+∞​xkd(e−λx)=−xke−λx​0+∞​+k∫0+∞​xk−1e−λxdx=λk​EXk−1由EX=λ1​,得EXk=λkk!​​​ DX=EX2−(EX)2=1λ2\\begin{align}DX=EX^2-(EX)^2=\\frac1{\\lambda^2}\\end{align}DX=EX2−(EX)2=λ21​​​ 正态分布X∼N(μ,σ2)X\\sim N(\\mu,\\sigma^2)X∼N(μ,σ2)： f(x)=12πσe−(x−μ)22σ2\\begin{align}f(x)=\\frac1{\\sqrt{2\\pi}\\sigma}e^{\\Large-\\frac{(x-\\mu)^{\\large2}}{2\\sigma^{\\large2}}}\\end{align}f(x)=2π​σ1​e−2σ2(x−μ)2​​​，x∈Rx\\in Rx∈R ∫−∞+∞f(x)dx=1\\begin{align}\\int_{-\\infty}^{+\\infty}f(x)\\mathrm dx=1\\end{align}∫−∞+∞​f(x)dx=1​​，你可以由归一性使用结论，其证明过程如下(标准正态分布为例)： 令I=∫−∞+∞e−x22dx,则I2=∫−∞+∞e−x22dx∫−∞+∞e−y22dy=∫−∞+∞∫−∞+∞e−x2+y22dxdy=lim⁡a→+∞∫−aa∫−aae−x2+y22dxdy\\begin{align}\u0026amp;令I=\\int_{-\\infty}^{+\\infty}e^{\\large-\\frac{x^{\\normalsize2}}{2}}\\mathrm dx,则I^2=\\int_{-\\infty}^{+\\infty}e^{\\large-\\frac{x^{\\normalsize2}}2}\\mathrm dx\\int_{-\\infty}^{+\\infty}e^{\\large-\\frac{y^{\\normalsize2}}2}\\mathrm dy\\\\\u0026amp;=\\int_{-\\infty}^{+\\infty}\\int_{-\\infty}^{+\\infty}e^{-\\frac{x^2+y^2}2}\\mathrm dx\\mathrm dy=\\lim_{a\\rightarrow+\\infty}\\int_{-a}^{a}\\int_{-a}^{a}e^{-\\frac{x^2+y^2}2}\\mathrm dx\\mathrm dy\\end{align}​令I=∫−∞+∞​e−2x2​dx,则I2=∫−∞+∞​e−2x2​dx∫−∞+∞​e−2y2​dy=∫−∞+∞​∫−∞+∞​e−2x2+y2​dxdy=a→+∞lim​∫−aa​∫−aa​e−2x2+y2​dxdy​​ 虽然被积区域不是圆,但可以用夹逼定理使其极坐标变换D1\u0026lt;D\u0026lt;D2,其中D1为内切圆、D2为外接圆在D1上,lim⁡a→+∞∫02πdθ∫0a2re−r22dr=2π在D2上,lim⁡a→+∞∫02πdθ∫0a2re−r22dr=2π因此I=2π,∫−∞+∞f(x)dx=I2π=1\\begin{align}\u0026amp;虽然被积区域不是圆,但可以用夹逼定理使其极坐标变换\\\\\u0026amp;D_1\u0026lt;D\u0026lt;D_2,其中D_1为内切圆、D_2为外接圆\\\\\u0026amp;在D_1上,\\lim_{a\\rightarrow+\\infty}\\int_{0}^{2\\pi}\\mathrm d\\theta\\int_{0}^{\\large\\frac{a}2}re^{\\Large-\\frac{r^{\\normalsize2}}2}\\mathrm dr=2\\pi\\\\\u0026amp;在D_2上,\\lim_{a\\rightarrow+\\infty}\\int_{0}^{2\\pi}\\mathrm d\\theta\\int_{0}^{\\large\\frac{a}{\\sqrt2}}re^{\\Large-\\frac{r^{\\normalsize2}}2}\\mathrm dr=2\\pi\\\\\u0026amp;因此I=\\sqrt{2\\pi},\\int_{-\\infty}^{+\\infty}f(x)\\mathrm dx=\\frac I{\\sqrt{2\\pi}}=1\\end{align}​虽然被积区域不是圆,但可以用夹逼定理使其极坐标变换D1​\u0026lt;D\u0026lt;D2​,其中D1​为内切圆、D2​为外接圆在D1​上,a→+∞lim​∫02π​dθ∫02a​​re−2r2​dr=2π在D2​上,a→+∞lim​∫02π​dθ∫02​a​​re−2r2​dr=2π因此I=2π​,∫−∞+∞​f(x)dx=2π​I​=1​​ EX=μ、EX2=σ2+μ2EX=\\mu、EX^2=\\sigma^2+\\mu^2EX=μ、EX2=σ2+μ2 EXk=∫−∞+∞xk2πe−x22dx=−∫−∞+∞xk−12πd(e−x22)=0+∫−∞+∞(k−1)xk−22πe−x22dx=(k−1)EXk−2\\begin{align}\u0026amp;EX^k=\\int_{-\\infty}^{+\\infty}\\frac{x^k}{\\sqrt{2\\pi}}e^{\\Large-\\frac{x^2}{2}}\\mathrm dx=-\\int_{-\\infty}^{+\\infty}\\frac{x^{k-1}}{\\sqrt{2\\pi}}\\mathrm d\\left(e^{\\Large-\\frac{x^2}2}\\right)\\\\\u0026amp;=0+\\int_{-\\infty}^{+\\infty}\\frac{(k-1)x^{k-2}}{\\sqrt{2\\pi}}e^{\\Large-\\frac{x^2}2}\\mathrm dx=(k-1)EX^{k-2}\\end{align}​EXk=∫−∞+∞​2π​xk​e−2x2​dx=−∫−∞+∞​2π​xk−1​d​e−2x2​​=0+∫−∞+∞​2π​(k−1)xk−2​e−2x2​dx=(k−1)EXk−2​​ 标准正态分布： EXk=(k−1)!!,k是偶数EXk=0,k是奇数\\begin{align}\u0026amp;EX^k=(k-1)!!,k是偶数\\\\\u0026amp;EX^k=0,k是奇数\\end{align}​EXk=(k−1)!!,k是偶数EXk=0,k是奇数​​ DX=σ2DX=\\sigma^2DX=σ2 随机变量函数 二维随机变量 二维正态分布 统计量 正态分布N(μ,σ2)N(\\mu,\\sigma^2)N(μ,σ2)的nnn个简单随机样本的样本均值： Xˉ=1n∑i=1nXi\\begin{align}\\bar X=\\frac{1}{n}\\sum_{i=1}^nX_i\\end{align}Xˉ=n1​i=1∑n​Xi​​​ EXˉ=μE\\bar X=\\muEXˉ=μ DXˉ=σ2n\\begin{align}D\\bar X=\\frac{\\sigma^2}n\\end{align}DXˉ=nσ2​​​ 正态分布N(μ,σ2)N(\\mu,\\sigma^2)N(μ,σ2)的nnn个简单随机样本的样本方差： S2=1n−1∑i=1n(Xi−Xˉ)2\\begin{align}S^2=\\frac1{n-1}\\sum_{i=1}^n(X_i-\\bar X)^2\\end{align}S2=n−11​i=1∑n​(Xi​−Xˉ)2​​ ES2=μ2+σ2ES^2=\\mu^2+\\sigma^2ES2=μ2+σ2 由 U=(n−1)S2σ2∼χ2(n−1)\\begin{align}U=\\frac{(n-1)S^2}{\\sigma^2}\\sim\\chi^2(n-1)\\end{align}U=σ2(n−1)S2​∼χ2(n−1)​​， DS2=σ4(n−1)2DU=σ4(n−1)2(n−1)DX2DX2=EX4−(EX2)2=3EX2−(EX2)2=2DS2=2n−1\\begin{align}\u0026amp;DS^2=\\frac{\\sigma^4}{(n-1)^2}DU=\\frac{\\sigma^4}{(n-1)^2}(n-1)DX^2\\\\\u0026amp;DX^2=EX^4-(EX^2)^2=3EX^2-(EX^2)^2=2\\\\\u0026amp;DS^2=\\frac2{n-1}\\end{align}​DS2=(n−1)2σ4​DU=(n−1)2σ4​(n−1)DX2DX2=EX4−(EX2)2=3EX2−(EX2)2=2DS2=n−12​​​ 标准正态分布：不再赘述，设总体X∼N(μ,σ2)X\\sim N(\\mu,\\sigma^2)X∼N(μ,σ2)，以下为常见的构造 n(Xˉ−μ)σ∼N(0,1)\\begin{align}\\frac{\\sqrt n(\\bar X-\\mu)}{\\sigma}\\sim N(0,1)\\end{align}σn​(Xˉ−μ)​∼N(0,1)​​ Xˉ1−Xˉ2+(μ1−μ2)σ12n1+σ22n2∼N(0,1)\\begin{align}\\frac{\\bar X_1-\\bar X_2+(\\mu_1-\\mu_2)}{\\sqrt{\\frac{\\sigma^2_1}{n_1}+\\frac{\\sigma^2_2}{n_2}}}\\sim N(0,1)\\end{align}n1​σ12​​+n2​σ22​​​Xˉ1​−Xˉ2​+(μ1​−μ2​)​∼N(0,1)​​ 卡方分布U∼χ2(n)U\\sim\\chi^2(n)U∼χ2(n)：设X1,⋯ ,XnX_1,\\cdots,X_nX1​,⋯,Xn​为服从标准正态分布且相互独立的随机变量 U=∑i=1nXi2\\begin{align}U=\\sum_{i=1}^nX_i^2\\end{align}U=i=1∑n​Xi2​​​ EU=nEX2=nEU=nEX^2=nEU=nEX2=n DU=nDX2=n(EX4−(EX2)2)=2nDU=nDX^2=n(EX^4-(EX^2)^2)=2nDU=nDX2=n(EX4−(EX2)2)=2n ttt分布U∼t(n)U\\sim t(n)U∼t(n)：设X∼N(0,1)、Y∼χ2(n)X\\sim N(0,1)、Y\\sim \\chi^2(n)X∼N(0,1)、Y∼χ2(n)，且它们相互独立 U=XYn\\begin{align}U=\\frac{X}{\\sqrt{\\large\\frac{Y}{n}}}\\end{align}U=nY​​X​​​ 假设X1∼nX_{1\\sim n}X1∼n​来自N(0,1)N(0,1)N(0,1)，则 n(Xˉ−μ)S∼t(n−1)\\begin{align}\\frac{\\sqrt n(\\bar X-\\mu)}{S}\\sim t(n-1)\\end{align}Sn​(Xˉ−μ)​∼t(n−1)​​ FFF分布U∼F(n,m)U\\sim F(n,m)U∼F(n,m)：设X∼χ2(n)、Y∼χ2(m)X\\sim\\chi^2(n)、Y\\sim\\chi^2(m)X∼χ2(n)、Y∼χ2(m)，且它们相互独立 U=XnYm\\begin{align}U=\\frac{\\large\\frac{X}{n}}{\\large\\frac{Y}{m}}\\end{align}U=mY​nX​​​​ ","permalink":"https://echo.ishian.me/2025/05/05/probability-rdnvar/","summary":"汇总常见离散与连续随机变量的分布、期望与方差推导及高阶矩结论","title":"概率论与数理统计: 常见的随机变量汇总"},{"content":"函数式编程 类的初始化代码 在一个类中由{}框住的代码块称为初始化代码，在构造方法之前执行\njavaclass ClassName { { // 实例创建后构造方法执行前运行 } static { // 类加载后执行一次 } } 匿名类 语法：\njavanew Parent() { // 类体 } 其中Parent必须是存在的类或接口，这个匿名类相当于继承这个Parent类或Parent接口\n若为实现一个接口，则类体必须实现这个接口的方法\n运行到匿名类声明的地方时，会创建一个匿名实例，由于匿名类没有构造方法(因为没有名字)，所以使用初始化代码块{}来初始化\nlambda表达式 语法：\njava(参数列表) -\u0026gt; { 函数体 } // 小括号与大括号可省略 从Java 8开始，可以用lambda表达式作为单方法接口的一个实例，表达式的函数体作为该接口的这个方法的实现 单方法接口指由@FunctionalInterface注解的含一个抽象实例方法的接口，可以包含default或static方法 被该注解修饰的接口称为函数式接口，java.util.function提供了一系列基础的泛型函数式接口\n方法引用：除了lambda表达式，还可以用类名::方法名表示一个方法的引用，这个方法引用只能作为参数传递，不能赋值给一个变量，前提是形参是一个单方法接口且方法签名与提供的方法引用的签名一致 用类名::new表示该类的构造方法引用 若传递的方法为实例方法，则其第一个参数的类型被视为该实例对应的类型\n内置的函数式接口：\nFunction\u0026lt;T, R\u0026gt;：提供的方法只有一个类型为T的参数，且返回类型为R Consumer\u0026lt;T\u0026gt;：提供的方法只有一个类型为T的参数，且没有返回值 Predicate\u0026lt;T\u0026gt;：相当于Function\u0026lt;T, Boolean\u0026gt; BiFunction, BiConsumer, BiPredicate：相比于上述函数式接口，允许接受两个参数 Supplier\u0026lt;T\u0026gt;：提供的方法不接受任何参数，且返回类型为T Comparator\u0026lt;T\u0026gt;：自定义比较器，须实现compare()方法 Runnable：自定义线程，须实现run()方法 Callable, FileFilter, PathMatcher, InvocationHandler, PropertyChangeListener, ActionListener等 由于java的泛型无法使用基本数据类型，因此为了效率，还提供了一系列类似TypeToTypeFunction, TypeConsumer的针对基本数据类型的函数式接口 流式API 流式API指java.util.stream，和java.io提供的输入输出流完全不同，文件io用于处理输入和输出以及序列化、反序列化，而Stream\u0026lt;T\u0026gt;是顺序输出的一系列同类型对象 Stream和List等序列集合的区别是，前者既可以表示有限个集合，也可以通过存储函数映射关系来实现“存储”序列对象，通过实时计算的方式“存储”无限个元素 当然，无限个元素很多时候只需要有限个，所有Stream提供的功能都可以用集合框架实现，但流式API可以使一系列集合操作变得紧凑而清晰 Stream对象创建方法： Stream.of(T...)：提供一个有限序列 Stream.generate(Supplier\u0026lt;T\u0026gt;)：提供一个Supplier的实例，调用聚合函数时通过该方法实时计算元素 通过Collection实例的stream()方法获取 通过流对象的转换方法获取 常用转换方法：可以链式调用以获取新的流式对象的实例方法 需要注意，调用转换方法不会有任何实际计算开销 limit(long)：流虽然能获取无限个元素，但所有情况下都只需要有限个元素，且处理时也只需要有限个元素，因此可以使用limit()截取前若干个元素的Stream对象 skip(long)：同上，流可以跳过若干个元素 map(Function\u0026lt;T, R\u0026gt;)：将流的所有元素通过该方法转换为R类型的元素 forEach(Consumer\u0026lt;? super T\u0026gt;)：使流中的每个元素经过该函数，要求流是有限集合 filter(Predicate\u0026lt;? super T\u0026gt;)：筛选流中的元素，将不符合条件的元素去除 sorted()和sorted(Comparator\u0026lt;T\u0026gt;)：排序 distinct()：去重 concat()：拼接两个流对象 flatMap(Function\u0026lt;T, Stream\u0026lt;R\u0026gt;\u0026gt;)：将所有元素经过该方法转化为流对象，再进行concat()使所有流对象转换为单个流对象，通常用于元素为集合类型的流的转换 parallel()：获取一个并行的流对象，不需要编写多线程代码 count()：获取流对象的元素数量 max/min(Comparator\u0026lt;T\u0026gt;)：获取最大值与最小值 allMatch(Predicate\u0026lt;T\u0026gt;)与anyMatch(Predicate\u0026lt;T\u0026gt;)：判断是否所有元素/存在元素满足条件 常用聚合方法：将一个流对象的所有元素聚合为一个统计量的实例方法，会实时计算需要的元素 reduce(BinaryOperator\u0026lt;T\u0026gt;)：提供一个二元运算函数，使用该函数对所有元素进行从左到右的累加，返回Optional\u0026lt;T\u0026gt; toList()：聚合为一个List\u0026lt;T\u0026gt;对象 toArray()：不传递任何参数则只能返回Object[]数组，所以一般需要传递T[]::new返回T[]数组 collect(Collector\u0026lt;? super T, A, R\u0026gt;)：通过Collector方法进行聚合 collect(Supplier\u0026lt;R\u0026gt;, BiConsumer\u0026lt;R, ? super T\u0026gt;, BiConsumer\u0026lt;R, R\u0026gt;)：通过第一个参数获取一个初始的R类型的值，然后通过第二个参数将元素聚合到这个初始值 第三个参数用于并行流的并行计算后，将两个R类型的值合并 通过上述方法，可以用简洁的代码对集合中的所有元素进行处理，其中collect()方法可以提供Collector，其工具类为Collectors，包含一系列返回Collector实例的静态方法，分别有三类： 恒等变换： 分组分区： 统计意义： Collector\u0026lt;T, A, R\u0026gt;三个泛型参数的实际意义是：元素类型、中间结果类型、收集器返回类型 源码实现为CollectorImpl\u0026lt;T, A, R\u0026gt;，需要提供四个参数： Supplier\u0026lt;A\u0026gt;：用于获取一个初始的中间容器 BiConsumer\u0026lt;A, T\u0026gt;：用于聚合所有元素到中间容器中 BinaryOperator\u0026lt;A\u0026gt;：用于将两个中间容器合并为一个中间容器，用于并行流 Function\u0026lt;A, R\u0026gt;：将中间容器转化为R类型的结果 Set\u0026lt;Characteristics\u0026gt;：Characteristics枚举的集合 该枚举包含三项： CONCURRENT：表示收集器可以并发执行合并操作而不影响结果 IDENTITY_FINISH：表示中间容器有标识功能，A可以直接强制转换为R类型 UNORDERED：表示收集器获取两个元素进行合并时，没有固定的获取顺序 ","permalink":"https://echo.ishian.me/2025/05/04/java-stream-api/","summary":"介绍 Java 匿名类、lambda 表达式与流式 API 等函数式编程特性","title":"Java: 函数式编程"},{"content":"数据链路层 提供的服务或功能 成帧Framing：将数据包封装成帧 介质访问控制(Media Access Control, MAC)协议：规定帧在链路上传输的规则，用于协调多个节点的帧传输 可靠交付：在无线环境下，可以提供纠错能力；在有线这种不那么容易出错的环境下，可能不会提供可靠交付服务 差错检测和纠正：比传输层、网络层更加复杂，且用硬件实现 差错检测和纠正 奇偶校验码： 循环冗余校验(Cyclic Redundancy Check, CRC)码： 多路访问问题 多路访问问题在于，大多数通信链路是广播链路，即是共享信道的，任何一台设备发送的报文可以被多个节点收到，但同一时间应该只有一台设备能够占有该信道，否则就会互相干扰 多路访问控制(Multiple Access Control, MAC)协议就是用来解决多路访问问题的\n信道划分协议(Channel Partitioning Protocol)：将共享信道根据某种属性分为多个子信道并按需分配出去 这类协议保证绝对不会发生冲突且绝对公平地分配资源\n时分多路复用(Time Division Mutiplexing, TDM)：将时间平等地分给接入共享信道的主机，会极大限制主机的平均发送速率，且降低了信道的使用率 虽然主机能在其时间段内能占用全部的带宽，但其平均速率需要除以节点数 一个主机必须等到它的专有时隙才能发送数据，即使只有它需要占用信道 频分多路复用(Frequency Division Mutiplexing, FDM)：将信道划分为不同的频段，仍会限制主机的平均发送速率，且降低信道的带宽使用率 主机只能占有一个频段，每个频段的带宽只有总带宽除以节点数这么多 一个主机最多只能占有一个频段的带宽而无法完全占有信道，即使只有它需要占用信道 码分多址(Code Division Multiple Access, CDMA)：为每个节点分配不同的编码，使得节点可以克服上述的问题 随机接入协议(Random Access Protocol)：传输节点总是完全占有信道地发送，发生碰撞时，随机地决定一个重发时延，直到发送出去为止 这类协议通常不能避免碰撞，但具有较高的信道资源利用率，且对每一个节点可能不是公平的\n时隙ALOHA(Slotted ALOHA)：这个协议将时间轴划分为多个时间帧，每个节点要发送数据时，在下一个时隙开始时发送，并在时隙结束前检测是否发生碰撞，若发生碰撞，则以一定的概率ppp决定下一个时隙是否重传 一个节点成功发送前经过的时隙数量N∼G(p)N\\sim G(p)N∼G(p)，即P(N=k)=(1−p)k−1pP(N=k)=(1-p)^{k-1}pP(N=k)=(1−p)k−1p 缺点在于，需要对所有节点保持同步\n非时隙ALOHA(Pure ALOHA)：与时隙ALOHA的区别在于，不用进行同步，但一个节点成功发送前经过kkk个时隙的概率为P(N=k)=(1−p)2(k−1)pP(N=k)=(1-p)^{2(k-1)}pP(N=k)=(1−p)2(k−1)p，效率为时隙ALOHA的一半\n带碰撞检测的载波侦听多路访问(Carrier Sense Multiple Access with Collision Detection, CSMA/CD)：要求主机主动地避免和别的主机发生冲突，具体来说，要实现载波侦听和碰撞检测 载波侦听即，每个节点在发送帧之前先侦听信道，等待没有其它节点占用信道时，才发送 碰撞检测即，虽然每个节点都会侦听，但信号的传输是有延迟的，可能存在多个节点“同时”(一个节点发送后还未传播到另一个想要发送的节点那儿)检测到信道为空，并“同时”发送帧；碰撞检测在于，一旦检测到碰撞发生，则停止发送帧，然后以指数增长的延迟决定什么时候重发帧 效率为\n11+5dprop/dtrans\\begin{align}\\frac1{1+5d_{prop}/d_{trans}}\\end{align}1+5dprop​/dtrans​1​​​，其中的延迟分别为传播延迟和传输延迟\n带碰撞避免的载波侦听多路访问(CSMA with Collision Avoidance, CSMA/CA)：无线网络是不能使用CSMA/CD的，因为发送出去的帧必须完整发出，无法检测碰撞，因此CSMA/CA采用在广播前尽量避免碰撞的策略，它不会进行冲突检测，而是发出数据后通过链路层确认来决定是否重发\n在发送方检测到信道为空闲时，等待一个分布式帧间间隔(Distributed Inter-frame Space, DIFS)再发送 接收方收到后，会等待短帧间间隔(Short Inter-frame Space, SIFS)后回应一个确认帧 如果因为冲突、或其它原因，接收方没有收到确认帧，则像CSMA一样以指数增长的延迟重发 虽然CA比CD多了一个DIFS，但它仍可能出现问题，即两个主机互相隐藏，但都需要向中间的第三方主机发送帧，这时将发生碰撞 解决这个问题依赖于无线接入点(Access Point, AP)，它是一个仲裁机构，所有需要广播的必须向AP发送**RTS(Request To Send)控制帧，表示请求发送，在没有发生冲突时，AP才会完整接收到RTS帧，并广播回应CTS**(Clear To Send)控制帧，表示允许发送；发送RTS的主机接收到后可以发送数据帧，其它主机接收到CTS后则会推迟访问共享信道\n","permalink":"https://echo.ishian.me/2025/05/03/comp-networking-datalinklayer/","summary":"讲解数据链路层的成帧、差错检测、MAC协议及CSMA/CD、CSMA/CA等多路访问控制机制","title":"计算机网络: 数据链路层"},{"content":"Rust基本语法 语句和表达式 通用规则 语句是不会返回值的命令；表达式是会返回值的命令\n如果一个表达式后接';'，那么它只是一个语句而不是表达式 相反，rust允许一个命令不接';'，这时这条命令是一条表达式 许多函数是可以作为表达式(不加分号)，也可以作为语句的\n块表达式：可以用{}包围一系列语句及最后接一个表达式，方便推理，块表达式会返回最后这个表达式的返回值\nrustlet a: i32 = { let b: i32 = 1; // 推理 b + 1 // 块表达式返回值 }; 控制流语句或表达式 rustif \u0026lt;bool型表达式\u0026gt; { } else if \u0026lt;bool型表达式\u0026gt; { } else { } loop：无限循环表达式，直到用户通过break退出 它会返回break后跟的值，类似return ... break也可以不跟值，这时loop块是一条语句\nrustfor \u0026lt;可变变量\u0026gt; in \u0026lt;range\u0026gt; {}其中\u0026lt;range\u0026gt;可用类似1..=10的语法糖快速创建\n变量所有权 RAII：\n类似java，标识符是一个在栈上的引用，指向常量池或堆上的值\n针对常量池中的值(例如整型、常量字符串)：赋值相当于将常量的引用赋给变量，若赋给多个变量，则它们的引用均指向这个常量\n针对堆上的值：每一个值都有一个所有者，且任意时刻只有一个所有者；这是为了摒弃GC做出的和其它语言的区别：一个值只能有一个引用，使用时只能转让或自动退出时被drop 当变量离开作用域时，值会被丢弃； 经过赋值操作时，值会被转移；也就是说，一个变量通过赋值将值绑定给另一个变量时，前者这个变量就不再拥有这个值\nrustlet s1 = String::from(\u0026#34;hello\u0026#34;); let s2 = s1; // s1不再拥有上述创建的\u0026#34;hello\u0026#34;, 而s2和\u0026#34;hello\u0026#34;绑定 rust和其它语言通过垃圾回收(java)或程序员手动分配(C/C++)内存不同，它通过所有权系统管理内存\n在编译时检查程序是否违反所有权机制，这也是容易编译不通过的原因 所有权机制是在编译时起作用的，不像垃圾回收那样，会对性能产生负面影响 数据类型 定义标识符 指定数据类型：rust虽然支持对变量自动识别类型，但毕竟是强类型语言，最好手动指定数据类型，语法为 \u0026lt;变量名\u0026gt;: \u0026lt;数据类型\u0026gt; = \u0026lt;值\u0026gt;\n定义变量：\nlet \u0026lt;变量名\u0026gt;：定义不可变变量(第一次绑定值后只读，且只允许通过let再次声明后重新绑定值) let mut \u0026lt;变量名\u0026gt;：定义可变变量 变量在声明时可以不绑定值，但此时必须指定数据类型 定义常量：const \u0026lt;变量名\u0026gt;: type = value\n必须手动指定数据类型，并在声明时绑定值 定义静态变量：static\n定义引用：所有类型都有引用类型，这个引用不同于java、python的引用，而更像C/C++的轻量级引用\nrustlet a = \u0026amp;oth_obj; // oth_obj的只读引用 let a = \u0026amp;mut oth_mutable_obj; // oth_mutable_obj的可变引用, 要求oth_mutable_obj为可变变量 在整个作用域中，只允许最多一个可变引用 结合一个标识符会在作用域结束时释放，可以实现类似以下的操作：\nrustlet mut obj = Box::new(10); { let ref1 = \u0026amp;mut obj; } // ref1释放 let ref2 = \u0026amp;mut obj; // 如果替换它们的顺序, 是不允许的 关于函数参数：这个引用类型比C++更严格，想传递一个引用参数的话，形参的类型必须为对应的引用类型\n变量名风格：snake case规范(所有字母小写，用下划线分隔单词)\n基本数据类型 整型： i8、i16、...：有符号整型 u8、u16、...：无符号整型 isize、usize：自适应有/无符号整型 字符型： char：Unicode字符 str：基本不会用到的字符串类型，因为是DST 类与函数 函数 fn \u0026lt;函数名\u0026gt; (\u0026lt;函数参数\u0026gt;) -\u0026gt; \u0026lt;返回值类型\u0026gt; {\u0026lt;函数体\u0026gt;} 在指定返回值类型后，函数体必须通过表达式或return返回指定类型的值 闭包函数，即所谓lambda表达式： 集成框架 ","permalink":"https://echo.ishian.me/2025/05/02/rust-beginner/","summary":"介绍Rust基本语法：语句与表达式、控制流、所有权与借用等入门概念","title":"Rust: 基本语法"},{"content":"传输层协议 可靠数据传输的组成 处理比特差错：比特差错指，分组能运输到主机，但具有比特位丢失或错误；又分为非响应分组错误和响应分组错误\n通过重传分组解决，又因为重传会导致分组冗余，故需要给分组添加编号 完备的解决方案： 发送方：发送分组后进入阻塞态，若收到有错误的响应报文，则重传；否则完成委托，等待下一个来自应用层的调用 接收方：有比特差错或无比特差错但序号错，则响应上一个序号的报文，并丢弃当前收到的报文；否则响应当前序号的报文，并增加确认序号 处理丢包：通过超时重传解决，超时即一段时间内未收到正确的响应报文，若超时则响应上一个序号的报文；在此处，发送方接收到有错响应报文时，不立刻重传，而是超时再重传 丢包大概率因为中间路由缓存满而丢弃分组，小概率因为链路中断\n上述是一次只传输一个分组的情况，称为停等协议(发送方在收到响应前进入阻塞)；为了效率，需要实现流水线传输，有以下方案：\n回退N步(GBN)协议：只要一切正常，效率就可以大幅提高 发送方：维护基序号b，发送方可以一次性发送序号为[b,b+N]内的分组，直到收到ACK_b，才将b右移，并将新的窗口中未发送的分组发送；否则，一旦超时，就将窗口内的分组全部重传 接收方：接收窗口长度只为1，即接收方只接收序号为b的分组，响应策略和停等协议一致(分组出错时响应ACK_b-1) 即，GBN协议是发送方可以发送多个分组的停等协议，当N=1时退化为停等协议，其始终等待b号响应报文的策略使其具有自然有序、且每一时刻序号小于b的报文均已可靠传输的性质\n选择重传(SR)协议：上述接收方的策略可以优化，接收方可以选择缓存并自行排序而非丢弃序号错误的分组，相当于将接收方窗口长度拉长，使接收方可以确认所有正确的报文(无论其序号是多少) 对于发送方的好处是，发送方下次超时重传时就不需要重传已经确认的分组，而不是像GBN那样全部重传，发送方的具体策略为：\n一开始全部发送后，对每个分组都设置定时器，一次超时事件只会重传一个分组(“选择重传”的来历) 接收到的ACK确认号不是b(一定会落在窗口内)，则进行标记 接收到的ACK确认号是b，则移动b到下一个没被标记的分组，发送新窗口的未被标记分组 接收方的具体策略为：\n收到序号不为b的报文：缓存，响应ACK_b 收到序号为b的报文：立刻交付它以及窗口内所有已缓存的报文，移动窗口到下一个没有收到过的序号 基于序号有限的现实问题，发送方窗口===接收方窗口≤\\le≤序号数量的一半，因为响应报文完整/丢失可能导致同步问题：如果接收方窗口长度超过序号数量的一半，那么在一次完美的传输后，接收方窗口会第二次使用相同的序号，此时若响应报文完整，则发送方会第二次使用相同的序号，此次分组实际是新分组；若响应报文丢失，则发送方会重传，此次分组实际是已确认分组，但接收方会误以为是新分组(因为序号相同) 长度不超过序号数量的一半就不会有这样的问题\nTCP性质及功能 全双工协议，即双方都可作为发送方或接收方 面向连接的协议，即双方真正传输数据前需要建立连接、后需要关闭连接 支持百分百的可靠数据传输，同时有一定的拥塞控制、流量控制、检错功能 通过端口号来标识进程，为了支持全双工，双方都需要提供IP和端口号 协商MSS(MSS=MTU+IP头部长+TCP头部长，即MSS本身不包含TCP头部)，以避免IP分组 主机是边缘设备，在传输层控制一个TCP分组的长度可以避免在发送方被网络层分组，有效降低网络负载(因为网络层分组/重组并保证完整传输相比不分组，需要更多开销) 现代路径发现MSS：为了防止在中途路由被分组，现代设备可以动态发现整个路径所有网段中的最小MTU而不仅仅是本地网段的MTU，进一步避免被网络层分组 TCP协议报文 第一行字段：源端口号、目的端口号\n每个端口号共16位，端口号分为**周知端口号(0~1023)**和非周知端口号(1024~65535) 周知端口号是给服务器使用的，这样客户端就可以默认向使用该端口号作为目的端口号 常见的周知端口号：HTTP~80、FTP传输数据~20、FTP传输控制信息~21、Telnet~23、DNS~53 第二行字段：32位序号，表示本报文所承载数据段的首字节编号\n第三行字段：32位确认号，表示本报文所希望的下一个数据段的首字节编号 小于确认号的序号的报文均已被接收方完整接收\n第四行字段：\n4位首部长度：标记首部的字节数，单位为4个字节 标记位：URG、ACK、PSH、RST、SYN、FIN 16位接收窗口：用于流量控制，流量控制用于解决发送方发送速率和接收方接收速率不匹配问题 触发流量控制的事件不是超时，超时是触发拥塞控制的事件，而流量控制依赖于响应报文的接收窗口，当接收窗口为0时，表示接收方已经无法再接收，发送方随即停止发送，并试探性地发送一个没有数据的报文直到响应报文的接收窗口不为0 TCP连接过程 假设发送方为A、接收方为B，发送方、接收方期望发送的第一个字节编号分别为a、b 那么三次握手过程为： 发送方发送报文，其中ACK=0、SYN=1、序列号=a-1 接收方发送报文，其中ACK=1、SYN=1、序列号=b-1、确认号=a 发送方发送报文，其中ACK=1、SYN=0、序列号=a、确认号=b 这里因为接收方不再发送响应报文，所以发送方下次发送的报文序号仍为a 四次挥手过程为： 主动结束方发送报文，其中FIN=1、ACK=1、序列号=x(上次接收的确认号)、确认号=y(上次接收的序列号+字节数) 此后主动结束方不会再发送数据报文，但仍能接收数据报文 被动结束方发送响应报文，其中FIN=0、ACK=1、序列号=y、确认号=x+1 被动结束方发送结束报文，其中FIN=1、ACK=1、序列号=z(被动结束方上次接收报文的确认号)、确认号=x+1 主动结束方发送响应报文，其中FIN=0、ACK=1、序列号=x+1、确认号=z+1 防止响应报文丢失，主动结束方最后会进入TIME_WAIT，如果响应报文丢失，被动结束方会超时重传，主动结束方则在TIME_WAIT内受理该重传的FIN报文 注意，这个TIME_WAIT和超时重传是两回事 TCP的附加功能 流量控制：不再赘述\n基于序号和确认号在同一份报文内的机制，通常不单独发送响应报文，而是在要发送的数据中捎带上确认信息\n期望RTT(往返时间)：通过对最近RTT采样，并加权重(越近的样本权重越大)计算期望RTT 方差RTT：测量期望RTT的变化 重传计时器的时间通常设为：期望RTT+4方差RTT 在超时事件未发生时，TCP通过它来指导计时器的时间\n快速重传：每次丢包都等超时后再重传效率过低，因为丢包后，有概率能接收到冗余ACK报文(确认号相同的报文)，发送方可以将这个事件视为丢包的暗示，进行快速重传\n拥塞起因：发送方的发送速率和中间路由转发速率不匹配导致数据分组冗余 拥塞控制：当丢包事件(超时或接收到若干个冗余ACK报文，被认为是丢包)发生时，触发拥塞控制机制，其中超时事件相比冗余ACK事件，更能说明链路拥塞(因为冗余ACK起码能接收到，丢包可能是因为物理链路出问题) TCP通过慢启动来控制发送速率：\n慢启动状态：初始发送窗口为一个MSS，但以指数增长 超时事件发生时，重新进入慢启动状态，将触发超时时候的发送窗口大小的一半设置为慢启动阈值 冗余ACK事件发生时，执行快速重传，进入快速恢复状态 指数增长到慢启动阈值(从未发生过超时时，为无穷大)时，进入拥塞避免状态 拥塞避免状态：谨慎地增加发送窗口长度，可能是线性地增加 超时事件发生时，进入慢启动状态，并更新慢启动阈值 冗余ACK事件发生时，需要作出反应但不应过于剧烈，因此将发送窗口减半而非重置(通常因为有k个冗余ACK，说明中间链路起码还有转发k个分组的能力，所以还要在减半后加上k个MSS)，并进入快速恢复状态 快速恢复状态：快速重传后，仍可能收到冗余ACK，每接收一个冗余ACK，相应地增加一个MSS 超时事件发生时，同上 当接收到新的ACK而不是冗余ACK时，将发送窗口置为慢启动阈值，进入拥塞避免状态 因此，进入慢启动状态一定是因为超时发生，且初始为1个MSS 进入拥塞避免状态是因为在慢启动时超过了慢启动阈值、或在快速恢复时收到新ACK，初始为慢启动阈值 进入快速恢复状态一定是因为收到冗余ACK，处于快速恢复状态的时间较短\n","permalink":"https://echo.ishian.me/2025/05/01/comp-networking-trsflayer/","summary":"讲解可靠数据传输原理、停等/GBN/SR协议及TCP报文格式、连接管理与拥塞控制","title":"计算机网络: 传输层协议"},{"content":"网络层协议 网络层的功能 主要功能：分组转发和路由选择 其它功能：不同系统的设备间通信、拥塞控制 IPv4协议 IP地址：在此介绍ipv4的32位地址(ipv6为128位)，由网络号和主机号组成，分为有类别和无类别两种地址；注意，网络层始终讲述的是不同的网络号间如何转发，并不关心同一网段下的不同主机如何转发(那是数据链路层的任务)\n有两种特殊的主机号不能分配给主机：全零和全一 主机号全零的IP地址表示一个网段本身 主机号全一的IP地址表示特定子网的广播地址，可以跨网段广播到特定网段的所有主机号(但这个功能默认被拦截，需要管理员开放) 有两个特殊的IP地址有特殊用途： 0.0.0.0表示本地网段内的所有主机，通常作为路由器的默认路由 255.255.255.255表示受限广播地址，和仅主机号全一的IP地址相比，它只能在本地网段广播该分组、而后者可以 有类别(比较古老的做法)：分为**A、B、C、D、E类，A、B、C类分别以前8、16、24位作为网络号 为了区分不同的有类别地址，它们有不同的前缀**，分别为**0、10、110、1110、1111** A类总共提供27=1282^7=12827=128个网络号，224=167772162^{24}=16777216224=16777216个主机号 B类总共提供214=163842^{14}=16384214=16384个网络号，216=655362^{16}=65536216=65536个主机号 C类总共提供221=20971522^{21}=2097152221=2097152个网络号，28=2562^{8}=25628=256个主机号 缺陷显而易见，非常不灵活，例如：当出现10310^3103量级的主机号数量需求时，显然只能用B类，导致白白浪费一个网段的大部分主机号 D类和E类不区分网络号和主机号，它们的后28位可以随意修改，D给因特网体系结构委员会使用，E类作为保留地址 无类别：网络号位数xxx可变的IP地址，用于无类别域间路由选择(Classless Inter Domain Routing, CIDR) 为了区分不同的无类别地址，不同网段由不同的子网掩码来标识，子网掩码是**网络号全为1、主机号全为0**的32位掩码，所谓子网掩码的长度指的是xxx的大小 无类别地址简记形式：a.b.c.d/xa.b.c.d/xa.b.c.d/x，xxx为网络号长度 如果主机号数量的需求为LLL，则找到2k−1−2\u0026lt;L≤2k−22^{k-1}-2\u0026lt;L\\le2^k-22k−1−2\u0026lt;L≤2k−2，其中的kkk即最少需要的主机号位数，因此子网掩码最长为x=32−kx=32-kx=32−k IPv4报文：\n第一行：版本号(4)+首部长度(4)+服务类型(8)+数据报长度(16)\n版本号：处于不同版本的兼容，版本号用于标识使用的IP协议版本 首部长度：单位为4个字节 服务类型：区分不同的IP报文，提供不同的优先级 数据报长度：单位为1个字节 第二行：标识(16)+标志(3)+片偏移(13)，用于IP分组(分片)，由于分片/重组相当耗时，ipv6不允许在中间路由进行IP分片，这一行字段被弃用，但应该了解分组的过程：\n标识：分割出去的分组的该字段继承原IP分组的标识 标志位：包含保留位、DF位(不分片位)和MF位(更多分片位) DF置1则不允许分片，并在数据大小超过MTU后返回ICMP错误信息 MF置1表示本分组是分片后的分组，且后续还有分组未到达 MF置0表示本分组是分片后的分组，且后续没有更多分片，MF方便重组方能够确认原来分组的大小 片偏移：标识本分组首字节在原分组首字节的位置，这个字段的单位是8个字节(因为13位比数据报长度16位少了3位)，即实际位置是片偏移乘8 也因为这个，每个分片(除最后一个分片)的有效数据大小必须能被8整除 本身分组分割就是因为要使IP分组小于MTU，因此要使分片的有效数据大小LLL满足： Lmax+首部长度≤MTUL_{max}+首部长度\\le MTULmax​+首部长度≤MTU且8∣Lmax8|L_{max}8∣Lmax​\n第三行：TTL(8)+上层协议(8)+首部检验和(16)\nTTL：Time-To-Live，一个分组的最长寿命，每经过一个路由减少一定寿命，防止分组永久地在网络中循环转发 第三、第四行：源IP地址、目的IP地址\n路由器工作原理 分组从一个网段到另一个网段，可能需要经过路由器转发 路由器需要先经过路由选择，才能进行转发\n路由选择：通过软件计算，选择一条从源主机到目的主机的端到端路径 转发：将分组从入接口移动到某个离出接口(由路由选择决定向哪个接口移动)，这是一次较快的硬件操作 静态转发表：由目标网络、子网掩码、下一站IP地址、离出接口组成 静态是指，由管理者手动增删表项\n目标网络号：标识本路由器可以到达的网段，通过使分组的目的IP地址和表项的子网掩码按位相与再和目标网络比对，得出离出接口 用0.0.0.0表示默认路由，即没有任何表项和分组的目的IP网段相匹配的时候，使用的表项 下一站**IP地址**：标识下一个路由设备接口的IP地址，这个是整个IP地址而不仅是网络号 每个路由器的每个接口都有IP地址，这个字段表示的就是接口的IP地址 C表示下一个接口就在本地路由器，即目的网段和本路由器相邻 离出接口：即本地对接口的标识 路由聚合和最长子网掩码匹配：有些时候，一个路由器有多个表项的离出接口相同，目标网络不同但有一定长度的公共前缀，这时可以进行路由聚合，将它们合为一个表项，因为它们虽然目的网段不同，但是转发结果是一致的，而路由表越短、传输延迟越短 但可能会出现问题：分组本应转发到接口A，但因为接口B的表项被路由聚合了，同时意味着这些表项的子网掩码变短了，按位相与后分组可能被误转发到接口B；最长子网掩码匹配即，分组总是先和子网掩码较长的表项比对，因为较长的子网掩码通常包含更详细的地址信息\n动态路由选择算法：仅少数表项由管理者初始化，其余表项由路由器学习得到\n集中式路由选择算法，又称链路状态(LS)算法：在路由开始学习前知道了所有链路的开销，使用Dijkstra算法学习 学习完成后，路由器可以得知和其它所有网段的最短距离以及最合适的离出接口 分布式路由选择算法，又称距离向量(DV)算法：使用Bellman-Ford算法，通过报文跳数估计距离，适用于中小型网络 LS和DV算法对比： 更新开销方面：前者需要向所有路由转发链路状态报文、后者只在相邻的链路发生改变时向相邻的路由器报告，后者开销较小 收敛速度和健壮性方面：前者较优，DV算法收敛很慢 自治系统：因特网是多个ISP(网络业务提供商)的网络，不同ISP有自己的管理方式，因此出现了自治域(Autonomous System, AS) AS内部有一类路由选择协议，它们统称为**IGP(Interior Gateway Protocol内部网关协议)；不同AS间需要用统一的路由选择协议管理，称为BGP**(Border Gateway Protocol边界网关协议) 即，IGP是一类开发商自行实现的协议、BGP是一个由RFC规范的协议，IGP有以下协议：\n路由信息协议(Routing Information Protocol, RIP)：它是一个DV协议，仅适用于中小型网络 内部网关路由协议(Interior Gateway Routing Protocol, IGRP)：它是一个DV协议，现在已经被淘汰，注意和IGP区分 开放最短路优先(Open Shortest Path First, OSPF)：Open意味着这个协议的实现细节是所有人可见的，它是一个LS协议，适用于大型网络，支持分层拓扑架构 中间系统到中间系统(IS-IS)：它是一个LS协议 BGP规定的边界网关的路由表经过高度路由聚合\nDHCP协议 DHCP协议全称Dynamic Host Configuration Protocol，即动态主机配置协议，又称即插即用(plug-and-play)或零配置(zeroconf)协议 DHCP为随时进入局域网的设备自动配置IP地址、设备离开后自动回收IP地址 发现报文：主机进入局域网后，没有IP地址可用，也不知道该网段的网络号，于是向该局域网内的**DHCP主机通过UDP协议广播DHCP发现报文**(端口号67)，使用**0.0.0.0作为本机地址** 提供报文：DHCP主机收到后(只有DHCP主机会进行处理)，仍用广播方式响应一个**DHCP提供报文**，其中在数据段附上推荐的IP地址，以及其它的限制如**IP地址租用期** 请求报文：局域网内可能有若干个DHCP主机，新进入的主机收到多个DHCP提供报文后，尝试选择一个IP地址作为自己的IP地址，并广播发送**DHCP请求报文**，并仍使用0.0.0.0作为本机地址 使用0.0.0.0作为源地址的原因：需要得到DHCP的认可后才能正式租用该IP地址 使用255.255.255.255作为目的地址的原因：因为可能有多个DHCP服务器，广播是为了让其它未被选择的DHCP主机也接收到租用请求报文，更新它们的可用IP表 响应报文：只有被选中的DHCP主机会用广播形式发送**DHCP响应报文**，交互完毕后，新主机就可以使用租用的IP地址了；在租用期结束前，新主机可能再次发送请求报文进行续租 仍用广播，是因为新主机还没有正式租用IP地址 NAT NAT全称Network Address Translation，即网络地址转换 用于搭建专用网络，专用网络内的所有主机的IP地址对外界都不可见，仅有一个NAT路由器有合法的IP地址 可以节省网络号，但NAT是基于TCP或UDP端口号实现的，在面对不经过传输层的通信要求时需要额外配置 对于专用网络内部，一切通信都是正常的，当需要和外部通信时，NAT路由将所有IP分组的源IP地址改为NAT路由的IP地址，并将里面的端口号改为一个由NAT路由服务器新分配的端口号 同时通过NAT转发表，维护从**NAT路由端口号到内部主机IP及原端口号**的映射 最后在接收来自外部的报文时，将目的IP地址和目的端口号再映射回去 ICMP ICMP全称Internet Control Message Protocol，即因特网控制报文协议，通常用于差错报告 ping就是用ICMP实现的，通过发送类型8编码0的回显请求到目的主机，目的主机发送类型0编码0的回显回答来实现 Traceroute也是通过ICMP实现的，源主机通过逐渐增大UDP分组的TTL来发现到某个目的主机之间的所有路由，路由收到TTL恰好降为0的分组时，报告一个类型11编码0的TTL过期报文，主机就可以知道到这个路由的时延、包括该路由的IP地址 当TTL足够大以至于到达目的主机时，Traceroute刻意将UDP分组的端口号设置为不可达端口号，因此目的主机会报告一个表示目的端口不可达的ICMP报文，当源主机收到该报文时，就知道不用再发送UDP探测报文了 ARP和RARP ","permalink":"https://echo.ishian.me/2025/05/01/comp-networking-iplayer/","summary":"讲解网络层功能、IPv4地址分类与报文格式、IP分片及NAT、DHCP等协议","title":"计算机网络: 网络层协议"},{"content":"LCA算法 LCA()性质 LCA(Lowest Common Ancestor)，即最小公共祖先，LCA()LCA()LCA()接受一个结点集合，返回集合中所有结点的最小公共祖先结点 LCA({u})=uLCA(\\{u\\})=uLCA({u})=u，即任意结点的LCA是自身 LCA({u,v})=u⇔u是v的祖先LCA(\\{u,v\\})=u\\Leftrightarrow u是v的祖先LCA({u,v})=u⇔u是v的祖先 LCA(S1∪S2)=LCA({ LCA(S1), LCA(S2) })LCA(S_1\\cup S_2)=LCA(\\{\\ LCA(S_1),\\ LCA(S_2)\\ \\})LCA(S1​∪S2​)=LCA({ LCA(S1​), LCA(S2​) }) 给定结点集合S⊆VS\\subseteq VS⊆V，则LCA(S)LCA(S)LCA(S)在前序遍历中位于集合中所有结点之前，在后序遍历中位于所有结点之后 LCA(u,v)LCA({u,v})LCA(u,v)必然在u,vu,vu,v最短路上，且d(u,v)=h(u)+h(v)−2h(LCA({u,v}))d(u,v)=h(u)+h(v)-2h(LCA(\\{u,v\\}))d(u,v)=h(u)+h(v)−2h(LCA({u,v}))(无权树)，有权树只需额外添加权重即可 朴素算法 很容易想到，当两结点处于同一深度时，若两结点相同，则该结点为所求\n否则，目标结点为LCA(直接祖宗结点)LCA(直接祖宗结点)LCA(直接祖宗结点)\n由此，需要先进行预处理，使两结点达到同一深度，即让深度较大的结点上移(经过的结点不可能是所求)\n这样的算法要求结点结构能够访问父结点\nc\u0026#43;\u0026#43;struct node { int h, fa; }; node tree[LEN]; // 以 下标 为结点编号 int lca(int u, int v) { int uh = tree[u].h, vh = tree[v].h; // 存储两结点的深度方便最短路计算 if (uh \u0026lt; vh) { swap(u, v); } while (tree[u].h \u0026gt; tree[v].h) { u = tree[u].fa; } while (u != v) { u = tree[u].fa; v = tree[v].fa; } // uh + vh - 2 * tree[u].h 即为无权树两点间最短路 return u; }时间复杂度：单次查询时，时间为O(n)O(n)O(n)\n倍增算法 不难发现对于共同祖先这个性质，LCA({u,v})LCA(\\{u,v\\})LCA({u,v})的祖先一定是u,vu,vu,v的共同祖先，它的孩子一定不是u,vu,vu,v的共同祖先；回顾二分思想，若通过一个性质能对可能解集划分为两部分连续的区间，就能二分搜索出最优/最大/最小的具有该性质的解\n倍增算法就是二分思想优化的朴素算法，在朴素算法中，只能一级一级跳，单次查询时间为O(n)O(n)O(n)\n而倍增算法通过给每个结点多分配O(log⁡h)O(\\log h)O(logh)空间来存储比它高2j2^j2j处的祖先结点，达到倍增跳跃的效果\n类比朴素算法，分为两步：\n两结点先达到同一深度：先算出深度差，根据其二进制码决定在何时跳、跳多远 两结点共同跳跃，根据解的二分性质，取起始点为{left=0right=h(u)mid=(left+right)\u0026gt;\u0026gt;1\\begin{cases}left=0\\\\right=h(u)\\\\mid=(left+right)\u0026gt;\u0026gt;1\\end{cases}⎩⎨⎧​left=0right=h(u)mid=(left+right)\u0026gt;\u0026gt;1​ 重申倍增算法和朴素算法的区别：\n预处理时，每个结点额外消耗O(log⁡h)O(\\log h)O(logh)的时间和空间 查询目标方式使用二分查找，时间复杂度为O(log⁡n)O(\\log n)O(logn) 为了能快速到达目标，使用倍增祖先数组，使每次查询距离深度hhh的祖先的时间复杂度为O(log⁡h)O(\\log h)O(logh) 因此整个算法的预处理时间复杂度O(nlog⁡n)O(n\\log n)O(nlogn)，单次查询O(log⁡n)O(\\log n)O(logn) c\u0026#43;\u0026#43;int i, n, m, s, son, fa, f[500001][18]; struct node { int h, cnt, next[2]; }; node tree[500001]; void dfs() { int i, j; stack\u0026lt;int\u0026gt; stk; tree[s].h = 1; stk.push(s); while (!stk.empty()) { i = stk.top(); stk.pop(); for (j = tree[i].cnt - 1; j \u0026gt;= 0; --j) { stk.push(tree[i].next[j]); f[tree[i].next[j]][0] = i; tree[tree[i].next[j]].h = tree[i].h + 1; } j = son = 1; while (son \u0026lt; tree[i].h) { f[i][j] = f[f[i][j - 1]][j - 1]; ++j; son \u0026lt;\u0026lt;= 1; } } } int jmp(int u, int h) { int i = 0; while (h) { if (h \u0026amp; 1) { u = f[u][i]; } ++i; h \u0026gt;\u0026gt;= 1; } return u; } int lca(int u, int v) { if (tree[u].h \u0026lt; tree[v].h) { swap(u, v); } u = jmp(u, tree[u].h - tree[v].h); int left = 0, right = tree[u].h, mid; while (left \u0026lt; right) { mid = (left + right) \u0026gt;\u0026gt; 1; if (jmp(u, mid) == jmp(v, mid)) { right = mid - 1; } else { left = mid + 1; } } right = jmp(u, left); left = jmp(v, left); return left == right ? left : f[left][0]; } int main() { cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m \u0026gt;\u0026gt; s; queue\u0026lt;int\u0026gt; ans; for (i = 1; i \u0026lt; n; ++i) { cin \u0026gt;\u0026gt; son \u0026gt;\u0026gt; fa; tree[fa].next[tree[fa].cnt++] = son; } dfs(); for (i = 0; i \u0026lt; m; ++i) { cin \u0026gt;\u0026gt; son \u0026gt;\u0026gt; fa; ans.push(lca(son, fa)); } while (!ans.empty()) { cout \u0026lt;\u0026lt; ans.front() \u0026lt;\u0026lt; endl; ans.pop(); } }","permalink":"https://echo.ishian.me/2025/04/30/algo-tree-lca/","summary":"介绍LCA的性质、朴素算法与倍增算法的原理和实现","title":"树的算法: LCA（最近公共祖先）"},{"content":"概述 针对于无根树(所有结点均可作为根结点且对答案没有影响)，以树的重心为根结点能在分治思想中减少递归层次 其定义为：删除树的重心后，剩下的若干个连通分支各自的结点数不超过总结点数的一半 为表述方便，下称树的结点数→\\rightarrow→树的大小、向上/向下表示以一个确定的结点为根结点的情况下形成的有向树的方向、一个结点的子树→\\rightarrow→以该结点为根结点的有向树中，该结点的子树 树的重心的性质有： 树的重心有1∼21\\sim21∼2个，若有两个重心则它们相邻 在无权树中，所有点到某点的距离和中，到树的重心的距离和是最小的(若有两个重心则到它们的距离和相等) 合并两棵无根树，则新树的重心在两棵旧树重心之间的路径上 直接连接两个原有重心(而不是其它结点)能够最快速度找到新树的重心(两个原有重心之一、或两个都是) 若上述两棵无根树中的一棵为平凡树，则新树重心不仅在旧树重心和该平凡树之间的路径上，而且离旧树重心最多一条边 算法结构 考虑以任意结点为根结点(转化为有根树)，要求树中某个分支结点的子树大小，其向下子树大小只需要深搜即可 而其向上子树(只有一个、不包括该结点)大小为总结点数减去其向下子树(包括该结点)大小 因此对于所有结点，均可用这种方法求出其子树大小，然后根据定义(记录子树的最大值)求重心(所有结点子树大小最大值中最小的那个不超过总结点数的一半)即可\n时间复杂度O(n)O(n)O(n)\nc\u0026#43;\u0026#43;// 随意选择初始结点作为根结点, 这里选择结点1 // 采用链式前向星存树(且为了方便、边的next为0时表示空) int i, size[VN], w[VN], g[2]; // 分别表示 向下子树大小、所有子树大小的最大值、重心编号 void getCenter(int pos, int fa) { // 表示当前遍历的结点、当前结点的爹 // 该函数只会访问每个结点一次, 初始时设置当前结点的大小为1(后续更新), 表示包括其本身 size[pos] = 1; w[pos] = 0; // 当前没有计算出任何子树的大小, 最大值自然为0 for (i = head[pos]; i; i = edge[i].next) { if (edge[i].to != fa) { // 不计算向上子树 getCenter(edge[i].to, pos); // 深搜计算出size[edge[i].to] size[pos] += size[edge[i].to]; w[pos] = max(w[pos], size[edge[i].to]); // 向下子树的最大值 } } w[pos] = max(w[pos], VN - size[pos]); // 再和向上子树大小比 if (w[pos] \u0026lt;= (VN \u0026gt;\u0026gt; 1)) { g[g[0] != 0] = pos; // 记录该重心 } } ","permalink":"https://echo.ishian.me/2025/04/30/algo-tree-gravity/","summary":"讲解树的重心的定义与性质，并给出DFS求重心的O(n)算法实现","title":"树的算法: 寻找树的重心"},{"content":"c\u0026#43;\u0026#43;#include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int d[11000010 \u0026lt;\u0026lt; 1], l = 0, r = -1, i, ans, n; string t; char s[11000010 \u0026lt;\u0026lt; 1]; int main() { cin \u0026gt;\u0026gt; t; s[0] = \u0026#39;#\u0026#39;; for (i = 0; i \u0026lt; t.size(); ++i) { s[(i \u0026lt;\u0026lt; 1) + 1] = t[i]; s[(i \u0026lt;\u0026lt; 1) + 2] = \u0026#39;#\u0026#39;; } n = (i \u0026lt;\u0026lt; 1) + 3; for (i = 0; i \u0026lt; n; ++i) { if (i \u0026lt;= r) { d[i] = min(d[l + r - i], r - i); } while (i - d[i] \u0026gt;= 0 \u0026amp;\u0026amp; i + d[i] \u0026lt; n \u0026amp;\u0026amp; s[i - d[i]] == s[i + d[i]]) { ++d[i]; } if (i + d[i] \u0026gt; r) { l = i - d[i]; r = i + d[i]; } ans = max(ans, d[i]); } cout \u0026lt;\u0026lt; ans - 1 \u0026lt;\u0026lt; endl; }","permalink":"https://echo.ishian.me/2025/04/26/algo-string-manacher/","summary":"给出Manacher算法求最长回文子串的完整代码实现","title":"字符串算法: 马拉车（manacher）回文串统计"},{"content":"埃氏筛法 埃氏筛法，全名埃拉托斯特尼筛法 若一个数是素数，那么必然不可能存在有除1和它自身的因子，也就是比它小的数(除了1)都无法整除它 那么，从小到大遍历除1外的正整数，将所有素因子的倍数都标记上，如果遍历到一个数i，它没有被标记，那么它就是素数；初始时，notprime[2]=false(即2是素数)：\nc\u0026#43;\u0026#43;bool notprime[MAXN]; void getPrime() { for (int i = 2; i \u0026lt; MAXN; ++i) { if (!notprime[i]) { // 说明它是素数 for (int j = 2; i * j \u0026lt;= MAXN; ++j) { notprime[i * j] = true; } } } }可以简单地写出时间表达式：\n⌊N2⌋+⌊N3⌋+⌊N5⌋+⋯+⌊Np⌋\\begin{align}\\left\\lfloor\\frac{N}{2}\\right\\rfloor+\\left\\lfloor\\frac{N}{3}\\right\\rfloor+\\left\\lfloor\\frac{N}{5}\\right\\rfloor+\\cdots+\\left\\lfloor\\frac{N}{p}\\right\\rfloor\\end{align}⌊2N​⌋+⌊3N​⌋+⌊5N​⌋+⋯+⌊pN​⌋​​，ppp为小于或等于MAXN的最大质数，证明过程是很难的，只需要知道埃氏筛法的时间复杂度为O(nlog⁡(log⁡(n)))O(n\\log(\\log(n)))O(nlog(log(n)))即可 也就是说，在10610^6106数量级内，且需要快速判断一个大范围内的素数时，埃氏筛法是有效的\n欧拉筛法 ","permalink":"https://echo.ishian.me/2025/04/22/algo-prime-selection/","summary":"讲解埃氏筛与欧拉筛两种质数筛法的原理、实现及时间复杂度","title":"数论: 质数筛法"},{"content":"SQL语法 基本数据类型 域和属性 在数据库中，域是具有相同数据类型的有意义的值的集合，属性是域的实例 对应于编程语言，可以不严谨地把域看作数据类型，把属性看作变量，把某一行中的某一属性值看作变量的取值 域的组成： 相同数据类型：要求一个域必须基于一个基本数据类型上作约束 有意义：用户可以自定义约束，限制取值范围，使属性的取值符合现实 域的第一要求(或者说域的第一范式)：域是原子性的，即属性的每一个取值都不可分割 例如，如果某一个取值是元组或值列表，那就不符合域的第一范式，因为元组是可分的 数值类型 整型：各种数据库都支持不同大小的整型类型，如小整型、整型、大整型，但命名不同 定点数：decimal(P, D)或numeric(P, D) 在SQL标准中它们有细微差别：numeric会严格保证运算过程中精度不变，而decimal在存储时会分配更多空间以便运算时提高精度，只在显示时收敛精度；各种数据库对它们的实现和标准也有差别 但在实践中，可以认为没什么差别 P表示总的有效位数 D大于0时，表示小数点后的有效位数；小于0时，表示小数点前的有效位数；不能等于0 浮点数：比定点数运算更快，但会损失精度，适用于存储近似值 各种数据库的命名不同 字符串类型 各种数据库的字符串类型命名一致 char(size)：定长字符串，存储长度固定为size的字符串，无论值的长度是多少 varchar(size)：可变长字符串，最大存储长度为size，实际存储长度和值的长度相等 字符串数据是用单引号括起的字符串 表名、列名、属性名等标识符是用双引号括起的字符串(当属性名中存在空格时，需要用\u0026quot;\u0026quot;括起) 但应该遵循命名规范：用小写字母、不同单词用下划线连接 其它类型 文本类型：数据库通过在表外存储大文本，在表内存储对应的指针来实现文本类型，存储效率较低 各种数据库命名不一致 布尔类型： 日期和时间： DATE：日期类型 TIME：时间类型 日期时间类型：各种数据库命名不同 TIMESTAMP：时间戳类型 字面量：为符合规范，尽量使用标准格式的字面量YYYY-MM-DD HH:MM:SS或显式转换 类型转换 隐式类型转换： 显式类型转换： 不同数据库的实现 待查\n表达式 表达式的本质 表达式就是一个函数，只不过是编程语言通过一些语法糖对表达式进行了简化 表达式的输入输出都是值，输入可以是一组值，输出只能是标量值\n原子表达式：指那些不能再分割的表达式，基于原子表达式可以通过运算符、标量值函数组合得到新的表达式\n字面量 列引用：即某个表中某个属性的名称 Sql语句的输入输出都是表，是多组值，一个表经过表达式，相当于输入一个列向量(每个元素是一组值)，表达式会对该列向量按元素求值，返回一个新的列向量(每个元素是一个标量值) 例如：\nsql-- e 是一个元组 1\t-- f(e) = 1\t1是一个整型字面量 attr\t-- f(e) = e.attr\tattr是一个列引用 attr \u0026gt; 1\t-- f(e) = boolean(e.attr \u0026gt; 1) -- f(表) ~ f([e1, e2, ...]) ~ [f(e1), f(e2), ...] 数值表达式及部分函数 标量值：指单个不可分割的值 绝大多数数据库对返回一个复合类型(如集合等)的支持很有限，这里讨论的大多是返回标量值的表达式 所以可以将标量值看作一个数值表达式的返回值\n值列表：用()括住一系列标量值，表示由这些值组成的集合\n基本算术运算：+ - * / % ^\nCASE：对输入值从上到下地进行逻辑判断，若成立则返回指定的标量值\nsql-- 较灵活的写法 CASE WHEN logic_expr1(input) THEN value1 WHEN ... THEN ... ELSE default_value\t-- 若不加则为ELSE null END -- 较偷懒的写法, 只支持等值比较 case input WHEN val1 THEN ret_val1 WHEN ... THEN ... ELSE default_value END null：空值，表示一种不确定的状态 若参与运算则一定返回null，参与一般比较一定会返回false，会被聚集函数无视 只能通过is null和is not null判断是否为空\n空值处理：涉及空值的函数 聚集函数值：对多行进行聚集，返回标量值 很多函数都只接受一行数据的输入，输出一个标量值 而聚集函数接受多行数据的输入，仍输出一个标量值\n逻辑表达式 连接逻辑表达式的运算符：and、or、not 连接数值表达式的运算符： is null和is not null：若左值是/不是空则返回真 in和not in，前跟标量值，后跟值列表，若左值属于/不属于该值列表则返回真 between left_val and right_val：闭区间比较 连接数值表达式和子查询语句的函数： any()/some() all() 数据完整性约束 限制不可重复 primary key：声明主键，主键能够唯一标识元组，天生包括了非空约束及唯一性约束 为了性能，虽然关系是一个集合，但数据库不会时时刻刻保证没有重复，除非添加了主码约束 添加后，若插入元组中的主键已经存在，将会报错\nsql-- 建表时声明 主键 (1) \u0026lt;属性名\u0026gt; \u0026lt;属性类型\u0026gt; primary key\t这样的主键只有一个属性 (2) primary key (attr1, attr2, ...)\t允许声明一组属性作为主键 unique：声明唯一性约束，被约束的属性值的集合只能存在至多一次 和主键不同，它允许值为空，且允许有多个记录的该属性的值为空\nsql-- 建表时 声明方式与主键类似, 但unique可声明多次, 被约束的属性组可以重叠、甚至重复 (1) \u0026lt;属性名\u0026gt; \u0026lt;属性类型\u0026gt; unique (2) unique (attr1, attr2, ...)\t这时它们会作为一个整体受到唯一性约束 限制输入范围 not null：声明非空约束，一个非空约束只能针对一个属性，即只能用上述的第一种方式声明 字面意义，若没有默认值约束，则插入元组时必须指定该属性的值，且不能为null\ndefault(value)：声明默认值约束，一个默认值约束只能针对一个属性，即只能用上述的第一种方式声明 平时就相当于default (null)，指定后将在插入元组且未指定值时填充默认值 声明default约束必须指定默认值value\ncheck(logic_expr)：声明检查约束，检查约束必须指定逻辑表达式，插入元组时会检查，若违反约束则会报错 实际上检查约束无论用哪一种方法声明都无所谓，因为是通过表达式来约束属性的 最好是使用第二种方式声明\n外码 foreign key (\u0026lt;参照属性组\u0026gt;) refereces \u0026lt;被参照表\u0026gt;(\u0026lt;被参照属性组\u0026gt;)：声明外键约束，其中被参照属性组必须是被参照表的主键 声明后，\u0026lt;参照属性组\u0026gt;的值要么是被参照表中已有的主键值，要么为**null** 外键的声明方式有点特别：\nsql(1) \u0026lt;属性名\u0026gt; \u0026lt;属性类型\u0026gt; references \u0026lt;关系名\u0026gt;(\u0026lt;属性名\u0026gt;) (2) foreign key \u0026lt;属性名\u0026gt; references \u0026lt;关系名\u0026gt;(\u0026lt;属性名\u0026gt;)需要注意，如果一个表被其它表参照，那么它是不能被删除的\n约束对象 约束实际上是一个数据库对象，是可以起名的(默认是系统起名)，起名也有两种方式(都是在键前添加constraint \u0026lt;约束名\u0026gt;)：\nsql(1) \u0026lt;属性名\u0026gt; \u0026lt;属性类型\u0026gt; constraint \u0026lt;约束名\u0026gt; references tbl_name(attr_name) \u0026lt;属性名\u0026gt; \u0026lt;属性类型\u0026gt; constraint \u0026lt;约束名\u0026gt; unique (2) constraint \u0026lt;约束名\u0026gt; foreign key () references ..() constraint \u0026lt;约束名\u0026gt; primary key ()实践中，最好自己起约束名\n最后对比一下两种声明方式：\n第一种声明方式针对一个属性，可以用空格分隔并声明多个约束 可以看作是在一个属性后列出其约束 第二种声明方式针对含多个属性的约束，一条只能声明一个约束，但能约束一个属性组 DDL(数据定义语言) 数据库对象 database：数据库 schema：模式 table：表 view：视图 function：函数 constraint：约束 通用语法 通用语法是指，所有数据库对象创建/更改/删除时都需要用到的主语句，例如create、drop等\ncreate：创建某种数据库对象\nsqlcreate \u0026lt;type\u0026gt; \u0026lt;obj_name\u0026gt; drop：删除某种数据库对象\nsqldrop \u0026lt;type\u0026gt; \u0026lt;obj_name\u0026gt; alter：更新某种数据库对象的结构 在实践中，alter尽量作为最后手段，一切结构尽量在创建时定义完整\nsqlalter \u0026lt;type\u0026gt; \u0026lt;obj_name\u0026gt; 关于表 创建表需要在表名后附加元组，表示每一列的属性名、属性类型，以及表的约束 创建表时添加约束的语法已在前面详细讲过\nsqlcreate table \u0026lt;table_name\u0026gt; ( \u0026lt;attr_name\u0026gt; \u0026lt;attr_type\u0026gt; \u0026lt;attr_constraint\u0026gt;, \u0026lt;attr_constraint\u0026gt; ); 更新表的结构：\nsqlalter table \u0026lt;table_name\u0026gt;\t-- 后跟子句: add column \u0026lt;column_name\u0026gt;\t-- 添加列 drop column \u0026lt;column_name\u0026gt; -- 删除列 -- 重命名列 add constraint \u0026lt;constraint_name\u0026gt; (\u0026lt;attrs\u0026gt;)\t-- 添加约束, 定义方式同上 drop constraint \u0026lt;constraint_name\u0026gt;\t-- 删除约束 查询语句 FROM FROM子句用于添加数据源，后跟表或视图，有两种添加数据源的方式\n笛卡尔积：直接用','连接表，FROM会将它们进行笛卡尔积后交给下一个子句 这样的FROM通常配合WHERE子句进行限制\n内连接：用**JOIN ON子句连接数据源，这样的子句必须跟ON及一个逻辑表达式 会将两表进行笛卡尔积并只保留使该逻辑表达式为真的行**，再交给下一个子句\nsqlFROM t1 JOIN t2 ON \u0026lt;logic_expr\u0026gt; 自然连接：部分数据库支持，等价于ON后跟一系列同名属性的等值比较的内连接 因此不需要跟ON\nsqlFROM t1 NATURAL JOIN t2 交叉连接：部分数据库的语法，等价于直接进行笛卡尔积\nsqlFROM t1 CROSS JOIN t2 外连接：在内连接的基础上，保留那些不满足逻辑表达式的行，连接后未知的值设为NULL 分为LEFT JOIN、RIGHT JOIN、FULL JOIN 分别是：保留左表、保留右表、保留两边的表 使用外连接要注意，如果要进行多表连接，那么一个内连接很有可能丢弃掉外连接保留下来的行(因为部分属性是NULL，很容易经过逻辑表达式后返回假)\nWHERE WHERE子句用于进一步限制由FROM得来的查询结果，后跟逻辑表达式 逻辑表达式的细节不再赘述 GROUP BY GROUP BY子句用于根据某些属性进行分组，需要和SELECT等语句区分开，GROUP BY后跟一系列属性名(列引用)，而不是值列表 后跟多个属性时，GROUP BY会根据元组的等值比较规则来分组 分组后由于域的原子性这个第一范式，只能选择分组依据或者聚集函数值作为原子表达式 很多时候用户会在使用GROUP BY后SELECT那些不能作为原子表达式的属性，并误以为是SELECT的问题，实际上是GROUP BY后能作为原子表达式的内容变为了分组依据或聚集函数值 HAVING HAVING子句用于过滤分组，后跟逻辑表达式 前面说了，WHERE只能用于限制由FROM得来的查询结果 而又有分组后再根据聚集函数值来限制结果的需求，因此有了HAVING子句 一旦使用HAVING子句则默认使用了GROUP BY(没有GROUP BY时将整个表视为一组)，因此如GROUP BY中说的那样，只能用分组依据或聚集函数值作为原子表达式 SELECT SELECT是整个查询语句的核心，后跟一个数值表达式列表，也就是值列表，SELECT会对输入表的每一行计算值列表的结果，并输出查询结果呈现给用户 SELECT的返回值是一个表，不是视图，不要因视图的创建语法而误判 DISTINCT关键字：只能在SELECT后添加，用于对得到的结果去重 去重规则：将整一行视为元组，若存在两行完全一致，则去重 可配合count()统计不重复的值的数量 ORDER BY ORDER BY子句用于排序，后跟值列表，ORDER BY会像SELECT那样对SELECT输出的表的每一行计算值列表的值，并根据结果排序 当值列表中有多个值时，排序规则是进行元组的比较，即从左到右，若前一个值相等则比较后一个值，从此类推下去\n自定义排序规则：因为跟的是数值表达式列表，可以配合CASE表达式来自定义排序规则\n默认下，会对计算结果进行升序排序，即**ASC** 如要降序排序，需要在表达式后添加**DESC**，会单独对该表达式值进行降序排序\nNULLS FIRST和NULLS LAST：部分数据库支持，会将计算结果为NULL的行放在最前/最后\nsqlSELECT * FROM t ORDER BY attr1, NULLS FIRST 虽然ORDER BY在SELECT后执行，但它实际上能看到所有没被SELECT选中的属性 所以ORDER BY仍能使用SELECT能使用的那些属性作为原子表达式\nLIMIT LIMIT子句用于限制表的显示前若干行，后跟一个整型表示要显示的行数 子查询 不相关子查询 WITH AS： 相关子查询 集合查询 UNION INTERSECT EXCEPT或MINUS ALL关键字：集合运算默认进行去重，将ALL添加在上述语句后，会保留所有重复的行 其它DML INSERT INTO UPDATE DELETE SQL函数 常见空值处理函数 coalesce(...)：返回从左到右第一个非null值(除非不存在非null值)，要求所有值的数据类型相同 不想无视某些为空值的属性时，可用coalesce()\nnullif(a, b)：若a=b，返回null，否则返回a 前面说到空值一旦参与运算即返回空且会被聚集函数无视，可以通过nullif忽略某些非空值\nis [not] distinct with：空值安全比较，相比于=和!=，将null视为一般值 即null = null返回真/null != null返回真\nnulls first和nulls last：空值排序，将该字段放在order by的某个属性后面，会把为null的元组放在最前/最后显示\nsql 常见聚集函数 固有的聚集函数(所有数据库都支持)：count()、avg()、sum()、min()、max() 统计类聚集函数：variance()、stddev()、corr(x, y) 字符串聚集函数：string_agg(..., delim_char) 自定义聚集函数 ","permalink":"https://echo.ishian.me/2025/04/20/sql-std/","summary":"介绍SQL基本语法：数据类型、表的增删改查及约束与查询子句用法","title":"SQL: 基本用法"},{"content":"扩展欧拉定理 扩展欧拉定理通常用于快速模幂中的降幂，其内容为：ab≡{ab mod φ(m),gcd⁡(a,m)=1a(b mod φ(m))+φ(m),gcd⁡(a,m)≠1,b≥φ(m)ab,gcd⁡(a,m)≠1,b\u0026lt;φ(m) (mod m)a^b\\equiv\\begin{cases}a^{b\\ {\\rm mod}\\ \\varphi(m)},\u0026amp;\\gcd(a,m)=1\\\\a^{(b{\\rm \\ mod}\\ \\varphi(m))+\\varphi(m)},\u0026amp;\\gcd(a,m)\\ne1,b\\ge\\varphi(m)\\\\a^b,\u0026amp;\\gcd(a,m)\\ne1,b\u0026lt;\\varphi(m)\\end{cases}\\ \\ \\ \\ ({\\rm mod}\\ m)ab≡⎩⎨⎧​ab mod φ(m),a(b mod φ(m))+φ(m),ab,​gcd(a,m)=1gcd(a,m)=1,b≥φ(m)gcd(a,m)=1,b\u0026lt;φ(m)​ (mod m)\n即，若底数和模数互素，可将幂bbb降为φ(m)\\varphi(m)φ(m) 否则，若指数比模数大，可将幂bbb降为(bmod φ(m))+φ(m)(b\\mod\\varphi(m))+\\varphi(m)(bmodφ(m))+φ(m) 很简单可以得到，如果模数为质数，则φ(m)=m−1\\varphi(m)=m-1φ(m)=m−1，则ab≡ab mod (m−1) (mod m)a^b\\equiv a^{b{\\rm\\ mod}\\ (m-1)}\\ ({\\rm mod}\\ m)ab≡ab mod (m−1) (mod m) 实际上，当gcd⁡(a,m)=1\\gcd(a,m)=1gcd(a,m)=1时，第二项仍成立，这里的意思是优先考虑第一项、如果不互素才考虑第二项；其实可以把gcd⁡()\\gcd()gcd()限制去掉直接记第二项 即\nab≡{a(b mod φ(m))+φ(m),b≥φ(m)ab,b\u0026lt;φ(m)\\begin{align}\u0026amp;a^b\\equiv\\begin{cases}a^{(b\\ {\\rm mod}\\ \\varphi(m))+\\varphi(m)},\u0026amp;b\\ge\\varphi(m)\\\\a^b,\u0026amp;b\u0026lt;\\varphi(m)\\end{cases}\\end{align}​ab≡{a(b mod φ(m))+φ(m),ab,​b≥φ(m)b\u0026lt;φ(m)​​​ 证明：\n第一项是容易得出的，由欧拉定理，若gcd⁡(a,m)=1\\gcd(a,m)=1gcd(a,m)=1，则aφ(m)≡1 (mod m)a^{\\varphi(m)}\\equiv1\\ ({\\rm mod}\\ m)aφ(m)≡1 (mod m) 那么ab≡anφ(m)+(b mod φ(m))≡1n⋅ab mod φ(m) (mod m)a^b\\equiv a^{n\\varphi(m)+(b\\ {\\rm mod}\\ \\varphi(m))}\\equiv1^n·a^{b\\ {\\rm mod}\\ \\varphi(m)}\\ \\ ({\\rm mod}\\ m)ab≡anφ(m)+(b mod φ(m))≡1n⋅ab mod φ(m) (mod m)\n第二项：因为gcd⁡(a,m)≠1\\gcd(a,m)\\ne1gcd(a,m)=1，将aaa拆解为多个质因数的幂的乘积形式，即\na=∏pi为素数∧pi∣apiri⇒ab=∏pi为素数∧pi∣apib⋅ri\\begin{align}a=\\prod_{p_i为素数\\wedge p_i|a} p_i^{r_i}\\Rightarrow a^b=\\prod_{p_i为素数\\wedge p_i|a} p_i^{b·r_i}\\end{align}a=pi​为素数∧pi​∣a∏​piri​​⇒ab=pi​为素数∧pi​∣a∏​pib⋅ri​​​​ 对每一个质因子pip_ipi​：\n如果gcd⁡(pi,m)=1\\gcd(p_i,m)=1gcd(pi​,m)=1，则由欧拉定理得piφ(m)≡1 (mod m)⇒pib≡pib mod φ(m) (mod m)p_i^{\\varphi(m)}\\equiv1\\ ({\\rm mod}\\ m)\\Rightarrow p_i^{b}\\equiv p_i^{b\\ {\\rm mod}\\ \\varphi(m)}\\ ({\\rm mod}\\ m)piφ(m)​≡1 (mod m)⇒pib​≡pib mod φ(m)​ (mod m)\n否则，将mmm写成pip_ipi​因子和非pip_ipi​因子的两部分m=s⋅pikm=s·p_i^km=s⋅pik​，其中k\u0026gt;0k\u0026gt;0k\u0026gt;0 由于sss不是pip_ipi​的幂，即gcd⁡(s,pi)=gcd⁡(s,pik)=1\\gcd(s,p_i)=\\gcd(s,p_i^k)=1gcd(s,pi​)=gcd(s,pik​)=1，再次用欧拉定理和欧拉函数的性质得piφ(s)≡1 (mod s)、φ(m)=φ(s⋅pik)=φ(s)φ(pik)p_i^{\\varphi(s)}\\equiv1\\ ({\\rm mod}\\ s)、\\varphi(m)=\\varphi(s·p_i^k)=\\varphi(s)\\varphi(p_i^k)piφ(s)​≡1 (mod s)、φ(m)=φ(s⋅pik​)=φ(s)φ(pik​)\n因此piφ(m)≡piφ(s)φ(pik)≡1φ(pik) (mod s)p_i^{\\varphi(m)}\\equiv p_i^{\\varphi(s)\\varphi(p_i^k)}\\equiv1^{\\varphi(p_i^k)}\\ ({\\rm mod}\\ s)piφ(m)​≡piφ(s)φ(pik​)​≡1φ(pik​) (mod s) 两边同乘pikp_i^kpik​，得piφ(m)+k≡pik (mod m)p_i^{\\varphi(m)+k}\\equiv p_i^k\\ ({\\rm mod}\\ m)piφ(m)+k​≡pik​ (mod m) 因此pib≡pib−k+k≡pib−k+k+φ(m)≡pib+φ(m) (mod m)p_i^b\\equiv p_i^{b-k+k}\\equiv p_i^{b-k+k+\\varphi(m)}\\equiv p_i^{b+\\varphi(m)}\\ ({\\rm mod}\\ m)pib​≡pib−k+k​≡pib−k+k+φ(m)​≡pib+φ(m)​ (mod m)，其中b≥φ(m)b\\ge\\varphi(m)b≥φ(m) 于是可以对bbb进行变换，得pib−φ(m)≡pib (mod m)p_i^{b-\\varphi(m)}\\equiv p_i^{b}\\ ({\\rm mod}\\ m)pib−φ(m)​≡pib​ (mod m)，其中b−φ(m)≥φ(m)b-\\varphi(m)\\ge\\varphi(m)b−φ(m)≥φ(m)，即b≥2φ(m)b\\ge2\\varphi(m)b≥2φ(m) 即只有当b≥2φ(m)b\\ge2\\varphi(m)b≥2φ(m)时，可以降幂；于是幂的最小值范围为[φ(m),2φ(m))[\\varphi(m),2\\varphi(m))[φ(m),2φ(m)) 即降幂的最小值为(b mod φ(m))+φ(m)(b\\ {\\rm mod}\\ \\varphi(m))+\\varphi(m)(b mod φ(m))+φ(m)\n结合两种情况(第一种情况可以变成第二种情况的形式)，对aaa的任意质因子pip_ipi​，有pib≡pi(b mod φ(m))+φ(m) (mod m)p_i^b\\equiv p_i^{(b\\ {\\rm mod}\\ \\varphi(m))+\\varphi(m)}\\ ({\\rm mod}\\ m)pib​≡pi(b mod φ(m))+φ(m)​ (mod m)，即pibri≡pi((b mod φ(m))+φ(m))ri (mod m)p_i^{br_i}\\equiv p_i^{((b\\ {\\rm mod}\\ \\varphi(m))+\\varphi(m))r_i}\\ ({\\rm mod}\\ m)pibri​​≡pi((b mod φ(m))+φ(m))ri​​ (mod m) 即ab≡a(b mod φ(m))+φ(m) (mod m)a^b\\equiv a^{(b\\ {\\rm mod}\\ \\varphi(m))+\\varphi(m)}\\ ({\\rm mod}\\ m)ab≡a(b mod φ(m))+φ(m) (mod m)，原式得证\n第三项：既然b\u0026lt;φ(m)b\u0026lt;\\varphi(m)b\u0026lt;φ(m)了，自然不能降幂了\n","permalink":"https://echo.ishian.me/2025/04/20/algo-math-ext-euler-theorem/","summary":"陈述并证明扩展欧拉定理，用于快速模幂中不互素情形的降幂","title":"数论: 扩展欧拉定理"},{"content":"应用层体系结构 应用层处于最高层，享受下层提供的可靠稳定传输服务，应用层只需要关心数据的内容，而不需要关心数据如何传输、数据的大小等等(当然开发者需要选择合适的传输层协议进行传输，这里的“不需要关心”指的是下层的具体实现) 应用层体系结构分为客户端-服务器体系结构和**P2P**(对等)结构 前者为仅客户端发送请求、服务器进行响应，是集中服务的，将资源集中在服务器端能高效地服务、维护应用程序 后者为非集中的结构，双方均为用户主机，自扩展性较强 应用层协议间的通信是一个主机某进程到另一个主机某进程的，因此网络层提供了IP地址来标记主机、传输层提供了端口号属性来标记进程 应用层协议定义了数据的实际意义，例如： 标记数据来自请求方还是响应方，传输层TCP报文中的序号、确认号看上去也许和它有些类似，但实际上是完全不同的；主机可以通过判断报文是请求或是响应来作出不同的反应，而传输层的这些属性是用于判断进行接收方还是发送方的行为 包含各种语义的字段，这些字段组合并根据规则实现具体的某个应用功能，这些规则的集合便称作“协议” HTTP HTTP(HyperText Transfer Protocol，超文本传输协议)是**用于传输网页(Web page)**的应用层协议 网页本身是由HTML文件作为框架(这个框架定义了其中文本的属性，是文本的文本，因此称为超文本)，其中嵌入各种类型的文件资源组成的 而HTTP则定义了网页的编码方式等 ","permalink":"https://echo.ishian.me/2025/04/20/comp-networking-applayer/","summary":"介绍应用层体系结构及HTTP等应用层协议的原理与报文格式","title":"计算机网络: 应用层协议"},{"content":"计算机网络指标 计算机网络为通信技术和计算机技术的结合，互连、自治(无主从关系) 数据率/数据传输率/比特率：单位时间传输的比特数 ~波特率 时延：路由器缓存未满时、数据排队造成的延迟 结点处理：数据检错、确定通信链路，通常少于毫秒 排队延迟：通过流量强度描述，0~1为正常值、越大表示排队延迟越大 传输延迟： f(数据量带宽)\\begin{align}f\\left(\\frac{数据量}{带宽}\\right)\\end{align}f(带宽数据量​)​​，是路由器推出分组分批的时间(带宽有限，需要分批次传输分组数据包) 传播延迟： f(通信链路长度信号传播速率)\\begin{align}f\\left(\\frac{通信链路长度}{信号传播速率}\\right)\\end{align}f(信号传播速率通信链路长度​)​​，是路由器传播一批数据所需时间，与两个路由器之间的距离、传输线路性能有关 时延带宽积：传播时延和带宽的乘积，可视作以比特为单位的链路长度 计算传播延迟除了原有公式，还可用时延带宽积除以带宽得到 丢包：路由器缓存满时、后来数据将被丢弃；丢包率： 丢包数分组包数\\begin{align}\\frac{丢包数}{分组包数}\\end{align}分组包数丢包数​​​ 网络体系结构 从功能上描述计算机网络结构，是一种分层结构 每层均遵循某些协议，完成其功能 对所有网络体系结构，每一层的实体使用下层透明的服务、实现本层功能、向上层提供服务 同层相邻实体通过本层协议通信 相邻层实体通过SAP(Service Access Point，服务访问点)交换原语，使用服务 OSI(开发系统互联)参考模型：用于参考的国际标准模型 该模型学术价值高、但工程价值低，难以实现且难以扩展 每一层都有自己的PDU(协议数据单元)，前接本层的缩写来区分不同层的PDU 物理层：接收DL-PDU进行比特编码、通过物理介质传输数据 接口特性：机械、电气、功能、规程 传输模式：单工、半双工、全双工 数据链路层(Data link)：完成结点-结点传输 负责组帧(Framing)/拆帧，并在帧头添加发送端/接收端的物理地址，帮助物理层确定目标主机 组帧：接收N-PDU，将数据分为若干帧(每帧由若干比特位组成)，并添加帧头DH、帧尾DT 拆帧为组帧的反过程 控制功能：流量控制、差错控制(检错并重传)、访问控制(决定哪些设备拥有通信链路的控制权) 网络层(Network)：完成源主机-目的主机的分组传输 负责分组，并添加发送端/接收端的逻辑地址，来唯一标识目标主机 路由(Routing)：由路由器(Router)及网关等互连网络，由网络层进行路由(选择较优路径进行数据传输) 传输层(Transport)：完成端-端进程的报文传输 负责接收S-PDU，进行分段 添加SAP地址，如端口号等信息，来确定目标端的进程 会话层(Session)：负责对话控制，例如在数据流中插入同步点 表示层(Presentation)：将A-PDU通过编码、加密、压缩，转化为P-PDU，处理来自应用层数据的语法、语义问题 应用层：支持用户通过用户代理、网络接口等使用网络服务 结合OSI、TCP/IP参考模型，有五层参考模型(将会话层、表示层合并到应用层中) 数据传输过程中，需经过交换机(Switch)、路由器(Router) 交换机仅含物理层、链路层，通过MAC地址确定目的地 由于只能分割冲突域，因此常用于局域网的互联，且安全性不足 路由器仅含物理层、链路层、网络层，通过IP地址确定目的地 通常用于连接不同网段，进行跨网络数据传输 ","permalink":"https://echo.ishian.me/2025/04/18/comp-networking-profile/","summary":"介绍计算机网络的性能指标、时延与丢包概念及OSI、TCP/IP分层体系结构","title":"计算机网络: 概览"},{"content":"Java IO java.io 文件类 File：文件类对象可表示一个文件，包括普通文件和目录文件，在文档中称为“抽象路径”\nFile类对象只能修改文件的属性，不能读写文件\nFile构造方法：提供文件路径或其它File类对象\nFile文件操作在大多数情况下不常用且不好用，更多是查询文件的属性，很多时候应该使用Files或apache提供的io方法，因此这里不介绍\nRandomAccessFile：随机访问文件类和文件类完全不同，不能修改文件的元属性，但是能随机访问或修改文件的内容\nRandomAccessFile构造方法：提供文件路径或其它File类对象，并提供用字符串表示的模式参数\n\u0026quot;r\u0026quot;：只读打开，文件不存在或发生写操作会抛出IOException受检异常 \u0026quot;rw\u0026quot;：读写打开，文件不存在会创建，此处的“写”是覆盖 \u0026quot;rwd\u0026quot;：读写打开，并在缓存满时或调用close()时同步 \u0026quot;rws\u0026quot;：读写打开，并在每一次写操作后同步 RandomAccessFile常用方法：\nlong getFilePointer()：返回当前的文件指针 seek(long pos)：将文件指针移到pos处 close()：关闭并释放这个随机访问流 Type readType()和writeType(Type x)：支持读写并自动解析一个基本数据类型的数据 String readLine()：读一行文本 readFully(byte[] b, int off, int len)：读取当前指针后最多len个字节，读入b+off中 此函数有readFully(byte[] b)重载，将当前指针后的所有字节读入b中 writeBytes(String)和writeChars(String)：将字符串以byte序列形式、Unicode编码形式写入文件 String readUTF()和writeUTF(String s)：以UTF8编码读写字符串 getFD()：获取FileDescriptor对象 FileDescriptor：不含任何操作功能，而是文件资源在java中抽象的描述符\nboolean valid()：检查描述符是否有效 sync()：将缓冲区内容同步到硬盘，上述RandomAccessFile的同步就是调用getFD().sync()完成的 流的概念 流是java提供的另一种操作数据输入输出的机制，和RandomAccessFile不同，它们是顺序读写的，曾经读过的数据无法再读(除非支持标记)，在读取数据前必须将所有前面的数据全部读取 按数据流向分类，分为输入流、输出流 按数据单位分类，分为字符流、字节流，字符流以两个字节为单位、字节流以单个字节为单位 java对这两个分类依据有较易区分的命名风格，输入字符流为XxxReader、输出字符流为XxxWriter、输入字节流为XxxInputStream、输出字节流为XxxOutputStream 按功能分类，分为节点流、处理流，节点流对象直接和数据源绑定，仅提供读取字节或字符的功能，处理流则是节点流的封装，具体体现在构造方法上，节点流需要资源描述符对象，而处理流需要流对象 字节流 所有字节流类都继承InputStream或OutputStream InputStream：仅提供读取单个或多个字节的方法，除此之外： long skip(long n)：跳过最多n个字节，返回实际跳过的字节数 boolean markSupported()：测试该输入流是否支持mark()和reset() mark(int lim)：对现在指针作标记，该标记在此后的lim个字节内才有效 reset()：复位到上一个有效的标记，若不存在则抛出异常 通过mark()和reset()配合可以实现重复读取某一段连续字节 OutputStream：仅提供写入单个或多个字节的方法，除此之外有flush()方法能快速清空缓冲区 节点流最重要的是FileInputStream和FileOutputStream，可以提供文件路径或抽象文件对象构造文件字节流，FileOutputStream可以提供append布尔值参数表示是否以追加模式写入 文件流本身不支持标记，ByteArray系列的流支持但并不常用(因为只能处理小文件)，更常用的是处理流Buffered系列的流 处理流则非常多样： BufferedInput/OutputStream：十分常用的缓冲处理流，本身不提供解析之类的方法，而是用于管理缓冲区的流，支持mark DataInput/OutputStream：能将字节数据解析为一系列基本数据类型的处理流，和RandomAccessFile提供的输入/输出方法一致 readLine()方法已被弃用，建议使用BufferedReader提供的readLine()方法 ObjectInput/OutputStream：能将字节数据解析为Object对象的处理流，用于序列化和反序列化，要求类实现Serializable空接口；java原生的序列化有安全、性能问题 PipedInput/OutputStream：用于线程间通信，输入流或输出流通过connect()方法连接配套的输出流或输入流 SequenceInputStream：序列处理流，用于将一系列输入流串联起来，统一由SequenceInputStream对象管理 需要用到传统的Enumeration枚举器接口，调用Vector\u0026lt;InputStream\u0026gt;对象的elements()方法即可，但实际上现在已经很少使用了 PrintStream：提供的最常用的方法是print()、println()的一系列重载方法，System.out是PrintStream类的final对象 字符流 所有字符流类都继承Reader或Writer 处理对象数据或二进制数据时，字节流通常更好用，但当数据是字符时，用字符流类来处理更简单(例如编码问题)，所以一般会用InputStreamReader将输入字节流转化为输入字符流，处理后通过OutputStreamWriter这个输出字符流将内容写入输出字节流 java.nio java.io是阻塞型IO(BIO)，是面向流的，而java.nio是java 1.4+支持的New IO(或译为Non-Block IO)，是面向通道、缓冲区、选择器的非阻塞IO 这两种IO都是同步的，需要程序主动检查IO任务是否完成，传统的IO优势在于小规模、低并发的操作(例如小文件操作)，而在网络IO中nio有较大优势 java.nio的读写是全双工的 Buffer 在nio中，Buffer用于处理数据，其子类包含IntBuffer、ByteBuffer、CharBuffer、LongBuffer，最常用的自然是ByteBuffer\nBuffer及上述几个子类都是抽象类，不能被实例化但其子类可以通过以下静态方法获取Buffer对象：\nallocate(int)：获取一个自动分配静态数组的Buffer实例对象(HeapXxxBuffer) 使用JVM堆内内存 wrap(type[])将提供的静态数组封装为Buffer对象 allocateDirect(int)：获取一个自动分配静态数组的Buffer实例对象(DirectXxxBuffer) 使用堆外的本地内存，本地内存在分配与回收时消耗更大，但面对大量、频繁的读写更快 Buffer有三个重要属性：mark(标记)、position(指向当前指针)、limit(现存数据大小)，mark\u0026lt;=position\u0026lt;=limit\nBuffer也提供随机读写的功能，允许对象在position属性小于limit属性时读写，并提供了以下和position与mark有关的方法：\nposition()和position(int)：获取或设置position属性 limit()和limit(int)：获取或设置limit属性 mark()：标记当前的position为mark reset()：设置当前的position为当前的mark flip()：设置limit为当前的position，再将position设置为0，相当于buf.limit(buf.position()).position(0) rewind()：设置mark为当前的position remaining()和hasRemaining()：返回limit与position的差值、判断该差值是否大于零 它们均返回对象本身，因此可以级联操作，其中flip()很常用，因为通常需要向Buffer对象写入数据后紧接着读取这些数据 ByteBuffer提供的读写方法在命名上和流有所区别：read变为get、write变为put，它们会自动更新position和limit 除了相对存取，还允许提供index参数作为第一个参数，称为绝对存取，绝对存取不影响position和limit\nBuffer提供duplicate()方法创建一个共享同一个数据数组的缓冲区对象 XxxBuffer提供asXxxBuffer()和asReadOnlyBuffer()创建一个其它类型的、或只读的本类型的Buffer对象，仍然共享同一个数据数组\nFileChannel Channel是一个接口，实现该接口的实例对象表示数据源到缓冲区的通道，它不能处理数据，而是负责存取和传输数据 有以下类实现了该接口： FileChannel：文件通道，可以通过RandomAccessFile对象或流对象的getChannel()方法获取，也可以通过静态方法open()获取，其中open()方法实际上是nio.2提供的，将在下一节介绍 SocketChannel：客户端的TCP套接字通道，可以通过Socket对象的getChannel()方法获取 ServerSocketChannel：服务端TCP套接字连接的通道，可以通过ServerSocket对象的getChannel()方法获取 DatagramChannel：UDP套接字的通道 在这里仅介绍FileChannel提供的方法，其余通道将在网络编程中详细介绍 read(Buffer dest)和write(Buffer src)是通道类最重要的两个方法 force(false)方法允许强制刷盘，立刻同步通道的更改到硬盘中，force(true)不仅会同步内容，还会同步文件元数据 truncate(long size)：截断通道的内容，丢弃第size个字节及之后的数据 MappedByteBuffer MappedByteBuffer：文件映射缓冲，是文件在内存中的直接映射，避免了传统IO那样需要切换内核态进行存取操作，效率更高，但和硬盘上的数据不是随时同步的，可以通过force()立刻刷新缓冲区 通常通过通道对象调用map()方法创建，同样是本地内存，因此读写更快而分配与回收较慢，适用于大型文件 映射有三种模式：FileChannel.MapMode的READ_ONLY与READ_WRITE与PRIVATE 其中READ_WRITE模式的更改会同步到文件但不会同步到其它缓冲区，PRIVATE模式的更改只会修改当前的缓冲区，必须通过force()刷盘同步更新 MappedByteBuffer和DirectByteBuffer的区别是它和通道有直接关联，所以可以不通过FileChannel进行read()和write()，而是依靠缺页中断直接在内存映射文件中读写数据 java.nio.charset nio2 AsynchronousChannel nio2或称AIO，支持异步操作和更全面的跨文件系统支持，主体为java.nio.file包 除此之外提供异步的通道，名称均以Asynchronous开头，它们和nio的通道的方法一致，不再赘述 Path和Paths Paths是工具类，仅提供静态方法用于创建Path对象，但在新版本中由于接口允许含有静态方法，Paths.get()内部被替换为Path.of() 所以直接调用Path.of()创建Path对象即可，需要提供URI(网络资源)或String(本地资源) 在Path中，.表示classpath Path和File起的作用类似，均表示一个抽象路径，提供以下实例方法： getFileName()：获取String表示的文件名 getFileSystem()：获取FileSystem对象 getNameCount()：获取该Path上级目录的个数 getName(int idx)：获取第idx层上级目录的Path对象，idx从0开始表示最上级目录 iterator()：获取用于遍历上级目录的迭代器 getParent()：获取绝对路径中上级目录的Path对象，若为相对路径则返回null getRoot()：获取绝对路径中根目录的Path对象，若为相对路径则返回null isAbsolute()：判断是否为绝对路径 normalize()：规范化路径，将所有.和..解析并去除 relative(Path p)：返回该对象和p之间的相对路径 resolve(Path p)：若p为绝对路径则返回p，否则拼接在本Path对象之后并返回 resolveSibling(Path p)：若p为绝对路径则返回p，否则拼接在本Path对象的getParent()对象之后并返回 startsWith()和endsWith()：判断该对象是否以某个Path或某个String起始、结尾 toAbsolutePath()：返回对应的绝对路径 toUri()：返回对应的URI Files Files是工具类，提供更丰富的文件操作 copy(Path, Path, CopyOption...)：复制文件，默认行为为非原子地复制文件内容和最后修改时间到未存在文件，不保留硬链接、若源文件为软链接则取其指向文件的数据 其中CopyOption是空接口，有枚举StandardCopyOption实现 ATOMIC_MOVE：原子地移动，仅支持move() REPLACE_EXISTING：若目标已存在则覆盖内容 COPY_ATTRIBUTES：复制文件元数据，包括权限和所有者等 move(Path, Path, CopyOption...)：移动文件，默认行为为非原子性地移动文件内容到未存在文件，若为软链接则复制软链接本身 java.nio.file.attribute PathMatcher ","permalink":"https://echo.ishian.me/2025/04/17/java-io/","summary":"介绍 Java IO 流体系与 nio、nio2 的 Buffer、Channel 等用法","title":"Java: IO"},{"content":"Java数据结构与算法 包装类 概念 java中只有八种基础数据类型，都是全小写的，出于一切皆对象的理念和泛型参数对引用类型的需求，java提供了八个包装类\n其中，Number是所有数值类型Byte、Short、Integer、Long、Float、Double的基类\nCharacter和Boolean对应char和boolean\n虽说包装类是引用类型，但它们的构造方法已经被弃用，取而代之的是自动装箱机制：\njavaInteger i = 1; // 自动装箱, 不需要new Integer i = new Integer(1); // 已被弃用 包装类的存储位置：\n当数值是一个整型且范围为-128~127时，JVM会使用缓存池中的数值\njavaInteger i = 1; Integer i2 = 1; i == i2; // 将返回true, 虽然i和i2都是引用, 但由于使用的是缓存池中的值, 所以i和i2指向同一份内存 其余情况，每次声明包装类的对象并初始化时，JVM均会在堆中创建新对象\n关于常量池的细节：要求这个类实现Constable接口，则JVM会将这个类的对象存储在常量池\n例如包装类、String、Enum等都实现了该接口\nNumber及其子类 Number是一个抽象类，在java.lang包下，常用实例方法如下： typeValue()：将对象转化为类型为type的值，例如byteValue()等 整型包装类，Integer、Short、Byte、Long，以Integer为例： 构造器：Integer(String)和Integer(int) 常用静态成员：MAX_VALUE和MIN_VALUE 常用实例方法： boolean compareTo()：实现了Comparable接口 String toString()：实现了转字符串方法 常用静态方法： int bitCount(int)：返回二进制表示中1的个数 int parseInt(String, int)：将字符串解析为整数，第二个参数可选，表示基数 Integer valueOf(String, int)：同上，但是返回引用对象 int signum(int)：返回符号函数值 String toString(int)：将一个基础数据类型的整型转化为字符串 String toHexString(int)：转十六进制字符串，其它进制如toBinaryString()、toOctalString() 浮点数包装类，Double、Float，以Double为例： 构造器类似整型类 常用静态成员： MAX_VALUE和MIN_VALUE NaN：不是一个数 POSITIVE_INFINITY和NEGETIVE_INFINITY：正负无穷 常用实例方法： boolean isInfinite()：是否为无穷 boolean isNaN()：是否为NaN String toStirng()：转字符串 常用静态方法： 同样有parseDouble()、toString()、valueOf()系列方法 还有isFinite()、isInfinite()、isNaN()等判断方法 数组 []语法糖 通过type[]可声明一个类型为type的数组\njavaint[] arr1 = new int[] {}; int[] arr2 = new int[N]; // 允许N是变量, 即允许动态绑定 只能通过初始化设定数组具体的值，编译器自动解析数组的长度\nlength是静态数组最重要的属性，用于获取其长度\n由于静态数组本质是一个引用类型，一个引用可以随时更换其指向，因此二维数组每一行的元素个数可以不同：\njavaint[][] a = new int[8][]; // 声明一个长度为8的二维数组, 每一行的元素个数未知(为null) a[0] = new int[3]; // 将int[3]赋给a[0] int[][] a = new int[8][9]; // 声明一个长度为8的二维数组, 每一行都是int[9]对象 Array java的数组实际上是java.lang.reflect.Array类的对象，其构造方法是私有的 Array本身并不提供更多的方法，而是归纳于集合框架里的Arrays类和Collections类 字符串 CharSequence CharSequence(字符序列)是一个接口，字符串类均实现了该接口 该接口的常用方法包括： char charAt(int)：0-idx地获取字符串的字符 boolean isEmpty()：判空 int length()：返回长度 String toString()：返回String对象 String String是一个不可变类，其所有属性都被final修饰\n和包装类的整型常量池同样道理，字符串常用所以不应该经常在堆中创建，希望它能够成为一个封装好的字面量，因此String变量同样有两种初始化方式：\njavaString s = \u0026#34;123\u0026#34;; // 在字符串常量池中检索, 若没有则创建, 若有则指向它 String s2 = \u0026#34;123\u0026#34;; // s == s2 将返回 true String s3 = new String(\u0026#34;123\u0026#34;); // 单独在堆中创建, 其内容是\u0026#34;123\u0026#34;的副本, 因此 s2 == s3 将返回 false如果你尝试使用String(String)构造器而不是String(StringBuilder)或其它构造器，你会受到警告Unless an explicit copy of original is needed, use of this constructor is unnecessary since Strings are immutable.，即除非真的需要原字符串的副本，否则使用这样的构造器是不必要的，因为String对象是不可变的\n重新复习一下不可变对象：其属性全都被final修饰，只能通过用=赋值修改变量的指向，而不能通过该对象的方法修改其指向的值\nString实现了CharSequence接口的方法，除此之外还包括以下常用方法：\n构造器：接受byte[]或char[]或StringBuilder或StringBuffer或String参数 常用实例方法： String concat(String)：返回拼接后的字符串，和+语法糖效果一致 boolean contains(CharSequence)：判断是否包含目标字符序列，可以是任何实现了该接口的类 boolean contentEquals(CharSequence)：因为不止含String一个字符串类，StringBuilder等不是String的子类，通过contentEquals()可以快速判断两个类型不同的字符串是否内容相等，用s.equals(sb.toString())也可 boolean equalsIgnoreCase(String)：忽略大小写地判断两个String是否相等 byte[] getBytes()：以默认编码转换为byte[]，JVM默认编码为操作系统的编码集 可以传递字符集参数例如getBytes(\u0026quot;GBK\u0026quot;)或getBytes(\u0026quot;UTF-8\u0026quot;)解析为对应编码的字节数组，再用new String(byte[], String charset)转化为String对象 int indexOf(s)：找到字符或子串s的起始位置，找不到返回-1，诸如indexOf(char)或indexOf(String)，还可以加上fromIndex和endIndex参数 需要注意的是java没有正整数一说，若找不到，返回-1，是不会像cpp一样等同于无穷大的 int lastIndexOf()：和indexOf()的区别是，从右开始找 int length()：返回长度 boolean matches(String regex)：判断字符串是否匹配正则表达式regex String replace(char, char)：将所有的前者字符改为后者字符，该方法还有一个字符串替换成字符串的重载版本 String[] split(String regex)：根据正则表达式regex匹配字符串，并以匹配成功的子串为分隔，分割字符串 String toUpperCase()和String toLowerCase() String trim()：去除前后空白(包括空格、制表符) String substring(int begin, int offset)：获取子串[begin, begin + offset) StringBuilder 先讲一下StringBuffer类，这是十分古老的java提供的可变字符串类，是线程安全的 但就算是线程安全的，也不能完全保证拼接的逻辑正确，反而因为加锁导致开销极大，因此基本没有适用场景 因此，StringBuilder类应运而生，它实现了CharSequence接口和Appendable接口，提供以下方法： indexOf()系列 append()系列 delete()系列 insert()系列 StringBuilder reverse()：使该对象逆序，然后返回它自己 void setCharAt(int, char)：将前者指向的字符改为后者 集合框架 Iterable\u0026lt;E\u0026gt;接口 Iterable\u0026lt;E\u0026gt;是所有元素集合都实现了的接口\nIterator\u0026lt;E\u0026gt; iterator()：获取一个迭代器\nvoid forEach(Consumer\u0026lt;? super E\u0026gt;)：为集合的每一个元素都执行一遍你提供的lambda表达式\n所有实现了Iterable接口的类均可使用语法糖for (type e : c)，其中e是临时变量，是集合对象c中的元素\nIterator\u0026lt;E\u0026gt;接口包含以下方法：\nboolean hasNext()：判断是否有下一个元素 E next()：返回下一个元素，若没有则抛出NoSuchElementException异常 void remove()：用于安全地删除当前元素 在使用迭代器遍历时，一旦通过对象进行增删行为，调用迭代器的方法将抛出异常 因此应该使用迭代器的remove()删除当前指向的元素 当当前迭代器从未使用过next()或已经使用过remove()时，调用remove()将抛出IllegalStateException异常 Iterator以及集合框架是随着泛型的实现而出现的，传统的枚举器是Enumeration接口，现在已经很少使用\n通过非语法糖的方式遍历集合的方法如下(如果需要在遍历过程中删除元素)：\njavaList\u0026lt;Integer\u0026gt; list; var it = list.iterator(); while (it.hasNext()) { var e = it.next(); if (e == 1) { it.remove(); } } 为什么Iterator是一个接口，在文档中也找不到实现它的类，为什么能获取到一个实例化的对象？\n因为实现这个接口的是在各个容器内部定义的私有内部类\nCollection\u0026lt;E\u0026gt;接口 Collection\u0026lt;E\u0026gt;接口继承自Iterable接口，所有元素集合都实现了该接口 定义的方法如下，实现它的类可以有不同的实现，但语义满足： boolean add(E)：调用后，集合中应能够找到该元素 void clear()：调用后，集合应没有元素 boolean contains(Object)：若集合存在该对象应返回true boolean isEmpty()：若集合为空应返回true boolean remove(Object)：若该对象存在于集合中，调用该方法后应保证删除它 int size()：应返回集合的长度 Object[] toArray()：应返回由集合中元素构成的静态数组 List\u0026lt;E\u0026gt;接口 List接口继承自Collection接口，所有有序集合(列表)都实现了该接口 该接口额外包含以下常用方法： void add(int idx, E)：向第idx位置插入元素，0-idx void set(int idx, E)：设置第idx位置的元素 E get(int)：获取指定位置的元素 E getFirst()和E getLast() void remove(int)、E removeFirst()、E removeLast() List\u0026lt;E\u0026gt; subList(int, int)：获取子列表，以视图形式存在(没有在堆中新分配内存) 实现List接口的常用类有ArrayList、LinkedList、Vector、Stack，分别是数组实现、链表实现、线程安全列表、线程安全栈，其中LinkedList类还实现了Deque接口 Set\u0026lt;E\u0026gt;接口 Set接口同样继承自Collection接口，所有唯一性集合都实现了该接口 Set本身没有提供更多的方法，实现它的常用类有HashSet、TreeSet、LinkedHashSet，分别是哈希表实现(乱序)、红黑树实现(键有序)、哈希表实现(插入顺序排序) Queue\u0026lt;E\u0026gt;和Deque\u0026lt;E\u0026gt;接口 Queue接口同样继承自Collection接口，所有只操作端元素的集合都实现了该接口 Queue接口提供了额外的方法，它们的功能和Collection接口定义的方法有对应关系，但Queue新增的方法在操作失败时会返回特殊值，而Collection的方法会抛出异常 offer()和add() peek()和element() poll()和remove() Deque接口猜都能猜到，在方法名后加First和Last 实现了Queue的常用类有PriorityQueue(优先队列)和ArrayDeque(数组实现的双端队列) Map\u0026lt;K, V\u0026gt;接口 Map接口并不继承Iterable和Collection，所有映射集合都实现了该接口 Map提供的常用方法有： clear() containsKey()和containsValue() V get(K) put(K, V) remove(Object) 含有公有内部类Entry\u0026lt;K, V\u0026gt; 实现了Map接口的常用类有HashMap、TreeMap、HashTable、WeakHashMap、LinkedMap，分别是哈希表实现、红黑树实现、线程安全的哈希表实现、基于弱引用的哈希表实现、哈希表实现(插入顺序排序) 内置算法 Math Arrays Collections ","permalink":"https://echo.ishian.me/2025/04/10/java-collection/","summary":"介绍 Java 包装类、数组、字符串与集合框架等常用数据结构","title":"Java: 数据结构与算法"},{"content":"对顶堆 对顶堆是用于解决寻找序列中第kkk小数的数据结构，其中序列可动态变化、kkk可动态变化 对顶堆较易实现，比实现权值线段树、平衡树有码量上的优势\n基本组成：一个大顶堆和一个小顶堆(辅助堆)，大顶堆维护前kkk小的元素(包括kkk)，小顶堆维护其它元素(即比第kkk元素大的元素)\n这样，大顶堆的堆顶就是第kkk个元素、小顶堆的堆顶是第k+1k+1k+1个元素 如果要求寻找序列中第kkk大的数，则反过来用小顶堆维护前kkk大的元素、用大顶堆作辅助堆即可\n每次涉及到动态修改(如插入、修改kkk值)，需要在操作结束后维护： 若大顶堆元素超出kkk个，则将大顶堆的堆顶弹出后插入小顶堆中 否则将小顶堆堆顶插入大顶堆中\n插入元素：若插入元素不小于大顶堆堆顶，则插入小顶堆中，否则插入大顶堆中\n查询第kkk小元素：返回大顶堆堆顶\n删除第kkk小元素：推出大顶堆堆顶和小顶堆堆顶，将后者插入大顶堆中\n动态修改kkk值：与维护操作描述一致，若kkk增大，则推出小顶堆堆顶插入大顶堆中；否则推出大顶堆堆顶插入小顶堆中\n时间复杂度：查询O(1)O(1)O(1)，其余操作O(log⁡n)O(\\log n)O(logn)\n","permalink":"https://echo.ishian.me/2025/04/10/algo-linked-forward-star/","summary":"讲解对顶堆的组成与维护方式，用于动态求解序列中第k小元素","title":"图的遍历: 链式前向星"},{"content":"对顶堆 对顶堆是用于解决寻找序列中第kkk小数的数据结构，其中序列可动态变化、kkk可动态变化 对顶堆较易实现，比实现权值线段树、平衡树有码量上的优势\n基本组成：一个大顶堆和一个小顶堆(辅助堆)，大顶堆维护前kkk小的元素(包括kkk)，小顶堆维护其它元素(即比第kkk元素大的元素)\n这样，大顶堆的堆顶就是第kkk个元素、小顶堆的堆顶是第k+1k+1k+1个元素 如果要求寻找序列中第kkk大的数，则反过来用小顶堆维护前kkk大的元素、用大顶堆作辅助堆即可\n每次涉及到动态修改(如插入、修改kkk值)，需要在操作结束后维护： 若大顶堆元素超出kkk个，则将大顶堆的堆顶弹出后插入小顶堆中 否则将小顶堆堆顶插入大顶堆中\n插入元素：若插入元素不小于大顶堆堆顶，则插入小顶堆中，否则插入大顶堆中\n查询第kkk小元素：返回大顶堆堆顶\n删除第kkk小元素：推出大顶堆堆顶和小顶堆堆顶，将后者插入大顶堆中\n动态修改kkk值：与维护操作描述一致，若kkk增大，则推出小顶堆堆顶插入大顶堆中；否则推出大顶堆堆顶插入小顶堆中\n时间复杂度：查询O(1)O(1)O(1)，其余操作O(log⁡n)O(\\log n)O(logn)\n","permalink":"https://echo.ishian.me/2025/04/10/algo-minmax-heap/","summary":"讲解对顶堆的组成与维护方式，用于动态求解序列中第k小元素","title":"数据结构: 对顶堆"},{"content":"递归式线段树 概述 线段树基于分治思想，本质是将分治过程中产生的子区间解存储在树中，以达到快速查询的效果\n典型如求解最大连续子数组，虽然运用贪心算法是更快的单次算法，但如果大区间不变，而只是进行多次的子区间解查询时，线段树比贪心算法更快\n若加上简单的维护，则能使其很简单地支持区间修改，同时不用消耗更多的资源\n适用的修改操作：所有符合结合律的运算，例如比大小、求和等 这也是和树状数组的区别，树状数组是维护所有前缀区间的信息来查询任意子区间的信息，而线段树是直接维护一些有代表性的子区间的信息并将它们组合来得到任意子区间的信息，因此不需要运算的元素拥有逆元\n适用问题：要求维护连续子区间的问题，例如RMQ(区间最大/最小查询)和RSQ(区间求和查询)问题\n递归式线段树的组成 基本结构：\n假设根结点编号为111，则所有结点iii维护一个子区间[l,r][l,r][l,r]的信息，其左儿子2i2i2i维护 [l,⌊l+r2⌋]\\begin{align}\\left[l,\\left\\lfloor\\frac{l+r}{2}\\right\\rfloor\\right]\\end{align}[l,⌊2l+r​⌋]​​，右儿子2i+12i+12i+1维护另一半区间 叶子结点维护长度为一的区间信息，它们没有儿子 递归式线段树不存在度为111的结点，且是一棵平衡二叉树 但它不一定是完全二叉树，不过没什么影响就是了 建树：\n分治过程通常通过递归实现：如果结点的区间长度不为111，则进行上述递归，最后合并左、右儿子返回的值作为本结点的值，然后返回给父结点 否则，直接返回这个长度为111的区间的值(即原数组中对应的值) 空间消耗：有用的叶子结点即nnn个，同时存在着无用的叶子结点，在不存在无用的叶子结点时，深度达到最大，为⌈log⁡n⌉\\left\\lceil\\log n\\right\\rceil⌈logn⌉，则以最大深度为深度的满二叉树数组一定可以满足下标的分配，即空间为2⌈log⁡n⌉+1−12^{\\lceil\\log n\\rceil+1}-12⌈logn⌉+1−1 一个容易记的形式是，因为⌈log⁡n⌉≤⌊log⁡n⌋+1\\lceil\\log n\\rceil\\le\\lfloor\\log n\\rfloor+1⌈logn⌉≤⌊logn⌋+1，因此最大空间\u0026lt;2⌊log⁡n⌋+2\u0026lt;4⋅2log⁡n=4n\u0026lt;2^{\\lfloor\\log n\\rfloor+2}\u0026lt;4·2^{\\log n}=4n\u0026lt;2⌊logn⌋+2\u0026lt;4⋅2logn=4n，直接取4n4n4n即可 区间操作：\n区间查询：同样通过分治来得到答案，不过一部分分治不需要深入到叶子结点 将待查询区间分为两种：完全覆盖当前结点维护的区间(即当前结点区间是待查询区间的子集)、以及另一种区间 对于前者，不需要继续递归，直接返回维护的值即可 对于后者，分为向左儿子递归和向右儿子递归的部分，如果查询区间的左端小于结点区间的中间点，则向左儿子递归；向右儿子递归同理，需要查询区间的右端大于结点区间的中间点；人话就是保证递归下去的区间和待查区间有交集 最后合并来自各个结点的极大区间信息即可 可以证明，这些极大区间最多有O(log⁡n)O(\\log n)O(logn)个，即区间查询操作是O(log⁡n)O(\\log n)O(logn)的 区间修改：需要维护一个懒惰标记，类似写时拷贝的概念，取若干个构成待修改区间的结点进行修改，需要修改时将结点的lazy置1，只修改这个结点的值，但不去修改子结点的值 具体来说，将这次修改看作区间查询，将待修改区间分为若干个极大的子区间，所有被置lazy的区间都是待修改区间的极大子集(同时告知需要增加/减去的值)，对同一个子区间的多次连续修改只需要将值叠加即可；当然，最后需要像合并更新上面的结点 直到下一次查询，如果待查询区间不完全覆盖当前区间(即需要递归时)，则要将当前结点的懒惰标记下放以保证同步 注意这里的懒惰标记并非表示本结点未修改，而是标记子结点未修改 为了防止懒惰标记溢出，通常在区间修改时也需要下放标记 不同操作的线段树：\n区间内所有值赋为k：懒惰标记初始化为输入范围外的k来表示没有赋值 区间内所有值加k：懒惰标记初始化为0 区间内所有值乘k：懒惰标记初始化为1 三种操作结合：赋值操作是优先级最高的，乘法操作会影响以前的加法操作，加法操作对其他操作没什么影响 对于极大子集(即不着急下放标记的那个分支)： 若为赋值，则应初始化乘法标记、加法标记，因为它们没有意义了 若为乘法，则让乘法标记乘上k，同时会影响以前的加法操作，需要使加法标记乘上k 若为加法，直接让加法标记加上k即可 对于下放操作： 若赋值标记存在，则应首先下放，同时初始化左右儿子的乘法、加法标记 由于乘法标记、加法标记在初始状态下放时，不会对信息造成影响，所以不需要判断是否存在这些标记；且加法标记在之前已经经过处理，可以直接加到左右儿子区间信息上 拿维护最大值举例，下放乘法、加法标记时，左右儿子的最大值需乘上p的乘法标记、再加上p的加法标记；左右儿子的乘法标记直接乘以p的乘法标记；左右儿子的加法标记先乘上p的乘法标记、再加上p的加法标记；最后初始化p的乘法、加法标记 区间加、区间求和的模板实现：\nc\u0026#43;\u0026#43;#include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int n; long long b[100010]; struct Info { int l, r, s, lz; // s表sum }t[100010 \u0026lt;\u0026lt; 2]; void build(int p, int l, int r) { t[p].l = l; // 初始化结点p管辖的区间 t[p].r = r; if (l == r) { // 到叶子结点, 可以初始化了 t[p].ans = b[l]; return; } build(p \u0026lt;\u0026lt; 1, l, l + r \u0026gt;\u0026gt; 1); // 左边 bulid(p \u0026lt;\u0026lt; 1 | 1, (l + r \u0026gt;\u0026gt; 1) + 1, r); // 右边 t[p].s = t[p \u0026lt;\u0026lt; 1].s + t[p \u0026lt;\u0026lt; 1 | 1].s; // 可以将合并操作单独写一个函数 } void pushdown(int p) { if (t[p].lz) { // 下放标记 t[p \u0026lt;\u0026lt; 1].s += (t[p \u0026lt;\u0026lt; 1].r - t[p \u0026lt;\u0026lt; 1].l + 1) * t[p].lz; t[p \u0026lt;\u0026lt; 1 | 1].s += (t[p \u0026lt;\u0026lt; 1 | 1].r - t[p \u0026lt;\u0026lt; 1 | 1].l + 1) * t[p].lz; t[p \u0026lt;\u0026lt; 1].lz += t[p].lz; t[p \u0026lt;\u0026lt; 1 | 1].lz += t[p].lz; t[p].lz = false; } } void update(int p, int l, int r, int v) { if (l \u0026lt;= t[p].l \u0026amp;\u0026amp; r \u0026gt;= t[p].r) { t[p].s += (t[p].r - t[p].l + 1) * v; t[p].lz += v; return; } pushdown(p); int m = t[p].l + t[p].r \u0026gt;\u0026gt; 1; if (l \u0026lt;= m) update(p \u0026lt;\u0026lt; 1, l, r, v); if (r \u0026gt; m) update(p \u0026lt;\u0026lt; 1 | 1, l, r, v); t[p].s = t[p \u0026lt;\u0026lt; 1].s + t[p \u0026lt;\u0026lt; 1 | 1].s; } long long query(int p, int l, int r) { if (l \u0026lt;= t[p].l \u0026amp;\u0026amp; r \u0026gt;= t[p].r) { return t[p].s; } pushdown(p); int m = t[p].l + t[p].r \u0026gt;\u0026gt; 1; long long ans = 0; if (l \u0026lt;= m) ans += query(p \u0026lt;\u0026lt; 1, l, r); if (r \u0026gt; m) ans += query(p \u0026lt;\u0026lt; 1 | 1, l, r); return ans; } 常见区间信息的设计 首先要判断一道题是否能用线段树来做：\n对题目要求的信息，是否满足区间可加性；即是否能由子区间的值求出父区间的值 对要求区间修改的线段树，是否满足懒惰标记可加性和区间信息易改，即子结点的懒惰标记可以由父结点下放得到，而父结点可以快速求出整个区间进行某种修改后的值(而不需要递归) 如果不能，看是否能降为暴力单点修改(适用于经过有限次修改后就不再改变的信息) 区间信息及懒惰标记的设计：\n设计区间信息取决于要求什么样的区间信息： 通常要考虑到怎么通过维护某些额外的信息：使得区间修改时，区间信息易改 一般是将修改前和修改后的待求信息进行对比，来得出额外信息： 例如区间加+区间和： s=∑ai、s′=∑(ai+d)\\begin{align}s=\\sum a_i、s\u0026#x27;=\\sum(a_i+d)\\end{align}s=∑ai​、s′=∑(ai​+d)​​，因此额外信息为区间长度 区间加+区间平方和： s=∑ai2、s′=∑(ai+d)2=s+2d∑ai+∑d2\\begin{align}s=\\sum a_i^2、s\u0026#x27;=\\sum(a_i+d)^2=s+2d\\sum a_i+\\sum d^2\\end{align}s=∑ai2​、s′=∑(ai​+d)2=s+2d∑ai​+∑d2​​，因此额外信息为区间和、区间长度 设计懒惰标记取决于要进行什么样的区间操作： 懒惰标记要能唯一地标识两个信息，即子区间要不要改、子区间怎么改 例如区间加就将上述两个信息合并，因为懒惰标记为000时，自然表示不用改 其它如区间赋值，因为000不代表不用改，所以需要：一个布尔值标识要不要改，一个值表示要赋的值 或将这个值初始化设为范围外的值，来表示不用改 一些常见的区间操作及它们的组合：\n区间赋值、区间加：不再赘述 区间乘：下放乘标记时直接相乘即可，因为整数的乘法是半群；此外，乘法标记初始化为111 一些常见的区间信息：\n区间hash：区间哈希一般用于比较两个区间是否相等，用树状数组也可 学过字符串哈希就知道，求一个区间的基本哈希值是将它看作ppp进制数，即 hash(a[1,n])=a1pn−1+a2pn−2+⋯+an−1p+anhash(a[1,n])=a_1p^{n-1}+a_2p^{n-2}+\\cdots+a_{n-1}p+a_nhash(a[1,n])=a1​pn−1+a2​pn−2+⋯+an−1​p+an​ 这其实有点像前缀和，只是多了一个ppp的幂的因子，通过简单推导可以得出： hash(a[1,r])=hash(a[1,l−1])×pr−l+1+hash(a[l,r])hash(a[1,r])=hash(a[1,l-1])\\times p^{r-l+1}+hash(a[l,r])hash(a[1,r])=hash(a[1,l−1])×pr−l+1+hash(a[l,r]) 也就是：hash(a[l,r])=hash(a[l,m])×pr−m+hash(a[m+1,r])hash(a[l,r])=hash(a[l,m])\\times p^{r-m}+hash(a[m+1,r])hash(a[l,r])=hash(a[l,m])×pr−m+hash(a[m+1,r]) 这里不考虑如何解决冲突，选择一个大质数作为模数和**比max(a)max(a)max(a)大的ppp**即可 再不济，就用双值哈希 特定区间的最大连续子区间和(所有连续子区间中，元素和最大的子区间)：\nc\u0026#43;\u0026#43;#include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int i, n, m, k, a, b, gd[500005]; struct Info { int lm, rm, am, ll, rr, sum; }tr[500005 \u0026lt;\u0026lt; 2]; void edit(Info\u0026amp; p, Info sl, Info sr) { p.lm = max(sl.lm, sl.sum + sr.lm); p.rm = max(sr.rm, sr.sum + sl.rm); p.sum = sl.sum + sr.sum; p.am = max(max(sl.am, sr.am), (sl.rm \u0026lt; 0 \u0026amp;\u0026amp; sr.lm) ? max(sl.rm, sr.lm) : max(0, sl.rm) + max(0, sr.lm)); } void build (int p, int l, int r) { tr[p].ll = l; tr[p].rr = r; if (l == r) { tr[p].lm = tr[p].rm = tr[p].am = tr[p].sum = gd[l]; return; } int m = (l + r) \u0026gt;\u0026gt; 1; build(p \u0026lt;\u0026lt; 1, l, m); build(p \u0026lt;\u0026lt; 1 | 1, m + 1, r); edit(tr[p], tr[p \u0026lt;\u0026lt; 1], tr[p \u0026lt;\u0026lt; 1 | 1]); } void update(int p, int t, int v) { // 单点修改 if (tr[p].ll == tr[p].rr \u0026amp;\u0026amp; tr[p].ll == t) { tr[p].lm = tr[p].rm = tr[p].am = tr[p].sum = v; return; } int m = tr[p].ll + tr[p].rr \u0026gt;\u0026gt; 1; if (t \u0026lt;= m) { update(p \u0026lt;\u0026lt; 1, t, v); } else { update(p \u0026lt;\u0026lt; 1 | 1, t, v); } edit(tr[p], tr[p \u0026lt;\u0026lt; 1], tr[p \u0026lt;\u0026lt; 1 | 1]); } Info query(int p, int l, int r) { if (l \u0026lt;= tr[p].ll \u0026amp;\u0026amp; r \u0026gt;= tr[p].rr) { return tr[p]; } int m = tr[p].ll + tr[p].rr \u0026gt;\u0026gt; 1; if (l \u0026lt;= m \u0026amp;\u0026amp; r \u0026gt; m) { Info ans; edit(ans, query(p \u0026lt;\u0026lt; 1, l, r), query(p \u0026lt;\u0026lt; 1 | 1, l, r)); return ans; } else if (l \u0026lt;= m) { return query(p \u0026lt;\u0026lt; 1, l, r); } else { return query(p \u0026lt;\u0026lt; 1 | 1, l, r); } } 较复杂线段树：\nc\u0026#43;\u0026#43;#include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int n, m, i, opt, L, R; bool a[100006]; struct Info{ bool fil, filv, swt; int l, r, lm[2], rm[2], am[2], sum, len; Info() { fil = swt = lm[0] = lm[1] = rm[0] = rm[1] = am[0] = am[1] = sum = len = 0; } }tr[100006 \u0026lt;\u0026lt; 2]; void merge(Info\u0026amp; p, Info l, Info r) { p.lm[0] = l.lm[0] + (l.lm[0] == l.len ? r.lm[0] : 0); p.lm[1] = l.lm[1] + (l.lm[1] == l.len ? r.lm[1] : 0); p.rm[0] = r.rm[0] + (r.rm[0] == r.len ? l.rm[0] : 0); p.rm[1] = r.rm[1] + (r.rm[1] == r.len ? l.rm[1] : 0); p.am[0] = max(l.rm[0] + r.lm[0], max(l.am[0], r.am[0])); p.am[1] = max(l.rm[1] + r.lm[1], max(l.am[1], r.am[1])); p.len = l.len + r.len; p.sum = l.sum + r.sum; } void build(int p, int l, int r) { tr[p].l = l; tr[p].r = r; if (l == r) { tr[p].len = 1; tr[p].lm[0] = tr[p].rm[0] = tr[p].am[0] = !a[l]; tr[p].lm[1] = tr[p].rm[1] = tr[p].am[1] = tr[p].sum = a[l]; return; } build(p \u0026lt;\u0026lt; 1, l, l + r \u0026gt;\u0026gt; 1); build(p \u0026lt;\u0026lt; 1 | 1, (l + r \u0026gt;\u0026gt; 1) + 1, r); merge(tr[p], tr[p \u0026lt;\u0026lt; 1], tr[p \u0026lt;\u0026lt; 1 | 1]); } void pushdown(int p) { if (tr[p].fil) { tr[p \u0026lt;\u0026lt; 1].fil = tr[p \u0026lt;\u0026lt; 1 | 1].fil = true; tr[p \u0026lt;\u0026lt; 1].swt = tr[p \u0026lt;\u0026lt; 1 | 1].swt = false; tr[p \u0026lt;\u0026lt; 1].filv = tr[p \u0026lt;\u0026lt; 1 | 1].filv = tr[p].filv; tr[p \u0026lt;\u0026lt; 1].lm[tr[p].filv] = tr[p \u0026lt;\u0026lt; 1].rm[tr[p].filv] = tr[p \u0026lt;\u0026lt; 1].am[tr[p].filv] = tr[p \u0026lt;\u0026lt; 1].len; tr[p \u0026lt;\u0026lt; 1].lm[!tr[p].filv] = tr[p \u0026lt;\u0026lt; 1].rm[!tr[p].filv] = tr[p \u0026lt;\u0026lt; 1].am[!tr[p].filv] = 0; tr[p \u0026lt;\u0026lt; 1].sum = tr[p].filv ? tr[p \u0026lt;\u0026lt; 1].len : 0; tr[p \u0026lt;\u0026lt; 1 | 1].lm[tr[p].filv] = tr[p \u0026lt;\u0026lt; 1 | 1].rm[tr[p].filv] = tr[p \u0026lt;\u0026lt; 1 | 1].am[tr[p].filv] = tr[p \u0026lt;\u0026lt; 1 | 1].len; tr[p \u0026lt;\u0026lt; 1 | 1].lm[!tr[p].filv] = tr[p \u0026lt;\u0026lt; 1 | 1].rm[!tr[p].filv] = tr[p \u0026lt;\u0026lt; 1 | 1].am[!tr[p].filv] = 0; tr[p \u0026lt;\u0026lt; 1 | 1].sum = tr[p].filv ? tr[p \u0026lt;\u0026lt; 1 | 1].len : 0; tr[p].fil = false; } if (tr[p].swt) { tr[p \u0026lt;\u0026lt; 1].swt = !tr[p \u0026lt;\u0026lt; 1].swt; swap(tr[p \u0026lt;\u0026lt; 1].am[0], tr[p \u0026lt;\u0026lt; 1].am[1]); swap(tr[p \u0026lt;\u0026lt; 1].rm[0], tr[p \u0026lt;\u0026lt; 1].rm[1]); swap(tr[p \u0026lt;\u0026lt; 1].lm[0], tr[p \u0026lt;\u0026lt; 1].lm[1]); tr[p \u0026lt;\u0026lt; 1].sum = tr[p \u0026lt;\u0026lt; 1].len - tr[p \u0026lt;\u0026lt; 1].sum; tr[p \u0026lt;\u0026lt; 1 | 1].swt = !tr[p \u0026lt;\u0026lt; 1 | 1].swt; swap(tr[p \u0026lt;\u0026lt; 1 | 1].am[0], tr[p \u0026lt;\u0026lt; 1 | 1].am[1]); swap(tr[p \u0026lt;\u0026lt; 1 | 1].rm[0], tr[p \u0026lt;\u0026lt; 1 | 1].rm[1]); swap(tr[p \u0026lt;\u0026lt; 1 | 1].lm[0], tr[p \u0026lt;\u0026lt; 1 | 1].lm[1]); tr[p \u0026lt;\u0026lt; 1 | 1].sum = tr[p \u0026lt;\u0026lt; 1 | 1].len - tr[p \u0026lt;\u0026lt; 1 | 1].sum; tr[p].swt = false; } } void update(int p, int l, int r, int mode) { if (l \u0026lt;= tr[p].l \u0026amp;\u0026amp; r \u0026gt;= tr[p].r) { switch (mode) { case 0: case 1: tr[p].fil = true; tr[p].filv = mode; tr[p].lm[mode] = tr[p].rm[mode] = tr[p].am[mode] = tr[p].len; tr[p].lm[!mode] = tr[p].rm[!mode] = tr[p].am[!mode] = 0; tr[p].sum = mode ? tr[p].len : 0; tr[p].swt = false; break; case 2: tr[p].swt = !tr[p].swt; swap(tr[p].am[0], tr[p].am[1]); swap(tr[p].rm[0], tr[p].rm[1]); swap(tr[p].lm[0], tr[p].lm[1]); tr[p].sum = tr[p].len - tr[p].sum; break; } return; } pushdown(p); int m = tr[p].l + tr[p].r \u0026gt;\u0026gt; 1; if (l \u0026lt;= m) { update(p \u0026lt;\u0026lt; 1, l, r, mode); } if (r \u0026gt; m) { update(p \u0026lt;\u0026lt; 1 | 1, l, r, mode); } merge(tr[p], tr[p \u0026lt;\u0026lt; 1], tr[p \u0026lt;\u0026lt; 1 | 1]); } Info query(int p, int l, int r) { if (l \u0026lt;= tr[p].l \u0026amp;\u0026amp; r \u0026gt;= tr[p].r) { return tr[p]; } pushdown(p); int m = tr[p].l + tr[p].r \u0026gt;\u0026gt; 1; Info ans1, ans2, ans; if (l \u0026lt;= m) { ans1 = query(p \u0026lt;\u0026lt; 1, l, r); } if (r \u0026gt; m) { ans2 = query(p \u0026lt;\u0026lt; 1 | 1, l, r); } merge(ans, ans1, ans2); return ans; } int main() { cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; for (i = 1; i \u0026lt;= n; ++i) { cin \u0026gt;\u0026gt; a[i]; } build(1, 1, n); while (m--) { cin \u0026gt;\u0026gt; opt \u0026gt;\u0026gt; L \u0026gt;\u0026gt; R; switch (opt) { case 0: case 1: case 2: update(1, L + 1, R + 1, opt); break; case 3: cout \u0026lt;\u0026lt; query(1, L + 1, R + 1).sum \u0026lt;\u0026lt; endl; break; case 4: cout \u0026lt;\u0026lt; query(1, L + 1, R + 1).am[1] \u0026lt;\u0026lt; endl; break; } } }动态开点线段树 使用p \u0026lt;\u0026lt; 1和p \u0026lt;\u0026lt; 1 | 1作为结点的左右儿子编号，显然是很浪费空间的(因为部分结点可能从不会被读、被写) 所以一个自然的优化是，用我们维护的cnt记录当前的总结点数，然后需要创建新的结点时(即需要访问从未维护过的子区间时)将cnt+1作为其编号，并找到有直接父子关系的结点，维护其ls或rs(就像存储二叉树那样) 这样的话，通常只需要4mlog⁡n4m\\log n4mlogn(mmm为查询次数)的空间，如果m≤105、n≤109m\\le10^5、n\\le10^9m≤105、n≤109，那么大概需要10710^7107空间；当然，如果nnn较小，那是没有必要用动态开点的(除非要实现更复杂的数据结构) 需要说明，区间信息必须能快速地初始化，否则如果要依赖子结点的区间信息，那么用动态开点就没有意义了\n代码实现：和一般线段树相比，不需要建树，只多了一步判断是否需要新增结点的操作 很多参数的设计是需要根据题意修改的，这里仅提供一个区间加、求区间和的模板\nc\u0026#43;\u0026#43;int n, cnt, rt; // 以1作为根结点 long long pre[MAXN]; // 假设已经预处理好了前缀和, 注意这样无法处理n过大的情况 const int LEN = 适合的大小; int ls[LEN], rs[LEN]; struct Info{ long long s, lz; // 因为无法事先初始化, 所以结点就不存l, r了 }t[LEN]; void init(int\u0026amp; p, int l, int r) { p = ++cnt; t[p].s = pre[r] - pre[l - 1]; } void pushdown(int p, int l, int r) { if (t[p].lz) { int m = l + r \u0026gt;\u0026gt; 1; if (!ls[p]) init(ls[p], l, m); // 子结点未创建 if (!rs[p]) init(rs[p], m + 1, r); t[ls[p]].s += t[p].lz * (m - l + 1); t[rs[p]].s += t[p].lz * (r - m); t[p].lz = 0; } } void update(int\u0026amp; p, int l, int r, int L, int R, int v) { if (!p) init(p, L, R); if (l \u0026lt;= L \u0026amp;\u0026amp; r \u0026gt;= R) { t[p].s += v * (R - L + 1); t[p].lz += v; return; } pushdown(p, L, R); int m = L + R \u0026gt;\u0026gt; 1; if (l \u0026lt;= m) update(ls[p], l, r, L, m, v); if (r \u0026gt; m) update(rs[p], l, r, m + 1, R, v); t[p].s = t[ls[p]].s + t[rs[p]].s; } void query(int\u0026amp; p, int l, int r, int L, int R) { if (!p) init(p, L, R); if (l \u0026lt;= L \u0026amp;\u0026amp; r \u0026gt;= R) { return t[p].s; } pushdown(p, L, R); int m = L + R \u0026gt;\u0026gt; 1; long long ans = 0; if (l \u0026lt;= m) ans += query(ls[p], l, r, L, m); if (r \u0026gt; m) ans += query(rs[p], l, r, m + 1, R); return ans; } ","permalink":"https://echo.ishian.me/2025/04/10/algo-recursive-segment-tree/","summary":"讲解递归式线段树的结构、建树、懒标记与区间修改、区间查询的实现","title":"数据结构: 递归式线段树"},{"content":"快读快写 快读有两套模板：\n使用内置getchar()\nc\u0026#43;\u0026#43;int read() { int x = 0, sig = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == \u0026#39;-\u0026#39;) { sig = -1; } ch = getchar(); } while (isdigit(ch)) { x = x * 10 + (ch - \u0026#39;0\u0026#39;); ch = getchar(); } return sig * x; } 使用文件fread()加速getchar()：此后不能再通过终端输入数据，但在OI是可行的因为输入数据都是文件\nc\u0026#43;\u0026#43;// 即, 一次性将文件所有内容读入buf, 然后让p1(文件开头)递增到p2(文件结尾)处, 每次调用递增一次并返回字符 // 这里的判断条件分为两部分 // 1: p1 == p2 初始状态时, 会立刻执行条件2读入文件 // 2: p2 = (p1 = buf) + fread(), p1 == p2 执行后, p1在文件开头, p2在文件结尾 // 读入后, 若p1 == p2则立刻返回EOF, 否则返回第一个字符, p1递增 // 此后调用gc(), 除非到达文件结尾, 否则不会进入2, 直接返回*p1后递增 // 到达文件结尾后, 由于stdin没有字符了, 这里调用fread()也没有变化, p1 == p2 == buf // fread()最多调用2次, 相当于将文本一次性读到缓冲区 #define gc() (p1 == p2 \u0026amp;\u0026amp; (p2 = (p1 = buf) + fread(buf, 1, 1 \u0026lt;\u0026lt; 20, stdin), p1 == p2) ? EOF : *p1++) char buf[1 \u0026lt;\u0026lt; 23], *p1 = buf, *p2 = buf; int read() { int x = 0, sig = 1; char ch = gc(); while (!isdigit(ch)) { if (ch == \u0026#39;-\u0026#39;) { sig = -1; } ch = gc(); } while (isdigit(ch)) { x = x * 10 + (ch - \u0026#39;0\u0026#39;); ch = getchar(); } return sig * x; } 快写：\nc\u0026#43;\u0026#43;void write(long long x) { if (x \u0026lt; 0) { putchar(\u0026#39;-\u0026#39;); x = -x; } if (x \u0026gt; 9) { write(x / 10); } putchar(x % 10 + \u0026#39;0\u0026#39;); } ","permalink":"https://echo.ishian.me/2025/04/07/algo-fastio/","summary":"介绍算法竞赛中基于getchar与fread的快读快写模板及其原理","title":"算法竞赛: 常数优化之快读快写"},{"content":"Java异常处理 异常的分类 所有异常分为受检异常、非受检异常、错误\n声明某方法可能会抛出某异常：throws关键字 在某方法内抛出某异常new后跟一个异常类对象\njavavoid method() throws Exception { throw new Exception(); } 受检异常：编译器会在编译期检查的异常，如果用户的某个方法调用了会抛出受检异常的方法，但既没有处理也没有显式地抛出，就会有红色波浪线提醒 所有受检异常都是Exception的子类且不是RuntimeException的子类 受检异常一般表示程序员无法控制的异常，它们必须包含显式处理，例如由文件系统发出的、动态引入库发出的等\n非受检异常：运行时可能抛出的异常，不需要显式地用throws声明 所有非受检异常都是RuntimeException的子类 非受检异常一般表示程序员能够通过提高编程技巧而避免的异常\n错误：这种异常是脱离程序的，可能是硬件出了问题，例如内存溢出\n异常的实质 graph TD Throwable --\u0026gt; Error Throwable --\u0026gt; Exception Exception --\u0026gt; RuntimeException Exception --- Checked((\u0026#34;检查性异常\\nChecked Exception\u0026#34;)) --\u0026gt; IOException RuntimeException --\u0026gt; A(...) Checked --\u0026gt; ParseException Checked --\u0026gt; ... 所有异常和错误类都实现了Throwable接口\n自定义异常：很简单，只需要根据需求继承Exception或RuntimeException(通常是后者)并实现构造方法即可 通常由业务层抛出，用户接口层捕获并处理\n子类重写的方法抛出的受检异常范围不能比父类方法throws的受检异常范围大\n异常处理 catch-try-finally：finally可选，表示try后(即时出现异常)必须执行的代码块，例如关闭文件等 try-with-resource：指在try()中定义需要close()的资源，此时不需要使用finally就可自动关闭try的资源 ","permalink":"https://echo.ishian.me/2025/04/04/java-exception/","summary":"介绍 Java 异常的分类、实质与异常处理方式","title":"Java: 异常处理"},{"content":"进程组成 进程(Process)是程序的执行过程，也即运行中的程序；一个进程即某个程序在给定输入下的一次执行\n在其它OS中，可称为作业(Job)、任务(Task) 在宏观上，进程支持多任务，看起来进程在“同时”执行，是并发的 不同进程是串行执行的(通过调度分时执行) 多任务环境下，进程是OS分配资源的基本单位\n多任务(Multitasking)是现代操作系统的基本属性 最早为批处理系统(Batch System)，通过将一系列任务打包送给操作系统队列运行而非人工提交单个任务，来提高CPU利用率 由单道(内存中只载入一个任务)到多道(前方任务进行I/O读写等操作时使CPU处理后方任务)，均为批处理系统 现代提供特定的交互功能(用户可干预进程的优先级、状态等)并进行优化，成为专用的多任务系统 进程的结构：\nPCB(Process Control Block，进程控制块)：管理进程的唯一数据结构 PID：唯一标识一个进程的标识符 进程映像：独立的存储进程地址的内存空间，包含指向各种数据的地址指针 数据段 进程的控制功能：\n创建/撤销 阻塞：进程提出系统请求，但内核未响应时，将调用阻塞命令 唤醒：阻塞进程所等待的事件完成/发生，将调用唤醒命令 进程状态：\nnew→admittedready⇆interruptrunning→exitterminated 事件发生↑ ↓等待事件 waiting\\begin{align}\u0026amp;\\rm new\\stackrel{admitted}\\rightarrow ready\\stackrel{interrupt}\\leftrightarrows running\\stackrel{exit}\\rightarrow terminated\\\\\u0026amp;\\ \\ \\ \\rm \\ \\ \\ \\ \\ \\ \\ \\ \\ {\\small事件发生}\\uparrow\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\downarrow{\\small等待事件}\\\\\u0026amp;\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\rm waiting\\end{align}​new→admittedready⇆interrupt​running→exitterminated 事件发生↑ ↓等待事件 waiting​​ 有时因内存空间不足，OS会将一些进程主动移出内存(放入交换空间)，称为挂起 被挂起的进程可能处于就绪或等待状态，因此挂起队列有两种 挂起等待进程在等待事件发生后，变为挂起就绪 取消挂起状态需要由OS主动将其载入内存\n进程调度 调度(分配CPU资源)：管理处于不同状态的多个进程 将所有进程根据状态归入阻塞队列、就绪队列、部分OS含挂起队列\n通过支持CPU调度，可实现并发，即宏观上同时执行多个进程 与并行的区别：并发强调调度提高CPU利用率，并行需要多核实现真正的同时执行\n频繁调度将导致频繁进行系统调用(切换执行态)，降低执行效率\n切换进程时，需保护上下文，上下文可分为：\n用户级上下文：代码段、数据段、用户堆栈、共享内存区 寄存器上下文：所有寄存器的数据 系统级上下文：进程控制块、内存管理信息、内核栈 调度层次：\n短程调度：从就绪队列中选择合适进程被CPU执行 中程调度：交换进程，即将进程从内存移出/从交换空间中取出 长程调度：将用户提交的批作业载入内存 IPC(进程间通信) IPC用于支持不同进程间的相互协作、共享/交换信息 IPC的底层实现有： 低级通信：用于传递控制信息，通过信号量、互斥锁实现 高级通信：用于进程间大量信息的交换/共享，通过共享内存区/消息传递实现 消息传递：进程通过调用内核的发送/接收函数，和其它进程通信 由于使用隐式同步(阻塞式通信/协议通信)且传递的消息为副本，过程清晰可查，编程简单易于调试而不易出错；但效率较低 阻塞式通信：即进程调用函数后进入阻塞队列 由于消息为副本，进程修改消息不会对其它进程产生影响 共享内存：所有进程可直接读写共享内存区内的数据 由于使用同一份数据，为保证进程安全，需进行显式的同步控制(低级通信) 使一份数据同时仅能被一个进程访问 编程较复杂，调试难度高，但效率高 根据用户的需求不同，有不同的通信模型(上述方法的接口)： LPC(Local Produce Call，本地过程调用)：同一进程内/同一主机的不同进程调用IPC的接口 RPC(Remote Produce Call，远程过程调用)：不同主机的进程调用IPC的接口，涉及网络通信 管道(Pipe)：是Linux中消息传递机制的具体实现，由内核为两个进程开创管道 套接字(Socket)：套接字即一组信息组成的唯一标识，是计网模型里低层协议为高层协议提供的接口，在RPC中通过IP地址、协议、端口地址组成套接字来唯一标识一个进程(因为远程IPC中pid无法唯一标识进程) 线程 线程(Thread)是更细粒度的计算单位，一个线程是进程中一个单一顺序的控制流(独立于其它线程的代码流)\n线程的引入支持充分利用多核资源，实现多个线程的并行执行，加快一个进程的执行速度 线程独有TCB(线程控制块)、堆栈、 和其它在同一进程下的不同线程共享代码段、共享内存区，同一进程下不同线程间通信较IPC简单 分为用户级线程(用户在高级语言中使用线程库)、内核级线程(由内核直接管理)\n用户级线程由线程库实现，不需要内核线程的支持；但也意味着它们并非真正的并行执行\n在用户眼中，用户级线程可随意调度，可由用户编写专用的调度算法 在内核眼中，用户级线程不可见因此不可调度，当一个用户级线程进入阻塞态，其所属的进程也进入阻塞态\n内核级线程由内核支持，创建线程时需要进行系统调用，并真正更新在线程表中，由内核调度 实现并行执行，但消耗内核资源多\n~协程\n线程模型：用户级线程需要通过内核级线程运行\nM:1模型：多个用户级线程绑定到一个内核级线程，会导致某个用户级线程阻塞后，其它M-1个用户级线程也被阻塞 1:1模型 M:N模型 混合模型 进程调度算法 调度过程： CPU调度：进行一次短程调度(处于内核态) 派遣：切换上下文→\\rightarrow→切换回用户态→\\rightarrow→执行从就绪队列推出的任务 调度时机： 进程阻塞/结束：进程主动放弃CPU，属于非抢占点 进程被唤醒/被中断：因中断等事件发生，该进程从阻塞状态/运行状态进入就绪队列，属于可抢占点，两种原因造成的可抢占的资源分别是非CPU资源、CPU资源 调度算法评价指标： CPU利用率：倾向使CPU不含空余时间 进程吞吐率：倾向选择短进程执行 周转时间：进程从第一次到达就绪队列到全部运行结束的时间 带权周转时间：进程的周转时间除以运行的总时间 平均周转时间/平均带权周转时间：多个进程的平均周转时间/平均带权周转时间，可表示进程的总体执行效率 等待时间：进程就绪后等待执行所花费的全部时间 响应时间：进程提交后到第一次获得CPU的时间 抢占式/非抢占式调度：进程进入就绪队列后，是否会影响运行中的进程，如果是抢占式调度，在新进程根据调度算法比运行中进程更优时，新进程将和运行中进程交换 优先级调度算法：任务队列为优先数的最小堆(分为非抢占式/抢占式) FCFS(First Come First Serve，先进先服务)算法：就绪队列为一般的先进先出队列，等待前方进程结束后执行下一进程 各指标不稳定，且为非抢占式调度 SJF(Shortest Job First，短作业优先)算法：就绪队列为剩余时间(所需时间-已执行时间)的最小堆 追求更低的平均周转时间，但响应时间长 为非抢占式调度，先到的任务一定比后到的任务先完成(尽管执行中进程的剩余时间比后到任务多) 存在饥饿问题：剩余时间长的进程可能永远得不到调度 SRTF(Shortest Remaining Time First，最短剩余时间优先)算法：就绪队列及运行中进程组成剩余时间的最小堆 为抢占式调度，就绪队列每次更新时都需要对比，使执行中任务始终为剩余时间最短的 存在饥饿问题 HRRN(Highest Response Ratio Next，最高响应比优先)算法：就绪队列为响应比 (R=周转时间执行时间=等待时间+执行时间执行时间)\\begin{align}\\left(R=\\frac{周转时间}{执行时间}=\\frac{等待时间+执行时间}{执行时间}\\right)\\end{align}(R=执行时间周转时间​=执行时间等待时间+执行时间​)​​的最大堆 饥饿现象：优先级调度算法中，无论是非抢占式还是抢占式调度，均会发生饥饿现象，即某进程因优先级过低而等待时间过长，导致即使其最终完成也失去其实际意义(饿死) 可使用老化机制，即优先级同时需考虑其等待时间，HRRN即一种采用老化机制的优先级调度算法 RR(Round Robin，轮转)调度：轮转调度考虑进程的公平性(使它们的平均值最短)，将时间分为多个时间片，每个进程执行一个时间片后必须返回就绪队列，使资源被轮转使用 平均等待时间与时间片长度成正比，最坏等待时间为(就绪进程数-1)*时间片长度，使时间片较短，可提升交互体验 吞吐率较低，批处理速度慢 通过计时器中断实现 一个进程可以通过产生多个子进程，增大本身的占有CPU的时间 MLQ(Multi-Level Queue，多级队列)调度：全体就绪进程采用优先级调度算法，但处于同一优先级的进程采用轮转调度(或其他调度算法) MLFQ(Multi-Level Feedback Queue，多级反馈队列)调度：在MLQ调度基础上，支持动态优先级；即任务执行一个时间片释放资源后，增加考虑是否降低其优先级的步骤(允许进程在不同优先级队列间移动) 进程同步 进程协作以异步为主(保证效率)，同步为辅(保证进程间通信的数据安全)\n进程同步根据IPC方式有所区别：\n进程直接协作：采用消息传递，属于隐式同步，进程间有特定的执行顺序，此方发送/接收消息时需进入阻塞态、直到对方接收/发送消息时被唤醒 进程间接协作：采用共享内存，进程间不互锁，因此需要显式同步以保证数据一致性 一个进程的临界区(连续的访问共享内存的代码)执行过程中不能被其它进程的临界区影响 在并发执行中，起因为抢占式调度/RR调度算法等的分时执行 针对间接协作的同步控制(互斥)，有以下解法：\nPeterson解法(互斥的软件解法)：\n互斥锁(互斥的硬件解法，也称互斥量Mutex)：\n信号量(Semaphore)：\nP-V问题： 读者写者问题： 互斥信号量必须紧贴临界区\n管程：\n同步控制造成死锁：\n特点/必要条件： 进程在运行结束前不释放资源 b c d 死锁预防：在程序未执行时，进程的申请阶段 要求进程执行前先申请整个进程所需的所有资源，仅当全部所需资源均满足时分配(破坏) 要求进程按资源的编号顺序申请资源(破坏循环等待) 死锁避免：程序的执行阶段(未发生死锁) 对进程的动态申请资源进行审查 银行家算法： 死锁检测： 检测算法：构建进程等待图，求拓扑排序检测是否有环 鸵鸟机制：由于死锁检测代价高昂且死锁概率十分小，一般不进行处理 死锁恢复：直接终止相关进程，让它们释放资源 ","permalink":"https://echo.ishian.me/2025/04/03/os-process/","summary":"整理操作系统进程管理：进程组成与PCB、进程状态转换、调度、同步与通信机制","title":"操作系统: 进程管理"},{"content":"一维前缀和 对一个原数组a1∼na_{1\\sim n}a1∼n​，令\nsi=∑k=1iak\\begin{align}s_i=\\sum_{k=1}^ia_k\\end{align}si​=k=1∑i​ak​​​，即称sss为aaa的前缀和数组\n不难发现有以下性质：\nlen(a)=len(s){\\rm len}(a)={\\rm len}(s)len(a)=len(s) 任意以a1a_1a1​为起始的子区间[a1,ai][a_1,a_i][a1​,ai​]，其元素和为sis_isi​ 对于满足如下要求的运算：\n元素间运算满足结合律 所有元素具有逆元，即满足消去律(可由运算结果及其中一个操作数得知另一个操作数) 满足上述要求的区间信息包括加和、乘积、异或等 令s0=0s_0=0s0​=0，则有sum(ai,aj)=sj−si−1sum(a_i,a_j)=s_j-s_{i-1}sum(ai​,aj​)=sj​−si−1​\n原地实现：\nc\u0026#43;\u0026#43;int a[MAXN], t; for (int i = 1; i \u0026lt;= n; ++i) { cin \u0026gt;\u0026gt; t; a[i] += t; a[i + 1] = a[i]; } 二维前缀和 类似一维前缀和，对一个nnn行mmm列的二维数组，令\nsij=∑i=1n∑j=1maij\\begin{align}s_{ij}=\\sum_{i=1}^n\\sum_{j=1}^ma_{ij}\\end{align}sij​=i=1∑n​j=1∑m​aij​​​，即称sss为aaa的前缀和数组\n类似一维前缀和，结合容斥原理，对于特定的运算，任意一个子区间(左上角为aija_{ij}aij​，右下角为axya_{xy}axy​)的元素和为sxy+s(i−1)(j−1)−sx(j−1)−s(i−1)ys_{xy}+s_{(i-1)(j-1)}-s_{x(j-1)}-s_{(i-1)y}sxy​+s(i−1)(j−1)​−sx(j−1)​−s(i−1)y​，其中s0⋅=s⋅0=0s_{0·}=s_{·0}=0s0⋅​=s⋅0​=0\n原地实现：\nc\u0026#43;\u0026#43;int a[MAXN][MAXM], t; for (int i = 1; i \u0026lt;= n; ++i) { for (int j = 1; j \u0026lt;= m; ++j) { cin \u0026gt;\u0026gt; t; a[i][j] += t; a[i][j + 1] += a[i][j] - a[i - 1][j]; // 容斥 a[i + 1][j] = a[i][j]; } }也可以不依赖容斥，像扫地一样实现(虽然高维前缀和不实用，但这种方法能比较清晰地算出高维前缀和)：\nc\u0026#43;\u0026#43;for (int i = 1; i \u0026lt;= n; ++i) { for (int j = 1; j \u0026lt;= m; ++j) { cin \u0026gt;\u0026gt; t; a[i][j] += t; a[i][j + 1] = a[i][j]; } } for (int i = 1; i \u0026lt;= n; ++i) { for (int j = 1; j \u0026lt;= m; ++j) { a[i + 1][j] += a[i][j]; } } ","permalink":"https://echo.ishian.me/2025/03/19/algo-prefix-sum/","summary":"介绍一维与二维前缀和的原理、性质、原地实现及差分应用场景","title":"数据结构: 前缀和"},{"content":"概述 树状数组(Binary Indexed Tree，BIT，或称Fenwick Tree)支持区间查询、单点修改 一个区间的总信息由区间内所有元素共同贡献而得 然而一般的树状数组具有限制(与前缀和数组类似，因为朴素树状数组查询取件信息依赖于前缀和)： 元素间运算满足结合律 所有元素具有逆元，即满足消去律(可由运算结果及其中一个操作数得知另一个操作数) 满足上述要求的区间信息包括加和、乘积、异或等 一些特殊类型的树状数组可实现一些不满足上述要求的区间查询问题： 双树状数组求区间最值 扩展树状数组求不满足消去律的运算的区间信息 树状数组可求解的问题是线段树的子集，在后面可见其结构并非一棵完美的二叉树 但其价值在于能以更简单的代码、更小的空间/时间常数因子来完成相似任务 基本结构 为了实现最基本的树状数组的功能，需要考虑(接下来以求区间和为例)：\n数组的每个元素代表哪一个区间的区间信息(称为基本区间) 如何通过基本区间信息，来求任意范围的区间信息 修改单点值时，如何更新所有涉及该点的元素 设数组索引为iii(1≤i≤n1\\le i\\le n1≤i≤n)，g(i)g(i)g(i)为iii二进制最右边第一个111的位置(g(i)≥0g(i)\\ge 0g(i)≥0) 则aia_iai​管辖的范围为[i−2g(i)+1, i][i-2^{g(i)}+1,\\ i][i−2g(i)+1, i] 例如，a1a_1a1​管辖[1,1][1,1][1,1]、a2a_2a2​管辖[1,2][1,2][1,2]、a4a_4a4​管辖[1,4][1,4][1,4]、a6a_6a6​管辖[5,6][5,6][5,6] 考虑2g(i)2^{g(i)}2g(i)的具体实现：实际就是i−(i\u0026amp;(i−1))=i\u0026amp;(−i)i-(i\\\u0026amp;(i-1))=i\\\u0026amp;(-i)i−(i\u0026amp;(i−1))=i\u0026amp;(−i)\n区间查询：获取任意范围的区间信息便很简单，由于前缀区间信息非常易于获取、且运算满足消去律 因此若设h(x,y)h(x,y)h(x,y)为区间[x,y][x,y][x,y]的区间信息，则h(x,y)=h(1,y)−h(1,x−1)h(x,y)=h(1,y)-h(1,x-1)h(x,y)=h(1,y)−h(1,x−1)，其中-为该运算的逆运算 获取前缀区间信息的方式：令iii迭代为i−2g(i)i-2^{g(i)}i−2g(i)直到i=0i=0i=0退出循环、将途径的基本区间信息aia_iai​求和即可，是O(log⁡n)O(\\log n)O(logn)的\n单点修改：\n先考虑其树形态，即不同元素区间之间的包含关系，可以发现： 若有x\u0026lt;y\u0026lt;x+2g(x)x\u0026lt;y\u0026lt;x+2^{g(x)}x\u0026lt;y\u0026lt;x+2g(x)，则ay:[y−2g(y)+1,y]a_y:[y-2^{g(y)}+1,y]ay​:[y−2g(y)+1,y]、ax:[x−2g(x)+1,x]a_x:[x-2^{g(x)}+1,x]ax​:[x−2g(x)+1,x] 由于y−2g(y)+1\u0026gt;xy-2^{g(y)}+1\u0026gt;xy−2g(y)+1\u0026gt;x，故此时ay、axa_y、a_xay​、ax​管辖区间互不包含 且axa_xax​区间真包含于ax+2g(x)a_{x+2^{g(x)}}ax+2g(x)​区间 其树形结构便是：对于一个结点yyy，其直接子结点xxx必须满足x+2g(x)=yx+2^{g(x)}=yx+2g(x)=y 对任意x\u0026lt;y\u0026lt;x+2g(x)x\u0026lt;y\u0026lt;x+2^{g(x)}x\u0026lt;y\u0026lt;x+2g(x)，x、yx、yx、y所在结点的最近共同祖宗不是xxx或yyy 因此单点修改的过程类似获取前缀区间信息的逆过程：令iii迭代为i+2g(i)i+2^{g(i)}i+2g(i)直到i\u0026gt;ni\u0026gt;ni\u0026gt;n退出循环、使途径的基本区间信息ai←ai+new−olda_i\\leftarrow a_i+new-oldai​←ai​+new−old 因此也是O(log⁡n)O(\\log n)O(logn)的 建树：\nO(nlog⁡n)O(n\\log n)O(nlogn)：变为nnn次单点修改，其中new=ri、old=0new=r_i、old=0new=ri​、old=0 O(n)O(n)O(n)：在O(nlog⁡n)O(n\\log n)O(nlogn)的方法中，每次从叶子结点(原数组元素)上升到根结点，多花了log⁡n\\log nlogn； 由于该树中，父结点的编号一定比所有直接子结点的编号大 因此除了方法一ci=∑ak[i−2g(i)+1≤k≤i]c_i=\\sum a_k[i-2^{g(i)}+1\\le k\\le i]ci​=∑ak​[i−2g(i)+1≤k≤i]，还可ci=∑ak[k+2g(k)=i]c_i=\\sum a_k[k+2^{g(k)}=i]ci​=∑ak​[k+2g(k)=i] 因此在从小到大更新结点时，可顺便更新其直接父结点：ai←ai+ri、ai+2g(i)←ai+2g(i)+aia_i\\leftarrow a_i+r_i、a_{i+2^{g(i)}}\\leftarrow a_{i+2^{g(i)}}+a_iai​←ai​+ri​、ai+2g(i)​←ai+2g(i)​+ai​ O(n)O(n)O(n)：在输入时不进行建树，而是计算前缀和，输入完成后进行区间查询来建树 因此空间、时间复杂度的常数因子比方法二略大 原数组的区间修改与单点查询：差分运算也可使用树状数组，因为差分数组的前缀和即为原数组元素，对于这个问题，只需要多一步计算差分数组的步骤 即，如果要求是原数组的区间修改+单点查询，可转化为原数组的差分数组的：两点修改+求出区间和，这个要求和之前的分析一致 因为区间[x,y][x,y][x,y]修改后，差分数组只有ax、ay+1a_x、a_{y+1}ax​、ay+1​需要修改 此时使用方法二建树时，由于只使用一个数组，因此需记录ai−1a_{i-1}ai−1​来计算aia_iai​的值(令a0=0a_0=0a0​=0) 时间复杂度：仅在预处理时多了一遍O(n)O(n)O(n)，各种操作的复杂度均不变\n原数组的区间修改与区间查询：沿用上述方法，用差分数组来实现O(log⁡n)O(\\log n)O(logn)的区间修改，对于区间查询，通过两个前缀和相减得到 可以对式子展开：如果原数组为aaa，差分数组为bbb，那么\nsk=∑i=1kai=∑i=1k∑j=1ibj=∑i=1k(k−i+1)bi=k∑i=1kbi−∑i=1k(i−1)bi\\begin{align}s_k=\\sum_{i=1}^ka_i=\\sum_{i=1}^k\\sum_{j=1}^ib_j=\\sum_{i=1}^k(k-i+1)b_i=k\\sum_{i=1}^kb_i-\\sum_{i=1}^k(i-1)b_i\\end{align}sk​=i=1∑k​ai​=i=1∑k​j=1∑i​bj​=i=1∑k​(k−i+1)bi​=ki=1∑k​bi​−i=1∑k​(i−1)bi​​​ 即，维护两个树状数组，一个是其差分数组，另一个是带i−1i-1i−1系数的差分数组(更新时乘上系数即可) 计算原数组的前缀和sks_ksk​时，使第一个树状数组的前缀和乘kkk再减去第二个树状数组的前缀和即可 时间复杂度：各种操作的复杂度的常数因子增大\n朴素树状数组模板：\nc\u0026#43;\u0026#43;#define lbit(x) (x \u0026amp; (-x)) int a[100001], n, i, t; int query(int x) { // 查询前缀和 int ans = 0; while (x) { ans += a[x]; x -= lbit(x); } return ans; } int query(int x, int y) { // O(log n) 区间查询 return query(y) - query(x - 1); } void set_a(int idx, int val) { // O(log n) 单点修改 val = val - a[idx]; while (idx \u0026lt;= n) { a[idx] += val; idx += lbit(idx); } } int main() { cin \u0026gt;\u0026gt; n; // 使用方法二建树 for (i = 1; i \u0026lt;= n; ++i) { cin \u0026gt;\u0026gt; t; a[i] += t; if ((t = i + lbit(i)) \u0026lt;= n) { a[t] += a[i]; } } } 扩展树状数组 BIT应用 统计逆序对问题 即冒泡排序的最小交换次数[ NOIP 2013 提高组] 火柴排队 区间最值问题 区间与区间求和问题 ","permalink":"https://echo.ishian.me/2025/03/19/algo-fenwick-tree/","summary":"讲解树状数组的基本结构、lowbit原理、区间查询与单点修改的实现及适用范围","title":"数据结构: 树状数组"},{"content":"Java泛型编程 泛型声明 泛型类似cpp的模板编程，用于重用代码\n泛型类型必须为引用类型，所以基础数据类型都提供了包装类\n泛型类或接口：\njavapublic class ClassName \u0026lt;T\u0026gt; {} 泛型方法：\njavamodifiers \u0026lt;T\u0026gt; returnType methodName(必须包含类型为T的参数) {} 泛型参数命名规范 E：集合或数组元素的类型 T/U/S/P：泛指所有类 K/V：键值类 N：Number类 通配符? 通配符?不是一种类型参数，也不是标记符，不能在声明泛型参数时使用，但可以在类型里面使用：\njavaclass ClassName \u0026lt;?\u0026gt; {} // 错误 class ClassName \u0026lt;T\u0026gt; { public static method() { ClassName\u0026lt;?\u0026gt; a; // 正确, 相当于ClassName a; } } 仅使用?和省略尖括号没有区别，如果省略尖括号会有RawTypes警告\n在较新版本中，使用\u0026lt;\u0026gt;可由编译器自动推断类型，例如：\njavaMap\u0026lt;String\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); 泛型类或接口的继承 泛型类是一个整体，即时泛型参数之间有继承关系，泛型类或接口也没有继承关系：\njavaclass A {} class B extends A {} // B是A的子类 class ClassName \u0026lt;A\u0026gt; {} class ClassName \u0026lt;B\u0026gt; {} // 但ClassName\u0026lt;B\u0026gt;不是ClassName\u0026lt;A\u0026gt;的子类 泛型类的继承规则：\n若子类不是泛型类，则父类必须确定泛型参数\njavaclass Father \u0026lt;T\u0026gt; {} class Son extends Father\u0026lt;String\u0026gt; {} // Father\u0026lt;\u0026gt;必须指定参数 若子类是泛型类，则父类的类型参数必须是子类所使用的参数，或者是具体的参数\njavaclass Father \u0026lt;T\u0026gt; {} class Son \u0026lt;T\u0026gt; extends Father\u0026lt;String\u0026gt; {} // 具体的参数 class Son \u0026lt;T, S\u0026gt; extends Father\u0026lt;T\u0026gt; {} // Son提供的泛型参数 限定符extends、super 正因上述问题，java提供了extends和super表示了其上界和下界 extends TypeOrInterface：虽然只提供了extends，但这个限定符后面可以跟类，也可以跟接口，表示这个类型参数必须继承或实现自TypeOrInterface类或接口 super TypeOrInterface：表明这个类型参数必须是TypeOrInterface的父类或父接口 两者可以一起使用 ","permalink":"https://echo.ishian.me/2025/03/18/java-generics/","summary":"介绍 Java 泛型声明、通配符、限定符与泛型继承规则","title":"Java: 泛型编程"},{"content":"Java面向对象编程 数据类型 java的数据类型分为两种：基础数据类型和对象类型 java允许自动提升，不允许自动down cast，需要强制类型转换 基础数据类型只有八种，且全小写，分别为byte、short、int、long、float、double、boolean、char，它们直接存储在栈中 不带后缀的整数均为int型字面量 不带后缀的浮点数均为double型字面量 boolean类型字面量只有true和false char类型字面量均为Unicode编码，一个字符占两个字节 其它类型的字面量：0x????、????h、0b????、?.??f 对象类型是类类型，它们真正存在于堆中，由new关键字创建 所有对象类型的变量存储在栈中的值都是指针，称为引用，指向在堆中的对象 java得益于有GC，所以没有delete也没有析构函数，此乃一胜 null为空指针 基本数据类型的默认值为零，且不能将null赋给基本数据类型 运算符 赋值运算符=：所有的赋值均为传值，是将复制栈中的值给另一个变量，给方法传参也是赋值 比较运算符：所有的比较均为比较栈中的值，所以针对引用变量时，没有意义 obj instanceof Type：只用于引用类型，如果obj是Type或Type派生类的对象时，返回true 按位运算符：其中移位运算\u0026gt;\u0026gt;\u0026gt;为无符号右移，\u0026gt;\u0026gt;为有符号右移，\u0026lt;\u0026lt;在移动位数大于总位数时会将移动位数自动对总位数取模 逻辑运算符：||、\u0026amp;\u0026amp; 算术运算符：针对基础数据类型中的整型类型，最小单位是int，例如两个byte运算时会把byte提升到int然后返回int 成员访问运算符.：针对引用类型变量，对比cpp就是-\u0026gt; Object Object是所有类的基类，所有类若没有显式地继承哪个类，就会隐式地继承Object Object对象提供的方法一般没有意义，重要在于提供了一套规范的方法名，重点在于重写 public boolean equals(Object o)：判等，注意参数为Object类型 public int hashCode()：获取哈希值 public String toString()：将对象转化为字符串，若实现该方法，则可以通过字符串的+语法糖自动转换 protected Object clone() throws CloneNotSupportedException：创建并返回本对象的副本(浅拷贝副本)，会抛出受检异常CloneNotSupportedException，防止你没有重写但又在某个地方调用了该方法 重写它一般需要声明implements Cloneable(一个空接口)并public地重写 但是clone()是很没用的，一般使用一些集成框架提供的深拷贝或浅拷贝的克隆方法 Class\u0026lt;?\u0026gt; getClass()：获取引用指向的对象所属的类，是堆中正在运行的那个对象的类，而不是引用的类 详见java反射机制 this、super this引用：指向现在在JVM中正在运行的那个对象，无论this在哪个类中，其本质是一个动态绑定的引用 例如以下例子：\njavaclass A { public void a() {} void b() { a(); } } class B extends A { public void a() { b(); } }若声明A a = new B()，并调用a.b()，即执行A类的b()方法，即执行this.a()，即执行B类中重写的方法(因为this指向一个B类的实例)，所以这是一个无限循环的调用\nsuper关键字：super和this不同，其本质只是一个关键字，静态绑定所在类的父类的一个对象 同样是上面的例子：\njavaclass BaseA { public void a() {} } class A extends BaseA { public void a() {} void b() { a(); super.a(); } } class B extends A { public void a() { b(); } }同样，调用b()后会调用this.a()，执行B类的a() 而super.a()静态绑定了所在类A的父类BaseA的a()方法，和this指向的对象隔了两代，所以区别还是很大的\n但总而言之，this和super都不应该用来访问类的静态成员\nthis(参数)和super(参数)还可以引用构造方法，同上this动态绑定当前运行的对象，super静态绑定所属类的父类对象\n类的构造器 构造器： 若没有自定义构造器，编译器提供一个没有参数的构造器 若有自定义构造器，编译器不再提供没有参数的构造器 所有构造器的第一条语句，如果不是通过this或super引用的构造方法，则会隐式地添加super()，即父类的无参构造器 所以当父类没有无参构造器且子类没有构造器时，编译器会检查出错误 java创建一个对象分为四步(初始化的顺序)： new：分配内存 默认赋值：基础类型赋为零，引用类型赋为null 显式初始化：若在声明属性时用=赋值了，那么会将其放在this或super引用的构造器之后，构造方法代码之前 执行用户的代码 注解(Annotation) 注解是一种元数据，编译器不会跳过注解，而是根据注解进行检查或其它操作\n注解以@开头，例如重写方法要用@Override注解，下面介绍一下java标准库的注解\n针对代码的注解：\n@Override：表明方法被重写 @Deprecated：表明方法已过时，检查到使用该方法时，会警告 @SuppressWarnings(value={\u0026quot;key\u0026quot;})：标注部分内容，令编译器忽视被标注内容发出的警告 针对注解的注解(元注解)：\n@Retention：标识该如何存储注解 @Documented：标识注解应该包含在用户文档中 @Inherited：标识注解继承于哪个注解 自定义注解模板：\njava@Documented // 可选, 表明该注解可以存在于Javadoc中 @Target({ElementType.TYPE, ...}) // 指定注解的类型, ElementType是一个枚举, 用于限制该注解作用的范围 public @interface MyAnnocation {} // 自定义注解需要实现Annocation接口, @interface表明该注解实现了它 自定义注解的作用主要体现在反射机制上\n接口 Java 8后支持在接口内存在有实现的方法或类，具体规则在本节不讨论 接口同样支持多态，可以通过接口类型的引用访问实现了该接口的类对象实例 接口的作用： 空接口：接口支持多重实现，空接口可以作为一个标记，然后通过泛型编程就能筛选掉那些没有这个标记的类 例如java.io.Serializable，可序列化空接口 接口可以将程序划分为多个模块，模块解耦实现多人分工 用接口定义公共API更为清晰 标准库常用接口： Comparable\u0026lt;T\u0026gt;：包含public int compareTo(T o) Runnable：包含void run()，用于多线程编程 集合框架的接口 ","permalink":"https://echo.ishian.me/2025/03/06/java-oop/","summary":"介绍 Java 数据类型、运算符、构造器、接口与注解等面向对象基础","title":"Java: 面向对象编程"},{"content":"Java基本语法 包管理 包的声明 Java包的功能类似C++的命名空间，减少类名冲突；但它并不是随意声明和引用的： 包名必须是当前文件/目录的父目录相对于项目根目录的路径，用.运算符表明父子关系 例如，若.为根目录，当前文件的路径为./test/math，则声明如下：\njavapackage test.math; 一个文件除非直接在项目文件夹下，否则必须声明包，且必须在文件开头(在import前)\n一个.java文件只能有一个public类或接口，且类名必须和文件名相同 不同.java文件在同一个包体内，指的是它们处于同一个目录下，声明的package路径相同 命名规范：所有包名均应小写 外部包的引用 引用外部包的类或接口需要使用import关键字，用于减少包名冗余导致的可读性下降\njavaimport pkg1.pkg2.MyClass; // 可快捷使用MyClass import pkg1.*; // 可快捷使用pkg1下的所有公有类或接口, 但不包含子包下的公有类或接口 引用外部包的类的静态成员或静态方法时需要使用import static\njavaimport static java.lang.Math.*; 实践时，引入外部包类等情形不推荐使用通配符*，引用静态成员等可酌情使用，推荐按以下顺序引入：\njavaimport java.util.?; // 先引入java标准库 import org.springframework.?; // 再引入java扩展的第三方库, 按照依赖顺序, 每个不同的库之间隔一空行 import jakarta.persistence.?; // 第三方库 import dlut.?; // 最后引入自己开发的模块 实践时，推荐使用格式化插件\njavac命令 javac用于编译.java文件，生成可被JVM执行的.class文件\n最常用的编译命令如下(多个文件使用:分隔，Windows下是;)：\nsh# -d(destination)参数表示保留文件的包路径信息，并生成到目标目录下 javac -d 目标目录 待编译的.java文件 其它参数：-encoding，指定文件编码\n实践中，推荐使用IDE插件提供的脚本服务自动编译\njava命令 java用于运行.class文件\n最常用的命令如下：\nsh# -cp(classpath)参数标明`JVM`应从哪些路径下查找 # 主类名必须包含`main`，链接由JVM在运行时进行，被链接的类同样是在指定的目录下查找 # 注意主类名不是文件名，不能包含`.class` java -cp 目录 主类名 java的main函数模板：\njavapublic class MainClass { public static void main(String[] args) {} } jar命令 jar命令与tar命令极其相似，用于归档\n常用参数如下：\n-v：verbose，显示归档/解压详情 -c：create，创建归档文件 -f：file，指明归档文件名 -u：update，更新归档文件 -x：extract，解压归档文件 常用语句如下：\nbashjar -cf a.jar 类列表 jar -uf a.jar 新加入类列表 jar -xf a.jar 解压a.jar 归档后，可以通过java -cp xxx.jar MainClass运行主类MainClass，所有需要链接的类都在归档文件中找到\n关键字及声明型语句 访问控制修饰符 本节不讨论内部类\njava有四个访问控制权限，分别对应public、protected、default、private四个修饰符\npublic：可以修饰类、接口、方法、属性，表示所有包可访问，是最常用的修饰符\nprotected：可以修饰方法、属性，表示同包内均可访问，外部包中仅子孙类可通过this(自己或子孙类的对象)访问，例如\njavapackage a; import b.A; // 假设含有protected void methodA() {}方法 class B extends A { void func() { methodA(); // 正确 A a = new A(); a.methodA(); // 错误 } } default：实际上不存在这个关键字，当类或方法或属性没有显式地声明访问控制修饰符时，含有default权限，常称为包级私有，仅包内所有类可访问\nprivate：可以修饰方法、属性，仅类内可访问\nstatic、abstract、final、record static：声明静态对象或方法或内部类，和cpp类似\nabstract：声明抽象方法或抽象类，和cpp的virtual定义类似\nfinal：\n修饰类时：表明该类不能被继承 修饰方法时：表明该方法不能被重写 修饰变量时：声明常变量，注意只是限制在栈中的值不能改变 abstract不能和final或private或static同时出现，因为没有意义\nfinal不能修饰接口，因为没有意义\n修饰符的顺序对程序没有影响，但规范是先是访问控制修饰符，然后是其它修饰符\nfinal修饰引用对象时只能限制该变量不能改变指向，但没有限制用户通过该变量改变其指向的实例\n因为final修饰方法只代表该方法不能被重写，所以做不到cpp那样用一个类型声明可变或不可变对象\n在java中，一个类要么是可变类，要么是不可变类，不可变类的所有属性都被final修饰且不提供setter方法，例如String就是典型的不可变类，不要把实例方法和引用赋值搞混：\njavaString s = \u0026#34;\u0026#34;; // 引用赋值 s = s.trim(); // 实际上是改变了s的指向使其指向s.trim()的返回值，而没有改变原来指向堆中的实例 StringBuilder sb = new StringBuilder(\u0026#34;\u0026#34;); // 引用赋值 sb.append(\u0026#34;a\u0026#34;); // StringBuilder是一个可变类, append()修改了sb指向的实例的属性 重写后的方法的访问控制权不能比父类的该方法低\nrecord关键字可用于修饰类，表示一个类是不可变类，自动添加final等修饰符、自动实现equals(), hashCode()方法、自动实现getter方法\nsynchronized、transient、volatile、sealed、permits synchronized用于同步\ntransient修饰表示无法被序列化\nvolatile修饰表示每次读写都对主存读写，但仍不能保证原子性\nsealed和permits是JDK 17及以上提供的继承修饰符，与final相区分，sealed修饰的类只能被其permits的类继承\njavapublic sealed SuperClass permits SubClass {} class SubClass extends SuperClass {} // 允许 标识符声明 声明变量：仅支持用=初始化新变量，且需要指明类型\njavaString obj = \u0026#34;\u0026#34;; 声明类：java没有函数，所有函数体都在类内，抽象类不能被实例化\njavamodifiers class ClassName {ClassBody} // 不需要\u0026#39;;\u0026#39; 声明方法：抽象方法所属类必须为抽象类\njavamodifiers returnType methodName(paramList) {methodBody} 声明接口：java提供所谓接口，是一系列抽象方法的集合，是其它模块访问你编写的模块的入口，编译时会自动在接口的所有方法前加上public修饰符\njavapublic interface InterfaceName { // 接口内的方法默认为public，且没有实现 returnType methodName(paramList); } 声明一个实现了某个接口的类，一个类可以实现多个接口：\njavapublic class ClassName implements InterfaceName { // 必须重写所有该接口的方法 } 声明一个继承了某个类的类，或一个继承了某个接口的接口，只能单继承：\njavapublic class ClassName extends SuperClass {} public interface InterfaceName extends SuperInterface {} extends应该在implements之前\n标识符规范 包名规范：应全小写 变量名和方法名规范：构成标识符的所有单词，首单词全小写，其余单词仅首字母大写 类名和接口名规范：构成标识符的所有单词首字母均大写 常量名规范：应全大写 protected权限的解释 在引入类(链接在.class文件中的字节码)时，JVM会加载类/接口的元数据进方法区，并构建方法表来区分不同的访问控制域 在创建对象时，JVM会申请一段内存，并创建对象的方法表，用于单独存储所有非静态成员 类、对象的方法表是不同的 每个类都会维护一个引用，指向允许访问的类或对象的方法表 这个引用能指向所有同一包的类及其对象、所有引入的外包类的方法表 能指向，同时符合访问修饰符的范围，才能正常调用 因此protected是极其特殊的： 包外子类不能通过基类的对象访问保护型方法，因为包外的子类不能指向对象的方法表 包外子类可以通过基类访问保护型静态方法，因为包外子类能指向类的方法表，同时符合protected的范围 包内子类可以通过基类及其对象访问保护型方法，因为包内类能指向类及其对象的方法表，同时符合protected的范围 java的语法糖 java不支持运算符重载，仅有部分特殊的类含有类似的语法糖(String支持+拼接，但没有[]运算符)\njava不支持默认参数\njava支持函数重载，重载要求方法名相同但参数列表不同\njava支持变长参数，java变长参数的语法如下：\njava// paramList可以是零或多个String参数 void method(String... paramList) {} java不支持多继承\njava不支持常方法，cpp支持用const修复一个方法保证不能被非常量调用\njava和cpp均支持类型自动推断，在cpp中是auto而在java中是var\njava的Type[]整体表示一个数组类型，元素的类型为指定的Type\n结构型语句 if-else if-else where-continue-break for(;;)-continue-break for(type e : c)语法糖 try-catch-finally try-with-resource switch-case-break switch-case -\u0026gt; {yield} ","permalink":"https://echo.ishian.me/2025/03/04/java-base/","summary":"介绍 Java 包管理、修饰符、关键字与标识符声明等基本语法","title":"Java: 基本语法"},{"content":"c\u0026#43;\u0026#43;#include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int n, i, u, v, w, head[100002], cnt = 1, cnt01 = 1, ch[100002 * 32][2], ans, sum[100002]; struct Edge{ int to, next, w; }e[100002 \u0026lt;\u0026lt; 1]; void addEdge(int from, int to, int ww) { e[cnt].to = to; e[cnt].w = ww; e[cnt].next = head[from]; head[from] = cnt++; } void addWt(int x) { bool m; for (int k = 31, s = 0; k \u0026gt;= 0; --k) { m = x \u0026amp; (1 \u0026lt;\u0026lt; k); if (!ch[s][m]) { ch[s][m] = cnt01++; } s = ch[s][m]; } } void dfs(int p, int fa, int xorw) { sum[p] = xorw; addWt(xorw); for (int k = head[p]; k; k = e[k].next) { if (e[k].to != fa) { dfs(e[k].to, p, xorw ^ e[k].w); } } } void find(int x) { bool m; int nans = 0; for (int k = 31, s = 0; k \u0026gt;= 0; --k) { m = x \u0026amp; (1 \u0026lt;\u0026lt; k); if (ch[s][!m]) { nans \u0026lt;\u0026lt;= 1; ++nans; s = ch[s][!m]; } else { nans \u0026lt;\u0026lt;= 1; s = ch[s][m]; } } ans = max(ans, nans); } int main() { cin \u0026gt;\u0026gt; n; for (i = 1; i \u0026lt; n; ++i) { cin \u0026gt;\u0026gt; u \u0026gt;\u0026gt; v \u0026gt;\u0026gt; w; addEdge(u, v, w); addEdge(v, u, w); } dfs(1, 0, 0); for (i = 2; i \u0026lt;= n; ++i) { find(sum[i]); } cout \u0026lt;\u0026lt; ans \u0026lt;\u0026lt; endl; }","permalink":"https://echo.ishian.me/2025/02/21/algo-bin-01tire/","summary":"介绍01字典树的原理与实现，并以树上异或路径最大值问题为例给出代码","title":"博弈论: 01字典树"},{"content":"c\u0026#43;\u0026#43;#include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; bool vis[10004]; int head[10004], cnt = 1, w[10004], sw[10004], i, n, m, u, v, dfn[10004], rt[10004], scecnt = 1, schead[10004], ncnt, aans[10004]; stack\u0026lt;int\u0026gt; stk; struct Edge{ int from, to, next; }e[100004], se[100004]; void getScc(int p) { dfn[p] = rt[p] = ++ncnt; vis[p] = true; stk.push(p); for (int k = head[p]; k; k = e[k].next) { if (!dfn[e[k].to]) { getScc(e[k].to); rt[p] = min(rt[p], rt[e[k].to]); } else if (vis[e[k].to]) { rt[p] = min(rt[p], rt[e[k].to]); } } if (rt[p] == dfn[p]) { while (stk.top() != p) { rt[stk.top()] = dfn[p]; vis[stk.top()] = false; sw[rt[p]] += w[stk.top()]; stk.pop(); } vis[p] = false; sw[rt[p]] += w[p]; stk.pop(); } } void getSccgraph() { for (int k = 1; k \u0026lt;= m; ++k) { if (rt[e[k].from] != rt[e[k].to]) { se[scecnt].to = rt[e[k].to]; se[scecnt].next = schead[rt[e[k].from]]; schead[rt[e[k].from]] = scecnt++; } } } int dfs(int p) { if (vis[p]) { return aans[p]; } vis[p] = true; if (!schead[p]) { return aans[p] = sw[p]; } int ans = 0; for (int k = schead[p]; k; k = se[k].next) { ans = max(ans, dfs(se[k].to) + sw[p]); } return aans[p] = ans; } int main() { cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; for (i = 1; i \u0026lt;= n; ++i) { cin \u0026gt;\u0026gt; w[i]; } for (i = 1; i \u0026lt;= m; ++i) { cin \u0026gt;\u0026gt; u \u0026gt;\u0026gt; v; e[cnt].from = u; e[cnt].to = v; e[cnt].next = head[u]; head[u] = cnt++; } for (i = 1; i \u0026lt;= n; ++i) { if (!dfn[i]) { getScc(i); } } getSccgraph(); int realans = 0; for (i = 1; i \u0026lt;= n; ++i) { if (dfn[i] == rt[i]) { realans = max(realans, dfs(rt[i])); } } cout \u0026lt;\u0026lt; realans \u0026lt;\u0026lt; endl; }","permalink":"https://echo.ishian.me/2025/02/11/algo-graph-scc/","summary":"给出Tarjan算法求解有向图强连通分量并缩点建图的完整代码实现","title":"图的算法: SCC（强连通分量）"},{"content":"操作系统概述 OS本质 软件分为系统软件、支撑软件、应用软件(功能专用)，操作系统是一种系统软件 系统软件包括OS(运行程序)，编译器(生成程序)，数据库系统DBMS(管理数据)等 计算机由上至下可分为用户层、程序层、库例程、系统调用、操作系统、硬件 狭义上的操作系统仅包含系统调用和操作系统，它们合称内核Core/Kernal 广义上还包含库例程、系统程序及硬件层 根据侧重点不同，可分类： 资源共享及工作效率：Unix 易用性：Windows 资源高效利用：移动端OS 安全性：嵌入式OS OS功能模块概述 CPU管理： 包括进程管理(对任务进行管理)、CPU调度(支持多任务)、进程同步控制(支持并发) 内存管理： 包括内存分配、虚存管理 文件管理： 包括接口设计及其实现 设备管理： 包括I/O管理，设备分类，设备驱动 通信 OS服务 UI(user interface，用户接口)服务： CLI(command-line interface，命令行接口)：Unix、Linux、Windows Server 效率高且稳定 通过终端(terminal)键入命令，如今均使用特殊的终端环境控制台(Console) 终端环境统称TTY(teletypewriter) 键入的命令为某种Shell，为脚本语言 GUI(graphical user interface，图形用户接口)：Windows TSI(touch screen interface，触控接口)：MacOS 程序执行： 先将可执行文件(windows-.exe、linux-.elf、macOS-.mach-o)/脚本(shell/python)文件加载到内存 由CPU取指执行 I/O管理 文件系统： 接口：如命名、读写等方法 具体实现：需考虑存储结构、性能 通信：任务间通信等 错误检测(error detection)：即软件中断 服务保障(保证上述服务正常运行的服务)：(CPU、内存、磁盘)资源回收、运行日志、系统资源访问控制 OS内核分类 单内核(宏内核)：DOS、Unix 用户层包含应用、库例程 内核包含文件系统、IPC(Inter-Process Communication，进程间通信)、I/O控制、进程管理等性能要求高的服务 微内核：Mach 文件系统归入用户层 性能效率低，但可移植、可扩展性强、且支持分布式系统 混合内核：Windows、XNU、MacOS 系统调用 系统调用是用户程序(及标准库等)和系统服务间的最原始接口，用户程序通过系统调用来主动访问系统程序 和函数调用的区别：将用户态转换为核心态，以访问处于保护状态的程序和设备；执行完后切换回用户态 核心态是特殊的执行模式，使核心服务代码在被保护的状态下执行 使用系统调用与函数调用类似，仅仅是由操作系统完成期间的执行模式转换操作 系统调用的参数传递方式： 寄存器传递：效率高，优先使用；但可传递参数数量少 内存块传递：分配内存块给待传递参数，将块地址传递给系统程序 栈传参：用户程序将参数入栈，系统程序退栈使用参数 ","permalink":"https://echo.ishian.me/2025/02/10/os-structure/","summary":"介绍操作系统的本质、内核层次划分、功能模块与系统调用等基本概念","title":"操作系统: 基本结构"},{"content":"前置命令行工具知识 argparse是用于开发命令行工具接口的库，通过内建的解析器来解析自定义的命令行参数 相比于optparse(py2.7后被argparse替代，并不再维护)，接口功能更强，但灵活性略低 关于getopt，它是基于C的getopt()函数族(通常可以在Linux看到)实现的一套很底层的接口，比较难用(对比optparse接口功能更加单一，需要手动实现复杂功能)\n对于一个开发完毕的命令行工具脚本(假设名为a.py)，其固有(库会自动添加)的命令是-h或--help\nshpython a.py -h将输出开发者自定义的命令参数及其帮助字符串 常使用命令行工具的开发者应该已经知道，含'-'和'--'前缀的参数为可选项，其中前者为命令缩写、后者为命令全称；上述-h的全称为--help 称不含此类前缀的参数为位置参数/占位参数，即用户必须传递的参数 此类可选项为可选参数，即用户可以根据情况传递的参数(并附上选项缩写或全称)\n必须进行约定，以区分开发者向接口传递的参数和用户输入的参数，我们将后者始终称作用户输入(或使用位置参数和可选参数这两个名词)，其余的参数均为接口的参数、或明确说明是“开发者传递的” 用户输入可选参数，需要指出选项缩写或全称，再跟实际要传递的参数 用户输入位置参数，不需要上述操作，例如：\nsh$ python a.py 1 # 位置参数不需要选项名 $ python a.py -o 1 # 可选参数需要以选项名-o为该项参数的起点, 后续1为实际要传递的参数 # 假设有若干个位置参数(a1, a2, ..., an), 那么它们必须按部就班地顺序排列 $ python a.py 1 2 ... n # 可选参数可以在任意位置插入其中可选参数可以随意插入其中并以选项名作为起点，那么解析器如何区分它们是连续的位置参数、还是掺杂着可选参数的位置参数呢？ 实际上，可选参数后面的实际要传递的参数，默认只能传递一个 后面可以看见，可以设置让可选参数后可跟多个实际参数，此时可以用显式分隔符--解决上述问题：\nshpython a.py -o 1 2 3 # [1, 2, 3] -\u0026gt; o python a.py -o 1 2 -- 3 # [1, 2] -\u0026gt; o、3 -\u0026gt; 位置参数 最常用API argparse库通过ArgumentParser类(参数解析器)实现其功能，创建一个解析器对象：\npythonp = argparse.ArgumentParser() 构造方法的默认参数基本不用修改(几乎都是对帮助信息的设置)，故可以为空；如果想添加对该命令行工具概括性的描述，可以传递关键字参数prog:str、usage:str、description:str 该描述将在用户传递--help时在positional/optional argument前将程序名称、程序使用说明、功能描述打印在屏幕上 一些其次的参数： add_help:bool=true：是否自动添加帮助命令 parents:Sequence[ArgumentParser]：从其它解析器对象继承参数(如要使用，其它解析器对象的add_help最好设置为false) prefix_chars:str='-'：如其名 add_augument()：添加用户需要传递的命令行参数，包含一个可变参数及若干关键字参数 解析器将用户输入的参数存储在一个命名空间，以用户输入的参数作为值、 以开发者传入的选项名作为键，传入的选项名应该符合命名规范(除前缀符)\nhelp:str：对于开发者来说是必填的，将显示在帮助文档中对应参数后，表示该项参数的功能\n*name_or_flags:str： 开发者传递单个字符串时，可以是位置参数或可选参数；传递多个字符串时，只能全部是可选参数 若为可选参数，如果全为缩写(即前缀为-)，则将第一个缩写视为键 如果存在全称(即前缀为--)，则将第一个全称视为键 对一个正常的开发者来说，只会设置一个缩写与一个全称，所以不用了解更多的机制 一个简单的例子：\npython# 设置时没有设置每个参数的数量, 默认为接收1个 p.add_argument(\u0026#39;position_name1\u0026#39;)# 添加position_name1作为位置参数 p.add_argument(\u0026#39;p_name2\u0026#39;) # 添加p_name2作为位置参数, 必须在上述参数后传递 p.add_argument(\u0026#39;-a\u0026#39;, \u0026#39;--aaa\u0026#39;) # 添加全称aaa作为可选参数 args = p.parse_args() # 用户视角: \u0026gt; python a.py 1 -a 2 3 # 传递后, args.position_name1== \u0026#39;1\u0026#39;、args.p_name2 == \u0026#39;3\u0026#39;、args.aaa == \u0026#39;2\u0026#39; # args.a不存在, 因为提供了全称aaa type：上述例子中，传递后的属性均为字符串，因为解析器会默认将输入视为字符串 可以用type=otherType让解析器进行类型转换，其中otherType应是一个类型转换的函数指针 其默认可以选择int、float并在参数不合法时抛出argparse.ArgumentTypeError 开发者可以自定义类型转换用于处理传入的字符串，记得在参数不合法时手动raise argparse.ArgumentTypeError 也可以把该参数看作一种对用户输入的类型限制\nchoices:list：用于限定输入的值(必须是列表内的值)，注意列表元素的类型应与type一致\ndest：用于限定输入值存储于指定属性(属性名必须符合规范)；默认为本条argument的名称 这样就可以随意命名选项名了，可以不符合命名规范\npythonp.add_argument(\u0026#39;-a\u0026#39;) # 用户输入-\u0026gt;args.a p.add_argument(\u0026#39;-a\u0026#39;, dest=\u0026#39;b\u0026#39;) # 用户输入-\u0026gt;args.b p.add_argument(\u0026#39;-a--abc\u0026#39;, dest=\u0026#39;b\u0026#39;) # 用户输入-\u0026gt;args.b default：设置default参数后，用户可以选择**不输入该项(包括选项名)**并以default的值作为用户输入\nconst：设置const参数后，用户可以选择不输入该项的实际参数并以const的值作为用户输入 要使用const，必须同时设置nargs='?'、或者设置action为常量相关行为\nnargs：指定用户输入的数量\n非负整数N：指定用户必须输入N个值\n'?'：用户可以输入零/一个值\n'*'：用户可以输入零/多个值，存储为列表，用户可以用显式分隔符--分割不同参数项\n'+'：用户可以输入一/多个值，存储为列表\n设置为收集为列表的选项时，自动设置action='extend'\naction:str='store'：用于解析器对用户输入最初的行为 视action的值，其它参数可能不允许设置 一些常见的action：\nstore：默认值，存储用户输入\nstore_true/store_false：将本argument视作布尔值，仅在本条参数为可选参数时有效 用户只需要指出这个可选项的名称(缩写或全称)，后面不允许接任何参数 当action设置为store_true时，用户指出这个可选项则本条参数设置为true store_false同理\npythonp.add_argument(\u0026#39;-c\u0026#39;, action=\u0026#39;store_true\u0026#39;) # 用户视角: \u0026gt; python a.py -c 1 # 出错, -c后不允许跟参数(除非有其它位置参数在后面) \u0026gt; python a.py -c # 正常, 且args.c == True \u0026gt; python a.py # 正常, 且args.c == False因其特性，nargs、type、choices不允许设置\nappend：允许多次使用本选项(而不是一个选项名后接若干个参数)，并将所有参数全部收集成为列表类型\nstore_const：行为类似于store_true，用户不允许提供实际参数，并使用const参数的值作为属性值；仍不允许设置nargs、type、choices append_const：在store_const的基础上增加了append的特性，其它参数的限制同上\n也可以继承Action类自定义行为，有兴趣可以了解Python 命令行之旅_博客园\nparse_args()：返回一个命名空间，其中存储用户输入的各个参数值\n其它API(了解即可) parse_known_args()：在parse_args()的基础上，收集用户多余的输入(不报错)；返回二元组，前者为parse_args()的返回值、后者为收集到的多余输入(列表)\nadd_argument_group()：返回一个参数分组，用法和解析器对象一致，方便将用户输入分给多个解析器对象管理，适用于维护大型命令行工具 参数分组只能添加参数/添加参数分组，其本身及其子分组收集到的参数均上交给解析器，并由parse_args()展现给开发者\nadd_mutually_exclusive_group()：在add_argument_group()的基础上，限制用户一次命令只能输入组内的其中一个参数\nadd_subparsers()：返回子解析器，并允许开发者设置子命令，当主解析器遇到子命令时，将解析任务交给子解析器 add_parsers(name:str)：子解析器可以调用该方法返回一个子命令参数解析器，必须指定子命令的名称name，其它参数和ArgumentParser()构造方法一致 拥有多个子解析器是没有意义的，一个子解析器足矣，正确用法是通过这个子解析器调用多次add_parsers()返回多个参数解析器对象，这些对象的API和之前学过的常用API一致 可以将子解析器看作一个中间件\npythonp = argparse.ArgumentParser() subp = p.add_subparsers() subp1 = subp.add_parsers(name=\u0026#39;init\u0026#39;) subp1.add_argument(\u0026#39;-a\u0026#39;) # 用户视角 \u0026gt; python a.py init 1 # 1 -\u0026gt; subp1.parser_args().a init为子命令1 error()：默认的错误处理方法\n可以将解析器的error函数指针替换为自定义错误处理函数\n","permalink":"https://echo.ishian.me/2025/01/20/python-argparse/","summary":"讲解argparse库的解析器、位置参数与可选参数等CLI开发用法","title":"Python: 使用argparse开发CLI"},{"content":"pandas介绍 pandas名字来源于panel datas，即面板数据 pandas主要用于数据分析及数据处理 pandas最常用的两种数据结构是Series和DataFrame 从形状上来看，DataFrame是二维数据结构的封装，Series是DataFrame中的一列，是一维数据结构的封装 pandas提供的更重要的API包括数据清洗、数据预处理、数据操作、数据读取和导出 pandas.Series 可以将Series看作更强的字典，提供了许多类似numpy数组的API\n构造方法：Series(data=None, index=None, dtype=None, name=None, copy=None)\ndata：数据源，可以是列表、字典、数组\nindex：索引列表/数组\n当data提供的是列表或数组时，index默认为以0开头的递增索引序列，也可以提供长度和data相同的一维数据结构\n当data提供的是字典时，index若为None则默认index为该字典的键，Series中的值为该字典的值，若index不为None则起筛选作用，以index的值为索引值，若某索引值存在于data中则取data的值，否则为numpy.nan\n本质上是自动转换为一个AxesData对象(用于存储索引信息的数据对象)，可以通过自定义index实现类似字典的索引\nname：该Series对象的名字，可以在DataFrame等结构中索引\nSeries实例属性：\nindex：Index对象，表示索引数据\nvalues：numpy数组，表示其存储的数据\ndtype：存储数据的数据类型，可以在构造方法中指定，否则自动推断\nshape：Series对象的形状，Series永远将data视为一维结构，即使传递一个二维数组\nSeries实例方法：\ndescribe()：返回Series对象，包含原对象的各种统计数据，包括'count', 'mean', 'std', 'min', 'max'\n也可以通过调用原对象的count()、mean()方法直接获取单个统计数据，其中sum(), cov(), corr()等是describe()没有涉及到的\nunique()：返回去重后的numpy数组\nsort_index()和sort_values()：返回根据索引或根据值排序后的Series对象\ndropna()：返回删除了所有nan后的Series对象\nfillna(v)：返回将所有nan用v填充后的Series对象\nto_list()和to_frame()：转化为原生列表和DataFrame对象\nloc和iloc：pandas支持切片语法，同时提供了另一种索引方法loc(line of code)，通过loc[label]或iloc[index]可以更清晰地表示索引，它们和原生索引的最明显区别是:切片符是双闭切片\n无论是原生切片符还是loc切片，Series对象都同样支持条件过滤，而且它实现了一般的运算符重载，均为逐元素运算，同numpy数组一样在运算时会自动对齐\ncumsum(), cumprod(), cummax(), cummin()：返回前缀求和/求积/取最大/取最小处理后的Series对象\n构造方法示例：\npythonimport pandas as pd s1 = pd.Series([1, 2]) # 0:1, 1:2 s2 = pd.Series([1, 2], index=[2, 1]) # 2:1, 1:2 s3 = pd.Series([[1, 2], [3, 4]]) # shape: (2,) 0:list[1,2], 1:list[3,4] s4 = pd.Series({1:2, \u0026#39;a\u0026#39;:3}) # 1:2, \u0026#39;a\u0026#39;:3 s5 = pd.Series({1:2, \u0026#39;a\u0026#39;:3}, index=[1, 2]) # 1:2, 2:None pandas.DataFrame Series可以看作更强的字典，而DataFrame则可以看作值全为Series对象的字典，因此它是二维数据结构\n与Series不同的是，DataFrame有行索引和列索引\n构造方法：DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)\ndata：可以是列表、数组、字典，DataFrame永远将data视为二维数据结构来解析\n当data提供的是字典，则使用字典的键作为columns(列索引)，用字典的值作为列，要求每一列结构相同，索引统一为index参数(行索引)\n此外，DataFrame将Series看作一列，将二维数组的第一维看作行、第二维看作列\nindex和columns参数分别为行索引和列索引\nDataFrame实例方法：\n大多数方法和Series的实例方法一致，此外详细介绍loc系列索引方法\nloc[x][y]：按照先列再行的顺序索引，和原生的切片符不同，本质是先通过列索引取得Series再用Series.loc\nloc[x, y]：取x行y列，和原生的切片符一致\nloc[[x1, x2, ...], [y1, y2, ...]]：选取第x1, x2, ...行、第y1, y2, ...列的元素并返回DataFrame，其中这些值是离散的，不能用:\nloc[[x1, x2, ...]]：相当于在上述方法的基础上，默认选择全部列，只选择x1, x2, ...行的元素\n类型转换方法：to_csv(), to_excel(), to_json(), to_sql()\ntranspose()或T：返回转置后的DataFrame\n读取数据 读取csv文件：pandas.read_csv(filepath_or_buffer, sep, head, names, dtype, index_col)\n读取excel文件：pandas.read_excel(io, sheet_name, header, names, index_col, usecols, dtype, engine, ...)\n读取数据库文件：pandas.read_sql(query, conn_obj)\n读取网页文件：pandas.read_html(url)\n","permalink":"https://echo.ishian.me/2025/01/18/python-pandas/","summary":"介绍Pandas的Series与DataFrame结构及数据清洗、读取导出等常用API","title":"Python: 使用Pandas处理数据"},{"content":"Tensor(张量) 张量源于数学，并在微分几何中得到应用，其实早在线性代数中就已经学过，向量(一维张量)可以通过线性变换(乘上坐标变换矩阵，即一个二维张量)，在不同的坐标系下表示同一个坐标 在物理中，张量的特性与相对论假设一拍即合，用于描述多个物理量的排列组合，并可以通过线性变换在不同参考系下保持不变，看过广义相对论的科普视频可以知道，这种帮助其它张量线性变换的张量还叫度规张量，并且非常常用 在机器学习中，张量就是一个多维数组，整个张量是表示数据的基本单位，如果具体到数组中的每一个元素，可能它们并没有什么实际意义 引入张量是为了提供一个便于用户计算的接口：例如前向传播中，每一步都要进行矩阵运算，如果只提供一个一维数组，那功能也太弱了；更何况，图片、音频、视频等数据在编码后也需要更高维的数组进行存储\ntorch是在神经网络领域上能够完全替代numpy的库，除了一些小众的数学计算，许多数学函数、多维数组操作torch都能提供，并在此基础上添加了便于深度学习的函数 如果你学过numpy，可以跳过一大部分这里介绍的方法和机制，很多时候将np.换成torch.其效果是一样的\n张量的属性：\ndtype：一个张量的所有元素类型必须是一致的，被封装为torch.dtype，支持各种位数的整数、浮点数等类型 只能通过张量的实例属性dtype访问该张量的元素类型，因为当你试图通过索引访问张量的元素时，返回的仍是torch.Tensor(即使只访问一个元素)，type()是看不到内部的元素属性的 很多深度学习相关方法要求张量的dtype为浮点类型\ndevice：device对象，标记张量部署的设备，如cpu、cuda等\nshape：返回torch.Size对象，是封装后的元组，表示张量的尺寸 元组的长度表示张量的维度，元组的第i个元素表示这个维度上的元素个数，例如：\npythontensor(1) # Size([]) 元组为空, 表示标量 tensor([1]) # Size([1]) 元组长1, 表示一维、1个元素 tensor([1, 2]) # Size([2]) 元组长1, 表示一维、2个元素 tensor([[1, 2]]) # Size([1, 2]) 元组长2, 表示二维、一行两列可以用切片符获取Size元组的信息，和内置元组操作一致 实例方法size()返回值和shape一致、size(dim=num)和shape[num]一致 顺便提一下ndim属性，即维度大小\nrequires_grad：布尔值，标记这个张量是否需要梯度；要想让torch在反向传播中自动跟踪并计算梯度，就必须使requires_grad=True 后续，所有基于requires_grad=True的张量运算而得的张量，requires_grad自动设置为True 梯度会被保存在grad属性中(如果是叶子张量) 需要注意，只有浮点类型的张量才允许需要梯度\nis_leaf：布尔值，标记这个张量是否为叶子张量 在创建的一瞬间，如果这个张量不需要梯度，或者需要梯度但由用户直接创建，则为叶子张量 否则，如果这个张量需要梯度、并且由其它张量运算得出，则不是叶子张量\npythona = torch.tensor([1]) # 默认不需要梯度 b = torch.tensor([1], requires_grad=True) # 需要梯度 aa = a * 2 bb = b * 2 # requires_grad自动设置为True # obj is_leaf # a True # b True 需要梯度, 且由用户直接创建, 为叶子张量 # aa True 不需要梯度, 为叶子张量 # bb False 需要梯度, 但不由用户创建, 而是运算得出, 不是叶子张量开发者不需要过于关心is_leaf的值，但要知道，只有is_leaf=True的张量的梯度会被保留 requires_grad决定是否计算该张量的梯度、is_leaf决定一个张量的梯度是否释放 这是理所当然的，一般我们只需要权重、偏置张量的梯度，而不需要中间结点值的梯度 grad_fn：布尔值，标记创建该张量的函数对象，所有叶子张量的grad_fn均为None 如果这个张量需要梯度，且由其它张量运算得出，则grad_fn指向这个运算函数\nlayout：layout对象，表示张量的内存布局，默认strided(密集张量)、可选aparse_coo(COO格式的稀疏矩阵 stride(步长)表明，无论维度如何，所有数据的物理地址是连续的，通过步长来访问维度 例如对于三行三列的二维张量，当用户访问第三行第三列的元素(第9个元素)时，指针(初始指向第1个元素)先增加两次第一维度的步长3、再增加两次第二维度的步长1，找到目标的实际物理地址 实际上，张量在实现上就是用步长元组stride和一维列表UntypedStorage存储的 你可以用untyped_storage()返回张量的一维映射，用于理解其实现\n创建张量：\n深拷贝：使用工厂函数**torch.tensor(data)** 通过创建副本的方式返回Tensor对象 data可以是列表、元组、np数组等，由tensor()挑选一个最适合的dtype作为元素类型 如果将元组传递给tensor()，后续修改张量也没有影响，因为会转换为可读写张量 允许用户自定义dtype、device、requires_grad，默认不需要梯度\n浅拷贝：使用工厂函数torch.as_tensor(data) 默认情况下，对于除np数组、张量以外的对象，和tensor()一致，对于np数组以及张量，通过浅拷贝返回张量(前者调用from_numpy()) 但用户可以指定dtype、device，如果指定的这两个属性和np数组、张量这些默认进行浅拷贝行为的数据不一致，则进行深拷贝而不是浅拷贝 其它特殊的方式：\ntorch.detach(Tensor)或self.detach()：产生和原张量共享内存的新张量，这个新张量的requires_grad=False，但是对其数据的改动会影响原张量\nzeros(Seq)、ones(Seq)、empty(Seq)、full(Seq, fill_val)：创建全为零、全为一、未初始化、全为指定值的张量 其中Seq是表示其维度的序列 其它参数的使用与tensor()类似\nrand(Seq)、randn(Seq)：创建其值的分布满足U(0,1)、N(0,1)U(0,1)、N(0,1)U(0,1)、N(0,1)的张量 self.uniform_(beg, end)、normal(size、mean、std)：创建其值的分布满足U(beg, end)、N(mean、std2)U({\\rm beg,\\ end})、N({\\rm mean、std^2})U(beg, end)、N(mean、std2)的张量，前者是张量的实例方法(依赖已有张量的尺寸)、后者是工厂函数(用户指定尺寸)\narange()、linspace()、logspace()：用法和内置方法差不多，返回值变成张量而已\npython# beg-\u0026gt;(endnum\u0026lt;end)的间隔为step的等差数列 torch.arange(beg=0, end, step=1) # beg-\u0026gt;end的元素个数为steps的等差数列 torch.linspace(beg, end, steps) # 以base为底, 以linspace(b,e,steps)返回的序列作为幂, 得到的张量 torch.logspace(b, e, steps, base) eye()、diag()、triu()、tril()：创建单位、对角、上三角、下三角矩阵\n其它功能有限但也有人用的方式： Tensor()：构造方法，使用float32作为dtype，且用户不能指定 各种指定类型的构造方法，如FloatTensor、BoolTensor，可能有人喜欢用 from_numpy()：只能传递np数组作为data，与as_tensor(np)效果一致\n张量的梯度相关操作：\nbackward()：对该张量所在计算图进行反向传播，计算所有前置结点的梯度 当张量为标量时，无需提供任何参数 当张量不为标量时，需要提供gradient:Tensor表示求和时的权重系数 两者其实没什么区别；例如，在多输出神经网络中，习惯对各个输出结点产生的损失进行求和得到总损失再向前求梯度，此时相当于手动地进行了gradient=ones(n)的求和，让损失从向量变成了标量，例如：\npython# 输出结果相同 b = torch.tensor([[1, 2], [3, 4]], requires_grad=True, dtype=torch.float32) c = b.pow(4) # 向量 c.backward(torch.ones(c.shape)) # 初始权重系数全为1, 等效于单纯的求和 print(b.grad) b.grad = None d = b.pow(4).sum() # 标量, 原向量单纯的求和 d.backward() print(b.grad)自动求导的好处是，不用再计算什么δ\\boldsymbol\\deltaδ，只需要调用backward()即可\nrequires_grad_()：设置张量为需要梯度(是原位赋值方法)\nwith torch.inference_mode():：创建不追踪梯度的上下文环境，在其下的代码块中所有的张量运算不记录到计算图里 是新版本中with torch.no_grad():的上位替代\nretain_grad()：之前学过is_leaf，所有该属性为False的张量的梯度不会被保留 使用该方法可以在计算后保留非叶子结点的梯度\ndetach()：产生张量的一个映射，该映射不需要梯度，不再赘述\n关于梯度清零：在若干次计算后，历史梯度会直接累加，应该尝试对梯度清零，不过这是优化器的事了\n张量的类numpy操作：具体的参数都不重要，看到别人代码能看懂即可\n切片索引符：基于内置列表的切片索引符，用逗号来分隔不同维度的切片，例如\npython# a是二维张量 a[0:2, 0:3] # 第一维度切0:2, 第二维度切0:3, 所以返回前两行、前三列围成的张量 add()、sub()、mul()、div()对应+、-、*、/，为张量逐元素加减乘除 matmul()对应@，为二维张量的矩阵相乘 exp()、log()、pow()：逐元素e的指数、e的对数、幂运算 t()对应属性T，返回二维张量的转置(和原张量共享内存) 所有上述方法都有方法名末尾加后缀'_'的实例方法版本，它们不创建新张量对象而是直接修改原有张量，称为原位赋值；之前的zeros()等也有原位赋值版本\n条件操作：张量配合条件运算符(例如比较运算符)，将逐元素判断并返回dtype=bool的张量 这个布尔张量可以用作掩码并经过索引符来筛选并返回一维张量，按顺序存储通过筛选的元素 **torch.where(cond,input,other)或self.where(cond,other)**则是更通用的方法，类似于C的?:三元运算符的多维数组版本，cond是一个布尔张量，逐元素判断，当条件满足时返回input在该位置的值、不满足时返回other在该位置上的值 torch.where(cond,self,other)和self.where(cond,other)效果一致\npythona = torch.tensor([[1, 2],[3,4]]) m = a \u0026gt; 1 # 布尔张量 Tensor([[False,True],[True,True]]) a[m] # 一维张量 Tensor([2, 3, 4]) a[m] = 1 # 支持原位赋值, 现在a为Tensor([[1,1],[1,1]]) # a[m]=1和a=torch.where(a\u0026gt;1,1,a)效果一致,区别在于前者是原位赋值 sum()、max()、min()、mean()、std()、var()求张量的和、最大值、最小值、均值、标准差、方差\ndot()、cross()：一维张量的点积、叉积\nself.view(Size)、reshape(Size)：改变张量的shpae并返回新张量 这个新张量和原有张量共享内存，reshape()在view()的基础上添加了保底(无法共享内存时进行深拷贝创建新张量)\n广播机制：当张量运算时，如果维度不相符，并且符合广播的要求，则会根据广播机制扩展某一方的维度 其实和numpy的广播机制一个样：Broadcasting — NumPy\nsave()、load()：张量的序列化、反序列化，人话说就是内存存到外存、外存加载到内存\nto()：可以修改张量的类型、部署的设备，返回一个对原数据进行深拷贝后的新张量\n更多矩阵操作(例如求秩)在torch.linalg中\n","permalink":"https://echo.ishian.me/2025/01/15/pytorch-tensor/","summary":"介绍Pytorch张量的属性、创建方式、维度操作及常用运算方法","title":"Pytorch: Tensor(张量)"},{"content":"Bash Shell命令 进程 获取进程信息 跟进进程(process)是很重要的任务，包括查看、终止、执行者和执行时间等等\u0026hellip;\nps命令就是用来获得进程信息的：\nsh# 不带选项情况下,获得当前终端、在当前瞬间、且进程属主为当前用户的进程的: # PID TTY(所处终端) TIME(占用的CPU时间) CMD(命令名) $ ps ps options: -A/-e # 获取所有进程 -a # 获取除无终端进程、控制进程外的所有进程 -f # 获取详细信息,以Unix风格输出 -u # 获取详细信息,以BSD风格输出 监测进程信息 ps用于获取瞬时的进程信息，而top用于监测一个时段的信息：\nshtop # 实时获取进程信息,每过一段时间会更新结束进程 用kill来杀死进程：\nshkill PID # 杀死PID指向的进程 pkill CMD # 杀死命令名为CMD的进程,支持正则表达式磁盘空间 ","permalink":"https://echo.ishian.me/2025/01/11/linux-infos/","summary":"介绍使用 Bash 命令监控进程、磁盘空间等系统信息的方法","title":"Linux: 高效地监控操作系统"},{"content":"公平组合游戏 一个博弈游戏是公平组合游戏意味着，场上所有物品对于所有玩家都是可操作的，且玩家们必须遵循同样的操作规则；即对于操作(无论是操作规则还是操作对象)来说是公平的 但玩家总会有操作的顺序，事实上许多游戏先手和后手的胜利期望是不同的，博弈论研究的就是，当场上所有玩家都作出最“合理”的操作时，某个玩家必胜/必输的情况；不难得到，一个玩家在一局游戏里，要么必胜、要么必输 这里的合理，意味着玩家总是枚举完所有的结局，再选出最有利的那个选择；这里有一个很直观的结论就是，如果后续所有结局都是输，那么此时一定是必输的；否则，此时一定是必胜的 那么有没有办法，不需要枚举所有的结局，而只需要知道后续状态的必胜、必输，就可以递推出当前的必胜/必输呢？当然是可以的\n实际上，对于双人的游戏而言，先手必胜意味着后手必输，因此我们只讨论先手必胜/先手必输的情况 一个约定：先手必胜/先手必输此后简称必胜/必输；能使先手必胜/必输的操作对象的初始状态(例如数量为nnn)此后简称必胜/必输状态 对操作规则不会因操作对象状态改变的游戏而言，必胜、必输只和操作对象的状态有关，那么一个脑筋急转弯的思考就是：\n一个状态是必胜状态，当且仅当存在一个后续状态是必输状态(这里的后续指的是只通过一次操作转移到的状态) 充分性：如果存在一个后续的必输状态，那么此时先手可以选择转移到这个状态，此时后手变成了“先手”，于是必输 必要性显然 一个状态是必输状态，当且仅当没有后续状态、或所有后续状态是必胜状态 这个命题的一部分是上述命题的逆否命题；特别地，如果没有后续状态，说明此时的先手已经无法再操作了，意味着先手必输 对于博弈问题，可以将上述状态视作结点，并画成一个有向无环图，标记所有无出度的节点为必输结点，然后经过一次dfs即可得到所有结点的必胜/必输性质 而在一些简单的脑筋急转弯题目中更常见的做法是，对所有状态进行归纳，即找到一个特征AAA，使该特征满足AAA无论如何都无法变为AAA、而Aˉ\\bar AAˉ总存在一种操作使得Aˉ\\bar AAˉ变为AAA\n巴什博弈：有nnn个物品，两个人轮流取物，每次只能取[1,m][1,m][1,m]个物品，最后取光者胜利\n结论：在巴什博弈中，这个特征是(m+1)∣n(m+1)|n(m+1)∣n 一个结点n=kn=kn=k的所有前置状态为n∈[k+1,k+m]n\\in[k+1,k+m]n∈[k+1,k+m]、所有后置状态为n∈[k−m,k−1]n\\in[k-m,k-1]n∈[k−m,k−1] 很明显，只有一个出度为零的状态，即n=0n=0n=0时，先手必输 因此n=0n=0n=0的所有前置状态n∈[1,m]n\\in[1,m]n∈[1,m]均为必胜状态⇒n=m+1\\Rightarrow n=m+1⇒n=m+1是必输状态、所有n∈[m+2,2m+1]n\\in[m+2,2m+1]n∈[m+2,2m+1]是必胜的(因为后续状态包含n=m+1n=m+1n=m+1) 递推得到，所有n=k(m+1),k∈Nn=k(m+1),k\\in Nn=k(m+1),k∈N均为必输状态，其余为必胜状态 如果先手必胜，那么先手的操作就应是：使操作后n=k(m+1)n=k(m+1)n=k(m+1)，即取n%(m+1)n\\%(m+1)n%(m+1)个 Nim游戏：共nnn堆物品，每堆有aia_iai​个，两个玩家轮流取任意一堆的任意数量个物品，最后取光者胜利\n在Nim游戏中，这个特征是各堆数量的异或和(也称Nim和)为零，Nim和定理：若\n⨁1≤i≤nai=0\\begin{align}\\bigoplus_{1\\le i\\le n}a_i=0\\end{align}1≤i≤n⨁​ai​=0​​，则先手必输；否则先手必胜(其中⊕\\oplus⊕表异或) 证明这个定理，需要证明当异或和为000时，不存在任何操作使其异或和仍未000；且当异或和不为000时，总是存在操作使其异或和变为000\n对于第一个命题：假设玩家将aia_iai​改为ai′a_i\u0026#x27;ai′​，那么由异或操作可以得出ai=ai′a_i=a_i\u0026#x27;ai​=ai′​，即不取，明显不符合规则，因此不存在这样的操作 对于第二个命题：假设当前异或和为kkk，那么异或上kkk即为000，我们取合适的aia_iai​，使其变为ai⊕ka_i\\oplus kai​⊕k即可 假设kkk的最高的为111的位是第jjj位，那么至少存在一个aia_iai​，它的第jjj位也为111，选取这个aia_iai​，可以得到ai⊕k\u0026lt;2j≤aia_i\\oplus k\u0026lt;2^j\\le a_iai​⊕k\u0026lt;2j≤ai​，因此这个操作是合法的 先手必胜的操作就是，不断地使操作后的Nim和为000，此时后手的任何操作只会让Nim和再变为非000，直到最后一次使Nim和为000，此时所有堆的数量均为000，先手玩家得到胜利 也就是检查所有aia_iai​(或检查所有满足第__lg(k)\\_\\_lg(k)__lg(k)位为111的)，找到任意一个异或kkk后不超过aia_iai​的，取ai−(ai⊕k)a_i-(a_i\\oplus k)ai​−(ai​⊕k)即可\nRoy\u0026amp;October之取石子 Roy\u0026amp;October之取石子II\n可以发现，000是必输的，而n∈[1,5]n\\in[1,5]n∈[1,5]是必胜的，然后n=6n=6n=6是必输的 然后我们假设特征是6∣n6|n6∣n，即n=6kn=6kn=6k 它是否无论如何都无法使下一个状态为6∣n6|n6∣n：这需要证明任意质数的幂pkp^kpk不是666的倍数 除了222以外的质数，它们的幂都是奇数，因此不可能是666的倍数 222的幂不含333的因子，因此也不可能是666的倍数，得证 它的补，即6∤n6\\nmid n6∤n是否总存在一种取法使得下一个状态为6∣n6|n6∣n：因为6∤n6\\nmid n6∤n，我们取n%6n\\%6n%6即可 因此答案是：若6∣n6|n6∣n，则先手必输；否则先手必胜 后题与前题类似，只不过特征变成了4∣n4|n4∣n 欧几里德的游戏：有些时候，答案不能简单地归纳，而必须通过递归得到\n取mmm为两数中较小者，nnn为较大者，并改写为n=km+rn=km+rn=km+r，rrr为n mod kn\\ {\\rm mod}\\ kn mod k 可以得到退出递归的情况：r==0r==0r==0或k≥2k\\ge 2k≥2，此时操作者必胜 前者显然，而后者总是可以一步(直接减去kmkmkm)或两步(先减去(k−1)m(k-1)m(k−1)m，此时数对为(m,m+r)(m,m+r)(m,m+r)，后手只能减去mmm)使数对变为(m,r)(m,r)(m,r)；由于(m,r)(m,r)(m,r)一定是必胜或必败的，它必胜就两步变换、必败就一步变换，就能保证数对为(m,n)(m,n)(m,n)时的操作者必胜了 否则，只能进行递归：f(m,n,player)==f(r,m,¬ player)f(m,n,player)==f(r,m,\\neg\\ {player})f(m,n,player)==f(r,m,¬ player) ","permalink":"https://echo.ishian.me/2025/01/07/algo-game-fairgames/","summary":"讲解公平组合游戏的必胜必输状态判定，并推导巴什博弈与Nim游戏的结论","title":"博弈论: 公平组合游戏"},{"content":"最小生成树 概述 一个连通图的生成树是最简单的连通子图，它连通了图的所有节点，并且只含有节点数减一条边，即没有环\n最小生成树是指，假设原图的每条边都含有权重，它的最小生成树的权重和是所有生成树中权重和最小的那棵\n能解决的问题：\n如结构所描述的那样，多个节点可以互相连通，但最终需要将图化为生成树，则可以用最小生成树算法解决最优问题 图论基础 图的概念 图是非线性的数据结构，可用于表示诸如网络、社会关系、地图、电路图等 称图中任意一对节点的关系为边，如果所有边不强调方向，则称为无向图，否则为有向图 称一个节点的边数为它的度 若无向图中任意两点有且仅有一条边，则称其为无向完全图；若有向图中任意两点有且仅有两条方向相反的边，则称其为有向完全图 若在图中能沿着边从一个节点找到另一节点，则称两点连通；若无向图中任意两点都连通，则称其为连通图；若有向图中任意两节点互相连通，则称其为强连通图 若两点互相连通且最短路径没有其它节点，则称这两点互为邻接节点 邻接矩阵 最简单的图的存储结构就是邻接矩阵，一个无向图需要存储节点间的关系，即边，且边是双向的，这是多对多的逻辑结构\n假设图的总结点数为n，其中两个节点为i和j，用一个邻接矩阵就可以简单表示两者关系：\nc\u0026#43;\u0026#43;int arcs[n][n] {}; arcs[i][j] = 1; // 数组表示节点i与节点j是否邻接(01). // 如要存储权重,则应用inf表示不邻接. // 无向图矩阵是对称矩阵. 在有向图中，需定义行数为边的箭头或边的结尾，以表示方向的信息\n通过下标映射，数组表示法能够快速查询两点是否邻接，并取得这条边的权重，但有许多缺点：\n节点数量较多时，花费空间巨大 无法得知跨节点是否连通，也难以快速求出路径 无法直接取得节点的度，需要遍历一维数组 c\u0026#43;\u0026#43;template \u0026lt;typename Note, typename Weight, bool Dir=false\u0026gt; struct Graph { // Note表示节点类型,Weight表示权重类型,Dir表示有/无向(默认). static W MAX = INT_MAX; // 无穷表示不连通. Note* notes; // 一维数组存储节点信息. W** edges; // 邻接数组存储边信息. map\u0026lt;Note, int\u0026gt; Map; // 为了通过节点地址而不是整型下标来访问图, 需要构建映射. };邻接表 邻接表使用链表而不是数组来存储边的信息，将边和权重压缩成边节点，将一个数据节点的所有边用边链表表示：\nc\u0026#43;\u0026#43;template \u0026lt;typename Weight\u0026gt; struct Edge { // 不需要无穷,因为不会存储不邻接节点. Weight w; int connect; // 这条边的终点下标. Edge\u0026lt;Weight\u0026gt;* next; }; template \u0026lt;typename Note, typename Weight, bool Dir=false\u0026gt; struct Graph { Note* notes; // 存储数据节点. Edge\u0026lt;Weight\u0026gt;* edges; // 存储边链表的头结点. map\u0026lt;Note, int\u0026gt; Map; // 为了通过节点地址而不是整型下标来访问图, 需要构建映射. };有向图需要维护两个边链表，分别表示出/入方向的边\n最小生成树实现 Prim算法 首先初始化最小生成树，它只有一个节点，基于贪心，每轮外层循环中，通过内层循环遍历所有以这棵树中节点为起点、以未加入节点为终点的边，取最小，并将终点拉入最小生成树，过程中外层一共循环n-1次，可以顺便维护最小路径值\nc\u0026#43;\u0026#43;Graph Prim(const Graph\u0026amp; _g) { // 伪代码. Edge* tmp, head; Graph ans {_g[0]}; for ( i= 0 : _g.size()-1 ) { head = _g.edge(i); // 取得生成树第i节点的边链表. tmp = head; for ( j= i : 0 ) { for (head : end) { if (ans.find(head.connect) == -1) // 终点未加入生成树. tmp = tmp-\u0026gt;w \u0026lt; head-\u0026gt;w ? tmp : head; } head = _g.edge(j); } ans.addNote(_g[tmp-\u0026gt;connect]); } }","permalink":"https://echo.ishian.me/2024/12/09/algo-graph-minimum-spanning-tree/","summary":"从图论基础与图的存储讲起，介绍最小生成树的概念及Prim、Kruskal等算法","title":"图的算法: 最小生成树"},{"content":"计算机的算术运算 数据的表示与存储 计算机码 真值：符合人类习惯的十进制数字；机器数：数字化后的以机器码存储的数字；常用8421 BCD8421\\ \\rm BCD8421 BCD有权码\n其它编码：\n余三码：在842184218421基础上，添加011011011的偏置量，是无权码 242124212421：权值不同的有权码 ASCII：编码英文字符 IEEE标准规定计算机内的有符号定点数都以补码形式存储，且使用8421 BCD8421\\ \\rm BCD8421 BCD码\n定点数表示 仅有符号定点数有原、反、补码的定义\n有符号定点数视最高位为符号位，假设−2n≤x\u0026lt;2n-2^n\\le x\\lt 2^n−2n≤x\u0026lt;2n(n≥0n\\ge 0n≥0，即只表示纯整数或纯小数)：\n正数的原码、反码、补码相同：\n原码：由符号位和尾数组成，对人来说是最直观的机器数\n不需要记住原码的计算公式，只需记住原码无法表示最小值−2n-2^n−2n(000有两个原码) 数值部分位数恰好为nnn 反码：对原码的尾数按位取反后的码值(仍无法表示最小值)\n补码的定义：\n计算公式(负数)：[x]补=2n+1+x[x]_{补}=2^{n+1}+x[x]补​=2n+1+x，当要通过补码计算原值时，nnn恰为数值部分位数\n简单理解：可看作相同位数无符号定点数中最高位的权值改为−2n-2^n−2n，使相同位数无符号定点数表示范围0≤x\u0026lt;2n+10\\le x\\lt 2^{n+1}0≤x\u0026lt;2n+1中≥2n\\ge 2^n≥2n的部分能一一映射到负数集合里(最高位权值由2n2^{n}2n变为−2n-2^{n}−2n，即这部分定点数均减去2n+12^{n+1}2n+1来对应负数)\n不难理解为何正数的补码为其本身，实际上真正的计算公式中加上2n+12^{n+1}2n+1后需要模2n+12^{n+1}2n+1，这个公式是正负数补码通用的，化简模运算后得到正负数补码不同的计算方式\n由于一个数加一个负数xxx等价于加其补码后模2n+12^{n+1}2n+1，故设计运算时只需自然舍去最高位进位即可统一加减法\n证明：A+x=(A+2n+1+x)mod 2n+1=(A+[x]补)取n+1位A+x=(A+2^{n+1}+x)\\mod 2^{n+1}=(A+[x]_{补})_{取n+1位}A+x=(A+2n+1+x)mod2n+1=(A+[x]补​)取n+1位​ 当补码符号位为111，数值部分全为000时，达到能表示的最小值−2n-2^n−2n，此时原码不存在\n补码的快捷计算(原码存在时)：反码加111，最高位自然舍去\n移码：定义为在补码的基础上，符号位翻转一次；其意义为偏移了2n2^n2n，使比较正负数补码的大小时符合直观判断；可在浮点数中判机器零\n浮点数表示 可将任意分子为纯整数、分母为222的次幂的有理数 x2y\\begin{align}\\frac{x}{2^y}\\end{align}2yx​​​表示小数为2−y×(x)22^{-y}\\times(x)_22−y×(x)2​；称−y-y−y为阶码(含阶符)、纯整数xxx为尾数(含数符)，就是一般的浮点数表示形式 规格化： 使整数xxx变为纯小数，且使其大于2−k2^{-k}2−k(按2k2^k2k规格化)，尾数左移阶码减一，反之加一 机器零：当阶码小于等于其能表示的最小值、或尾数为零时，将该数当作零(而实际不是) 浮点数IEEE标准 IEEE浮点数：阶码、尾数均为补码，数符在最前，阶码添加偏移量，规格化使尾数为1∼21\\sim21∼2间的小数且整数部分省略(隐藏位)，如下：\n数符 阶码(含阶符) 尾数(省略最高位的1)数符\\ \\ 阶码(含阶符)\\ \\ 尾数(省略最高位的1)数符 阶码(含阶符) 尾数(省略最高位的1)\n有如下类型及其对应位数，其中临时实数不使用隐藏位：\n数符阶码尾数总位数阶码偏移量短实数1823327FH长实数11152643FFH临时实数11564803FFFH\\begin{matrix}\u0026amp;数符\u0026amp;阶码\u0026amp;尾数\u0026amp;总位数\u0026amp;阶码偏移量\\\\短实数\u0026amp;1\u0026amp;8\u0026amp;23\u0026amp;32\u0026amp;\\rm 7FH\\\\长实数\u0026amp;1\u0026amp;11\u0026amp;52\u0026amp;64\u0026amp;\\rm 3FFH\\\\临时实数\u0026amp;1\u0026amp;15\u0026amp;64\u0026amp;80\u0026amp;\\rm 3FFFH\\end{matrix}短实数长实数临时实数​数符111​阶码81115​尾数235264​总位数326480​阶码偏移量7FH3FFH3FFFH​\n校验码 奇偶校验码：给有效信息额外添加111位校验码，使整体111的个数为奇数(奇校验)或偶数(偶校验)\n求偶校验位值的过程就是让所有有效信息位进行异或 偶校验信息的过程就是让整条信息的各位异或，此时正确信息中各位的异或结果为000 检错能力为111位，没有纠错能力 海明校验码：检错能力为222位，纠错能力为111位\n使用多个校验位，并采用偶校验，每个校验位被分配到不同组，分组进行偶校验，按顺序排列为整数kkk，则{k=0无错k≠0出错,且该值指明了出错的位置序号\\begin{cases}k=0\u0026amp;无错\\\\k\\ne0\u0026amp;出错,且该值指明了出错的位置序号\\end{cases}{k=0k=0​无错出错,且该值指明了出错的位置序号​ 各校验位的位置为Pi=2i−1P_i=2^{i-1}Pi​=2i−1处，信息位DiD_iDi​按顺序排放在其余位置，位置序号为ppp 分组：将所有ppp用二进制数表示，则校验位PiP_iPi​将和所有第iii位为111的ppp所指向的信息位分为一组，例如P1P_1P1​将和p=3(011)、5(101)、7(111)⋯p=3(011)、5(101)、7(111)\\cdotsp=3(011)、5(101)、7(111)⋯上的信息位分为一组 检错：再添加一位全校验位，对整体进行偶校验，可判断111位错或222位错：全校验位={×其它校验位全为0时,无错11位出错02位出错全校验位=\\begin{cases}\\times\u0026amp;其它校验位全为0时,无错\\\\1\u0026amp;1位出错\\\\0\u0026amp;2位出错\\end{cases}全校验位=⎩⎨⎧​×10​其它校验位全为0时,无错1位出错2位出错​ 纠错：若222位出错，需重传；若111位出错，则对第kkk位取反即可 定点数运算 移位 最快的运算\n针对无符号定点数，采用逻辑移位，即在缺失位处补000，所有位均参与移位\n针对有符号补码，采用算术移位：符号位不参与移位，在缺失位处补原符号位\n乘以2n2^n2n幂：C++编译器将采用左移运算符而不是乘号\n除以2n2^n2n幂：C++编译器将采用右移运算符而不是除号\n对无符号整数，均直接逻辑右移，结果均为向下舍入(即向更小的数字舍入)\n向下舍入原因：二进制串的最低位会被覆盖，右移时最低位的产生的正小数部分丢失 对有符号负数，需要进行偏置：\n向下舍入的特性会导致负数舍入后离000更远，而我们希望除法后的舍入应该离000更近，例如−3/2=−1-3/2=-1−3/2=−1而不是−2-2−2，这种舍入是不合适的\n最简单的想法是先转化为正数，再右移，再转化为负数：⌈x/2k⌉=−((−x)\u0026gt;\u0026gt;k)\\lceil x/2^k\\rceil=-((-x)\u0026gt;\u0026gt;k)⌈x/2k⌉=−((−x)\u0026gt;\u0026gt;k)\n通过偏置进行向上舍入是最常用的办法，即在右移之前加上偏置量(1\u0026lt;\u0026lt;k)−1(1\u0026lt;\u0026lt;k)-1(1\u0026lt;\u0026lt;k)−1\n偏置量证明：假设需要右移kkk位，那么容易得到{前k位为0,不需要舍入前k位大于0,需要舍入\\begin{cases}前k位为0,\u0026amp;不需要舍入\\\\前k位大于0,\u0026amp;需要舍入\\end{cases}{前k位为0,前k位大于0,​不需要舍入需要舍入​\n若增加该偏置量，在不需要舍入的情况下，不改变结果\n在需要舍入的情况下，添加后将进位，使结果加111，即⌈x/2k⌉=⌊x/2k⌋+1\\lceil x/2^k\\rceil=\\lfloor x/2^k\\rfloor+1⌈x/2k⌉=⌊x/2k⌋+1\n由于向下舍入是更简单的方式，一些语言不采用向零舍入\n按位运算和逻辑运算及加减法 按位与运算可用于截取前kkk位二进制串\n加减法稍慢，减法需要先将后者的补码算术取反\n采用高速并行加法器，也称超前进位加法器(CLA)：\n回顾一位加法器的最少逻辑门实现：Si=A⊕B⊕Ci−1Ci=(A⊕B)Ci−1+AB\\begin{matrix}{\\rm S_i}=A\\oplus B\\oplus C_{i-1}\\\\{\\rm C_i}=(A\\oplus B)C_{i-1}+AB\\end{matrix}Si​=A⊕B⊕Ci−1​Ci​=(A⊕B)Ci−1​+AB​，即最低位的进位有5T5T5T的时延\n串行加法器：由于来自低位的进位参与高位进位的计算时不用通过异或门，故每个高位进位又有2T2T2T的时延，综合下来一个串行加法器的时延与位数nnn线性正相关\nCLA的思路就是，首先一次性、同时计算出各位的进位，由于Ci=AB+(A+B)Ci−1{\\rm C_i}=AB+(A+B)C_{i-1}Ci​=AB+(A+B)Ci−1​，将Ci−1C_{i-1}Ci−1​展开即可得到{Gi=AiBiPi=Ai+BiCi=Gi+PiGi−1+PiPi−1Gi−1+⋯+(PiPi−1⋅⋅⋅P1G0)+(PiPi−1⋅⋅⋅P0C0)\\begin{cases}G_i=A_iB_i\\\\P_i=A_i+B_i\\\\\\rm {C_i}=G_i+P_iG_{i-1}+P_iP_{i-1}G_{i-1}+\\cdots+(P_iP_{i-1}···P_1G_0)+(P_iP_{i-1}···P_0C_0)\\end{cases}⎩⎨⎧​Gi​=Ai​Bi​Pi​=Ai​+Bi​Ci​=Gi​+Pi​Gi−1​+Pi​Pi−1​Gi−1​+⋯+(Pi​Pi−1​⋅⋅⋅P1​G0​)+(Pi​Pi−1​⋅⋅⋅P0​C0​)​\n所有进位的时延都为$3T$ CLA的问题在于，位数越高所需逻辑电路越复杂，因此通常采用888位CLA串行连接产生非完全超前进位的更高位加法器\n有符号整数的算术取反(取相反数)为整体按位取反后末位加111：\n设有n+1位以补码存储的二进制有符号正数证明正→负:最高位表示0,其余位最多表示2n−1,当前数值为A整体取反后,最高位表示−2n,其余位变为2n−1−A,加一后等于2n−1−A易得0+A=−(−2n+2n−A)反之同理\\begin{align}\u0026amp;设有n+1位以补码存储的二进制有符号正数\\\\\u0026amp;证明正\\rightarrow负:最高位表示0,其余位最多表示2^n-1,当前数值为A\\\\\u0026amp;整体取反后,最高位表示-2^n,其余位变为2^n-1-A,加一后等于2^{n-1}-A\\\\\u0026amp;易得0+A=-(-2^n+2^n-A)\\\\\u0026amp;反之同理\\end{align}​设有n+1位以补码存储的二进制有符号正数证明正→负:最高位表示0,其余位最多表示2n−1,当前数值为A整体取反后,最高位表示−2n,其余位变为2n−1−A,加一后等于2n−1−A易得0+A=−(−2n+2n−A)反之同理​​ 加减法溢出位判断：当且仅当两操作数符号相同时可能溢出\n一位判断：结果符号位和原符号位不同时溢出\n二位判断：结果的两个符号位不同时溢出\n证明两个负数补码的数值部分相加后，若不溢出，最高位一定有进位：\n证:设两负数原值为x、y,[x]补=1,X、[y]补=1,Y;其中X、Y都为n位由[x]补=x+2n+1、[y]补=y+2n+1若x+y不溢出,即x+y≥−2n⇒[x]补+[y]补−2n+2≥−2n⇒[x]补+[y]补=X+Y+2n+1≥3⋅2n⇒X+Y≥2n⇒X+Y最高位一定有进位\\begin{align}\u0026amp;证:设两负数原值为x、y,[x]_{补}=1,X、[y]_{补}=1,Y;其中X、Y都为n位\\\\\u0026amp;由[x]_{补}=x+2^{n+1}、[y]_{补}=y+2^{n+1}\\\\\u0026amp;若x+y不溢出,即x+y\\ge-2^n\\\\\u0026amp;\\Rightarrow[x]_{补}+[y]_{补}-2^{n+2}\\ge-2^n\\\\\u0026amp;\\Rightarrow[x]_{补}+[y]_{补}=X+Y+2^{n+1}\\ge3·2^n\\\\\u0026amp;\\Rightarrow X+Y\\ge2^n\\Rightarrow X+Y最高位一定有进位\\end{align}​证:设两负数原值为x、y,[x]补​=1,X、[y]补​=1,Y;其中X、Y都为n位由[x]补​=x+2n+1、[y]补​=y+2n+1若x+y不溢出,即x+y≥−2n⇒[x]补​+[y]补​−2n+2≥−2n⇒[x]补​+[y]补​=X+Y+2n+1≥3⋅2n⇒X+Y≥2n⇒X+Y最高位一定有进位​​ 乘法 比加法更慢\n原码一位乘：因计算机是定长机，因此将笔算乘法中被乘数左移转换成部分积右移，并使其高位和被乘数相加(因乘数最低位可舍去，因此同时右移即可)；假设两个数值为nnn位的数相乘：\n符号位单独处理得到，乘法过程为绝对值相乘 注意使用逻辑右移，因为最高位本质上是数值位，总共需要移nnn位 部分积寄存器n+1n+1n+1位，多出的一位存假溢出 原码两位乘：将两次一位乘化为一次两位乘，其有五种情况\n000000：直接右移两位\n010101：加一次被乘数后右移两位\n101010：加两倍的被乘数(左移一次)后右移两位\n111111：因很难计算三倍，故先减一倍被乘数后记账一次(在右移两位后高位加一倍等价于移位前加四倍)\n​ 借位初始为000，每次都需要参与运算\n100100100：因有借位的参与，此时直接加四倍的被乘数(左移两次)后右移两位\n因涉及到减法，故需要真符号位，又被乘数最多可能左移两位，因此需要在被乘数前补三位\n并采用算术右移\n因涉及到借位，为防止最后借位仍为111，需在乘数前补位(偶补两位、奇补一位，由于是绝对值，补的是000，因此奇数位只需补111位而最后不会出现11 111\\ 111 1的情况)，用于处理该借位\n总共需要右移\n⌊乘数2⌋\\begin{align}\\left\\lfloor\\frac{乘数}2\\right\\rfloor\\end{align}⌊2乘数​⌋​​次两位，若乘数位数为奇数最终需再移一位\n补码一位乘(校正法)：被乘数符号任意、乘数取绝对值后，进行原码一位乘的过程，最后进行校正\n由于是补码，采用算术右移，且部分积寄存器需补一位真符号位\n若乘数本身为负数，需在移位完成后减去一倍的乘数，减后不需要移位\n补码一位乘(比较法/Booth算法)：在对校正法公式整理后，依据最后两位(需补附加位)有四种情况(后一位减前一位有三种情况)：\n00/1100/1100/11：直接右移一位\n010101：加一倍乘数后右移一位\n101010：减一倍乘数后右移一位\n乘数寄存器需补附加位(初始为零)\n注意最后一次比较后不移位，总共只移nnn位\n补码两位乘：将两次Booth乘法步骤合为一次，有八种情况：\n000000000：直接算术右移两位\n001001001：加一倍乘数后右移两位\n010010010：减一倍右移再加一倍右移，整理得加一倍后右移两位\n011011011：加两倍后右移两位\n100100100：减两倍后右移两位\n101101101：加一倍右移再减一倍右移，整理得减一倍后右移两位\n110110110：减一倍后右移两位\n111111111：直接右移两位\n因此部分积需补三位、乘数需补一位附加位\n此外，乘数算上附加位需补成奇数位，且最后一次比较后不移位\n快速幂算法：\nN=(an,an−1,⋯ ,a0)2N=(a_n,a_{n-1},\\cdots,a_0)_2N=(an​,an−1​,⋯,a0​)2​，计算ban2n⋅...⋅ba0⋅1b^{a_n2^n}·...·b^{a_0·1}ban​2n⋅...⋅ba0​⋅1即可\n或者，当NNN为偶数时使其自乘，为奇数时记录多余的bbb，在最后相乘：\nc\u0026#43;\u0026#43;int quick_power(int b, int N) { int ans = b, tmp = 1; while (N) { if (N \u0026amp; 1) { // 奇数时抽出一份 b, 记录在 tmp 中. tmp *= b; --N; // 幂数减1. } else { // 偶数时自乘, 幂数减半. ans *= ans; N \u0026gt;\u0026gt;= 1; } } return ans * tmp; } 除法和取模 极慢，取模和除法是同时进行，同时完成的\n恢复余数法：因定长机，将笔算过程中除数右移改为使余数和商左移，根据余数和除数的大小来上商\n预处理：被除数和除数先转换成其绝对值，计算结果的符号位需单独处理 若减除数后，新余数为负数，则要恢复余数，即加上除数，并在商的末位商000 否则直接在商的末位商111 两种情况均需使余数和商同时逻辑左移 原码的加减交替法(不恢复余数法)：\n每次减除数后，根据余数：{正数商1左移减负数商0左移加\\begin{cases}正数商1左移减\\\\负数商0左移加\\end{cases}{正数商1左移减负数商0左移加​\n共移nnn次，最后一次移位后仍需继续运算一次\n补码的加减交替法：\n取两位符号位，每次运算前比较余数和除数的符号位：{同号商1减除数异号商0加除数\\begin{cases}同号商1减除数\\\\异号商0加除数\\end{cases}{同号商1减除数异号商0加除数​\n共移nnn次，最后一次移位后恒商111，因此会导致些许误差\n快速模幂算法：为了减小因幂运算导致的数字极大，产生额外空间、时间消耗，故通过以下定理将其变为模数相乘再取模，数字会小很多\n前置定理：(a1⋅a2)mod n=((a1mod n)⋅(a2mod n))mod n(a_1·a_2){\\rm mod}\\ n=((a_1{\\rm mod}\\ n)·(a_2{\\rm mod}\\ n)){\\rm mod}\\ n(a1​⋅a2​)mod n=((a1​mod n)⋅(a2​mod n))mod n\n推论：aNmod n=(a mod n)Nmod na^N{\\rm mod}\\ n=(a{\\rm\\ mod}\\ n)^N{\\rm mod}\\ naNmod n=(a mod n)Nmod n\n假设计算bNmod nb^N{\\rm mod}\\ nbNmod n，为快速计算幂，这里复用快速幂算法，将NNN化为二进制(an,an−1,⋯ ,a0)2(a_n,a_{n-1},\\cdots,a_0)_2(an​,an−1​,⋯,a0​)2​，按权值展开后，bN=ban2n+⋯+a0⋅1=ban2n⋅...⋅ba0⋅1b^N=b^{a_n2^n+\\cdots+a_0·1}=b^{a_n2^n}·...·b^{a_0·1}bN=ban​2n+⋯+a0​⋅1=ban​2n⋅...⋅ba0​⋅1\nb2nb^{2^n}b2n虽然比bNb^NbN小，但还是较大，因此要证明每次迭代时，取模不影响结果，递归套用推论易证：b2nmod n=(b2n−1⋅b2n−1)mod n=(b2n−1mod n)2mod nb^{2^n}{\\rm mod}\\ n=(b^{2^{n-1}}·b^{2^{n-1}}){\\rm mod}\\ n=(b^{2^{n-1}}{\\rm mod}\\ n)^2{\\rm mod}\\ nb2nmod n=(b2n−1⋅b2n−1)mod n=(b2n−1mod n)2mod n\n所以计算nnn次幂的模时，可计算n−1n-1n−1次幂的模的平方的模\nc\u0026#43;\u0026#43;int quick_power_mod(int b_mod_n, int N, int n) { int ans = 1; b_mod_n %= n; // 预处理, (b^N)%n = ((b%n)^N)%n. while (N) { // 取N的二进制表示中的第一位,如果为1,则计算 (ans*(b^{权值}%n)) mod n. if (N \u0026amp; 1) { // 为保证数字始终较小,迭代 ans 时要再mod n. ans = (ans * b_mod_n) % n; } // 为保证数字始终较小,迭代 b的幂 时要再mod n, 使数字\u0026lt;n^2. b_mod_n = (b_mod_n * b_mod_n) % n; N \u0026gt;\u0026gt;= 1; } return ans; } 模数为222的nnn次幂(n\u0026gt;1n\u0026gt;1n\u0026gt;1)：直接用位运算截取前n−1n-1n−1位即可\nc\u0026#43;\u0026#43;int mod_2_power(int b, int n) { // 若n为2^k,则二进制表示为00...010...00, 唯一的1在第k+1位上. // 则n-1为00...01...11, 最高位的1在第k位上. // 故 b % n = b \u0026amp; (n-1); return b \u0026amp; (n - 1); } 定点数运算总结 浮点数运算 ALU设计 ","permalink":"https://echo.ishian.me/2024/12/06/coa-arithmetic-operations/","summary":"讲解计算机中数据的表示与存储、定点数原反补码及加减乘除运算的实现","title":"计算机组成原理: 算术运算"},{"content":"离线查询 离线查询，即将题目问的多组问题缓存下来，再进行一定顺序的求解 通常用于，一些查询的结果是另一些查询结果的一部分的情况；甚至于如果先求解后者，反而很难得到前者的情况 具有这些性质的查询，在离线下来后通常要按某个查询属性进行排序，然后迭代求解 它类似一个小技巧，很难总结出一些模板，或者方法论，所以下面给出一些例题帮助理解： [GZOI2017] 配对统计 问题可以抽象成，给你一堆二元组[x,y],x\u0026lt;y[x,y],x\u0026lt;y[x,y],x\u0026lt;y(原题通过排序可以轻松求出所有符合要求的数对)，你需要在每次区间[l,r][l,r][l,r]查询后，给出满足l≤x,y≤rl\\le x,y\\le rl≤x,y≤r的数对的个数\n由于是区间查询，考虑用树状数组或线段树；在本题，用权值树状数组tr[i]tr[i]tr[i]维护左值x==ix==ix==i的数对个数；但是，树状数组只能保证维护左值在区间当中，不关心数对的右值 因此通过离线查询，对所有二元组、待查询区间的右值/右端点大小进行排序，因此查询区间的右端点是单调递增的；对每个查询，将所有右值不超过该查询区间右端点的数对插入到树状数组中 这样，树状数组中所有数对对应的右值都不会超过当前查询的右端点rrr 那么如何使数对的左值处于当前查询区间中呢？通过权值树状数组查询[l,r][l,r][l,r]元素和即可(即左值在[l,r][l,r][l,r]区间内的数对个数)；同时由于y\u0026gt;xy\u0026gt;xy\u0026gt;x，因此也满足了使数对的右值不小于左端点lll c\u0026#43;\u0026#43;#define lbit(x) (x - (x \u0026amp; (x - 1))) #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int n, m, tr[300006], cnt; long long ans; void add(int x) { while (x \u0026lt;= n) { ++tr[x]; x += lbit(x); } } int query(int x) { int ans = 0; while (x) { ans += tr[x]; x -= lbit(x); } return ans; } int query(int l, int r) { return query(r) - query(l - 1); } struct Info{ int a, id; bool operator\u0026lt;(const Info\u0026amp; oth) const { return a \u0026lt; oth.a; } }a[300006]; struct Pair{ int l, r, id; bool operator\u0026lt;(const Pair\u0026amp; oth) const { return r \u0026lt; oth.r; } }p[300006], goodp[300006 \u0026lt;\u0026lt; 1]; int main() { cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; if (n == 1) { cout \u0026lt;\u0026lt; 0; return 0; } for (int i = 0; i \u0026lt; n; ++i) { cin \u0026gt;\u0026gt; a[i].a; a[i].id = i + 1; } sort(a, a + n); goodp[cnt++] = {min(a[0].id, a[1].id), max(a[0].id, a[1].id)}; goodp[cnt++] = {min(a[n - 1].id, a[n - 2].id), max(a[n - 1].id, a[n - 2].id)}; for (int i = 1; i \u0026lt; n - 1; ++i) { if (abs(a[i].a - a[i - 1].a) == abs(a[i].a - a[i + 1].a)) { goodp[cnt++] = {min(a[i].id, a[i + 1].id), max(a[i].id, a[i + 1].id)}; goodp[cnt++] = {min(a[i].id, a[i - 1].id), max(a[i].id, a[i - 1].id)}; } else if (abs(a[i].a - a[i - 1].a) \u0026lt; abs(a[i].a - a[i + 1].a)) { goodp[cnt++] = {min(a[i].id, a[i - 1].id), max(a[i].id, a[i - 1].id)}; } else { goodp[cnt++] = {min(a[i].id, a[i + 1].id), max(a[i].id, a[i + 1].id)}; } } sort(goodp, goodp + cnt); for (int i = 0; i \u0026lt; m; ++i) { cin \u0026gt;\u0026gt; p[i].l \u0026gt;\u0026gt; p[i].r; p[i].id = i + 1; } sort(p, p + m); int j = 0; for (int i = 0; i \u0026lt; m; ++i) { while (j \u0026lt; cnt \u0026amp;\u0026amp; goodp[j].r \u0026lt;= p[i].r) { add(goodp[j].l); ++j; } ans += 1ll * query(p[i].l, p[i].r) * p[i].id; } cout \u0026lt;\u0026lt; ans; } ","permalink":"https://echo.ishian.me/2024/11/26/algo-trick-offline-query/","summary":"介绍离线查询技巧的思想，并结合例题讲解排序后配合树状数组的求解方式","title":"算法竞赛: 离线查询"},{"content":"哈希表 概述 在FTP与HTTPS中，粗略介绍了哈希算法加密数据的概念；它用哈希算法将无限的数据转换成唯一的、用数字描述的值；对一个合格的哈希函数而言：\n哈希函数尽可能是单射，但哈希冲突不可避免 哈希函数不能是双射，即不可逆 在哈希表这种数据结构中，利用了哈希函数的转换，使传入的每一个键都对应一个唯一的值，便可以通过键快速地查找到数组中的存储的数据，它的应用场景很多：\n检索：键可以是多样的，例如纯数字的学号、纯字符串的姓名等，在开发中不能让用户通过数组下标来访问数据，就需要哈希表；在程序上的哈希表结构使用的算法一般较简单，不追求加密功能 文件加密：目前有特定的哈希算法，例如MD5,SHA-256等，算法复杂，但能生成更具有唯一性的摘要 它的特点有：\n哈希表的优势在于，只要不发生哈希冲突，就可以靠快速的哈希函数，取得映射的下标，直接访问地址，达到查找、插入、删除的复杂度都为O(1) 然而任何哈希函数都可能造成哈希冲突，即传入不同数据却生成相同的值 哈希表是通过数组实现的，因此需要大容量的数组，且数据量大于数组容量后，效率急速下降 通常需要将大范围的键值缩小映射到小范围内 实现哈希算法 对键进行hash的过程，相当于给数组的整型索引添加更高级的信息，使索引不再是单纯的数字\n设计原则 唯一性：不同的键映射后的值尽可能不同 简单性：计算过程应尽可能简单 均匀性：不同的键映射后的值应均匀分布在数组中 算法分类 哈希算法需要对输入数据有足够的敏感度，在许多做法中，通常会选择将输入的所有数据进行充分混合\n一般来说，哈希算法有以下类型的实现：\n位运算(常用移位和异或，用于混合整数的部分信息) 加法、乘法、除法(通常选择一个素数，例如(prime * x)%size对x进行哈希) 取模(通常是必要的) 整数键 这种情况很少，也较为简单，通常为了节省空间，不会采用直接取址，而是混合位运算、乘法、素数、模运算来实现，例如：\nc\u0026#43;\u0026#43;size_t hash(int _val, size_t _scope) { return (((size_t(_val) \u0026gt;\u0026gt; 16) ^ _val) * 0x5F58E29) % _scope; }其中，0x5f58e29是任取的素数，_scope是映射的范围\n位运算的选择(右移和异或搭配)原理：\n一般来说，预设的存储空间相对于无穷的数据来说，是较小的一方；因此在直接取模法中，高位的信息将丢失，哈希结果出现聚集现象(例如存储空间为5，11%5=21%5=1，十位的数据未参与哈希运算) 为了使高位信息参与运算，采用右移运算符 异或运算的结果集中，1和0是平均分配的(分别占一半)，比其它位运算更能保证高低位信息均匀混合 指针类型的数据实际上以十六进制整数存储，可以通过整数的逻辑设计哈希函数，只需要在计算时通过reinterpret_cast\u0026lt;size_t\u0026gt;(重新解释转换，较底层，有风险)强制类型转换即可\n字符串键 字符串所带信息更多，例如长度、下标、字符本身，每个字符都是一个整数\n最简单的字符串哈希：\nc\u0026#43;\u0026#43;size_t hash(char* _val, size_t _scope) { size_t ans = 0; for (int i = 0; i \u0026lt; strlen(_val); ++i) { ans += (i + 1) * _scope[i]; // 混合了下标和字符. } return ans % _scope; }经典的字符串哈希算法：\nBKDR Hash(Java采用)：将字符串视为P进制数，转换成十进制后取模即可\nc\u0026#43;\u0026#43;const int P = 131; // 通常取13/31/131/13131..., 实验证明它们最合适. size_t hash(char* _val, size_t _scope) { size_t ans = 0; for (int i = 0; i \u0026lt; strlen(_val); ++i) { // 采用从高位到低位的转换方式, 运算更快. ans = ans * P + _val[i]; // 隐含自然溢出,即超出ull范围后自动取模 } return ans % _scope; } 通过位运算进行哈希：RS、AP、FNV等算法\n在算法竞赛中，单哈希也许是不够的，常见的解决办法是进行双哈希(分别有不同的P和模数)，组成序偶\nBKDR算法还能快速地得到子串的哈希值(需要存储每一步的计算结果)：\nc\u0026#43;\u0026#43;size_t hash(char* _val, size_t _scope, size_t _left, size_t _right) { size_t hashCode[_right + 1] {0}; for (int i = 1; i \u0026lt; _right + 1; ++i) { hashCode[i] = hashCode[i - 1] * P + _val[i]; } // 相减后可能是负数, 加上一周进行补偿. return (hashCode[_right] - hashCode[_left - 1] * power(P, _right - _left + 1) + _scope) % _scope; }浮点数键 自定义键 在C++中，默认提供的哈希函数只有以上几种，如果希望能在unordered_map等std的哈希结构中使用自定义类型，那么该类型应满足：\n实现哈希函数hash() 实现重载运算符==(便于处理哈希冲突) 实现哈希函数并不难，通常使能进行哈希运算的成员数据的哈希结果互相进行位运算而得，依靠std中的哈希结构解决哈希冲突\n处理哈希冲突 ","permalink":"https://echo.ishian.me/2024/11/16/algo-hashmap/","summary":"介绍哈希函数的设计原则、常见哈希算法及哈希表的原理、冲突处理与实现","title":"数据结构: 哈希表"},{"content":"量子物理 旧量子论 黑体辐射 热辐射是由温度决定的电磁辐射\n任何物体、在任何温度下都有热辐射，且波长范围由零连续延伸到无穷大\n物体辐射本领越大、吸收本领越大\n辐射和吸收达到平衡时，物体温度不变\n单色辐射出射度(单色辐出度)：\nMλ(T)=dMλdλ\\begin{align}\u0026amp;M_\\lambda(T)=\\frac{\\mathrm dM_\\lambda}{\\mathrm d\\lambda}\\end{align}​Mλ​(T)=dλdMλ​​​​，表示一定温度下，物体表面单位面积、单位时间内、发出的辐射能对电磁波波长的一阶导；单位J⋅m−3⋅s−1\\rm J·m^{-3}·s^{-1}J⋅m−3⋅s−1\n辐出度：\nM(T)=∫0∞Mλ(T)dλ\\begin{align}M(T)=\\int_0^{\\infty}M_\\lambda(T)\\mathrm d\\lambda\\end{align}M(T)=∫0∞​Mλ​(T)dλ​​，表示一定温度下，物体表面单位面积单位时间内的总辐射能\n能表示一个物体的辐射本领，单位J⋅m−2⋅s−1\\rm J·m^{-2}·s^{-1}J⋅m−2⋅s−1\n辐出度和温度、材料有关 单色吸收比：\naλ(T)\\begin{align}a_\\lambda(T)\\end{align}aλ​(T)​​，能表示一个物体的吸收本领，其值为吸收辐射能和全部入射能量的比值\n黑体：单色吸收比为一的物体\n基尔霍夫定律：在同一温度下，各种材料的单色辐出度和单色吸收比的比值相等\n因此，研究各种材料的辐射吸收本领等价于研究黑体的辐射本领\n黑体不反射、不透射，能将入射光全部吸收\n黑体模型：不透明介质空腔上的小孔，小孔表面是黑体\n斯特藩——玻尔兹曼定律：黑体辐出度MB(T)=σT4M_B(T)=\\sigma T^4MB​(T)=σT4，其中σ=5.67×10−8W⋅m−2⋅K−4\\sigma=5.67\\times10^{-8}{\\rm W·m^{-2}·K^{-4}}σ=5.67×10−8W⋅m−2⋅K−4\n维恩位移定律：单色辐出度最大时对应波长\nλm=bT\\begin{align}\\lambda_m=\\frac bT\\end{align}λm​=Tb​​​，其中b=2.897×10−3m⋅Kb=2.897\\times10^{-3}{\\rm m·K}b=2.897×10−3m⋅K\n普朗克黑体辐射公式：\nMv(T)=2πhc2v3ehvkT−1\\begin{align}M_v(T)=\\frac{2\\pi h}{c^2}\\frac{v^3}{e^{\\large\\frac{hv}{kT}}-1}\\end{align}Mv​(T)=c22πh​ekThv​−1v3​​​，其中普朗克常量h=6.626×10−34J⋅sh=6.626\\times10^{-34}{\\rm J·s}h=6.626×10−34J⋅s\n普朗克能量子假说：辐射体由带电谐振子组成，频率为vvv的谐振子最小能量为能量子ε=hv\\varepsilon=hvε=hv，所有谐振子只能吸收或辐射ε\\varepsilonε的整数倍\n光电效应 以是否有光电子溢出作为光电效应现象是否发生的标准\n爱因斯坦光电效应方程：光子能量hv=Em+Ahv=E_m+Ahv=Em​+A，EmE_mEm​为光电子最大初动能，AAA为金属逸出功\n红线频率：hv0=Ahv_0=Ahv0​=A，v0v_0v0​是能让光电子溢出的最小频率\n遏止电压：\neU=Em=hv−A\\begin{align}eU=E_m=hv-A\\end{align}eU=Em​=hv−A​​，UUU是能使动能最大的光电子也无法到达阳极的反向电压\n饱和光电流：和单位时间内入射光子数NNN相关，由光强I=NhvI=NhvI=Nhv\n当入射光频率一定时，饱和光电流才和光强成正比\n康普顿效应 散射光的特点：出现新的、波长更长的散射光，随散射角增大而增大 光子理论：光子和核外电子弹性碰撞 和外层电子相碰撞，光子将部分能量传给电子，使散射光波长变长 ","permalink":"https://echo.ishian.me/2024/11/15/physics-quantum/","summary":"整理量子物理：黑体辐射、光电效应、物质波与薛定谔方程等基础概念","title":"大学物理: 量子力学"},{"content":"MATLAB基本语法及矩阵算术操作 基本语法 数据类型 创建变量无需显式声明数据类型，会自动隐式推断 数据类型分为： 数值：整型、长整型、单双精度、复数等 数组：索引从1开始 结构体数组：类似C的结构体 向量与矩阵：信息的基本单位，用于存储数值数据 字符串：实际上是字符型向量 元胞数组(cell array)：每个元胞允许存储不同类型、不同维度的数据，例如变量、数值、字符串等 自定义类 其它，如布尔、字符、函数指针、内置类等 约定：数组将泛指所有数组类型，涉及区分矩阵、元胞数组和结构体数组时不使用数组称呼 运算符 加减乘幂(+-*^)：矩阵的加减乘幂 左除(\\)、右除(/)：矩阵的除法(或矩阵方程的求解) X\\Y返回X−1YX^{-1}YX−1Y，X/Y返回XY−1XY^{-1}XY−1 即左除为左操作数的逆左乘右操作数，右除为右操作数的逆右乘左操作数 单个数被视作一行一列的矩阵，一定可逆且逆等于其倒数，数的右除即符合习惯的数值除法 前接点运算符(.* .^ .\\ ./)：对同型矩阵按元素求乘、幂、左除、右除 方括号([])：创建向量或矩阵 百分号(%)：后接注释 关系运算： 大多运算符与C相同，其中不等号为~=，均为对同型矩阵按元素比较，返回布尔矩阵 isequal：对整体比较，返回布尔值，且认为NaN != NaN isequaln：认为NaN == NaN 逻辑运算：与C不同，单个\u0026amp;、|表示逻辑与和逻辑或 按位运算：位运算都通过函数进行，函数名通常为**bit + 位运算**的形式 特殊变量、常量 对大小写敏感 ans：最近计算过的表达式的结果 Inf：数值量，无穷 NaN：数值量，not a num pi：数值量，π\\piπ i、j：数值量，−1\\sqrt{-1}−1​ 条件语句 条件表达式将隐式调用logical()，不需要用括号包围条件表达式 if、elseif、else switch、case、otherwise：otherwise对应default，在匹配到唯一的case并执行对应语句块后，立刻跳出switch块 for、while、continue、break：break专用于循环语句，不属于switch语句 end：所有if、for、while等条件语句都应匹配一个end，用于终止块 end在数组变量的括号内时，表示数组结尾的索引 为增加可读性，函数一般也添加end表示终止 常用命令或函数 MATLAB是脚本语言，所有脚本语句都可在命令行运行，语句分为表达式和命令，常用命令如下：\n脚本数据相关 who：返回多行一列的元胞数组，储存所有定义过的变量，单独使用时作为一条命令而不是表达式执行 whos：返回结构体数组，存储更多信息如长度、所占字节、类型等，单独使用时作为一条命令执行 format：改变精度，默认format为short，默认精度为4位小数，只能作为命令 format long：显示16位小数 format bank：四舍五入后显示2位小数 format short/default：改为默认精度 format rat(ional)：返回最接近的形如两个整数除法的表达式 clc：清屏，只能作为命令 clear：删除所有变量，只能作为命令 global：声明全局变量 输入输出相关 disp(arr|str)：显示数组或字符串 input(\u0026quot;提示\u0026quot;,format)：输出提示，等待输入，返回输入 ;：取消打印 向量及矩阵 方括号[]用于创建向量，其中若使用空格或逗号作分隔即为行向量，使用分号作分隔即为列向量，向量为一维数组\n仍用方括号创建矩阵，用空格或逗号区分列，用分号区分行，要求不同行向量的列数一致，矩阵为二维数组\nmatlab[1 2 3; 4 5 6] 1 2 3 4 5 6 特殊矩阵创建：\nrand(m, n)：创建m×nm\\times nm×n随机数矩阵，每个元素介于0和1之间 ones|zeros：单独时为一阶值为1|0的矩阵，参与运算时自动计算行列数 ones(m, n)|zeros(m,n)：全为1|0的m×nm\\times nm×n矩阵 eye：单独时为一阶单位矩阵，参与运算时将被视为ones eye(n)：n阶单位矩阵 eye(n,m)：m列的标准型矩阵 diag(x,n=0)：必须提供参数，其中x为指定的对角元素向量，n为对角线编号，{n\u0026gt;0主对角线向上数第n条n=0主对角线n\u0026lt;0主对角线向下数第∣n∣条\\begin{cases}n\u0026gt;0\u0026amp;主对角线向上数第n条\\\\n=0\u0026amp;主对角线\\\\n\u0026lt;0\u0026amp;主对角线向下数第|n|条\\end{cases}⎩⎨⎧​n\u0026gt;0n=0n\u0026lt;0​主对角线向上数第n条主对角线主对角线向下数第∣n∣条​，返回**size(M)+n阶**的三角型矩阵 diag(A,n=0)：必须提供参数，其中A为指定矩阵，返回由该矩阵的第n条对角线元素构成的列向量 magic(n)：n\u0026gt;2时产出魔方矩阵，即用1∼n21\\sim n^21∼n2生成所有行和、列和相等的矩阵 批量创建向量或矩阵：\n冒号(:)表达式： 创建的矩阵占有新的内存，而抽取出的矩阵相当于引用 Begin:End-\u0026gt;创建从Begin到End的等距行向量，距离为1 Begin:dist:End-\u0026gt;创建从Begin到End的等距行向量，距离为dist 数组(代表行索引的向量)：抽取第一列的部分元素 数组(代表行索引的向量, 代表列索引的向量)：抽取由这些行列构成的子阵 数组(:,j)：抽取第j列所有元素，抽取行向量同理 数组(:)-\u0026gt;用所有元素构成一个列向量 linspace(Begin, End, count=100)：返回从Begin到End的具有指定个数的等距行向量 其它： 矩阵的算术运算：\ntranspose()：返回转置后的数组 inv()：如果可逆，返回逆 rank()：返回秩 det()：如果可求行列式，返回行列式 rref()：返回行最简型 poly()：返回由特征方程det⁡(λE−A)=0\\det(\\lambda E-A)=0det(λE−A)=0的系数构成的行向量 eig()： e=eig(A)：返回由特征值构成的列向量 [V,D]=eig(A)：返回对角为特征值的对角型矩阵D，且V满足AV=VD，即V的每列是右特征向量 [V,D,W]=eig(A)：额外返回每列为左特征向量的满矩阵W istriu()|istril()：判断矩阵是否为上三角或下三角型 ","permalink":"https://echo.ishian.me/2024/11/14/matlab-beginner/","summary":"介绍 MATLAB 基本语法、常用命令与向量矩阵算术操作","title":"MATLAB: 基本语法"},{"content":"二项式系数 定义：\n(x+y)n=∑k=0n(nk)xkyn−k\\begin{align}(x+y)^n=\\sum_{k=0}^n\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)x^ky^{n-k}\\end{align}(x+y)n=k=0∑n​(nk​)xkyn−k​​ 下指数一定为整数，当其为负数时，二项式系数为零，否则：\n当上指数为整数时， (nk)=n!k!(n−k)!\\begin{align}\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=\\frac{n!}{k!(n-k)!}\\end{align}(nk​)=k!(n−k)!n!​​​，具有组合意义 上指数可推广为实数，此时 (nk)=nk‾k!\\begin{align}\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=\\frac{n^{\\underline k}}{k!}\\end{align}(nk​)=k!nk​​​​，可视为nnn的kkk次多项式((1+r)k(1+r)^k(1+r)k泰勒展开的系数) 当上指数为正整数时：\n∑k=0n(nk)=2n\\begin{align}\\sum_{k=0}^n\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=2^n\\end{align}k=0∑n​(nk​)=2n​​、 ∑k=0n(−1)k(nk)=0\\begin{align}\\sum_{k=0}^n(-1)^k\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=0\\end{align}k=0∑n​(−1)k(nk​)=0​​，均可通过定义证明 下指数任意， (nk)=(nn−k)\\begin{align}\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=\\left(\\begin{matrix}n\\\\n-k\\end{matrix}\\right)\\end{align}(nk​)=(nn−k​)​​ 当上指数为任意实数时：\n下指数不为零时， (nk)=nk(n−1k−1)\\begin{align}\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=\\frac nk\\left(\\begin{matrix}n-1\\\\k-1\\end{matrix}\\right)\\end{align}(nk​)=kn​(n−1k−1​)​​ (nk)=(n−1k)+(n−1k−1)\\begin{align}\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)=\\left(\\begin{matrix}n-1\\\\k\\end{matrix}\\right)+\\left(\\begin{matrix}n-1\\\\k-1\\end{matrix}\\right)\\end{align}(nk​)=(n−1k​)+(n−1k−1​)​​ 特殊值：\n(n0)=1\\left(\\begin{matrix}n\\\\0\\end{matrix}\\right)=1(n0​)=1 (n1)=n\\left(\\begin{matrix}n\\\\1\\end{matrix}\\right)=n(n1​)=n (nn)=1\\left(\\begin{matrix}n\\\\n\\end{matrix}\\right)=1(nn​)=1 序列构造母函数：\u0026lt;a0,⋯ ,an\u0026gt;\u0026lt;a_0,\\cdots,a_n\u0026gt;\u0026lt;a0​,⋯,an​\u0026gt;的母函数为A(x)=a0+a1x+a2x2+⋯+anxnA(x)=a_0+a_1x+a_2x^2+\\cdots+a_nx^nA(x)=a0​+a1​x+a2​x2+⋯+an​xn\n母函数乘积的系数：若C(x)=A(x)B(x)C(x)=A(x)B(x)C(x)=A(x)B(x)，则\ncn=∑k=0nakbn−k\\begin{align}c_n=\\sum_{k=0}^na_kb_{n-k}\\end{align}cn​=k=0∑n​ak​bn−k​​​ 证明范德蒙德卷积：由(1+x)a+b=(1+x)a(1+x)b(1+x)^{a+b}=(1+x)^a(1+x)^b(1+x)a+b=(1+x)a(1+x)b，设为C(x)=A(x)B(x)C(x)=A(x)B(x)C(x)=A(x)B(x) 由\nC(x)=∑k=0n(a+bk)xkA(x)=∑k=0n(ak)xkB(x)=∑k=0n(bk)xk\\begin{align}\u0026amp;C(x)=\\sum_{k=0}^n\\left(\\begin{matrix}a+b\\\\k\\end{matrix}\\right)x^k\\\\\u0026amp;A(x)=\\sum_{k=0}^n\\left(\\begin{matrix}a\\\\k\\end{matrix}\\right)x^k\\\\\u0026amp;B(x)=\\sum_{k=0}^n\\left(\\begin{matrix}b\\\\k\\end{matrix}\\right)x^k\\end{align}​C(x)=k=0∑n​(a+bk​)xkA(x)=k=0∑n​(ak​)xkB(x)=k=0∑n​(bk​)xk​​得\ncn=(a+bn)=∑k=0n(ak)(bn−k)\\begin{align}c_n=\\left(\\begin{matrix}a+b\\\\n\\end{matrix}\\right)=\\sum_{k=0}^n\\left(\\begin{matrix}a\\\\k\\end{matrix}\\right)\\left(\\begin{matrix}b\\\\n-k\\end{matrix}\\right)\\end{align}cn​=(a+bn​)=k=0∑n​(ak​)(bn−k​)​​ 常见母函数：\n(1+x)n=∑k=0n(nk)xk\\begin{align}(1+x)^n=\\sum_{k=0}^n\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)x^k\\end{align}(1+x)n=k=0∑n​(nk​)xk​​ 11−x=∑k≥0xk、11+x=∑k≥0(−1)kxk\\begin{align}\\frac1{1-x}=\\sum_{k\\ge0}x^k、\\frac1{1+x}=\\sum_{k\\ge0}(-1)^kx^k\\end{align}1−x1​=k≥0∑​xk、1+x1​=k≥0∑​(−1)kxk​​ 1(1−x)n+1=∑k≥0(n+kk)xk\\begin{align}\\frac1{(1-x)^{n+1}}=\\sum_{k\\ge0}\\left(\\begin{matrix}n+k\\\\k\\end{matrix}\\right)x^k\\end{align}(1−x)n+11​=k≥0∑​(n+kk​)xk​​ xn(1−x)n+1=∑k≥0(kn)xk\\begin{align}\\frac{x^n}{(1-x)^{n+1}}=\\sum_{k\\ge0}\\left(\\begin{matrix}k\\\\n\\end{matrix}\\right)x^k\\end{align}(1−x)n+1xn​=k≥0∑​(kn​)xk​​，通过上式移项换元得到 第二类斯特林数 定义： xn=∑k{nk}xk‾\\begin{align}x^{n}=\\sum_{k}\\left\\{\\begin{matrix}n\\\\k\\end{matrix}\\right\\}x^{\\underline k}\\end{align}xn=k∑​{nk​}xk​​​，组合意义为将nnn个元素分为kkk个无区别的非空集合 递推式： {nk}=k{n−1k}+{n−1k−1}\\begin{align}\\left\\{\\begin{matrix}n\\\\k\\end{matrix}\\right\\}=k\\left\\{\\begin{matrix}n-1\\\\k\\end{matrix}\\right\\}+\\left\\{\\begin{matrix}n-1\\\\k-1\\end{matrix}\\right\\}\\end{align}{nk​}=k{n−1k​}+{n−1k−1​}​​ 组合意义：将第nnn个元素分到kkk个原有盒子的方案为第一项，将它单独作为一个集合的方案数为第二项 特殊值： {n0}={1,n=00,n≠0\\left\\{\\begin{matrix}n\\\\0\\end{matrix}\\right\\}=\\begin{cases}1,\u0026amp;n=0\\\\0,\u0026amp;n\\ne0\\end{cases}{n0​}={1,0,​n=0n=0​ {n1}=1\\left\\{\\begin{matrix}n\\\\1\\end{matrix}\\right\\}=1{n1​}=1 {n2}=∑k=1n−1(nk)2=2n−1−1\\begin{align}\\left\\{\\begin{matrix}n\\\\2\\end{matrix}\\right\\}=\\frac{\\sum\\limits_{k=1}^{n-1}\\left(\\begin{matrix}n\\\\k\\end{matrix}\\right)}2=2^{n-1}-1\\end{align}{n2​}=2k=1∑n−1​(nk​)​=2n−1−1​​ {nn−1}=(n2)=n(n−1)2\\left\\{\\begin{matrix}n\\\\n-1\\end{matrix}\\right\\}=\\left(\\begin{matrix}n\\\\2\\end{matrix}\\right)=\\frac{n(n-1)}2{nn−1​}=(n2​)=2n(n−1)​ {nn}=1\\left\\{\\begin{matrix}n\\\\n\\end{matrix}\\right\\}=1{nn​}=1 第一类斯特林数 定义：xn‾=∑k≥0[nk]xkx^{\\overline n}=\\sum_{k\\ge0}\\left[\\begin{matrix}n\\\\k\\end{matrix}\\right]x^kxn=∑k≥0​[nk​]xk，组合意义为将nnn个元素分为kkk个轮换\n递推式：[nk]=(n−1)[n−1k]+[n−1k−1]\\left[\\begin{matrix}n\\\\k\\end{matrix}\\right]=(n-1)\\left[\\begin{matrix}n-1\\\\k\\end{matrix}\\right]+\\left[\\begin{matrix}n-1\\\\k-1\\end{matrix}\\right][nk​]=(n−1)[n−1k​]+[n−1k−1​]\n组合意义为将第nnn个元素插入到已有划分的n−1n-1n−1个位置上(第一项)，或作为单独一个轮换(第二项)\n特殊值：\n[n0]={0,n≠01,n=0\\left[\\begin{matrix}n\\\\0\\end{matrix}\\right]=\\begin{cases}0,\u0026amp;n\\ne0\\\\1,\u0026amp;n=0\\end{cases}[n0​]={0,1,​n=0n=0​ [n1]=(n−1)!\\left[\\begin{matrix}n\\\\1\\end{matrix}\\right]=(n-1)![n1​]=(n−1)! [nn−1]=(n2)=n(n−1)2\\left[\\begin{matrix}n\\\\n-1\\end{matrix}\\right]=\\left(\\begin{matrix}n\\\\2\\end{matrix}\\right)=\\frac{n(n-1)}2[nn−1​]=(n2​)=2n(n−1)​ [nn]=1\\left[\\begin{matrix}n\\\\n\\end{matrix}\\right]=1[nn​]=1 一般来说，第一类斯特林数大于第二类斯特林数，当k=0,n−1,nk=0,n-1,nk=0,n−1,n时两者相等 ∑k=0n[nk]=n!\\sum_{k=0}^n\\left[\\begin{matrix}n\\\\k\\end{matrix}\\right]=n!∑k=0n​[nk​]=n! 调和数 定义：\nHn=∑k=1n1k\\begin{align}H_n=\\sum_{k=1}^n\\frac1{k}\\end{align}Hn​=k=1∑n​k1​​​ 和第一类斯特林数关系：n!Hn=[n+12]n!H_n=\\left[\\begin{matrix}n+1\\\\2\\end{matrix}\\right]n!Hn​=[n+12​]\n证：\n[n+12]=n[n2]+(n−1)!⇒1n![n+12]=1(n−1)![n2]+1n,由递推式⇒1n![n+12]=Hn\\begin{align}\u0026amp;\\left[\\begin{matrix}n+1\\\\2\\end{matrix}\\right]=n\\left[\\begin{matrix}n\\\\2\\end{matrix}\\right]+(n-1)!\\\\\u0026amp;\\Rightarrow\\frac1{n!}\\left[\\begin{matrix}n+1\\\\2\\end{matrix}\\right]=\\frac1{(n-1)!}\\left[\\begin{matrix}n\\\\2\\end{matrix}\\right]+\\frac1n,由递推式\\\\\u0026amp;\\Rightarrow\\frac1{n!}\\left[\\begin{matrix}n+1\\\\2\\end{matrix}\\right]=H_n\\end{align}​[n+12​]=n[n2​]+(n−1)!⇒n!1​[n+12​]=(n−1)!1​[n2​]+n1​,由递推式⇒n!1​[n+12​]=Hn​​​ 当2k≤n\u0026lt;2k+1时,只需证明n=2k时Hn\u0026gt;⌊lg⁡n⌋+12且n=2k−1−1时Hn≤⌊lg⁡n⌋+1即可(1)当n=2k时,右式=k+12Hn=∑i=0k∑j=2i−12i1j\u0026gt;∑i=0k2j−12j\u0026gt;k+12(2)当n=2k+1−1时,右式=k+1Hn=∑i=0k∑j=i2i+1−11j≤∑i=0k\\begin{align}\u0026amp;当2^k\\le n\u0026lt;2^{k+1}时,只需证明n=2^k时H_n\u0026gt;\\frac{\\lfloor\\lg n\\rfloor+1}2\\\\\u0026amp;且n=2^{k-1}-1时H_n\\le\\lfloor\\lg n\\rfloor+1即可\\\\\u0026amp;(1)当n=2^k时,右式=\\frac{k+1}2\\\\\u0026amp;H_n=\\sum_{i=0}^k\\sum_{j=2^{i-1}}^{2^i}\\frac1j\u0026gt;\\sum_{i=0}^k\\frac{2^{j-1}}{2^j}\u0026gt;\\frac{k+1}2\\\\\u0026amp;(2)当n=2^{k+1}-1时,右式=k+1\\\\\u0026amp;H_n=\\sum_{i=0}^k\\sum_{j=i}^{2^{i+1}-1}\\frac1j\\le\\sum_{i=0}^k\\end{align}​当2k≤n\u0026lt;2k+1时,只需证明n=2k时Hn​\u0026gt;2⌊lgn⌋+1​且n=2k−1−1时Hn​≤⌊lgn⌋+1即可(1)当n=2k时,右式=2k+1​Hn​=i=0∑k​j=2i−1∑2i​j1​\u0026gt;i=0∑k​2j2j−1​\u0026gt;2k+1​(2)当n=2k+1−1时,右式=k+1Hn​=i=0∑k​j=i∑2i+1−1​j1​≤i=0∑k​​​","permalink":"https://echo.ishian.me/2024/11/01/concretemath-special-coefficient/","summary":"讲解二项式系数的性质、母函数运算及第二类斯特林数等特殊系数","title":"具体数学: 特殊系数"},{"content":"KMP算法 暴力算法的不足 假设主串为T，模式串为P，要找出第一个/共有多少个P子串，最简单的办法是暴力搜索\nc\u0026#43;\u0026#43;int indexOf(const string\u0026amp; t, const string\u0026amp; p) { int i = 0, j = 0, tl = t.length(), pl = p.length(); while (i \u0026lt; tl \u0026amp;\u0026amp; j \u0026lt; pl) { if (t[i] == p[j]) { ++i; ++j; } else { i = i - j + 1; j = 0; } } return j == pl ? i - j : -1; }容易算出，该回溯算法的时间复杂度为O(pl*tl)，算法慢在每次的回溯点为i-j+1，而事实上若能找到一个更合适的位置回溯，就能减少重复比较\n同一个串的前缀和后缀 定义一个字符串的前缀为s[0:i),i\u0026lt;end，即在末尾至少删除一个字符后剩下的子串\n后缀同理，为在开头至少删除一个字符后剩下的子串；规定两种子串不为空串\n例如字符串abcd的前缀集合为{a,ab,abc}，后缀集合为{d,cd,bcd}\n如果同一个串的某一前缀和某一后缀相匹配，就可以将前缀平移至后缀，如\nabcab‾ ab‾cab\\begin{align}\u0026amp;abc\\underline {ab}\\\\\u0026amp;\\ \\ \\ \\ \\ \\ \\underline {ab}cab\\end{align}​abcab​ ab​cab​​(前后缀ababab匹配)\nKMP算法的核心思想就是同一个串的前后缀匹配，实现忽略掉匹配失败部分的效果\n将上方看作主串T==\u0026quot;abcabeeee\u0026quot;的子串，下方看作模式串P==\u0026quot;abcabf\u0026quot;\n在暴力算法中，当遍历到\nabcabe‾eeeabcabf‾\\begin{align}\u0026amp;abcab\\underline eeee\\\\\u0026amp;abcab\\underline f\\end{align}​abcabe​eeeabcabf​​​的最后一个字符f时，需要回溯到\nab‾cabeeee a‾bcabf\\begin{align}\u0026amp;a\\underline bcabeeee\\\\\u0026amp;\\ \\ \\underline abcabf\\end{align}​ab​cabeeee a​bcabf​​但如果应用上前后缀匹配，已匹配串为abcababcababcab，前后缀ababab匹配，直接回溯到\nabcabe‾eee abc‾abf\\begin{align}\u0026amp;abc\\textcolor{red}{ab}\\underline eeee\\\\\u0026amp;\\ \\ \\ \\ \\ \\ \\textcolor{red}{ab}\\underline cabf\\end{align}​abcabe​eee abc​abf​​实际情况中，需要取最长公共前后缀(取最长，匹配失败时仍可以继续遍历较短的情况；而不取最长，就会漏掉最长的情况)\n假设提前知道了所有子串的最长公共前后缀的长度，可将暴力算法改写成：\nc\u0026#43;\u0026#43;int indexOf(const string\u0026amp; t, const string\u0026amp; p) { int i, j; while (i \u0026lt; tl \u0026amp;\u0026amp; j \u0026lt; pl) { if (t[i] == p[j]) { ++i; ++j; } else { if (j == 0) { // j回溯到0, 且和i不匹配, 则i肯定不是答案 ++i; } j = 子串的最长公共前后缀的长度; // 直接回溯到最长前缀子串的后一个字符 } } return j == pl ? i - j : -1; }该过程时间复杂度为O(tl)\n求出每个前缀子串的最长公共前后缀长 要使用上述算法，需要提前知道所有子串p[0:j)的最长公共前后缀长，即预处理\n这里的子串刚好是模式串的前缀子串，共有pl个(长度从零到pl-1)，故用next[pl]数组存储\nKMP算法的一大妙处为快速求出子串的公共前后缀长，实现O(pl)的预处理时间\n核心为模式串的子串自己和自己匹配，对于子串s=p[0:j)，新主串为s[end-k:end)，新模式串为s[0,k)\n这里用到动态规划思想(KMP算法的精髓)，回溯时要用到已求的最长公共前后缀长\n基础情况为next[0]=next[1]=0(长度为0和1的字符串，没有前后缀子串，故长度为零)\nc\u0026#43;\u0026#43;int indexOf(const string\u0026amp; t, const string\u0026amp; p) { ... int next[pl] {};// 除next[0,1]==0外, 其它也置为0, 没有公共前后缀时就不需要修改 i = 1; j = 0; while (i \u0026lt; pl - 1) { // 遍历剩下的前缀子串, 因为j一定小于i, 循环条件简化 if (p[i] == p[j]) { next[++i] = ++j; // 数组索引为子串长度, 值为该子串的最长公共前后缀的长度 } else { if (j == 0) { ++i; } j = next[j]; // 回溯到已求出的最长公共前后缀长 } } ... }这里为了代码简洁，将下标和长度混用了\n事实上整部分就是主算法的翻版，只不过需要存储最长公共前后缀长、主串偏移一个字符，可以说会写主算法就会写预处理\n最终代码 c\u0026#43;\u0026#43;int indexOf(const string\u0026amp; t, const string\u0026amp; p) { static int next[10000]; // 实际为了更快，不采用变长数组而是静态定长数组 int i = 1, j = 0, tl = t.length(), pl = p.length(); while (i \u0026lt; pl - 1) {// 如果需要算出p自身的最长前后缀长, 这里不用减1, 然而该算法不需要该信息 if (p[i] == p[j]) { next[++i] = ++j; } else { if (j == 0) { next[++i] = 0; // 采用静态数组, 因此需手动置0 } j = next[j]; } } i = j = 0; while (i \u0026lt; tl \u0026amp;\u0026amp; j \u0026lt; pl) { if (t[i] == p[j]) { ++i; ++j; } else { if (j == 0) { ++i; } j = next[j]; } // if (j == pl) { // 如果需要求出所有匹配成功的p串, 在j满时j回溯 // // 回溯前记录i - j // j = next[j]; // while条件可删去j \u0026lt; pl // } } return j == pl ? i - j : -1; }","permalink":"https://echo.ishian.me/2024/10/23/algo-string-kmpmatch/","summary":"从暴力匹配的不足出发，讲解KMP算法的前后缀原理与next数组的求法应用","title":"字符串算法: KMP匹配"},{"content":"狭义相对论 经典力学伽利略变换 在经典力学中，首先认为时间、质量、作用力是绝对的 在伽利略变换中，假定惯性系S′S\u0026#x27;S′相对于另一个惯性系SSS以uuu速度匀速直线运动，且设运动方向为xxx方向、两惯性系在初始时刻原点在同一位置 则有伽利略(逆)变换：{x=x′+ut′y=y′z=z′t=t′\\begin{cases}x=x\u0026#x27;+ut\u0026#x27;\\\\y=y\u0026#x27;\\\\z=z\u0026#x27;\\\\t=t\u0026#x27;\\end{cases}⎩⎨⎧​x=x′+ut′y=y′z=z′t=t′​ 推广到牛顿运动定律，v=(vx′+u, vy′, vz′)=v′+ui\\boldsymbol v=(v_x\u0026#x27;+u,\\ v_y\u0026#x27;,\\ v_z\u0026#x27;)=\\boldsymbol v\u0026#x27;+u\\boldsymbol iv=(vx′​+u, vy′​, vz′​)=v′+ui，a=(ax′,ay′,az′)=a′\\boldsymbol a=(a_x\u0026#x27;,a_y\u0026#x27;,a_z\u0026#x27;)=\\boldsymbol a\u0026#x27;a=(ax′​,ay′​,az′​)=a′，即一切力学定律在任何惯性系中形式相同 依据上述变换，在绝对时空观中，空间间隔、时间间隔、时间、质量、一切力学定律都是绝对的，而速度是相对的 相对论实验基础 首先，依据电磁学理论的广泛应用，证明真空中的光速\nc=1ε0μ0\\begin{align}c=\\frac1{\\sqrt{\\varepsilon_0\\mu_0}}\\end{align}c=ε0​μ0​​1​​​，和伽利略变换中速度的相对性不符\n迈克尔逊莫雷干涉实验：其本意为了验证绝对静止的惯性系——以太的存在，结果证明以太是不存在的\n即任何惯性系都是等价的，不存在任何一个惯性系优于其它惯性系\n实验内容： 洛伦兹变换 无论是经典力学还是狭义相对论，讨论的时空坐标变换都是关于惯性系的\n两条基本假设：\n爱因斯坦相对性原理：一切物理规律在所有的惯性系中形式相同\n对照伽利略变换中的结论，可知相对性原理是其推广而不是其简单否定 光速不变原理：在任何惯性系中，真空中的光速不变；即光速和光源的运动状态无关\n光速不变原理可视为一种物理规律，而经典力学中质量绝对等观念并不是物理规律\n另一表述：真空中的光速率是一切物体运动速率的极限\n注意是物体的速率，而非相对速率，相对速率可超过ccc\n一切假设与伽利略变换相同，则有洛伦兹变换：{x=x′+ut′1−(uc)2y=y′z=z′t=t′+uc2x′1−(uc)2\\begin{cases}x=\\frac{\\large x\u0026#x27;+ut\u0026#x27;}{\\sqrt{1-\\left(\\Large\\frac uc\\right)^2}}\\\\y=y\u0026#x27;\\\\z=z\u0026#x27;\\\\t=\\frac{\\large t\u0026#x27;+{\\Large\\frac u{c\\normalsize ^2}}x\u0026#x27;}{\\sqrt{1-\\left(\\Large\\frac uc\\right)^2}}\\end{cases}⎩⎨⎧​x=1−(cu​)2​x′+ut′​y=y′z=z′t=1−(cu​)2​t′+c2u​x′​​\n推导：根据相对论假设，{x=k′(x′+ut′)x′=k(x−ut)k=k′\\begin{cases}x=k\u0026#x27;(x\u0026#x27;+ut\u0026#x27;)\\\\x\u0026#x27;=k(x-ut)\\\\k=k\u0026#x27;\\end{cases}⎩⎨⎧​x=k′(x′+ut′)x′=k(x−ut)k=k′​，且在初始时刻x=ct,x′=ct′x=ct,x\u0026#x27;=ct\u0026#x27;x=ct,x′=ct′ 故xx′=k2(x′+ut′)(x−ut)=k2(ct′+ut′)(ct−ut)=k2tt′(c+u)(c−u)=c2tt′xx\u0026#x27;=k^2(x\u0026#x27;+ut\u0026#x27;)(x-ut)=k^2(ct\u0026#x27;+ut\u0026#x27;)(ct-ut)=k^2tt\u0026#x27;(c+u)(c-u)=c^2tt\u0026#x27;xx′=k2(x′+ut′)(x−ut)=k2(ct′+ut′)(ct−ut)=k2tt′(c+u)(c−u)=c2tt′\n解得\nk=c2c2−u2=11−(uc)2\\begin{align}k=\\sqrt\\frac{c^2}{c^2-u^2}=\\frac1{\\sqrt{1-\\left({\\Large\\frac uc}\\right)^2}}\\end{align}k=c2−u2c2​​=1−(cu​)2​1​​​ 当u\u0026lt;\u0026lt;cu\u0026lt;\u0026lt;cu\u0026lt;\u0026lt;c时，洛伦兹变换将退化成伽利略变换，即日常生活中的低速惯性系\n在相对时空观中，时间与运动方向上的空间位置有关，时间、时间间隔、空间间隔、质量等都是相对的，而一切物理规律、光速是绝对的\n相对论效应 以下讨论中，取\nγ=11−(uc)2\\begin{align}\\gamma=\\frac1{\\sqrt{1-\\left(\\large\\frac{u}{c}\\right)^2}}\\end{align}γ=1−(cu​)2​1​​​ 相对论中的速度和相对速度的区别：\n沿惯性系相对运动方向上，给定\ndxdt=vx\\begin{align}\\frac{\\mathrm dx}{\\mathrm dt}=v_x\\end{align}dtdx​=vx​​​，则有\nvx′=dx′dt′=dx′dtdtdt′=γ2(vx−u)(1+uc2vx′)\\begin{align}v_x\u0026#x27;=\\frac{\\mathrm dx\u0026#x27;}{\\mathrm dt\u0026#x27;}=\\frac{\\mathrm dx\u0026#x27;}{\\mathrm dt}\\frac{\\mathrm dt}{\\mathrm dt\u0026#x27;}=\\gamma^2(v_x-u)\\left(1+\\frac u{c^2}v_x\u0026#x27;\\right)\\end{align}vx′​=dt′dx′​=dtdx′​dt′dt​=γ2(vx​−u)(1+c2u​vx′​)​​整理得\nvx′=γ2(vx−u)1−γ2u(vx−u)c2=vx−u1−uc2vx\\begin{align}v_x\u0026#x27;=\\frac{\\gamma^2(v_x-u)}{1-{\\large\\frac{\\gamma^2 u(v_x-u)}{c^2}}}=\\frac{v_x-u}{1-{\\large\\frac{u}{c^2}}v_x}\\end{align}vx′​=1−c2γ2u(vx​−u)​γ2(vx​−u)​=1−c2u​vx​vx​−u​​​逆变换同理\nvx=vx′+u1+uc2vx′\\begin{align}v_x=\\frac{v_x\u0026#x27;+u}{1+{\\large\\frac{u}{c^2}}v_x\u0026#x27;}\\end{align}vx​=1+c2u​vx′​vx′​+u​​​ 一：在任意惯性系中测量任意物体的绝对速率，不可能超过ccc 二：而相对速度是在同一惯性系中观测不同事件的速度差，只是简单的速度叠加，可能超过ccc\n因速度有相对性，相对速度自然也是相对的，在不同惯性系中观测会有不同\n讨论两个惯性系的相对运动速度uuu时，相当于物体一静止，故uuu等同于物体二的绝对速度，根据第一条，uuu是不可能超过ccc的(虽然它是相对速度)\n例如：两相对地面系SSS以速率u1,2≤cu_{1,2}\\le cu1,2​≤c相向运动的惯性系S1,2S_{1,2}S1,2​，讨论S1,2S_{1,2}S1,2​原点的运动速度：\n在S系中观测,S1系运动速度为u1,S2系运动速度为−u2,S2相对S1速度为u1+u2,绝对值可能超过c在S1系中观测,S2系运动速度为u2′=−u2−u11−(−u1)(−u2)c2=−(u1+u2)cc2−u1u2c,其绝对值不会超过c仍在S1系中观测,S1系运动速度为0,故S2相对S1速度就是u2′,其绝对值不会超过c\\begin{align}\u0026amp;在S系中观测,S_1系运动速度为u_1,S_2系运动速度为-u_2,S_2相对S_1速度为u_1+u_2,绝对值可能超过c\\\\\u0026amp;在S_1系中观测,S_2系运动速度为u_2\u0026#x27;=\\frac{-u_2-u_1}{1-\\frac{(-u_1)(-u_2)}{c^2}}=-\\frac{(u_1+u_2)c}{c^2-u_1u_2}c,其绝对值不会超过c\\\\\u0026amp;仍在S_1系中观测,S_1系运动速度为0,故S_2相对S_1速度就是u_2\u0026#x27;,其绝对值不会超过c\\end{align}​在S系中观测,S1​系运动速度为u1​,S2​系运动速度为−u2​,S2​相对S1​速度为u1​+u2​,绝对值可能超过c在S1​系中观测,S2​系运动速度为u2′​=1−c2(−u1​)(−u2​)​−u2​−u1​​=−c2−u1​u2​(u1​+u2​)c​c,其绝对值不会超过c仍在S1​系中观测,S1​系运动速度为0,故S2​相对S1​速度就是u2′​,其绝对值不会超过c​​ 同时性的相对性：在S′S\u0026#x27;S′系中，沿uuu方向上有相对位移的不同地同时发生事件时，在SSS系中观测，在S′S\u0026#x27;S′中沿uuu方向后方的事件先发生\n令Δk\\Delta kΔk为前方事件坐标减后方事件坐标，则有\n{Δt′=0Δx′\u0026gt;0,Δt=γ(Δt′+uc2Δx′)\u0026gt;0\\begin{align}\\begin{cases}\\Delta t\u0026#x27;=0\\\\\\Delta x\u0026#x27;\u0026gt;0\\end{cases},\\Delta t=\\gamma\\left(\\Delta t\u0026#x27;+\\frac{u}{c^2}\\Delta x\u0026#x27;\\right)\u0026gt;0\\end{align}{Δt′=0Δx′\u0026gt;0​,Δt=γ(Δt′+c2u​Δx′)\u0026gt;0​​，因此前方事件后发生，Δt\\Delta tΔt即两事件在SSS系中的时间间隔\n当两个事件沿uuu方向上没有相对位移时，同时性是绝对的\n因此，不同地同时事件在另一惯性系中可能同时，也可能不同时\n时间悖论问题：既然上述推导中，Δt\u0026gt;0而Δt′=0\\Delta t\u0026gt;0而\\Delta t\u0026#x27;=0Δt\u0026gt;0而Δt′=0，那么是否存在一种情况使Δt\u0026gt;0而Δt′\u0026lt;0\\Delta t\u0026gt;0而\\Delta t\u0026#x27;\u0026lt;0Δt\u0026gt;0而Δt′\u0026lt;0(即颠倒时序)？\n因为uuu和任意信号传递速度vvv无法超过光速，因此 Δt′=γ(Δt−uc2Δx)=γΔt(1−uc2ΔxΔt)=γΔt(1−uvc2)≥0\\begin{align}\\Delta t\u0026#x27;=\\gamma\\left(\\Delta t-\\frac u{c^2}\\Delta x\\right)=\\gamma\\Delta t\\left(1-\\frac u{c^2}\\frac{\\Delta x}{\\Delta t}\\right)=\\gamma\\Delta t\\left(1-\\frac{uv}{c^2}\\right)\\ge0\\end{align}Δt′=γ(Δt−c2u​Δx)=γΔt(1−c2u​ΔtΔx​)=γΔt(1−c2uv​)≥0​​ 时间间隔的相对性(时间膨胀效应)：\n称在SSS中相对SSS静止(同地)的事件在该系中测得的时间间隔为原时，它是最短的\n在任意其它相对SSS系匀速直线运动的惯性系中观测，时间间隔会变长，称为测时，因为Δt′=γ(Δt)\\Delta t\u0026#x27;=\\gamma(\\Delta t)Δt′=γ(Δt)，其中γ\u0026gt;1\\gamma\u0026gt;1γ\u0026gt;1\n注意原时的判断，考察的不是SSS系动或不动，而是该事件的空间坐标在SSS系中不变\n例如在飞船上的时钟在地面观测会变慢，其中原时为在飞船系中的时间，测时为地面系中测得时间\n在运动的惯性系中，长度会收缩：\n因为时间间隔本身具有相对性，因此测量长度时要求同时测量以保证只探讨空间间隔的相对性\n若待测量物体的各空间坐标在SSS系中保持静止，则称在该惯性系中测得的长度为原长，它是最长的\n在任意其它相对SSS系匀速直线运动的惯性系中同时测量，沿运动方向上的空间间隔会变短，称为测长，因为\nLx′=Lxγ=Lx1−(uc)2\\begin{align}L_x\u0026#x27;=\\frac{L_x}{\\gamma}=L_x\\sqrt{1-\\left(\\frac{u}{c}\\right)^2}\\end{align}Lx′​=γLx​​=Lx​1−(cu​)2​​​ 质速方程：\n在同一惯性系中观测时，运动中物体的质量比在静止时大，\nm=m01−v2c2\\begin{align}m=\\frac{m_0}{\\sqrt{1-\\large\\frac{v^2}{c^2}}}\\end{align}m=1−c2v2​​m0​​​​，其中vvv为物体的速度、m0m_0m0​为静止时的质量\n相对论的动量定理：\nF=d(mv)dt=mt′v+ma\\begin{align}\\boldsymbol{F}=\\frac{\\mathrm d(m\\boldsymbol{v})}{\\mathrm dt}=m_t\u0026#x27;\\boldsymbol{v}+m\\boldsymbol{a}\\end{align}F=dtd(mv)​=mt′​v+ma​​，在经典力学中，mmm为常数，第一项为000\n质速方程很好地阐明了为什么牛顿运动定律形式仍然相同而速率无法超过ccc\n所有物体的能量等于静止能量加上动能，E=Ek+E0E=E_k+E_0E=Ek​+E0​，且{E=mc2相对论中的总能量E0=m0c2静能Ek=Δmc2动能\\begin{cases}E=mc^2\u0026amp;相对论中的总能量\\\\E_0=m_0c^2\u0026amp;静能\\\\E_k=\\Delta mc^2\u0026amp;动能\\end{cases}⎩⎨⎧​E=mc2E0​=m0​c2Ek​=Δmc2​相对论中的总能量静能动能​\n推导： dA=F⋅dr=dpdt⋅dr=v⋅dpdp=d(mv)=vdm+mdv因此dA=v⋅vdm+mv⋅dv=v2dm+m(∑i=x,y,zvidvi)=v2dm+m(∑i=x,y,zd(vi22))=v2dm+mvdv由质速方程m=m01−(vc)2⇒m2c2−m2v2=m02c2两边求全微分得2mc2dm−(2mv2dm+2m2vdv)=0代回原式得dA=c2dm动能定义Ek=A=∫12dA=∫m0mc2dm=Δmc2=mc2−m0c2\\begin{align}\u0026amp;\\mathrm dA=\\boldsymbol F·\\mathrm d\\boldsymbol r=\\frac{\\mathrm d\\boldsymbol p}{\\mathrm dt}·\\mathrm d\\boldsymbol r=\\boldsymbol v·\\mathrm d\\boldsymbol p\\\\\u0026amp;\\mathrm d\\boldsymbol p=\\mathrm d(m\\boldsymbol v)=\\boldsymbol v\\mathrm dm+m\\mathrm d\\boldsymbol v\\\\\u0026amp;因此\\mathrm dA=\\boldsymbol{v·v}\\mathrm dm+m\\boldsymbol {v·}\\mathrm d\\boldsymbol v=v^2\\mathrm dm+m\\left(\\sum_{i=x,y,z}v_i\\mathrm dv_i\\right)\\\\\u0026amp;=v^2\\mathrm dm+m\\left(\\sum_{i=x,y,z}\\mathrm d\\left(\\frac{v_i^2}{2}\\right)\\right)=v^2\\mathrm dm+mv\\mathrm dv\\\\\u0026amp;由质速方程m=\\frac{m_0}{\\sqrt{1-\\left(\\large\\frac{v}{c}\\right)^2}}\\Rightarrow m^2c^2-m^2v^2=m_0^2c^2\\\\\u0026amp;两边求全微分得2mc^2\\mathrm dm-(2mv^2\\mathrm dm+2m^2v\\mathrm dv)=0\\\\\u0026amp;代回原式得\\mathrm dA=c^2\\mathrm dm\\\\\u0026amp;动能定义E_k=A=\\int_1^2\\mathrm dA=\\int_{m_0}^mc^2\\mathrm dm=\\Delta mc^2=mc^2-m_0c^2\\end{align}​dA=F⋅dr=dtdp​⋅dr=v⋅dpdp=d(mv)=vdm+mdv因此dA=v⋅vdm+mv⋅dv=v2dm+m(i=x,y,z∑​vi​dvi​)=v2dm+m(i=x,y,z∑​d(2vi2​​))=v2dm+mvdv由质速方程m=1−(cv​)2​m0​​⇒m2c2−m2v2=m02​c2两边求全微分得2mc2dm−(2mv2dm+2m2vdv)=0代回原式得dA=c2dm动能定义Ek​=A=∫12​dA=∫m0​m​c2dm=Δmc2=mc2−m0​c2​​ 相对论中能量和动量的关系：\nEk=p2m+m0、E2=p2c2+E02\\begin{align}E_k=\\frac{p^2}{m+m_0}、E^2=p^2c^2+E_0^2\\end{align}Ek​=m+m0​p2​、E2=p2c2+E02​​​ 由m=m01−(vc)2⇒m2c4−m2v2c2=m02c4⇒E2=E02+p2⇒Ek2+2Ekm0c2+m02c4=m02c4+p2c2⇒Ek(mc2−m0c2+2m0c2)=p2c2⇒Ek=p2m+m0\\begin{align}\u0026amp;由m=\\frac{m_0}{\\sqrt{1-\\left({\\large\\frac vc}\\right)^2}}\\Rightarrow m^2c^4-m^2v^2c^2=m_0^2c^4\\\\\u0026amp;\\Rightarrow E^2=E_0^2+p^2\\Rightarrow E_k^2+2E_km_0c^2+m_0^2c^4=m_0^2c^4+p^2c^2\\\\\u0026amp;\\Rightarrow E_k(mc^2-m_0c^2+2m_0c^2)=p^2c^2\\\\\u0026amp;\\Rightarrow E_k=\\frac{p^2}{m+m_0}\\end{align}​由m=1−(cv​)2​m0​​⇒m2c4−m2v2c2=m02​c4⇒E2=E02​+p2⇒Ek2​+2Ek​m0​c2+m02​c4=m02​c4+p2c2⇒Ek​(mc2−m0​c2+2m0​c2)=p2c2⇒Ek​=m+m0​p2​​​ 光的波粒二象性：光子的速度为ccc，质量为000，设光的波长为λ\\lambdaλ，频率为vvv，普朗克常量为hhh，则\n光速c=λvc=\\lambda vc=λv 光子的能量E=mc2=pc=hvE=mc^2=pc=hvE=mc2=pc=hv 光子的动量 p=hvc=hλ\\begin{align}p=\\frac{hv}c=\\frac{h}{\\lambda}\\end{align}p=chv​=λh​​​ 光子的质量 m=Ec2=hvc\\begin{align}m=\\frac E{c^2}=\\frac{hv}c\\end{align}m=c2E​=chv​​​ ","permalink":"https://echo.ishian.me/2024/10/19/physics-relativity/","summary":"整理狭义相对论：洛伦兹变换、时空观修正与相对论力学的基本结论","title":"大学物理: 相对论"},{"content":" 对任意实数xxx，有x−1\u0026lt;⌊x⌋≤x≤⌈x⌉\u0026lt;x+1x-1\u0026lt;\\lfloor x\\rfloor\\le x\\le\\lceil x\\rceil\u0026lt;x+1x−1\u0026lt;⌊x⌋≤x≤⌈x⌉\u0026lt;x+1\n当xxx为整数时，⌊x⌋=⌈x⌉\\lfloor x\\rfloor=\\lceil x\\rceil⌊x⌋=⌈x⌉ 当xxx为小数时，⌊x⌋=⌈x⌉−1\\lfloor x\\rfloor=\\lceil x\\rceil-1⌊x⌋=⌈x⌉−1 两个取整函数关于原点对称：⌈−x⌉=−⌊x⌋\\lceil-x\\rceil=-\\lfloor x\\rfloor⌈−x⌉=−⌊x⌋，反之亦然\n回顾上阶幂和下阶幂的性质：(−x)m‾=(−1)mxm‾(-x)^{\\underline m}=(-1)^mx^{\\overline m}(−x)m​=(−1)mxm 加减法中，整数项可以随意移出取整符号，但乘除法不行\n若f(x)f(x)f(x)是任意连续的单调上升函数，且f(x)∈Z⇒x∈Zf(x)\\in Z\\Rightarrow x\\in Zf(x)∈Z⇒x∈Z，则⌊f(⌊x⌋)⌋=⌊f(x)⌋\\lfloor f(\\lfloor x\\rfloor)\\rfloor=\\lfloor f(x)\\rfloor⌊f(⌊x⌋)⌋=⌊f(x)⌋(上取整亦然)\n取整函数求区间内整数个数：\n[α,β][\\alpha,\\beta][α,β]：⌊β⌋−⌈α⌉+1\\lfloor\\beta\\rfloor-\\lceil\\alpha\\rceil+1⌊β⌋−⌈α⌉+1 [α,β)[\\alpha,\\beta)[α,β)：⌈β⌉−⌈α⌉\\lceil\\beta\\rceil-\\lceil\\alpha\\rceil⌈β⌉−⌈α⌉，可通过上式分析 (α,β](\\alpha,\\beta](α,β]：⌊β⌋−⌊α⌋\\lfloor\\beta\\rfloor-\\lfloor\\alpha\\rfloor⌊β⌋−⌊α⌋，可通过上式分析 (α,β)(\\alpha,\\beta)(α,β)：⌈β⌉−⌊α⌋−1\\lceil\\beta\\rceil-\\lfloor\\alpha\\rfloor-1⌈β⌉−⌊α⌋−1，记住 若\n⌊n3⌋\\n,n∈N+\\begin{align}\\lfloor\\sqrt[3]{n}\\rfloor\\backslash n,n\\in N^+\\end{align}⌊3n​⌋\\n,n∈N+​​，则称nnn为赢者数；求小于正整数NNN的赢者数的个数：\n分为两段，一段是可被取完的，另一段是无法被取完的，其分割点为kkk，它满足k3=⌊k3⌋=⌊N3⌋\\sqrt[3]{k}=\\lfloor\\sqrt[3]{k}\\rfloor=\\lfloor\\sqrt[3]{N}\\rfloor3k​=⌊3k​⌋=⌊3N​⌋ 令m=k3m=\\sqrt[3]{k}m=3k​\n则有原式=∑1≤i\u0026lt;m,n\u0026lt;(i+1)3[i\\n]+∑i=m,n\u0026lt;N[i\\n]=∑i=1m−1⌈(i+1)3−i3i⌉+⌈N−m3m⌉=∑i=1m−1⌈3i+3+1i⌉+⌈Nm⌉−m2=∑i=1m−1(3i+4)+⌈Nm⌉−m2=12(7+3m+1)(m−1)+⌈Nm⌉−m2=12m2+52m+⌈Nm⌉−4\\begin{align}\u0026amp;则有原式=\\sum_{1\\le i\u0026lt;m,n\u0026lt;(i+1)^3}[i\\backslash n]+\\sum_{i=m,n\u0026lt;N}[i\\backslash n]=\\sum_{i=1}^{m-1}\\left\\lceil\\frac{(i+1)^3-i^3}{i}\\right\\rceil+\\left\\lceil\\frac{N-m^3}{m}\\right\\rceil\\\\\u0026amp;=\\sum_{i=1}^{m-1}\\lceil3i+3+\\frac1i\\rceil+\\lceil\\frac{N}{m}\\rceil-m^2=\\sum_{i=1}^{m-1}(3i+4)+\\left\\lceil\\frac Nm\\right\\rceil-m^2\\\\\u0026amp;=\\frac12(7+3m+1)(m-1)+\\left\\lceil\\frac Nm\\right\\rceil-m^2=\\frac12m^2+\\frac52m+\\left\\lceil\\frac Nm\\right\\rceil-4\\end{align}​则有原式=1≤i\u0026lt;m,n\u0026lt;(i+1)3∑​[i\\n]+i=m,n\u0026lt;N∑​[i\\n]=i=1∑m−1​⌈i(i+1)3−i3​⌉+⌈mN−m3​⌉=i=1∑m−1​⌈3i+3+i1​⌉+⌈mN​⌉−m2=i=1∑m−1​(3i+4)+⌈mN​⌉−m2=21​(7+3m+1)(m−1)+⌈mN​⌉−m2=21​m2+25​m+⌈mN​⌉−4​​若允许等于NNN，则由\n⌈N+1−m3m⌉=⌈N+1m⌉−m2=⌊Nm⌋−m2+1\\begin{align}\\left\\lceil\\frac{N+1-m^3}{m}\\right\\rceil=\\left\\lceil\\frac{N+1}{m}\\right\\rceil-m^2=\\left\\lfloor\\frac{N}{m}\\right\\rfloor-m^2+1\\end{align}⌈mN+1−m3​⌉=⌈mN+1​⌉−m2=⌊mN​⌋−m2+1​​ (续上问)将\n⌈nm⌉\\begin{align}\\left\\lceil\\frac{n}{m}\\right\\rceil\\end{align}⌈mn​⌉​​替换为下取整形式(n,mn,mn,m为任意实数)：\n令n=mq+r,其中q=⌊nm⌋,0≤r\u0026lt;m则原式=⌈mq+rm⌉=q+⌈rm⌉,而⌊n+m−1m⌋=⌊(q+1)m+r−1m⌋=q+1+⌊r−1m⌋无论r=0,r≠0,两者均相等,因此原式=⌊n+m−1m⌋=1+⌊n−1m⌋\\begin{align}\u0026amp;令n=mq+r,其中q=\\left\\lfloor\\frac{n}{m}\\right\\rfloor,0\\le r\u0026lt;m\\\\\u0026amp;则原式=\\left\\lceil\\frac{mq+r}{m}\\right\\rceil=q+\\left\\lceil\\frac{r}{m}\\right\\rceil,\\\\\u0026amp;而\\left\\lfloor\\frac{n+m-1}{m}\\right\\rfloor=\\left\\lfloor\\frac{(q+1)m+r-1}{m}\\right\\rfloor=q+1+\\left\\lfloor\\frac{r-1}{m}\\right\\rfloor\\\\\u0026amp;无论r=0,r\\ne0,两者均相等,因此原式=\\left\\lfloor\\frac{n+m-1}{m}\\right\\rfloor=1+\\left\\lfloor\\frac{n-1}{m}\\right\\rfloor\\end{align}​令n=mq+r,其中q=⌊mn​⌋,0≤r\u0026lt;m则原式=⌈mmq+r​⌉=q+⌈mr​⌉,而⌊mn+m−1​⌋=⌊m(q+1)m+r−1​⌋=q+1+⌊mr−1​⌋无论r=0,r=0,两者均相等,因此原式=⌊mn+m−1​⌋=1+⌊mn−1​⌋​​ 证明spec(2),spec(2+2)spec(\\sqrt2),spec(2+\\sqrt2)spec(2​),spec(2+2​)是对正整数集合的划分：\n先证它们中小于等于n的正整数恰好等于n:N(2,n)=∑i[1≤⌊i2⌋≤n]=∑i[0\u0026lt;i\u0026lt;n+12]=⌈n+12⌉−12+2的谱同理,因此N(2,n)+N(2+2,n)=⌈n+12⌉+⌈n+12+2⌉−2=n+1+小数部分−2由于分母为无理数,小数部分和必为1,因此原式=n下证一个元素不可能同时出现在两个谱中:假设有正整数α,β,s,使⌊α2⌋=⌊β(2+2)⌋=s则有α2−1\u0026lt;s\u0026lt;α2,β(2+2)−1\u0026lt;s\u0026lt;β(2+2)即s2\u0026lt;α\u0026lt;s+12,s2+2\u0026lt;β\u0026lt;s+12+2两式相加,得s\u0026lt;α+β\u0026lt;s+1,两个相邻整数间没有整数,因此不成立,原命题成立\\begin{align}\u0026amp;先证它们中小于等于n的正整数恰好等于n:\\\\\u0026amp;N(\\sqrt2,n)=\\sum_i[1\\le \\left\\lfloor i\\sqrt2\\right\\rfloor\\le n]=\\sum_{i}[0\u0026lt;i\u0026lt;\\frac{n+1}{\\sqrt2}]=\\left\\lceil\\frac{n+1}{\\sqrt2}\\right\\rceil-1\\\\\u0026amp;2+\\sqrt2的谱同理,因此N(\\sqrt2,n)+N(2+\\sqrt2,n)=\\left\\lceil\\frac{n+1}{\\sqrt2}\\right\\rceil+\\left\\lceil\\frac{n+1}{2+\\sqrt2}\\right\\rceil-2\\\\\u0026amp;=n+1+小数部分-2\\\\\u0026amp;由于分母为无理数,小数部分和必为1,因此原式=n\\\\\u0026amp;下证一个元素不可能同时出现在两个谱中:\\\\\u0026amp;假设有正整数\\alpha,\\beta,s,使\\lfloor\\alpha\\sqrt2\\rfloor=\\lfloor\\beta(2+\\sqrt2)\\rfloor=s\\\\\u0026amp;则有\\alpha\\sqrt2-1\u0026lt;s\u0026lt;\\alpha\\sqrt2,\\beta(2+\\sqrt2)-1\u0026lt;s\u0026lt;\\beta(2+\\sqrt2)\\\\\u0026amp;即\\frac{s}{\\sqrt2}\u0026lt;\\alpha\u0026lt;\\frac{s+1}{\\sqrt2},\\frac{s}{\\sqrt2+2}\u0026lt;\\beta\u0026lt;\\frac{s+1}{\\sqrt2+2}\\\\\u0026amp;两式相加,得s\u0026lt;\\alpha+\\beta\u0026lt;s+1,两个相邻整数间没有整数,因此不成立,原命题成立\\end{align}​先证它们中小于等于n的正整数恰好等于n:N(2​,n)=i∑​[1≤⌊i2​⌋≤n]=i∑​[0\u0026lt;i\u0026lt;2​n+1​]=⌈2​n+1​⌉−12+2​的谱同理,因此N(2​,n)+N(2+2​,n)=⌈2​n+1​⌉+⌈2+2​n+1​⌉−2=n+1+小数部分−2由于分母为无理数,小数部分和必为1,因此原式=n下证一个元素不可能同时出现在两个谱中:假设有正整数α,β,s,使⌊α2​⌋=⌊β(2+2​)⌋=s则有α2​−1\u0026lt;s\u0026lt;α2​,β(2+2​)−1\u0026lt;s\u0026lt;β(2+2​)即2​s​\u0026lt;α\u0026lt;2​s+1​,2​+2s​\u0026lt;β\u0026lt;2​+2s+1​两式相加,得s\u0026lt;α+β\u0026lt;s+1,两个相邻整数间没有整数,因此不成立,原命题成立​​ nnn人均匀分为mmm组问题：最大组含\n⌈nm⌉\\begin{align}\\left\\lceil\\frac{n}{m}\\right\\rceil\\end{align}⌈mn​⌉​​人，最小组含\n⌊nm⌋\\begin{align}\\left\\lfloor\\frac{n}{m}\\right\\rfloor\\end{align}⌊mn​⌋​​可证明：\nn=∑i=0m−1⌈n−im⌉=∑i=0m−1⌊n+im⌋\\begin{align}n=\\sum_{i=0}^{m-1}\\left\\lceil\\frac{n-i}m\\right\\rceil=\\sum_{i=0}^{m-1}\\left\\lfloor\\frac{n+i}m\\right\\rfloor\\end{align}n=i=0∑m−1​⌈mn−i​⌉=i=0∑m−1​⌊mn+i​⌋​​推广到任意实数xxx(通过分析\nf(x)=xm\\begin{align}f(x)=\\frac xm\\end{align}f(x)=mx​​​的性质可去除内部下取整符号)：\n⌊mx⌋=∑i=0m−1⌊x+im⌋\\begin{align}\\lfloor mx\\rfloor=\\sum_{i=0}^{m-1}\\left\\lfloor x+\\frac{i}m\\right\\rfloor\\end{align}⌊mx⌋=i=0∑m−1​⌊x+mi​⌋​​ 平方根取整求和：\n∑0≤k\u0026lt;n⌊k⌋\\begin{align}\\sum_{0\\le k\u0026lt;n}\\lfloor\\sqrt k\\rfloor\\end{align}0≤k\u0026lt;n∑​⌊k​⌋​​解一:原式=∑m,k≥0m[k\u0026lt;n][m≤k\u0026lt;m+1]=∑m,k≥0m[k\u0026lt;n][m2≤k\u0026lt;(m+1)2]=∑m,k≥0m[m2≤k\u0026lt;(m+1)2≤n]+∑m,k≥0m[m2≤k\u0026lt;n\u0026lt;(m+1)2]=m=⌊n⌋∑0≤i\u0026lt;mi((i+1)2−i2)+m(n−m2)=mn−m3+∑i=1m−1(2i2+i)=mn−m3+∑1m(2i2‾+3i1‾)δi=23m3‾+32m2‾+mn−m3\\begin{align}\u0026amp;解一:原式=\\sum_{m,k\\ge0}m[k\u0026lt;n][m\\le\\sqrt k\u0026lt;m+1]\\\\\u0026amp;=\\sum_{m,k\\ge0}m[k\u0026lt;n][m^2\\le k\u0026lt;(m+1)^2]\\\\\u0026amp;=\\sum_{m,k\\ge0}m[m^2\\le k\u0026lt;(m+1)^2\\le n]+\\sum_{m,k\\ge0}m[m^2\\le k\u0026lt;n\u0026lt;(m+1)^2]\\\\\u0026amp;\\xlongequal[]{m=\\lfloor\\sqrt n\\rfloor}\\sum_{0\\le i\u0026lt;m}i((i+1)^2-i^2)+m(n-m^2)\\\\\u0026amp;=mn-m^3+\\sum_{i=1}^{m-1}(2i^2+i)=mn-m^3+\\sum_{1}^{m}(2i^{\\underline 2}+3i^{\\underline 1})\\delta i\\\\\u0026amp;=\\frac23m^{\\underline 3}+\\frac32m^{\\underline 2}+mn-m^3\\end{align}​解一:原式=m,k≥0∑​m[k\u0026lt;n][m≤k​\u0026lt;m+1]=m,k≥0∑​m[k\u0026lt;n][m2≤k\u0026lt;(m+1)2]=m,k≥0∑​m[m2≤k\u0026lt;(m+1)2≤n]+m,k≥0∑​m[m2≤k\u0026lt;n\u0026lt;(m+1)2]m=⌊n​⌋​0≤i\u0026lt;m∑​i((i+1)2−i2)+m(n−m2)=mn−m3+i=1∑m−1​(2i2+i)=mn−m3+1∑m​(2i2​+3i1​)δi=32​m3​+23​m2​+mn−m3​​ ","permalink":"https://echo.ishian.me/2024/10/16/concretemath-rounding/","summary":"讲解上下取整函数的性质、区间整数计数、谱系划分及均分分组等应用","title":"具体数学: 取整函数"},{"content":"电磁感应 电动势 电磁感应现象的本质是产生电动势来阻止磁通量的变化，若形成闭合回路，则表现出感应电流\n电动势定义：\n把单位正电荷从负极板移至正极板，非静电力所做的功，是标量 非静电性场的场强沿从负极板到正极板的直线路径的线积分，即 E=∫−+Ek⋅dl\\begin{align}\\mathscr E=\\int_-^+\\boldsymbol E_k\\boldsymbol ·\\mathrm d\\boldsymbol l\\end{align}E=∫−+​Ek​⋅dl​​ 和电势差的意义完全不一样，电动势始终指向电势升高的方向 展开后可得， E=−d(∬B⋅dS)dt\\begin{align}\\mathscr E=-\\frac{\\mathrm d\\left(\\iint\\boldsymbol {B·}\\mathrm d\\boldsymbol S\\right)}{\\mathrm dt}\\end{align}E=−dtd(∬B⋅dS)​​​ 法拉第电磁感应定律：\nE=−dϕmdt\\begin{align}\\mathscr E=-\\frac{\\mathrm d\\phi_m}{\\mathrm dt}\\end{align}E=−dtdϕm​​​​ 感应电动势的方向和感应电流的流向相同，且总是阻碍引起感应电流的原因\n判断时，可先判断感应电流磁场的方向，再得到感应电流的方向\n多匝线圈的磁通量称为全磁通，若每匝磁通相同，也称磁通链数\n其符号表示参考方向和实际方向的区别，实际方向以楞次定律判断后，若求出负值，说明参考方向和实际方向相反；以电磁感应定律求出的电动势的参考方向和原磁场方向呈右螺旋关系\n动生电动势：\nE=∫ab(v×B)⋅dl\\begin{align}\\mathscr E=\\int_a^b(\\boldsymbol{v\\times B})\\boldsymbol ·\\mathrm d\\boldsymbol l\\end{align}E=∫ab​(v×B)⋅dl​​，由洛伦兹力充当非静电力，由v×B\\boldsymbol{v\\times B}v×B充当非静电性场\n动生电动势公式在参考方向和实际方向的区别体现在线积分的方向，若线积分方向与实际方向相反，则求出的电动势为负值 可由v×B\\boldsymbol{v\\times B}v×B求出电动势方向 感生电动势：\nE=∮LEk⋅dl=−∬S∂B∂t⋅dS\\begin{align}\\mathscr E=\\oint_L\\boldsymbol{E_k·}\\mathrm d\\boldsymbol l=-\\iint_S\\frac{\\part\\boldsymbol B}{\\part t}\\boldsymbol ·\\mathrm d\\boldsymbol S\\end{align}E=∮L​Ek​⋅dl=−∬S​∂t∂B​⋅dS​​，其中闭合回路方向和磁场呈右螺旋关系\n感生电场由变化的磁场产生，是无源有旋场，因参考方向取上述方向，故根据电磁感应定律在前面有负号 自感和互感和磁能 磁通量出发，将所有与线圈本身有关的物理量合并成自感系数\nL=ϕmI\\begin{align}L=\\frac{\\phi_m}{I}\\end{align}L=Iϕm​​​​ 由法拉第电磁感应定律，自感电动势\nE=−LdIdt\\begin{align}\\mathscr E=-L\\frac{\\mathrm dI}{\\mathrm dt}\\end{align}E=−LdtdI​​​ 自感系数的一种求法：假设通有电流III，求出其磁通量；例如长直螺绕管的自感系数\nL=μn2V\\begin{align}L=\\mu n^2V\\end{align}L=μn2V​​ 自感系数的三层含义：\n线圈自感效应大小的量度 线圈电磁惯性大小的量度 线圈储存磁能本领的量度 互感系数MMM比自感系数多出一种影响因素，即有效占据面积，当两线圈所围平面同轴平行时，磁通量最大\n两线圈组成的系统中，互感系数相等\n计算111对222的互感系数时，需使用111的nnn，222的III，面积取有效占据面积\n线圈的磁能：\nWm=LI22\\begin{align}W_m=\\frac{LI^2}{2}\\end{align}Wm​=2LI2​​​ 磁场任意点的磁能密度：\nwm=BH2=μH22=B22μ\\begin{align}w_m=\\frac{BH}{2}=\\frac{\\mu H^2}{2}=\\frac{B^2}{2\\mu}\\end{align}wm​=2BH​=2μH2​=2μB2​​​ 麦克斯韦方程组 位移电流：(关于电流的连续性)变化的电场在周围激发的磁场等效于电流，其定量描述为：\n全电流安培环路定理：\n∮LH⋅dl=Ic+∬S∂D∂t⋅dS\\begin{align}\\oint_L\\boldsymbol{H·}\\mathrm d\\boldsymbol l=I_c+\\iint\\limits_S\\frac{\\part\\boldsymbol D}{\\part t}\\boldsymbol{·}\\mathrm d\\boldsymbol S\\end{align}∮L​H⋅dl=Ic​+S∬​∂t∂D​⋅dS​​，其中IcI_cIc​为传导电流\n由平行板电容器引出，电位移通量与极板上积聚的电荷相同，且电位移通量对时间的偏导在数值上等于传导电流、方向上与其一致 磁通连续定理：\n∫∫S◯B⋅dS≡0\\begin{align}\\int\\kern{-8pt}\\int\\limits_ S\\kern{-23mu}\\bigcirc\\boldsymbol{B·}\\mathrm d\\boldsymbol S\\equiv0\\end{align}∫S∫​◯B⋅dS≡0​​ 电场的高斯定理：\n∫∫S◯D⋅dS=∑q自由电荷=∭Vρ dV\\begin{align}\\int\\kern{-8pt}\\int\\limits_ S\\kern{-23mu}\\bigcirc\\boldsymbol{D·}\\mathrm d\\boldsymbol S=\\sum q_{自由电荷}=\\iiint\\limits_V\\rho\\ \\mathrm dV\\end{align}∫S∫​◯D⋅dS=∑q自由电荷​=V∭​ρ dV​​ 电场的环路定理：\n∮LD⋅dl=−∬S∂H∂t⋅dS\\begin{align}\\oint_L\\boldsymbol{D·}\\mathrm d\\boldsymbol l=-\\iint\\limits_S\\frac{\\part\\boldsymbol H}{\\part t}\\boldsymbol ·\\mathrm d\\boldsymbol S\\end{align}∮L​D⋅dl=−S∬​∂t∂H​⋅dS​​ 电磁波 波动方程：由麦克斯韦方程组导出，{(∇2−εμ∂2∂t2)E=0(∇2−εμ∂2∂t2)H=0\\begin{cases}\\left(\\grad^2-\\varepsilon\\mu\\large\\frac{\\part^2}{\\part t^2}\\right)\\boldsymbol E=0\\\\\\left(\\grad^2-\\varepsilon\\mu\\large\\frac{\\part^2}{\\part t^2}\\right)\\boldsymbol H=0\\end{cases}⎩⎨⎧​(∇2−εμ∂t2∂2​)E=0(∇2−εμ∂t2∂2​)H=0​的解即波动的定义，其中哈密顿算子为矢量三个二阶偏导之和 若以电磁波方向为xxx轴正向，可以解得{E=jE0cos⁡(ωt±ωxc+φ)H=kH0cos⁡(ωt±ωxc+φ)\\begin{cases}\\boldsymbol E=\\boldsymbol jE_0\\cos(\\omega t\\pm\\omega{\\large\\frac{x}{c}}+\\varphi)\\\\\\boldsymbol H=\\boldsymbol kH_0\\cos(\\omega t\\pm\\omega{\\large\\frac{x}{c}}+\\varphi)\\end{cases}{E=jE0​cos(ωt±ωcx​+φ)H=kH0​cos(ωt±ωcx​+φ)​，其中 ±ωx/y/zc/u\\begin{align}\\pm\\omega\\frac{x/y/z}{c/u}\\end{align}±ωc/ux/y/z​​​为传播项，分子定义传播方向，分母定义波速(真空/介质中)，负号为正向传播，正号反之 电磁波的性质： 是横波，传播方向由传播项体现，且满足eE×eB=eu\\boldsymbol{e_E\\times e_B}=\\boldsymbol{e_u}eE​×eB​=eu​ 是偏振波，且E、H\\boldsymbol E、\\boldsymbol HE、H同相，即cos⁡()\\cos()cos()内部完全一致 满足εE=μH\\sqrt\\varepsilon E=\\sqrt\\mu Hε​E=μ​H，波速 u=1εμ\\begin{align}u=\\sqrt{\\frac1{\\varepsilon\\mu}}\\end{align}u=εμ1​​​​ 在LCLCLC振荡电路中，由 u=qC=E=−LdIdt=−Ld2qdt2\\begin{align}u=\\frac qC=\\mathscr E=-L\\frac{\\mathrm dI}{\\mathrm dt}=-L\\frac{\\mathrm d^2q}{\\mathrm dt^2}\\end{align}u=Cq​=E=−LdtdI​=−Ldt2d2q​​​，得出简谐运动方程 d2qdt2+1LCq=0\\begin{align}\\frac{\\mathrm d^2q}{\\mathrm dt^2}+\\frac1{LC}q=0\\end{align}dt2d2q​+LC1​q=0​​ 当二阶常系数线性齐次微分方程中，若“常系数”为只与自身因素有关的正的常数，则称其为简谐运动 振荡角频率 ω=1LC\\begin{align}\\omega=\\sqrt\\frac1{LC}\\end{align}ω=LC1​​​​ 电磁辐射能：电能和磁能之和 能流密度/辐射强度/坡印廷矢量：S=(ωc+ωm)u\\boldsymbol S=(\\omega_c+\\omega_m)\\boldsymbol uS=(ωc​+ωm​)u ","permalink":"https://echo.ishian.me/2024/10/14/physics-elec-induction/","summary":"整理电磁感应：法拉第定律、动生与感生电动势、自感互感及磁场能量","title":"大学物理: 电磁感应"},{"content":"恒定磁场 电流的磁效应 真空磁导率：μ0=4π×10−7 T⋅m⋅A−1\\mu_0=4\\pi\\times 10^{-7}\\rm\\ T·m·A^{-1}μ0​=4π×10−7 T⋅m⋅A−1\n磁感应强度：1T=104Gs1\\rm T=10^4Gs1T=104Gs，定义运动正电荷所受磁力方向和其运动方向的叉乘为磁感应强度的方向\n毕奥-萨伐尔定律：\ndB=μ04πIdl×err2\\begin{align}\\mathrm d\\boldsymbol B=\\frac{\\mu_0}{4\\pi}\\frac{I\\mathrm d\\boldsymbol{l\\times e_r}}{r^2}\\end{align}dB=4πμ0​​r2Idl×er​​​​ 可配合磁场叠加原理求解场点的磁场，求解闭合回路电流产生的磁场时，需注意电流分流的影响\n载流直线产生磁场： B=μ0I4πr(cos⁡α1−cos⁡α2)\\begin{align}B=\\frac{\\mu_0I}{4\\pi r}(\\cos\\alpha_1-\\cos\\alpha_2)\\end{align}B=4πrμ0​I​(cosα1​−cosα2​)​​，其中rrr为场点到直线的距离，α1、α2\\alpha_1、\\alpha_2α1​、α2​分别为沿电流方向的起点、终点与场点的连线到载流直线的夹角 无限长载流直线： B=μ0I2πr\\begin{align}B=\\frac{\\mu_0I}{2\\pi r}\\end{align}B=2πrμ0​I​​​ 圆弧电流在中心的磁场： B=θ2πμ0I2r\\begin{align}B=\\frac{\\theta}{2\\pi}\\frac{\\mu_0I}{2r}\\end{align}B=2πθ​2rμ0​I​​​，其中θ\\thetaθ为圆弧的圆心角 磁场的高斯定理：\n∫∫S◯B⋅dS≡0\\begin{align}\\int\\kern{-8pt}\\int\\limits_ S\\kern{-23mu}\\bigcirc\\boldsymbol{B·}\\mathrm d\\boldsymbol S\\equiv0\\end{align}∫S∫​◯B⋅dS≡0​​，通过闭合曲面的磁通量为零，揭示磁场是无源场\n磁感应线的性质：无头无尾、不相交、环绕方向和III构成右手螺旋关系\n安培环路定理：\n∮B⋅dl=μ0∑I\\begin{align}\\oint\\boldsymbol{B·}\\mathrm d\\boldsymbol l=\\mu_0\\sum I\\end{align}∮B⋅dl=μ0​∑I​​，其中III为穿过闭合路径内的闭合电流 可通过它求高度对称磁场的磁感应强度\n类比空间所有电荷对电场强度的贡献，空间任意一点的磁感应强度由空间中所有电流贡献\n均匀密绕长直载流螺线管： B=nμ0I\\begin{align}\\boldsymbol B=n\\mu_0I\\end{align}B=nμ0​I​​，其中nnn为单位长度线圈匝数 环形螺线管： B=μ0NI2πr\\begin{align}\\boldsymbol B=\\frac{\\mu_0NI}{2\\pi r}\\end{align}B=2πrμ0​NI​​​ 无限大载流导体薄板： B=nμ0I2\\begin{align}\\boldsymbol B=\\frac{n\\mu_0I}2\\end{align}B=2nμ0​I​​​ 磁场对实物的作用 洛伦兹力：Fm=qv×B\\boldsymbol F_m=q\\boldsymbol{v\\times B}Fm​=qv×B\n载流子浓度：\nn=NV\\begin{align}n=\\frac{N}{V}\\end{align}n=VN​​​，单位体积内载流子的数目\n霍尔效应与洛伦兹公式：F=qE+qv×B\\boldsymbol F=q\\boldsymbol E+q\\boldsymbol{v\\times B}F=qE+qv×B，体现在一小片半导体中时，产生的横向电场的霍尔电势差\nVH=RHIBb\\begin{align}V_H=R_H\\frac{IB}{b}\\end{align}VH​=RH​bIB​​​，其中\nRH=1ne\\begin{align}R_H=\\frac1{ne}\\end{align}RH​=ne1​​​为霍尔系数，bbb为横向宽度，特别地称\nKH=1neb\\begin{align}K_H=\\frac1{neb}\\end{align}KH​=neb1​​​为霍尔灵敏度\n由 I=dQdt=nqvS\\begin{align}I=\\frac{\\mathrm dQ}{\\mathrm dt}=nqvS\\end{align}I=dtdQ​=nqvS​​，即单位时间内通过横截面的电荷量(单位时间内，流向位移为vvv，则vSvSvS这个体积块内的电荷均可通过横截面，其数目为nvSnvSnvS，乘上qqq即为总电荷量) 设半导体纵向(霍尔电场方向)宽度为aaa，横向(磁场方向)宽度为bbb，则平衡时有 I=nqvab、E=vB=VHa\\begin{align}I=nqvab、E=vB=\\frac{V_H}{a}\\end{align}I=nqvab、E=vB=aVH​​​​ 整理得 VH=IBneb\\begin{align}V_H=\\frac{IB}{neb}\\end{align}VH​=nebIB​​​ 由霍尔效应可判断半导体中载流子的正负性、制造速度选择器等 电流元(可视作极短直线的恒定电流)所受安培力：dF=Idl×B\\mathrm d\\boldsymbol F=I\\mathrm d\\boldsymbol{l\\times B}dF=Idl×B\n电流元所受安培力是NNN个定向移动电荷所受洛伦兹力的分力的合力，其中 N=nSdl\\begin{align}N=nS\\mathrm dl\\end{align}N=nSdl​​(dl\\mathrm d\\boldsymbol ldl和v\\boldsymbol vv方向一致)，故 dF=NFm=qN(v×B)=nqvSdl(ev×B)=Idl×B\\begin{align}\\mathrm d\\boldsymbol F=N\\boldsymbol F_m=qN(\\boldsymbol{v\\times B})=nqvS\\mathrm dl(\\boldsymbol{e_v\\times B})=I\\mathrm d\\boldsymbol {l\\times B}\\end{align}dF=NFm​=qN(v×B)=nqvSdl(ev​×B)=Idl×B​​ 均匀磁场中，任意闭合线圈所受磁场合力为零，但作用线不在一条直线上时会产生磁力矩 磁矩：m=IS=ISes\\boldsymbol m=I\\boldsymbol S=IS\\boldsymbol e_sm=IS=ISes​，表示线圈电流产生磁场的能力，其中磁矩方向和电流方向符合右手螺旋定则\n闭合带电线圈所受外磁场的合磁力矩：\nM=m×B\\begin{align}\\boldsymbol M=\\boldsymbol{m\\times B}\\end{align}M=m×B​​，和线圈旋转方向构成右手螺旋关系\n公式中，B\\boldsymbol BB为外磁场，而不是线圈产生的磁场 M=∮Lr×F=I∮L(r×dl×B)=IS×B=m×B\\begin{align}\\boldsymbol M=\\oint _L\\boldsymbol{r\\times F}=I\\oint_L(\\boldsymbol {r\\times}\\mathrm d\\boldsymbol{l\\times B})=I\\boldsymbol {S\\times B}=\\boldsymbol{m\\times B}\\end{align}M=∮L​r×F=I∮L​(r×dl×B)=IS×B=m×B​​ 磁力做功问题 首先明确，磁场是非保守力场，即磁力做功与路径有关，故一个位置相对另一个位置而言，没有固定的能量差，因此没有磁势的概念\n洛伦兹力对电子永不做功，安培力实际上是洛伦兹力在垂直导线方向上的分力，因此会对导线做功\n实际上，安培力做功等于感应电动势做功，恒流情况下，\nA=IΔϕm\\begin{align}A=I\\Delta\\phi_m\\end{align}A=IΔϕm​​​ 刚体力学引理：力矩做功实际上就是力做功，通过数学推导得dA=F⋅dr=Mdθ\\mathrm dA=\\boldsymbol{F·}\\mathrm d\\boldsymbol r=M\\mathrm d\\thetadA=F⋅dr=Mdθ，方便刚体转动计算\n匀强磁场对闭合曲线的合力为零，但除去特定角度会产生磁力矩，而磁力矩会对线圈做功\n可以发现，在匀强磁场中，磁力对闭合线圈做功\nA=∫ϕ1ϕ2Idϕm=∫ϕ1ϕ2Id(BScos⁡θ)=−∫θ1θ2IBSsin⁡θdθ=−∫θ1θ2Mdθ\\begin{align}A=\\int_{\\phi_1}^{\\phi_2}I\\mathrm d\\phi_m=\\int_{\\phi_1}^{\\phi_2}I\\mathrm d(BS\\cos\\theta)=-\\int_{\\theta_1}^{\\theta_2}IBS\\sin\\theta\\mathrm d\\theta=-\\int_{\\theta_1}^{\\theta_2}M\\mathrm d\\theta\\end{align}A=∫ϕ1​ϕ2​​Idϕm​=∫ϕ1​ϕ2​​Id(BScosθ)=−∫θ1​θ2​​IBSsinθdθ=−∫θ1​θ2​​Mdθ​​即，该做功只与θ(m,B)\\theta(\\boldsymbol m,\\boldsymbol B)θ(m,B)的始末位置有关，仿照电势能的概念，引入磁矩的势能，由上式可看出：\n磁矩势能的减少值即为磁力矩的做功，当磁矩势能为零时，磁通量达到最大值，线圈处于稳定平衡状态\n实际上，在一般磁场中也适用\n磁铁相互吸引的原理就是，磁力将磁矩势能转换为磁铁的动能，使其相互靠近 磁介质对磁场的作用 相对磁导率：μr\\mu_rμr​，根据其略小于一、略大于一、远大于一分为下面三类\n磁介质的分类：顺磁质、抗磁质、铁磁质\n磁化机理：\n分子可等效为具有固有磁矩的圆电流，根据 ∑m\\begin{align}\\sum\\boldsymbol m\\end{align}∑m​​(分子的固有磁矩)等于/不等于零，可区分抗磁质和顺磁质 磁介质在常态没有磁性的原因是：无规律等概率的热运动使介质的所有分子的固有磁矩和为零 外加磁场后，顺磁质的固有磁矩受磁场作用产生磁力矩，使所有分子电流趋向磁场方向，达到增强磁场的作用 所有磁介质都有抗磁能力(磁通量增大时产生的反向感应电流会产生反向磁场)，只是顺磁质的顺磁作用远强于抗磁作用 顺磁质、抗磁质的μr\\mu_rμr​为常数，而铁磁质的μr\\mu_rμr​不是线性的，受温度、磁化历史等影响 磁饱和：当激励电流达到一定程度时，产生的附加磁场有饱和值 剩磁现象：卸去磁化后的铁磁质的激励电流后，附加磁场有剩余，需加反向激励电流才能消磁 矫顽力：使能消磁的附加的反向磁场强度 磁滞现象：附加磁场从正饱和到负饱和的曲线和从负饱和到正饱和的曲线呈中心对称 根据铁磁质的磁滞回线形状，分为硬、软、矩磁材料：\n硬磁：矫顽力大，作永久磁铁 软磁：矫顽力小，作变压器 矩磁：剩磁和磁饱和近似相等，作磁盘等记忆元件 磁介质中的安培环路定理：\n∮LH⋅dl=∑I传导电流\\begin{align}\\oint_L\\boldsymbol{H·}\\mathrm d\\boldsymbol l=\\sum I_{传导电流}\\end{align}∮L​H⋅dl=∑I传导电流​​​，其中H\\boldsymbol HH为磁场强度且B=μ0μrH\\boldsymbol B=\\mu_0\\mu_r\\boldsymbol HB=μ0​μr​H\n推导前已知：磁化程度M\\boldsymbol MM，且\n∮LM⋅dl=∑I束缚、M=χmH\\begin{align}\\oint_L\\boldsymbol{M·}\\mathrm d\\boldsymbol l=\\sum I_{束缚}、\\boldsymbol M=\\chi_m\\boldsymbol H\\end{align}∮L​M⋅dl=∑I束缚​、M=χm​H​​ 推导：\n∮LB⋅dl=μ0∑(I传导+I束缚)=μ0∑I传导+μ0∮LM⋅dl⇒∮L(Bμ0−M)dl=∑I传导\\begin{align}\u0026amp;\\oint_L\\boldsymbol{B·}\\mathrm d\\boldsymbol l=\\mu_0\\sum\\left(I_{传导}+I_{束缚}\\right)=\\mu_0\\sum I_{传导}+\\mu_0\\oint_L\\boldsymbol{M·}\\mathrm d\\boldsymbol l\\\\\u0026amp;\\Rightarrow\\oint_L\\left(\\frac{\\boldsymbol B}{\\mu_0}-\\boldsymbol M\\right)\\mathrm d\\boldsymbol l=\\sum I_{传导}\\end{align}​∮L​B⋅dl=μ0​∑(I传导​+I束缚​)=μ0​∑I传导​+μ0​∮L​M⋅dl⇒∮L​(μ0​B​−M)dl=∑I传导​​​令\nH=Bμ0−M\\begin{align}\u0026amp;\\boldsymbol H=\\frac{\\boldsymbol B}{\\mu_0}-\\boldsymbol M\\end{align}​H=μ0​B​−M​​，则B=μ0(1+χm)H\\boldsymbol B=\\mu_0(1+\\chi_m)\\boldsymbol HB=μ0​(1+χm​)H\n令μr=1+χm\\mu_r=1+\\chi_mμr​=1+χm​，得B=μ0μrH\\boldsymbol B=\\mu_0\\mu_r\\boldsymbol HB=μ0​μr​H\n磁场的应用 质谱仪： mv22=qU、mv2r=qvB⇒v=2qUm、r=1B2mUq\\begin{align}\u0026amp;\\frac{mv^2}{2}=qU、\\frac{mv^2}{r}=qvB\\\\\u0026amp;\\Rightarrow v=\\sqrt{\\frac{2qU}{m}}、r=\\frac1B\\sqrt{\\frac{2mU}{q}}\\end{align}​2mv2​=qU、rmv2​=qvB⇒v=m2qU​​、r=B1​q2mU​​​​ 回旋加速器 磁聚焦 霍尔效应 速度选择器 磁流体发电机 ","permalink":"https://echo.ishian.me/2024/10/11/physics-magnetic-field/","summary":"整理恒定磁场：毕奥-萨伐尔定律、安培环路定理及洛伦兹力与霍尔效应","title":"大学物理: 恒定磁场"},{"content":"静电场 基本定律及常量 电荷的性质：\n量子性：基本电荷e=1.602×10−19Ce=1.602\\times10^{-19}\\rm Ce=1.602×10−19C，所有基本粒子所带电荷均为eee的整数倍 电荷守恒定律：在孤立系统内发生的任何过程中，总电荷数(所有电荷代数和)不变 正负性、相对论不变性 库仑定律：电荷间的相互作用力为\nF=14πε0q1q2r3r=q1q24πε0r2r0\\begin{align}\\boldsymbol{F}=\\frac1{4\\pi\\varepsilon_0}\\frac{q_1q_2}{r^3}\\boldsymbol{r}=\\frac{q_1q_2}{4\\pi\\varepsilon_0r^2}\\boldsymbol{r}_0\\end{align}F=4πε0​1​r3q1​q2​​r=4πε0​r2q1​q2​​r0​​​ 其中，真空介电常量ε0=8.85×10−12C2⋅N−1⋅m−2\\varepsilon_0=8.85\\times10^{-12}\\rm C^2·N^{-1}·m^{-2}ε0​=8.85×10−12C2⋅N−1⋅m−2 没有合并进去的4π4\\pi4π是方便工程中计算球体而引入的 场论 场是一种中介物质，物质间的相互作用要靠场来传递\n电磁场是量子化的，具有波粒二象性，对电场来说：\n相对于观察者静止的电荷产生静电场 电场是有能量的 电场强度(场强)E\\boldsymbol{E}E：\n反映了电场的固有属性，即空间中某一点的场强与在该点上的电荷无关，计算时需要剔除场点产生的场强\n由于电场力满足叠加原理，故电场强度也满足叠加原理\n要求某一点的场强，就是求空间中除该点外，所有电荷产生的场强的矢量和\n场强第一种求解方式(场强叠加原理)：\nE=∫dE=∫dq4πε0r3r=∫Ωρ r4πε0r3dΩ\\begin{align}\\boldsymbol{E}=\\int\\mathrm d\\boldsymbol{E}=\\int\\frac{\\mathrm dq}{4\\pi\\varepsilon_0r^3}\\boldsymbol{r}=\\int_\\Omega\\frac{\\rho\\ \\boldsymbol{r}}{4\\pi\\varepsilon_0r^3}\\mathrm d\\Omega\\end{align}E=∫dE=∫4πε0​r3dq​r=∫Ω​4πε0​r3ρ r​dΩ​​ 具体来说，大小通过积分来求，而方向通过划分坐标分量，各自求解并组合\n电通量Φ\\varPhiΦ和静电场中的高斯定理：\n指垂直通过某面的电场强度的代数和(电场线的条数)，和电场强度、穿过的面积成正比 Φ=∬SE dS=∭V÷E\\begin{align}\\varPhi=\\iint_S\\boldsymbol{E}\\ \\mathrm d\\boldsymbol{S}=\\iiint_V\\div{\\boldsymbol E}\\end{align}Φ=∬S​E dS=∭V​÷E​​(第二型曲面积分和高斯公式)，散度 ÷E=λε0\\begin{align}\\div{\\boldsymbol E}=\\frac{\\lambda}{\\varepsilon_0}\\end{align}÷E=ε0​λ​​​ 高斯定理：真空中，通过封闭曲面的电通量等于 ∑qε0\\begin{align}\\frac{\\sum q}{\\varepsilon_0}\\end{align}ε0​∑q​​​，其中qqq包括所有自由、极化电荷 高斯定理对所有电场都成立，但只能用于求解高度对称场的场强，例如(场强第二种求解方式)： 球对称图形的外部电场： E=q4πε0r2r0\\begin{align}\\boldsymbol E=\\frac{q}{4\\pi\\varepsilon_0r^2}{\\boldsymbol r_0}\\end{align}E=4πε0​r2q​r0​​​ 轴对称图形的外部电场： E=ρ2πε0rr0\\begin{align}\\boldsymbol E=\\frac{\\rho}{2\\pi\\varepsilon_0 r}\\boldsymbol r_0\\end{align}E=2πε0​rρ​r0​​​，ρ\\rhoρ为电荷线密度 面对称的匀强场： E=±λ2ε0n0\\begin{align}\\boldsymbol E=\\pm\\frac{\\lambda}{2\\varepsilon_0}\\boldsymbol n_0\\end{align}E=±2ε0​λ​n0​​​，这里的面无厚度 高斯定理揭示出的电场线性质： 有头有尾，始于正电荷、终于负电荷 任意两电场线不相交 任意电场线不闭合 任何正电荷(包括极化电荷)都能发出电场线，即电介质中需要画两种电场线 通过高斯定理求解步骤： 阐明对称性，选取合适高斯面以至于能快速去除积分号 求出电通量表达式和面内净电荷 电场力做功：\n电荷在电场中从aaa到bbb， A=∫abF⋅dr\\begin{align}A=\\int_a^b\\boldsymbol{F·}\\mathrm d\\boldsymbol{r}\\end{align}A=∫ab​F⋅dr​​ A=q∮LE⋅dr=q∬S∇×E⋅dS\\begin{align}A=q\\oint_L\\boldsymbol{E·}\\mathrm d\\boldsymbol{r}=q\\iint_S\\boldsymbol{\\grad\\times E·}\\mathrm d\\boldsymbol{S}\\end{align}A=q∮L​E⋅dr=q∬S​∇×E⋅dS​​(斯托克斯公式)，其中∇×E\\boldsymbol{\\grad\\times E}∇×E即旋度 电场力是保守力，做功与路径无关，沿闭合路径做功为000 场强环路定理：\n静电场是无旋场，是保守力场 电势能：定义电荷在aaa处的电势能为 Wa=q∫a0势E⋅dr\\begin{align}W_{a}=q\\int_a^{0势}\\boldsymbol{E·}\\mathrm d\\boldsymbol{r}\\end{align}Wa​=q∫a0势​E⋅dr​​ 电势：aaa处电势为 Ua=∫a0势E⋅dr\\begin{align}U_a=\\int_a^{0势}\\boldsymbol{E·}\\mathrm d\\boldsymbol{r}\\end{align}Ua​=∫a0势​E⋅dr​​，积分上下限通常为点到对称中心的距离 电势是相对量 电势差：定义aaa和bbb点间的电势差为 Ua−Ub=∫abE⋅dr\\begin{align}U_a-U_b=\\int_a^b\\boldsymbol{E·}\\mathrm d\\boldsymbol{r}\\end{align}Ua​−Ub​=∫ab​E⋅dr​​ 微分形式：∇U=−E\\grad U=-\\boldsymbol E∇U=−E，电场方向指向电势减小的方向 场强均匀的区域，电势不一定为定值 电势为定值的区域(不是等势面)，场强一定为零 通常作为定性考点 常见对称电场的电势(以无穷远为零势点)： 球对称图形的外部： q4πεr\\begin{align}\\frac{q}{4\\pi\\varepsilon r}\\end{align}4πεrq​​​ 轴对称图形的外部： λ2πε\\begin{align}\\frac{\\lambda}{2\\pi\\varepsilon}\\end{align}2πελ​​​ 无厚度面：只能求两面/两薄面的电势差 电势两种计算方式：定义法(对称场)、叠加法 导体：\n导体静电平衡条件：内部场强为零，表面附近场强处处与面元垂直\n若有空腔且空腔内有电荷QQQ，则内表面感应出−Q-Q−Q，外表面表现为q+Qq+Qq+Q，此时空腔内有电场 非对称导体表面带电情况只能通过实验测量，电荷面密度与表面曲率成正比\n导体是等势体\n电介质：\n两种分子：正负电荷中心重合/不重合→\\rightarrow→非极性分子/极性分子\n极化方式：位移极化/取向极化，极化电场只影响电介质内部电场\n电极化率：χe\\chi_eχe​\n电极化强度：P=αE\\boldsymbol P=\\alpha\\boldsymbol EP=αE、α=ε0χe\\alpha=\\varepsilon_0\\chi_eα=ε0​χe​，Pn=σ极化电荷P_n=\\sigma_{极化电荷}Pn​=σ极化电荷​\n相对介电常量：εr=1+χe\\varepsilon_r=1+\\chi_eεr​=1+χe​，无量纲；一般大于111，因此往电容两极板间加入电介质将增大电容\n绝对介电常量：ε=ε0εr\\varepsilon=\\varepsilon_0\\varepsilon_rε=ε0​εr​，有量纲\n电位移矢量：D=εE\\boldsymbol D=\\varepsilon\\boldsymbol ED=εE\n电介质的高斯定理：\n∮SD⋅dS=∑q自由电荷\\begin{align}\\oint_S\\boldsymbol{D·}\\mathrm d\\boldsymbol S=\\sum q_{自由电荷}\\end{align}∮S​D⋅dS=∑q自由电荷​​​，因此电位移线由正自由电荷发出\n推导：\n∮SP⋅dS=−∑q极化电荷∮SE⋅dS=1ε0∑(q极化电荷+q自由电荷)P=εr−1εrD=ε0(εr−1)E\\begin{align}\u0026amp;\\oint_S\\boldsymbol{P·}\\mathrm d\\boldsymbol S=-\\sum q_{极化电荷}\\\\\u0026amp;\\oint_S\\boldsymbol{E·}\\mathrm d\\boldsymbol S=\\frac1{\\varepsilon_0}\\sum (q_{极化电荷}+q_{自由电荷})\\\\\u0026amp;\\boldsymbol P=\\frac{\\varepsilon_r-1}{\\varepsilon_r}\\boldsymbol D=\\varepsilon_0(\\varepsilon_r-1)\\boldsymbol E\\end{align}​∮S​P⋅dS=−∑q极化电荷​∮S​E⋅dS=ε0​1​∑(q极化电荷​+q自由电荷​)P=εr​εr​−1​D=ε0​(εr​−1)E​​ 分别求极化电荷或自由电荷产生的场强时，不能通过电介质的高斯定理求解，该定理用于快速求解电介质中的合场强\n换句话说，不同种类电荷产生的场强和εr\\varepsilon_rεr​无关\n事实上，\nE0=σε0、E′=σ′ε0\\begin{align}E_0=\\frac{\\sigma}{\\varepsilon_0}、E\u0026#x27;=\\frac{\\sigma\u0026#x27;}{\\varepsilon_0}\\end{align}E0​=ε0​σ​、E′=ε0​σ′​​​ 电容：\nC=QΔU\\begin{align}C=\\frac{Q}{\\Delta U}\\end{align}C=ΔUQ​​​，两极相距单位电势差时正极板所带的电荷 常见电容器的电容： 孤立导体球： C=4πεR\\begin{align}C=4\\pi\\varepsilon R\\end{align}C=4πεR​​ 平行板电容器： C=εSd\\begin{align}C=\\frac{\\varepsilon S}{d}\\end{align}C=dεS​​​ 球形电容器(两同心球面)： C=4πεR1R2R2−R1\\begin{align}C=\\frac{4\\pi\\varepsilon R_1R_2}{R_2-R_1}\\end{align}C=R2​−R1​4πεR1​R2​​​​(R2\u0026gt;R1R_2\u0026gt;R_1R2​\u0026gt;R1​) 圆筒形电容器(两同轴圆柱面)： C=2πεlln⁡R2R1\\begin{align}C=\\frac{2\\pi\\varepsilon l}{\\ln{\\frac{R_2}{R_1}}}\\end{align}C=lnR1​R2​​2πεl​​​(R2\u0026gt;R1R_2\u0026gt;R_1R2​\u0026gt;R1​) 电容并联：C=∑CiC=\\sum C_iC=∑Ci​，串联：C−1=∑Ci−1C^{-1}=\\sum C_i^{-1}C−1=∑Ci−1​ 电容率：通常指两极板间介质的相对介电常量 电场能量：\n电场能量的性质：\n只要该点存在电场，就有电场能量 称某电场的能量为范围内所有点电场能量的总和 电容的能量：\nWe=CU22\\begin{align}W_e=\\frac{CU^2}{2}\\end{align}We​=2CU2​​​ 能量密度(一般电场某点的能量)：\nwe=εE22\\begin{align}w_e=\\frac{\\varepsilon E^2}{2}\\end{align}we​=2εE2​​​ 一般电场存储的能量：\nWe=∫VwedV\\begin{align}W_e=\\int_Vw_e\\mathrm dV\\end{align}We​=∫V​we​dV​​ 疑问知识点 平行板电容器补充 易知平行板电容器的电容为\nC=εSd\\begin{align}C=\\frac{\\varepsilon S}{d}\\end{align}C=dεS​​​ 一：往平行板电容器中平行于两板插入电介质和垂直于两板插入电介质的区别\n答：前者为两电容器串联，后者为两电容器并联\n二：插入介质后，平行板电容器间的场强在有/无电源时的区别\n答：无电源时，电势会改变，是因为插入介质后场强改变了，\nE=E0εr\\begin{align}E=\\frac{E_0}{\\varepsilon_r}\\end{align}E=εr​E0​​​​有电源时，电势不改变，则场强不改变(无论中间介质如何改变)，原理是由电源输送的自由电荷场强和插入介质中产生的极化电荷场强抵消\n三：插入介质后，平行板电容器的能量在无电源时的表现\n答：能量会减少，根据\nWe=Q22C\\begin{align}W_e=\\frac{Q^2}{2C}\\end{align}We​=2CQ2​​​可知\n原理是极化电荷削弱了电场，使其能量减少，但电容会增加\n电极化强度和电位移矢量的含义 电极化强度是单位体积内的电偶极矩的矢量和，且Pn=σ极化电荷P_n=\\sigma_{极化电荷}Pn​=σ极化电荷​；因为未极化电介质的电偶极矩矢量和为零，极化后不为零，故以此作为区分 电位移矢量是辅助矢量，是经数学简化后的叠加场 ","permalink":"https://echo.ishian.me/2024/10/06/physics-electrostatic-field/","summary":"整理静电场：库仑定律、电场强度、高斯定理、电势及导体与电介质","title":"大学物理: 静电场"},{"content":"经典力学 质点运动的数学解释 位置矢量：质点在指定参考系下的坐标向量，用r⃗\\vec rr表示，简称位矢 运动方程：位置矢量关于时间的方程，用r⃗=r⃗(t)\\vec r=\\vec r(t)r=r(t)表示，也称运动函数 速度矢量：位矢对时间的一阶导数，用vˉ(t)\\bar v(t)vˉ(t)表示，其方向与位移方向一致 加速度矢量：速度对时间的一阶导数，用aˉ(t)\\bar a(t)aˉ(t)表示，其方向总是指向轨道曲线的内侧 若已知质点的加速度和初始状态，求运动方程相当于解微分方程的初值问题 加速度可分解为a=atτ+ann\\boldsymbol{a}=a_t\\boldsymbol{\\tau}+a_n\\boldsymbol{n}a=at​τ+an​n，其中： ata_tat​为切向加速度，ana_nan​为法向加速度(向心加速度)，τ\\boldsymbol{\\tau}τ为速度的单位切向量，n\\boldsymbol{n}n为速度的单位法向量 at=dvdt、an=v2R\\begin{align}a_t=\\frac{\\mathrm dv}{\\mathrm dt}、a_n=\\frac{v^2}{R}\\end{align}at​=dtdv​、an​=Rv2​​​，其中vvv为速度的大小 牛顿定律 牛顿定律只对惯性系生效\n牛顿第一定律(惯性定律)：任何不受力物体的物体保持静止或匀速直线运动状态\n力是物体运动状态改变的原因 物体具有惯性，即保持其静止或匀速直线运动状态不变的属性 牛顿第二定律：\n动量p=mv\\boldsymbol{p}=m\\boldsymbol{v}p=mv 力 F=dpdt\\begin{align}\\boldsymbol{F}=\\frac{\\mathrm d\\boldsymbol{p}}{\\mathrm dt}\\end{align}F=dtdp​​​，当质量为关于ttt的常数时，F=ma\\boldsymbol{F}=m\\boldsymbol{a}F=ma 力恒定时，质量越大、加速度越小、物体惯性越大 牛顿第三定律：相互作用力总是大小相等、方向相反、作用在一条直线上\n参考系 参考系：可以是任意物体，以该物体作为参考系原点，即称其他物体是相对于该参考系的\n惯性系：在惯性系中，只要物体不受力，它就满足牛一；即惯性系本身不具有加速度\n绝对时空观：\n在不同参考系下(分别记为S、S′S、S\u0026#x27;S、S′，原点为O、O′O、O\u0026#x27;O、O′，质点为PPP)，满足O′P=r′(t)、t=t′\\boldsymbol{O\u0026#x27;P}=\\boldsymbol{r}\u0026#x27;(t)、t=t\u0026#x27;O′P=r′(t)、t=t′\n伽利略变换：设\nu=dOO′(t)dt\\begin{align}\\boldsymbol{u}=\\frac{\\mathrm d\\boldsymbol{OO\u0026#x27;}(t)}{\\mathrm dt}\\end{align}u=dtdOO′(t)​​​，则不同参考系下同一质点：\n位矢关系：r(t)=r′(t)+OO′(t)\\boldsymbol r(t)=\\boldsymbol r\u0026#x27;(t)+\\boldsymbol{OO\u0026#x27;}(t)r(t)=r′(t)+OO′(t)，即OP=OO′+O′P\\boldsymbol{OP}=\\boldsymbol{OO\u0026#x27;}+\\boldsymbol{O\u0026#x27;P}OP=OO′+O′P 速度关系：v=v′+u\\boldsymbol{v}=\\boldsymbol{v}\u0026#x27;+\\boldsymbol{u}v=v′+u 加速度关系： a=a′+dudt\\begin{align}\\boldsymbol{a}=\\boldsymbol{a}\u0026#x27;+\\frac{\\mathrm d\\boldsymbol{u}}{\\mathrm dt}\\end{align}a=a′+dtdu​​​ 伽利略坐标变换：若两惯性系在相对做匀速直线运动，则u=C\\boldsymbol{u}=\\boldsymbol{C}u=C，有：{x=x′+uty=y′z=z′t=t′\\begin{cases}x=x\u0026#x27;+ut\\\\y=y\u0026#x27;\\\\z=z\u0026#x27;\\\\t=t\u0026#x27;\\end{cases}⎩⎨⎧​x=x′+uty=y′z=z′t=t′​\n惯性力存在于非惯性系中，且Fi=−ma\\boldsymbol{F}_i=-m\\boldsymbol{a}Fi​=−ma，其中a\\boldsymbol{a}a为非惯性系的加速度\n关于动量 动量是牛顿第二定律的另一种形式 动量定理：力是动量对时间的导数，冲量是力对时间的积分 冲量 I=∫t1t2F(t)dt=∫t1t2dp=p2−p1\\begin{align}\\boldsymbol{I}=\\int_{t_1}^{t_2}\\boldsymbol{F}(t)\\mathrm dt=\\int_{t_1}^{t_2}\\mathrm d\\boldsymbol{p}=\\boldsymbol{p}_2-\\boldsymbol{p}_1\\end{align}I=∫t1​t2​​F(t)dt=∫t1​t2​​dp=p2​−p1​​​ 动量守恒定律：若某质点系所受合外力为零，则∑ipi=C\\sum\\limits_ i\\boldsymbol{p}_i=\\boldsymbol{C}i∑​pi​=C，例如质点系静止或做匀速直线运动 质心运动定理： 质心坐标 rc=∫Ωρ rdΩm\\begin{align}\\boldsymbol{r}_c=\\frac{\\int_\\Omega\\rho\\ \\boldsymbol{r}\\mathrm d\\Omega}{m}\\end{align}rc​=m∫Ω​ρ rdΩ​​​，其中ccc为质心这个点，r\\boldsymbol{r}r为质点系中任意一点的位矢 若该质点系满足动量守恒定律，则∑iFi=mac\\sum\\limits_ i\\boldsymbol{F}_i=m\\boldsymbol{a}_ci∑​Fi​=mac​ 关于角动量 设质点PPP相对于OOO的位矢为r\\boldsymbol{r}r，且所受外力为F\\boldsymbol{F}F，则定义F\\boldsymbol{F}F对OOO的力矩为M=r×F\\boldsymbol{M}=\\boldsymbol{r\\times F}M=r×F 力矩由力的大小、方向、作用点以及某固定点生成 角动量(动量矩)：与力矩的定义方式类似，L=r×p\\boldsymbol{L}=\\boldsymbol{r\\times p}L=r×p，它包含动量对某固定点的角度信息 角动量定理：力矩是角动量对时间的导数 角动量守恒定律：若某质点系所受合外力矩为零，则∑iLi=C\\sum\\limits_ i\\boldsymbol{L}_i=\\boldsymbol{C}i∑​Li​=C，例如质点系做直线运动或做匀速圆周运动 关于功和功率 力做功 A=∫LFdr=∫LF⋅v dt\\begin{align}A=\\int_L\\boldsymbol{F}\\mathrm d\\boldsymbol{r}=\\int_L\\boldsymbol{F·v}\\ \\mathrm dt\\end{align}A=∫L​Fdr=∫L​F⋅v dt​​，恒力做功A=F⋅ΔrA=\\boldsymbol{F·\\Delta r}A=F⋅Δr 根据格林公式及斯托克斯公式，将做功与路径无关，即F\\boldsymbol{F}F的雅可比行列式为000的力称为保守力 功率 P=dAdt=F⋅v\\begin{align}P=\\frac{\\mathrm dA}{\\mathrm dt}=\\boldsymbol{F·v}\\end{align}P=dtdA​=F⋅v​​ 动能定理： Ek=12mv2\\begin{align}E_k=\\frac12mv^2\\end{align}Ek​=21​mv2​​，A=ΔEkA=\\Delta E_kA=ΔEk​ 保守内力做功A=−ΔEpA=-\\Delta E_pA=−ΔEp​ 机械能守恒定律：如果系统只有保守内力做功，则EM=Ek+Ep=CE_M=E_k+E_p=CEM​=Ek​+Ep​=C 碰撞问题 完全非弹性碰撞：动量守恒，但有非保守内力做功，只满足m合v合=m1v1+m2v2m_合\\boldsymbol{v}_合=m_1\\boldsymbol{v}_1+m_2\\boldsymbol{v}_2m合​v合​=m1​v1​+m2​v2​ 完全弹性碰撞：动量守恒且机械能守恒 矢量式：m1v10+m2v20=m1v1+m2v2m_1\\boldsymbol{v}_{10}+m_2\\boldsymbol{v}_{20}=m_1\\boldsymbol{v}_1+m_2\\boldsymbol{v}_2m1​v10​+m2​v20​=m1​v1​+m2​v2​ m1v1022+m2v2022=m1v122+m2v222\\begin{align}\\frac{m_1v_{10}^2}2+\\frac{m_2v_{20}^2}2=\\frac{m_1v_{1}^2}2+\\frac{m_2v_{2}^2}2\\end{align}2m1​v102​​+2m2​v202​​=2m1​v12​​+2m2​v22​​​​ 若m2m_2m2​初速度为000，则 v1⋅v2=m1−m22m1v22\\begin{align}\\boldsymbol{v}_1\\boldsymbol{·v}_2=\\frac{m_1-m_2}{2m_1}v_2^2\\end{align}v1​⋅v2​=2m1​m1​−m2​​v22​​​，即 cos⁡\u0026lt;v1,v2\u0026gt;=m1−m22m1v2v1\\begin{align}\\cos\u0026lt;\\boldsymbol{v}_1,\\boldsymbol{v}_2\u0026gt;=\\frac{m_1-m_2}{2m_1}\\frac{v_2}{v_1}\\end{align}cos\u0026lt;v1​,v2​\u0026gt;=2m1​m1​−m2​​v1​v2​​​​ 若为对心碰撞，则可总结出更一般的速度式，因为此时cos⁡\u0026lt;v1,v2\u0026gt;=−1\\cos\u0026lt;\\boldsymbol{v}_1,\\boldsymbol{v}_2\u0026gt;=-1cos\u0026lt;v1​,v2​\u0026gt;=−1，动量守恒式变成标量式： {v1=(m1−m2)v10+2m2v20m1+m2v2=2m1v10+(m2−m1)v20m1+m2\\begin{align}\\begin{cases}v_1=\\frac{\\large(m_1-m_2)v_{10}+2m_2 v_{20}}{\\large m_1+m_2}\\\\v_2=\\frac{\\large2m_1v_{10}+(m_2-m_1)v_{20}}{\\large m_1+m_2}\\end{cases}\\end{align}⎩⎨⎧​v1​=m1​+m2​(m1​−m2​)v10​+2m2​v20​​v2​=m1​+m2​2m1​v10​+(m2​−m1​)v20​​​​​(借助平方差公式推导) 刚体力学 角速度大小：\nω=dθdt=1rdldt=vr\\begin{align}\\omega=\\frac{\\mathrm d\\theta}{\\mathrm dt}=\\frac1r\\frac{\\mathrm dl}{\\mathrm dt}=\\frac vr\\end{align}ω=dtdθ​=r1​dtdl​=rv​​​ 角速度方向：v=ω×r\\boldsymbol{v}=\\boldsymbol{\\omega\\times r}v=ω×r\n角加速度和加速度的关系：\nat=dvdt=rdωdt=rα\\begin{align}a_t=\\frac{\\mathrm dv}{\\mathrm dt}=r\\frac{\\mathrm d\\omega}{\\mathrm dt}=r\\alpha\\end{align}at​=dtdv​=rdtdω​=rα​​ an=v2r=rω2\\begin{align}a_n=\\frac{v^2}{r}=r\\omega^2\\end{align}an​=rv2​=rω2​​ a=d(ω×r)dt=α×r+ω×v\\begin{align}\\boldsymbol{a}=\\frac{\\mathrm d(\\boldsymbol{\\omega\\times r})}{\\mathrm dt}=\\boldsymbol{\\alpha\\times r}+\\boldsymbol{\\omega\\times v}\\end{align}a=dtd(ω×r)​=α×r+ω×v​​ 刚体绕定轴转动时，轴向动量和位矢垂直，故轴向角动量的大小Li=∣r×pi∣=mrivi=mri2ωiL_i=|\\boldsymbol{r\\times p}_i|=mr_iv_i=mr_i^2\\omega_iLi​=∣r×pi​∣=mri​vi​=mri2​ωi​\n定义刚体绕某轴旋转的转动惯量为\nJ=mri2=∫Ωρ ri2dΩ\\begin{align}J=mr_i^2=\\int_\\Omega \\rho\\ r_i^2\\mathrm d\\Omega\\end{align}J=mri2​=∫Ω​ρ ri2​dΩ​​，则轴向角动量的大小为Li=JωL_i=J\\omegaLi​=Jω，轴向力矩大小为Mi=JαM_i=J\\alphaMi​=Jα\n刚体定轴转动定律：刚体绕定轴的角加速度和轴向力矩成正比，类比加速度和力的关系F=maF=maF=ma\n转动问题中，\nA=∫θ1θ2Midθ\\begin{align}A=\\int_{\\theta_1}^{\\theta_2}M_i\\mathrm d\\theta\\end{align}A=∫θ1​θ2​​Mi​dθ​​，即外力做的功等于轴向力矩和角位移的积\n转动动能：\nEk=12Jω2\\begin{align}E_k=\\frac12J\\omega^2\\end{align}Ek​=21​Jω2​​ ","permalink":"https://echo.ishian.me/2024/10/03/physics-classic-force/","summary":"整理经典力学：质点运动描述、牛顿定律、参考系与伽利略变换及守恒定律","title":"大学物理: 经典力学"},{"content":"和式 表达方式 一般形式： ∑ρ(k)ak\\begin{align}\\sum_{\\rho(k)}a_k\\end{align}ρ(k)∑​ak​​​，用于简略描述和式，方便推导和理解 定界形式： ∑k=lowerupperak\\begin{align}\\sum_{k=lower}^{upper}a_k\\end{align}k=lower∑upper​ak​​​，用于表示最终结果 Iverson\\rm IversonIverson约定： ∑kak⋅[ρ(k)]\\begin{align}\\sum_{k}a_{k}·[\\rho(k)]\\end{align}k∑​ak​⋅[ρ(k)]​​，即遍历kkk，若满足命题ρ(k)\\rho(k)ρ(k)则加和，否则不加 求解和式 清单法：因和式能很简单地写成递推式{S0=0Sn=Sn−1+an\\begin{cases}S_0=0\\\\S_n=S_{n-1}+a_n\\end{cases}{S0​=0Sn​=Sn−1​+an​​，因此可使用清单法\n求和因子法：适用于求解形如{S0anSn=bnSn−1+cn\\begin{cases}S_0\\\\a_nS_n=b_nS_{n-1}+c_n\\end{cases}{S0​an​Sn​=bn​Sn−1​+cn​​的递推式\n两边乘sn,得snanSn=snbnSn−1+sncn其中sn满足snbn=sn−1an−1(1)因此Tn=snanSn=Tn−1+sncn⇒Tn=s0a0S0+∑k=1nskck其中s0a0只是为了满足(1)式假想的,故Sn=1snan(s1b1S0+∑k=1nskck)因为求和式中只用到s(1∼n),现根据(1)式求出sn,不难得到sn=an−1⋯a1bn⋯b2s1为方便求解,一般令s1=1观察式子可知an和bn不允许有零项\\begin{align}\u0026amp;两边乘s_n,得s_na_nS_n=s_nb_nS_{n-1}+s_nc_n\\\\\u0026amp;其中s_n满足s_nb_n=s_{n-1}a_{n-1}\u0026amp;(1)\\\\\u0026amp;因此T_n=s_na_nS_n=T_{n-1}+s_nc_n\\Rightarrow T_n=s_0a_0S_0+\\sum_{k=1}^ns_kc_k\\\\\u0026amp;其中s_0a_0只是为了满足(1)式假想的,故S_n=\\frac1{s_na_n}\\left(s_1b_1S_0+\\sum_{k=1}^ns_kc_k\\right)\\\\\u0026amp;因为求和式中只用到s(1\\sim n),现根据(1)式求出s_n,不难得到s_n=\\frac{a_{n-1}\\cdots a_1}{b_n\\cdots b_2}s_1\\\\\u0026amp;为方便求解,一般令s_1=1\\\\\u0026amp;观察式子可知a_n和b_n不允许有零项\\end{align}​两边乘sn​,得sn​an​Sn​=sn​bn​Sn−1​+sn​cn​其中sn​满足sn​bn​=sn−1​an−1​因此Tn​=sn​an​Sn​=Tn−1​+sn​cn​⇒Tn​=s0​a0​S0​+k=1∑n​sk​ck​其中s0​a0​只是为了满足(1)式假想的,故Sn​=sn​an​1​(s1​b1​S0​+k=1∑n​sk​ck​)因为求和式中只用到s(1∼n),现根据(1)式求出sn​,不难得到sn​=bn​⋯b2​an−1​⋯a1​​s1​为方便求解,一般令s1​=1观察式子可知an​和bn​不允许有零项​(1)​ 当sncns_nc_nsn​cn​是较好求的式子时，可用求和因子法\n事实上求和因子法大多数情况下无法求给定数列的和，只能解上述ana_nan​和bnb_nbn​满足特定性质的递推式\n因为给定数列ana_nan​，写成和式的递推式Sn=Sn−1+anS_n=S_{n-1}+a_nSn​=Sn−1​+an​，会发现sn=s1s_n=s_1sn​=s1​，最终仍无法求ana_nan​的和\n扰动法：取出首项或尾项，构造出一个等式，并使两部分的和式都和SnS_nSn​套上关系即可\n例如给定数列ana_nan​，有\nSn+1=Sn+an=a1+∑k=2n+1ak\\begin{align}S_{n+1}=S_n+a_n=a_1+\\sum_{k=2}^{n+1}a_k\\end{align}Sn+1​=Sn​+an​=a1​+k=2∑n+1​ak​​​扰动法的目的是将\n∑k=2n+1ak=∑k=1nak+1\\begin{align}\\sum_{k=2}^{n+1}a_k=\\sum_{k=1}^na_{k+1}\\end{align}k=2∑n+1​ak​=k=1∑n​ak+1​​​分解为SnS_nSn​和一个相对好求的和式\n例求\n∑k=0nkxk\\begin{align}\\sum_{k=0}^nkx^k\\end{align}k=0∑n​kxk​​：\n解:Sn+1=Sn+(n+1)xn+1=0+∑k=1n+1kxk=xSn+∑k=0nxk+1=xSn+xn+1−xx−1⇒Sn=(n+1)xn+1(x−1)−x−xn+1(x−1)2\\begin{align}\u0026amp;解:S_{n+1}=S_n+(n+1)x^{n+1}=0+\\sum_{k=1}^{n+1}kx^k\\\\\u0026amp;=xS_n+\\sum_{k=0}^nx^{k+1}=xS_n+\\frac{x^{n+1}-x}{x-1}\\\\\u0026amp;\\Rightarrow S_n=\\frac{(n+1)x^{n+1}(x-1)-x-x^{n+1}}{(x-1)^2}\\end{align}​解:Sn+1​=Sn​+(n+1)xn+1=0+k=1∑n+1​kxk=xSn​+k=0∑n​xk+1=xSn​+x−1xn+1−x​⇒Sn​=(x−1)2(n+1)xn+1(x−1)−x−xn+1​​​ 例如快速排序的平均比较次数(具体数学上和数据结构里不同，这里以具体数学为例)：\nCn={0,n=0n+1+2n∑i=0n−1Ci,n\u0026gt;0C_n=\\begin{cases}0,\u0026amp;n=0\\\\n+1+\\frac2n\\sum\\limits_{i=0}^{n-1}C_i,\u0026amp;n\u0026gt;0\\end{cases}Cn​=⎩⎨⎧​0,n+1+n2​i=0∑n−1​Ci​,​n=0n\u0026gt;0​\n即nCn=2∑i=0n−1Ci+n2+n(n−1)Cn−1=2∑i=0n−2Ci+(n−1)2+n−1得nCn−(n−1)Cn−1=2Cn−1+2n即nCn=(n+1)Cn−1+2n\\begin{align}\u0026amp;即nC_n=2\\sum_{i=0}^{n-1}C_i+n^2+n\\\\\u0026amp;(n-1)C_{n-1}=2\\sum_{i=0}^{n-2}C_i+(n-1)^2+n-1\\\\\u0026amp;得nC_n-(n-1)C_{n-1}=2C_{n-1}+2n\\\\\u0026amp;即nC_n=(n+1)C_{n-1}+2n\\end{align}​即nCn​=2i=0∑n−1​Ci​+n2+n(n−1)Cn−1​=2i=0∑n−2​Ci​+(n−1)2+n−1得nCn​−(n−1)Cn−1​=2Cn−1​+2n即nCn​=(n+1)Cn−1​+2n​​求和因子为sn=an−1⋯a1bn⋯b2s1=(n−1)!(n+1)⋯3s1=2(n+1)n,(令s1=1)snnCn=sn(n+1)Cn−1+2nsn=sn−1(n−1)Cn−1+4n+1因此snnCn=s1b1C0+4∑i=1n1i+1=4(Hn+1−1)Cn=2(n+1)(Hn+1−1)=2(n+1)(Hn+1n+1−1)=2(n+1)Hn−2n\\begin{align}\u0026amp;求和因子为s_n=\\frac{a_{n-1}\\cdots a_1}{b_n\\cdots b_2}s_1=\\frac{(n-1)!}{(n+1)\\cdots3}s_1=\\frac{2}{(n+1)n},(令s_1=1)\\\\\u0026amp;s_nnC_n=s_n(n+1)C_{n-1}+2ns_n=s_{n-1}(n-1)C_{n-1}+\\frac4{n+1}\\\\\u0026amp;因此s_nnC_n=s_1b_1C_0+4\\sum_{i=1}^{n}\\frac1{i+1}=4(H_{n+1}-1)\\\\\u0026amp;C_n=2(n+1)(H_{n+1}-1)=2(n+1)(H_n+\\frac1{n+1}-1)=2(n+1)H_n-2n\\end{align}​求和因子为sn​=bn​⋯b2​an−1​⋯a1​​s1​=(n+1)⋯3(n−1)!​s1​=(n+1)n2​,(令s1​=1)sn​nCn​=sn​(n+1)Cn−1​+2nsn​=sn−1​(n−1)Cn−1​+n+14​因此sn​nCn​=s1​b1​C0​+4i=1∑n​i+11​=4(Hn+1​−1)Cn​=2(n+1)(Hn+1​−1)=2(n+1)(Hn​+n+11​−1)=2(n+1)Hn​−2n​​ 多重和式 [1≤i\u0026lt;j≤n]+[1≤k\u0026lt;j≤n]=[1≤j,k≤n]−[1≤i=j≤n][1\\le i\u0026lt;j\\le n]+[1\\le k\u0026lt;j\\le n]=[1\\le j,k\\le n]-[1\\le i=j\\le n][1≤i\u0026lt;j≤n]+[1≤k\u0026lt;j≤n]=[1≤j,k≤n]−[1≤i=j≤n] 切比雪夫单调不等式：若两序列是单调的： {单调性相同,(∑k=1nak)(∑k=1nbk)≤n∑k=1nakbk单调性不同,上式为大于等于\\begin{align}\\begin{cases}单调性相同,\u0026amp;\\left(\\sum\\limits_{k=1}^na_k\\right)\\left(\\sum\\limits_{k=1}^nb_k\\right)\\le n\\sum\\limits_{k=1}^n a_kb_k\\\\单调性不同,\u0026amp;上式为大于等于\\end{cases}\\end{align}⎩⎨⎧​单调性相同,单调性不同,​(k=1∑n​ak​)(k=1∑n​bk​)≤nk=1∑n​ak​bk​上式为大于等于​​​ 应用于nkn^knk前nnn项和公式： 摄动求和：应对nk+1n^{k+1}nk+1摄动 清单求和：令Rn=1,n,n2,⋯ ,nk+1R_n=1,n,n^2,\\cdots,n^{k+1}Rn​=1,n,n2,⋯,nk+1 积分替换：求积分和求和的误差，并通过其递推式求解误差 下阶幂定义(xxx为实数，mmm为整数)：xm‾={x(x−1)⋯(x−m+1),m\u0026gt;01,m=01(x+1)⋯(x−m),m\u0026lt;0x^{\\underline m}=\\begin{cases}x(x-1)\\cdots(x-m+1),\u0026amp;m\u0026gt;0\\\\1,\u0026amp;m=0\\\\\\frac1{(x+1)\\cdots(x-m)},\u0026amp;m\u0026lt;0\\end{cases}xm​=⎩⎨⎧​x(x−1)⋯(x−m+1),1,(x+1)⋯(x−m)1​,​m\u0026gt;0m=0m\u0026lt;0​ 下阶幂的差分：Δ(xm‾)=(x+1)m‾−xm‾=mxm−1‾\\Delta(x^{\\underline m})=(x+1)^{\\underline{m}}-x^{\\underline m}=mx^{\\underline{m-1}}Δ(xm​)=(x+1)m​−xm​=mxm−1​ 定和分： ∑abg(x)δx=∑k=ab−1g(k)=f(x)∣ab,其中g(x)=Δf(x)\\begin{align}\\sum_a^bg(x)\\delta x=\\sum_{k=a}^{b-1}g(k)=f(x)\\Bigg|_a^b,其中g(x)=\\Delta f(x)\\end{align}a∑b​g(x)δx=k=a∑b−1​g(k)=f(x)​ab​,其中g(x)=Δf(x)​​ 下阶幂的定和分可配合第二类斯特林数的原始定义，求解xkx^kxk的和(kkk为常数) 指数函数的差分：Δ(cx)=cx+1−cx=(c−1)cx\\Delta(c^x)=c^{x+1}-c^x=(c-1)c^xΔ(cx)=cx+1−cx=(c−1)cx，因此**2x2^x2x的差分等于其自身** 和分求指数函数和： ∑k=0nck=∑0n+1cxδx=cn+1−1c−1\\begin{align}\\sum_{k=0}^nc^k=\\sum_{0}^{n+1}c^x\\delta x=\\frac{c^{n+1}-1}{c-1}\\end{align}k=0∑n​ck=0∑n+1​cxδx=c−1cn+1−1​​​ ","permalink":"https://echo.ishian.me/2024/10/02/concretemath-summation/","summary":"讲解和式的表示方法与清单法、求和因子法、扰动法等求和技巧及多重和式","title":"具体数学: 和式"},{"content":"并查集 概述 并查集用于解决不相交集合的问题，高效地提供两种方法：\n合并：用于将两个不相交集合合并 查询：用于查询一个元素在哪个集合里 基于上述操作，并查集这种数据结构可用于解决许多问题，例如：\n查询两个元素是否在同一集合，也即用于判断两个元素是否相通 合并两个不相交元素所在集合 找出集合的总个数 找出一个集合的元素总个数 但这种结构是有局限的，不能通过它来解决诸如以下的问题：\n分离集合问题，并查集中的集合只并不分，也不能删除元素 不会保存节点间的关系，只会保存集合与节点的关系；这种问题应依靠图来解决 结构与简单实现 具体结构 并查集是一种树状结构，对于一个集合，只选出一个元素作为代表元素，用它来标识一个集合；代表元素指向它自己，至于集合内的其它元素，或直接或间接地，最后都将指向这个代表元素\n在没有任何优化的并查集中，并和查是这样实现的：\n查：递归搜索节点的父节点，直到找到祖宗节点(指向自己的节点)，则它属于这个祖宗节点所代表的集合 并：寻找两元素的祖宗节点，让其中之一指向另外的节点即可 初始化 并查集最浅显的实现方法是普通数组，下标代表元素编号，数组存储的是元素的父节点下标，初始化时，让所有元素指向自己：\nc\u0026#43;\u0026#43;int fa[n] = {0,1,...,n-1};find() 查询操作最简单的方法是递归(无优化)：\nc\u0026#43;\u0026#43;int find(int number) { // 找到祖宗节点,递归结束. return number == fa[number] ? number : find(fa[number]); }Union() 合并操作需要借助查询(无优化)：\nc\u0026#43;\u0026#43;void Union(int num_1, int num_2) { fa[find(num_1)] = find(num_2); }方法优化 路径压缩 对查询来说，由于每次合并后都将使树深度加一，故递归层数加一，则可以采取路径压缩方法，即沿途让每个元素直接指向父节点：\nc\u0026#43;\u0026#43;int find() { return fa[number] = number == fa[number] ? number : find(fa[number]); }这种方法有极大可能缩短搜索路径，但第一次搜索依然有可能递归多层；对于最后一个节点来说，只要一直不对它进行查询，它所在路径深度仍然较大\n需要注意的是，这种优化会覆盖节点与父节点间的信息，如果实际问题中需要维护中间节点，就不应该采用这种优化方法\n按秩合并 对合并来说，每次合并应该让深度小的集合指向深度大的集合，这能够使新集合的秩(即深度)不增大(否则会加一)，这种优化需要额外维护一个rank数组，该数组下标表示元素编号，只存储祖宗节点所在集合的深度(未进行路径压缩)：\nc\u0026#43;\u0026#43;int rank[n] {1,1,...,1}; void Union(int num_1, int num_2) { int head_1 = find(num_1), head_2 = find(num_2); if (rank[head_1] \u0026lt; rank[head_2]) { fa[head_1] = head_2; return; } else if (rank[head_1] == rank[head_2]) { ++rank[head_1]; // 相等时树深仍加1. } fa[head_2] = head_1; // 1树更深或深度相同,都让2树指向1树. }如果希望同时进行路径压缩和按秩合并优化，那么可能需要维护全局的rank防止树深的随意变化\n或者更改秩的定义，在上述定义中，秩是树的绝对深度，如果要进行路径压缩：\n定义秩为树深的上界 定义秩为元素个数，因为元素多的树倾向于更深，同时还能存储元素个数 所有这些定义方法都只是尝试不增加树深，不一定合并出更浅的树\n管理非整型数据 上述并查集的节点数据都是基础类型的，如果需要管理其它类型，可以尝试让数组存储该类对象指针的结构体，在查询时，不采取递归而是循环的方式，返回头结点指针：\nc\u0026#43;\u0026#43;struct Struct { Class* me; int father; }; Struct fa[n] {{\u0026amp;Obj0,0},{\u0026amp;Objn_1,n-1}}; const Class* find(int index) { while (index != fa[index].father) { // 较难直观地进行路径压缩,除非允许再循环一次. index = fa[index].father; } return fa[index].me; }这样的并查集编号都为整型，实际问题中很难知道一个对象的具体编号，这时可通过指针查询：\nc\u0026#43;\u0026#43;class Class { // 需要该类含有指向父节点的指针. Class* fa; }; Class* find(const Class* p) { return p-\u0026gt;fa = p == p-\u0026gt;father ? p : find(p-\u0026gt;father); }但实际情况中，通过整型数组与哈希表映射(键为value_type，值为整型)相结合来实现更为有效\n代码 ","permalink":"https://echo.ishian.me/2024/10/02/algo-ufs/","summary":"介绍并查集的原理、合并与查询操作、路径压缩与按秩合并优化","title":"数据结构: 并查集"},{"content":"近世代数 群 半群定义 群和半群是特殊的代数，都只含一个非空集合和一个二元运算，因此元素运算时可省略运算符号 给定代数\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;： 半群：若∘\\circ∘可结合，称该代数为半群 可交换半群：在半群基础上，∘\\circ∘可交换 独异点/含幺半群/拟半群：在半群基础上，∘\\circ∘有幺元 子半群：半群的所有子代数都是半群，称为前者的子半群 循环半群：给定半群M,∘M,\\circM,∘，元素a∈Ma\\in Ma∈M，由aaa自身进行nnn次运算得到的集合为S:{ak∣k∈Z}S:\\{a^k|k\\in Z\\}S:{ak∣k∈Z}，称： aaa生成了循环半群\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;，aaa为生成元 \u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;为\u0026lt;M,∘\u0026gt;\u0026lt;M,\\circ\u0026gt;\u0026lt;M,∘\u0026gt;的循环子半群 生成集：给定集合G⊆SG\\subseteq SG⊆S，若所有SSS的元素可由GGG中元素及其相互运算后得到，则称GGG为生成集 积半群：两个半群的积代数为积半群 群的性质 群：若独异点中的每个元素都有逆元，称该独异点为群 基本性质： 封闭性：群作为一个代数拥有的性质 结合律：群作为一个半群拥有的性质 存在单位元：群作为一个独异点拥有的性质 所有元素作为逆元：群拥有的性质 幂运算规则： an={e,n=0aan−1,n\u0026gt;0(a−1)−n,n\u0026lt;0a^n=\\begin{cases}e,\u0026amp;n=0\\\\aa^{n-1},\u0026amp;n\u0026gt;0\\\\(a^{-1})^{-n},\u0026amp;n\u0026lt;0\\end{cases}an=⎩⎨⎧​e,aan−1,(a−1)−n,​n=0n\u0026gt;0n\u0026lt;0​ (ab)−1=b−1a−1(ab)^{-1}=b^{-1}a^{-1}(ab)−1=b−1a−1，利用可约律证 每个非零元素都是可约元，因此方程有唯一解 元素的阶： 使ar=ea^r=ear=e的最小的正整数rrr，记为∣a∣=r|a|=r∣a∣=r 若存在ak=ea^k=eak=e，则必有k≡0(mod r)k\\equiv0(\\rm{mod}\\ r)k≡0(mod r) ∣a−1∣=∣a∣|a^{-1}|=|a|∣a−1∣=∣a∣ Abel\\rm AbelAbel群：即可交换群，满足(ab)n=anbn(ab)^n=a^nb^n(ab)n=anbn；证明一个群为可交换群最简单的方法为证明abab=a2b2abab=a^2b^2abab=a2b2 子群 若群GGG的非空子集HHH关于GGG的运算构成群，称其为子群，根据集合关系有真子群之分，通过≤\\le≤、\u0026lt;\u0026lt;\u0026lt;说明其关系\n判定方法：\n∀a∀b(a,b∈H→ab∈H∧a−1∈H)\\forall a\\forall b(a,b\\in H\\rightarrow ab\\in H\\wedge a^{-1}\\in H)∀a∀b(a,b∈H→ab∈H∧a−1∈H)，即同时满足子代数、所有元素有逆元的性质 只需证明含幺元，aa−1=e∈Haa^{-1}=e\\in Haa−1=e∈H ∀a∀b(a,b∈H→ab−1∈H)\\forall a\\forall b(a,b\\in H\\rightarrow ab^{-1}\\in H)∀a∀b(a,b∈H→ab−1∈H) (a,b∈H→ab−1∈H)⇒(a,a)→e∈H⇒(e,a)→a−1⇒(a,b−1)→ab∈H\\begin{align}(a,b\\in H\\rightarrow ab^{-1}\\in H)\\Rightarrow(a,a)\\rightarrow e\\in H\\Rightarrow (e,a)\\rightarrow a^{-1}\\Rightarrow(a,b^{-1})\\rightarrow ab\\in H\\end{align}(a,b∈H→ab−1∈H)⇒(a,a)→e∈H⇒(e,a)→a−1⇒(a,b−1)→ab∈H​​ HHH有穷，且∀a∀b(a,b∈H→ab∈H)\\forall a\\forall b(a,b\\in H\\rightarrow ab\\in H)∀a∀b(a,b∈H→ab∈H) 有穷⇒(∃k)ak=e∈H∧aak−1=e∈H⇒a−1=ak−1∈H\\begin{align}有穷\\Rightarrow(\\exist k)a^k=e\\in H\\wedge aa^{k-1}=e\\in H\\Rightarrow a^{-1}=a^{k-1}\\in H\\end{align}有穷⇒(∃k)ak=e∈H∧aak−1=e∈H⇒a−1=ak−1∈H​​ 生成的子群：设a∈Ga\\in Ga∈G，则由aaa生成的群{ak∣k∈Z}\\{a^k|k\\in Z\\}{ak∣k∈Z}是GGG的子群，记作\u0026lt;a\u0026gt;\u0026lt;a\u0026gt;\u0026lt;a\u0026gt;\n中心子群：称{a∈G ∣ ∀x(x∈G∧ax=xa)}\\{a\\in G\\ |\\ \\forall x(x\\in G\\wedge ax=xa)\\}{a∈G ∣ ∀x(x∈G∧ax=xa)}为GGG的中心\n子群的并和交：若H,K≤GH,K\\le GH,K≤G\nH∩K≤GH\\cap K\\le GH∩K≤G H∪K⇔(H⊆K)∨(K⊆H)H\\cup K\\Leftrightarrow (H\\subseteq K)\\vee (K\\subseteq H)H∪K⇔(H⊆K)∨(K⊆H) 子群格：偏序集\u0026lt;{H ∣ H≤G},⊆\u0026gt;\u0026lt;\\{H\\ |\\ H\\le G\\},\\subseteq\u0026gt;\u0026lt;{H ∣ H≤G},⊆\u0026gt;为GGG的子群格\n陪集：HHH为GGG的子群，左陪集定义为aH={a∘h ∣ h∈H}aH=\\{a\\circ h\\ |\\ h\\in H\\}aH={a∘h ∣ h∈H}，其中a∈Ga\\in Ga∈G\n陪集相当于HHH在GGG中的扩充，陪集通常不一定是GGG的子群 存在陪集eH=HeH=HeH=H 所有陪集的阶相等，等于∣H∣|H|∣H∣ 若左陪集和右陪集总相等，称该子群为正规子群 左陪集关系：CH={\u0026lt;a,b\u0026gt; ∣ a,b∈G∧b−1a∈H}C_H=\\{\u0026lt;a,b\u0026gt;\\ |\\ a,b\\in G\\wedge b^{-1}a\\in H\\}CH​={\u0026lt;a,b\u0026gt; ∣ a,b∈G∧b−1a∈H}\n陪集关系是一种等价关系\naH=[a]CHaH=[a]_{C_H}aH=[a]CH​​，即GGG中任意元素在HHH上的陪集等于该元素所属的等价类\n充分性:∀h∈H,a、ah∈aH;令b=ah,即h=ba−1⇒a,b∈G∧ba−1∈H必要性:a,b∈G∧ba−1=h∈H⇒b=ah∈aH\\begin{align}\u0026amp;充分性:\\forall h\\in H,a、ah\\in aH;令b=ah,即h=ba^{-1}\\Rightarrow a,b\\in G\\wedge ba^{-1}\\in H\\\\\u0026amp;必要性:a,b\\in G\\wedge ba^{-1}=h\\in H\\Rightarrow b=ah\\in aH\\end{align}​充分性:∀h∈H,a、ah∈aH;令b=ah,即h=ba−1⇒a,b∈G∧ba−1∈H必要性:a,b∈G∧ba−1=h∈H⇒b=ah∈aH​​ 意义：在找出任意一个子群后，可通过陪集(扩充)来找到其余的所有陪集，而由陪集关系导出的等价类和陪集的对应关系，可得**HHH的不相同陪集个数等于总等价类个数**\n所有陪集的并集等于GGG，任意两个不相等陪集完全不相交\n拉格朗日定理：有限群的阶一定能被任意子群HHH的阶整除，且商等于等价类的个数\n存在a是k阶元⇔k整除∣G∣存在a是k阶元\\Leftrightarrow k整除|G|存在a是k阶元⇔k整除∣G∣ 循环群 若GGG中任意元素能由a∈Ga\\in Ga∈G的任意次幂表示，则称G=\u0026lt;a\u0026gt;G=\u0026lt;a\u0026gt;G=\u0026lt;a\u0026gt;，aaa为生成元 找出循环群GGG的生成元： 若GGG为无限循环群，则只有a,a−1a,a^{-1}a,a−1是生成元 若GGG为nnn阶循环群，则aka^kak是生成元，其中kkk小于nnn且与nnn互素，因此共有φ(n)\\varphi(n)φ(n)个生成元 循环群的子群仍是循环群，无限循环群的子群除平凡群外都是无限循环群 nnn阶循环群G=\u0026lt;a\u0026gt;G=\u0026lt;a\u0026gt;G=\u0026lt;a\u0026gt;的子群：若nnn有kkk个正因子，则恰有kkk个子群，每个正因子ddd唯一对应一个ddd阶子群，且 a∣G∣d\\begin{align}a^{\\large\\frac{|G|}{d}}\\end{align}ad∣G∣​​​为该子群的生成元 环、域、格 环 具有两个二元运算的群，通常定义为\u0026lt;R,+,×\u0026gt;\u0026lt;R,+,\\times\u0026gt;\u0026lt;R,+,×\u0026gt;，其中： \u0026lt;R,+\u0026gt;\u0026lt;R,+\u0026gt;\u0026lt;R,+\u0026gt;构成Abel\\rm AbelAbel群 \u0026lt;R,×\u0026gt;\u0026lt;R,\\times\u0026gt;\u0026lt;R,×\u0026gt;构成乘法半群 ×\\times×对+++满足分配律 称加法幺元为环的零元 可交换环、含幺环、无零因子环：乘法半群满足交换律、含幺元、若两数乘积为零元则必有一项为零元 布尔环：乘法半群满足等幂律 整环：同时满足可交换环、含幺环、无零因子环的性质 域 域的定义： RRR是整环的基础上，乘法群中任意元素有其逆元 RRR是环的基础上，乘法群是Able\\rm AbleAble群 格 偏序格：任意有两元素的子集都在整个集合中存在最小上界和最大下界的偏序集 代数格：代数\u0026lt;L,∘,×\u0026gt;\u0026lt;L,\\circ,\\times\u0026gt;\u0026lt;L,∘,×\u0026gt;，且∘,×\\circ,\\times∘,×满足交换律、结合律、等幂律、吸收律 其中，定义每个元素的∘\\circ∘运算为其最大下界，×\\times×运算为其最小上界 偏序格和代数格等价 完全格：每个非空子集均有上下确界 有界格：有上下确界，完全格一定是有界格 分配格：两运算满足分配律 有补格(有余格)：对任意两元素的最小上界等于整个格的上确界，最大下界等于整个格的下确界 离散数学（格与布尔代数） - gonghr - 博客园 (cnblogs.com)\n","permalink":"https://echo.ishian.me/2024/09/27/discretemath-abstract-algebra/","summary":"讲解半群、群、子群、循环群及环、域等近世代数结构的定义与性质","title":"离散数学: 近世代数（群、环、域）"},{"content":"递归问题分析 递归函数定义 递归被用于定义数学函数或归纳证明 递归的数学模型分为基础和递归两部分 通过递归证明命题被称为数学归纳法，以正确的最小命题为基础，分为递归基础、递归假设、递归步骤三部分 汉诺塔问题 问题描述：将nnn个从上到下大小依次递增的盘片将AAA柱借助BBB柱移到CCC柱，其中不允许大盘片在小盘片上，求解移动次数\n问题分析：将nnn盘从AAA柱移到CCC柱，需先将n−1n-1n−1盘通过CCC柱移到BBB柱，再将第nnn盘片直接移到CCC柱，然后将n−1n-1n−1盘通过AAA柱移到CCC柱；若只剩一盘，则可直接移动\n递归式：{T(1)=1T(n)=2T(n−1)+1\\begin{cases}T(1)=1\\\\T(n)=2T(n-1)+1\\end{cases}{T(1)=1T(n)=2T(n−1)+1​，即移动两次n−1n-1n−1盘，一次直接移动\n通项式：T(n)=2n−1T(n)=2^n-1T(n)=2n−1，可证明其是最优解\n代码求解移动过程：\nc\u0026#43;\u0026#43;void RecursionSolution(int n, int from, int use, int to) { if (n == 1) { // from -\u0026gt; to directly cout \u0026lt;\u0026lt; from \u0026lt;\u0026lt; \u0026#34;-\u0026gt;\u0026#34; \u0026lt;\u0026lt; to \u0026lt;\u0026lt; endl; return; } RecursionSolution(n - 1, from, to, use); cout \u0026lt;\u0026lt; from \u0026lt;\u0026lt; \u0026#34;-\u0026gt;\u0026#34; \u0026lt;\u0026lt; to \u0026lt;\u0026lt; endl; RecursionSolution(n - 1, use, from, to); } int HanoiTower(int n) { RecursionSolution(n, 0, 1, 2); // 输出移动过程 return (1 \u0026lt;\u0026lt; n) - 1; // 返回移动次数 } 直线划分平面问题 问题描述：用nnn条直线划分一个平面，求出能划出的最多的部分数\n问题分析：S(0)=1,S(1)=2S(0)=1,S(1)=2S(0)=1,S(1)=2；n≥2n\\ge 2n≥2时，每次使其和剩下n−1n-1n−1条直线都有交点，则划分部分最多\n递归式：{S(0)=1S(n)=S(n−1)+n\\begin{cases}S(0)=1\\\\S(n)=S(n-1)+n\\end{cases}{S(0)=1S(n)=S(n−1)+n​\n通项式：\nS(n)=n(n+1)2+1\\begin{align}S(n)=\\frac{n(n+1)}2+1\\end{align}S(n)=2n(n+1)​+1​​ 证明其必定能做到上述操作：将n−1n-1n−1次划分后的交点用圆围起来，用直径lll划分该圆，使交点平均地分布在圆的两部分(若交点数为奇数，则让lll穿过一点后再平均分配)，则在lll划分的两个部分中，这些直线在圆外是不相交(交点在圆内)且不为同一直线(同一直线被lll划分在其两边)，平移lll至圆外，则必定能交到n−1n-1n−1个交点\n推广：平面角划分平面问题\n分析：使角始终处于上次划分后区域最大的部分，延长角的两条射线，则转换为2n2n2n条直线划分平面问题\n递归式：S(n)=S(2n)−2n\\mathscr S(n)=S(2n)-2nS(n)=S(2n)−2n\n通项式：S(n)=2n2−n+1\\mathscr S(n)=2n^2-n+1S(n)=2n2−n+1\n圆划分平面问题：\n每增加一个圆，新增圆与其余所有圆都相交且有两个交点，新增部分数目与新增交点数目相同，即：\nTn={2,n=1Tn−1+2(n−1),n\u0026gt;1T_n=\\begin{cases}2,\u0026amp;n=1\\\\T_{n-1}+2(n-1),\u0026amp;n\u0026gt;1\\end{cases}Tn​={2,Tn−1​+2(n−1),​n=1n\u0026gt;1​ 因此\nTn=T1+2∑i=1n−1i=2+n(n−1)\\begin{align}T_n=T_1+2\\sum_{i=1}^{n-1}i=2+n(n-1)\\end{align}Tn​=T1​+2i=1∑n−1​i=2+n(n−1)​​ 约瑟夫问题 问题描述：使编号依次递增(编号从iii开始)的人(共有nnn人)按顺序围成一圈，每mmm次报数后踢出该人，求幸存者的编号\n问题分析：要知道f(n,m)f(n,m)f(n,m)，就需要先知道f(n−1,m)f(n-1,m)f(n−1,m)，但后者从iii开始，为了使前者能转换成后者，每次踢出后，需要找出其编号对应关系，若用[k][k][k]表示下一轮报数从kkk开始，则：{f(n,m)踢出一人:i,i+1,⋯ ,i+m−2,[i+m],⋯ ,i+nf(n−1,m):[i],i+1,⋯ ,i+n−1\\begin{cases}f(n,m)踢出一人:i,i+1,\\cdots,i+m-2,[i+m],\\cdots,i+n\\\\f(n-1,m):[i],i+1,\\cdots,i+n-1\\end{cases}{f(n,m)踢出一人:i,i+1,⋯,i+m−2,[i+m],⋯,i+nf(n−1,m):[i],i+1,⋯,i+n−1​\n当i=0i=0i=0时，可找到对应关系：f(n,m)=f(n−1,m)% nf(n,m)=f(n-1,m)\\%\\ nf(n,m)=f(n−1,m)% n\n使[][][]括起的编号一一对应后，可找到如下关系：f(n,m)=(f(n−1,m)+m−i)% n+if(n,m)=(f(n-1,m)+m-i)\\%\\ n+if(n,m)=(f(n−1,m)+m−i)% n+i\n其中iii为偏移量，减去iii即转换成编号从000开始的情况，最后加上iii得到原问题从iii开始的情况\n上述情况假设m\u0026lt;nm\u0026lt;nm\u0026lt;n，实际上m≥nm\\ge nm≥n的情况下，踢出一人后报数从i+(m% n)i+(m\\%\\ n)i+(m% n)开始，因模运算的性质也满足上述递归式\n递归式：{f(1,m)=if(n,m)=(f(n−1,m)+m−i)% n+i\\begin{cases}f(1,m)=i\\\\f(n,m)=(f(n-1,m)+m-i)\\%\\ n+i\\end{cases}{f(1,m)=if(n,m)=(f(n−1,m)+m−i)% n+i​，从iii开始编号\n若要求从j(i≤j≤i+n)j(i\\le j\\le i+n)j(i≤j≤i+n)开始报数，可先求从iii开始报数，然后增加j−ij-ij−i(注意模nnn)\n递归求解代码：\nc\u0026#43;\u0026#43;int Solution(int n, int m, int i) { // 从i开始编号, 从i开始报数 return n == 1 ? i : (Solution(n - 1, m, i) + m - i) % n + i; } int Josephus(int n, int m, int i, int j) { // 从i开始编号, 从j开始报数 return (Solution(n, m, i) - i) % n + j - i; } f(n,2)f(n,2)f(n,2)及推广的通项式求解 上述过程实际只给出了递归式，实际上当m=2m=2m=2时，原问题是以下问题的一个特例：\n给出递归式{f(j)=αj,0\u0026lt;j\u0026lt;mf(mn+j)=cf(n)+βj,0≤j\u0026lt;m,n\u0026gt;0\\begin{cases}f(j)=\\alpha_j,\u0026amp;0\\lt j\\lt m\\\\f(mn+j)=cf(n)+\\beta_j,\u0026amp;0\\le j\\lt m,n\u0026gt;0\\end{cases}{f(j)=αj​,f(mn+j)=cf(n)+βj​,​0\u0026lt;j\u0026lt;m0≤j\u0026lt;m,n\u0026gt;0​，求出f(x),x∈N+f(x),x\\in N^+f(x),x∈N+\n清单法(猜证法)：即给定f(n)f(n)f(n)可能的形式，通过给定特定的f(n)=X(n)f(n)=X(n)f(n)=X(n)(要求X(n)X(n)X(n)能求出唯一对应的(αj,βj)(\\alpha_j,\\beta_j)(αj​,βj​)值)，反过来推出f(n)f(n)f(n)中各系数的值\n容易得到，如果递推式中含kkk个变量，则至少需要找出kkk个上述函数\n当m=c=2m=c=2m=c=2时，即所谓f(n,2)f(n,2)f(n,2)，可很容易地找到三个函数：\n猜测\n令f(n)=1,解得{α1=1β0=β1=−1f(n)=1,解得\\begin{cases}\\alpha_1=1\\\\\\beta_0=\\beta_1=-1\\end{cases}f(n)=1,解得{α1​=1β0​=β1​=−1​，即A−B−C=1A-B-C=1A−B−C=1\n令f(n)=n,解得{α1=β1=1β0=0f(n)=n,解得\\begin{cases}\\alpha_1=\\beta_1=1\\\\\\beta_0=0\\end{cases}f(n)=n,解得{α1​=β1​=1β0​=0​，即A+C=nA+C=nA+C=n\n令f(n)=2k(其中n=2k+l),解得{α1=1β0=β1=0f(n)=2^k(其中n=2^k+l),解得\\begin{cases}\\alpha_1=1\\\\\\beta_0=\\beta_1=0\\end{cases}f(n)=2k(其中n=2k+l),解得{α1​=1β0​=β1​=0​，即A=2kA=2^kA=2k\n联立得{A=2kB=2k+1−n−1=2k−l−1C=n−2k=l\\begin{cases}A=2^k\\\\B=2^{k+1}-n-1=2^k-l-1\\\\C=n-2^k=l\\end{cases}⎩⎨⎧​A=2kB=2k+1−n−1=2k−l−1C=n−2k=l​，即f(n)=2kα1+(2k−l−1)β0+lβ1f(n)=2^k\\alpha_1+(2^k-l-1)\\beta_0+l\\beta_1f(n)=2kα1​+(2k−l−1)β0​+lβ1​\n当α1=1、β0=−1、β1=1\\alpha_1=1、\\beta_0=-1、\\beta_1=1α1​=1、β0​=−1、β1​=1时，正是f(n,2)=2l+1f(n,2)=2l+1f(n,2)=2l+1 很不幸，无法给出如此多的函数来使用清单法求出一般形式的通解，但能根据递推式给出如下关系：\n令n=(n)m=bkbk−1⋯b0n=(n)_m=b_kb_{k-1}\\cdots b_0n=(n)m​=bk​bk−1​⋯b0​，每右移一位相当于取nm+jnm+jnm+j中的nnn，原本的末位则是jjj：\nf((n)m)=cf((n)m\u0026gt;\u0026gt;1)+βb0=c(cf((n)m\u0026gt;\u0026gt;2)+βb1)+βb0=⋯=ckf(bk)+ck−1βbk−1+⋯+cβb1+βb0,其中bk\u0026lt;m,故f(bk)=αbk\\begin{align}\u0026amp;f((n)_m)=cf((n)_m\u0026gt;\u0026gt;1)+\\beta_{b_0}=c(cf((n)_m\u0026gt;\u0026gt;2)+\\beta_{b_1})+\\beta_{b_0}=\\cdots\\\\\u0026amp;=c^kf(b_k)+c^{k-1}\\beta_{b_{k-1}}+\\cdots+c\\beta_{b_1}+\\beta_{b_0},其中b_k\u0026lt;m,故f(b_k)=\\alpha_{b_k}\\end{align}​f((n)m​)=cf((n)m​\u0026gt;\u0026gt;1)+βb0​​=c(cf((n)m​\u0026gt;\u0026gt;2)+βb1​​)+βb0​​=⋯=ckf(bk​)+ck−1βbk−1​​+⋯+cβb1​​+βb0​​,其中bk​\u0026lt;m,故f(bk​)=αbk​​​​因此对任何nnn，将其写成mmm进制，则f(n)f(n)f(n)等于(αbkβbk−1⋯βb0)c(\\alpha_{b_k}\\beta_{b_{k-1}}\\cdots\\beta_{b_0})_c(αbk​​βbk−1​​⋯βb0​​)c​\n当限定为约瑟夫问题f(n,2)f(n,2)f(n,2)时，f(n)=(n)2循环左移一位f(n)=(n)_2循环左移一位f(n)=(n)2​循环左移一位\n","permalink":"https://echo.ishian.me/2024/09/21/concretemath-recursion/","summary":"讲解递归函数与数学归纳法，分析汉诺塔、直线划分平面等经典递归问题","title":"具体数学: 递归"},{"content":"硬件加速的二进制算法 求最高有效位 定义末位为第零位\n__builtin_clz()：原理bsr(bit scan reserve,逆向位扫描)，可视作O(1)，能求出前导零个数\n参数为零时返回结果未定义\n最高有效位为31-__builtin_clz()\n__lg()：直接给出，实际上编译后代码与上述方法一致\n且该方法是log2()的优替，快速返回一个整数的以222为底对数的下取整\n与运算 基本性质 与运算只保留两个操作数共同拥有的111 掩码 判奇偶性：A\u0026amp;1=1?奇:偶A\\\u0026amp;1=1?奇:偶A\u0026amp;1=1?奇:偶\n模2n2^n2n：A%2n=A\u0026amp;((1\u0026lt;\u0026lt;n)−1)A\\%2^n=A\\\u0026amp;((1\u0026lt;\u0026lt;n)-1)A%2n=A\u0026amp;((1\u0026lt;\u0026lt;n)−1)\n判断二进制码子集：若BBB是AAA的子集，则A\u0026amp;B=BA\\\u0026amp;B=BA\u0026amp;B=B\n计111的个数 调用库函数：C++:__builtin_popcount()、Java:Integer.bitCount()\n暴力算法：\nc\u0026#43;\u0026#43;while (x) { ans += x \u0026amp; 1; x \u0026gt;\u0026gt;= 1; } Brian Kernighan算法：跳过最右111和末位中间的零，直接对该一计数\nk=x\u0026amp;(x−1)k=x\\\u0026amp;(x-1)k=x\u0026amp;(x−1)的性质是：kkk和xxx相差最右边的111\n迭代直到x=0x=0x=0，循环次数即一的个数\nc\u0026#43;\u0026#43;while (x) { x \u0026amp;= x - 1; ++ans; } 分治算法(Java bitCount()所用方法)：\n问题难点在于不知道哪里有111，也就无从统计\n暴力算法是遍历所有位(优化后的BK算法也是O(n))使111加起来，换言之，就是将nnn位数分成nnn组\n那么，分治算法的思想是遍历所有两位，将其分为n2\\frac n22n​组，使1/21/21/2加起来\nc\u0026#43;\u0026#43;while (x) { // 这里需要先以每两位为一组, 求得1的个数, 覆盖回x ans += x \u0026amp; 3; // 3 == 0b0000....0011 x \u0026gt;\u0026gt;= 2; }那么首先就要知道每个组是0/1/20/1/20/1/2中的哪个数：\n将整数以每两位为一组划分，要求出每组的111的个数，就是要求每组中所有位的加和\n其对应关系为原码(1的个数)20000010110011110\\begin{matrix}原码\u0026amp;(1的个数)_2\\\\00\u0026amp;00\\\\01\u0026amp;01\\\\10\u0026amp;01\\\\11\u0026amp;10\\end{matrix}原码00011011​(1的个数)2​00010110​，那么如何将这个计量关系快速算出，且每组间互不影响？\n首先，取掩码0b0101..0101\\rm 0b0101..01010b0101..0101，即0x55..5\\rm 0x55..50x55..5，相与后能将每组中位于奇位置的111保留，偶位置全为零\n让原整数右移一位，则原本处于偶位置的111移到奇位置，原本处于奇位置的111移位相与后为零(故不影响其它组)\n将两次相与的结果相加，每组中111的个数就得到了，因为加和最大为222，故每组是独立的\n当然，分治算法不能满足于如此，这样还需遍历n2\\frac n22n​次\n既然一次合并就能降低一半复杂度了，尝试继续合并，最后ans=x\u0026amp;(全1)=xans=x\\\u0026amp;(全1)=xans=x\u0026amp;(全1)=x\n重复上述过程，但以每四位为一组划分：\n类似上述掩码，取0b0011..0011\\rm 0b0011..00110b0011..0011，即0x33..3\\rm 0x33..30x33..3，结果为x\u0026amp;M+(x\u0026gt;\u0026gt;2)\u0026amp;Mx\\\u0026amp;M+(x\u0026gt;\u0026gt;2)\\\u0026amp;Mx\u0026amp;M+(x\u0026gt;\u0026gt;2)\u0026amp;M\n形象表达：将xxx看作四进制数，每两位四进制数(即每四位为一组)为一组，求每组所有位的加和；即(原码)4(所有位的加和)4000001/100102/20/110221/12032210\\begin{matrix}(原码)_4\u0026amp;(所有位的加和)_4\\\\00\u0026amp;00\\\\01/10\u0026amp;01\\\\02/20/11\u0026amp;02\\\\21/12\u0026amp;03\\\\22\u0026amp;10\\end{matrix}(原码)4​0001/1002/20/1121/1222​(所有位的加和)4​0001020310​，因为加和最大为444，故每组间独立计算，互不影响\n重复每2n2n2n一组的划分，直到只剩下一组，对这一组完成最后的统计后，则剩余整数就是所求\n以323232位整数为例，最终一次运算应以323232位为一组，M=0x0000ffff、x=x\u0026amp;M+(x\u0026gt;\u0026gt;16)\u0026amp;MM={\\rm 0x0000ffff}、x=x\\\u0026amp;M+(x\u0026gt;\u0026gt;16)\\\u0026amp;MM=0x0000ffff、x=x\u0026amp;M+(x\u0026gt;\u0026gt;16)\u0026amp;M，此时xxx就是所求\nc\u0026#43;\u0026#43;// 对int/unsigned int 32位整数: x = (x \u0026amp; 0x55555555) + ((x \u0026gt;\u0026gt; 1) \u0026amp; 0x55555555); x = (x \u0026amp; 0x33333333) + ((x \u0026gt;\u0026gt; 2) \u0026amp; 0x33333333); x = (x \u0026amp; 0x0f0f0f0f) + ((x \u0026gt;\u0026gt; 4) \u0026amp; 0x0f0f0f0f); x = (x \u0026amp; 0x00ff00ff) + ((x \u0026gt;\u0026gt; 8) \u0026amp; 0x00ff00ff); x = (x \u0026amp; 0x0000ffff) + ((x \u0026gt;\u0026gt; 16) \u0026amp; 0x0000ffff); // 此时x为所求 分治算法在位运算上的优化(我也没想到这还能优化)：\n对第一步：每组中原码和统计后码值的关系可优化为原码减去偶数位值：节省一次与运算\n原码计算过程1的个数0000−0000101−0011010−1011111−110\\begin{matrix}原码\u0026amp;计算过程\u0026amp;1的个数\\\\00\u0026amp;00-0\u0026amp;00\\\\01\u0026amp;01-0\u0026amp;01\\\\10\u0026amp;10-1\u0026amp;01\\\\11\u0026amp;11-1\u0026amp;10\\end{matrix}原码00011011​计算过程00−001−010−111−1​1的个数00010110​，即x -= (x \u0026gt;\u0026gt; 1) \u0026amp; 0x55555555\n对第三步：因为经过前两步后，以每888位为一组时，组中所有位加和最多为888，最多占四位，因此可以先让高位和低位相加后再相与(相与为了去除组间的影响)：\nx = (x + (x \u0026gt;\u0026gt; 4)) \u0026amp; 0x0f0f0f0f\n根据这个思路，第一、二步无法通过这种方法优化，第一步最多占两位，第二步最多占三位，因此不能先加再与；第三步以后均可通过该方法优化\n对更后面的步骤(第四步及以后)：每组加和的最大值所需位数远小于每组位数的一半，例如第四步中，每组加和最大为161616，占五位；而每组位数的一半为888\n因此从上一个组移到下一个组的位不会有影响，例如：\n​ 第三步后：0x 0a0 0a0..0a0\\rm0x\\ 0a_0\\ 0a_0..0a_00x 0a0​ 0a0​..0a0​，a0≤1000a_0\\le1000a0​≤1000，若后续不和掩码相与\n​ 第四步后：0x 0a0bb0 aa1bb0..aa1bb0\\rm0x\\ 0a_0bb_0\\ aa_1bb_0..aa_1bb_00x 0a0​bb0​ aa1​bb0​..aa1​bb0​\n​ bb0=a0h+a0l≤10000、aa1=a0up+a0h≤10000bb_0=a_{0h}+a_{0l}\\le10000、aa_1=a_{0up}+a_{0h}\\le10000bb0​=a0h​+a0l​≤10000、aa1​=a0up​+a0h​≤10000\n​ 第五步后：0x 0a0bb0 aa2cc0 aa3cc0..aa3cc0\\rm0x\\ 0a_0bb_0\\ aa_2cc_0\\ aa_3cc_0..aa_3cc_00x 0a0​bb0​ aa2​cc0​ aa3​cc0​..aa3​cc0​\n​ cc0=bb0h+bb0l≤100000、aa2=0a0+aa1≤11000、aa3=aa1+aa1≤100000cc_0=bb_{0h}+bb_{0l}\\le100000、aa_2=0a_0+aa_1\\le11000、aa_3=aa_1+aa_1\\le100000cc0​=bb0h​+bb0l​≤100000、aa2​=0a0​+aa1​≤11000、aa3​=aa1​+aa1​≤100000\n​ 若和掩码相与：第四步0x00bb00bb..00bb\\rm0x00bb00bb..00bb0x00bb00bb..00bb、第五步0x000000cc..000000cc\\rm0x000000cc..000000cc0x000000cc..000000cc\n上述cccccc(有效位)只要在八位之内，多余位就可在最后一步和掩码相与后除去\n换言之，只要在计数之前除去多余位即可；由于323232位整数本就只用666位，因此第四、五步可以不与：\nc\u0026#43;\u0026#43;x -= (x \u0026gt;\u0026gt; 1) \u0026amp; 0x55555555; x = (x \u0026amp; 0x33333333) + ((x \u0026gt;\u0026gt; 2) \u0026amp; 0x33333333); x = (x + (x \u0026gt;\u0026gt; 4)) \u0026amp; 0x0f0f0f0f; x += x \u0026gt;\u0026gt; 8; x += x \u0026gt;\u0026gt; 16; x \u0026amp;= 0x3f; // 最后保留末6位即可 // 推广到64位, 因为有效位为7, 故: // (在第五步之后) x += x \u0026gt;\u0026gt; 32; x \u0026amp;= 0x7f; 和乘法的结合：和上述思想类似，因为有效位只有六位，在第三步后每组八位，就可在第三步后将末三组移到最高八位相加(末三组总和不超过六位，故不会影响最高组)\n即：(x+x\u0026lt;\u0026lt;8+x\u0026lt;\u0026lt;16+x\u0026lt;\u0026lt;24)\u0026gt;\u0026gt;24(x+x\u0026lt;\u0026lt;8+x\u0026lt;\u0026lt;16+x\u0026lt;\u0026lt;24)\u0026gt;\u0026gt;24(x+x\u0026lt;\u0026lt;8+x\u0026lt;\u0026lt;16+x\u0026lt;\u0026lt;24)\u0026gt;\u0026gt;24，分配律到乘法\n即：(x×0x1010101)\u0026gt;\u0026gt;24(x\\cross\\rm0x1010101)\u0026gt;\u0026gt;24(x×0x1010101)\u0026gt;\u0026gt;24，乘法得出的高位应强制转换成int来舍去它们\n乘法效率其实不如上述优化强，但代码更简洁：\nc\u0026#43;\u0026#43;unsigned int y = x; y -= (y \u0026gt;\u0026gt; 1) \u0026amp; 0x55555555; y = (y \u0026amp; 0x33333333) + ((y \u0026gt;\u0026gt; 2) \u0026amp; 0x33333333); y = (((y \u0026gt;\u0026gt; 4) \u0026amp; 0x0f0f0f0f) * 0x1010101) \u0026gt;\u0026gt; 24; 异或运算 基本性质 异或运算后，两操作数相同位得零、不同位得一\n异或运算是一种对称加密：A=(A⊕B)⊕B、B=(A⊕B)⊕AA=(A\\oplus B)\\oplus B、B=(A\\oplus B)\\oplus AA=(A⊕B)⊕B、B=(A⊕B)⊕A\n不通过第三个变量交换两个整数：A⊕B→A、A⊕B→B、A⊕B→AA\\oplus B\\rightarrow A、A\\oplus B\\rightarrow B、A\\oplus B\\rightarrow AA⊕B→A、A⊕B→B、A⊕B→A A⊕Aˉ=全1、A⊕A=0A\\oplus\\bar A=全1、A\\oplus A=0A⊕Aˉ=全1、A⊕A=0 满足差分性质：\n汉明距离 汉明距离：两个整数中不同位的个数\n计算一：调用库函数\nc\u0026#43;\u0026#43;__builtin_popcount(x ^ y); ","permalink":"https://echo.ishian.me/2024/09/19/algo-bin-beginner/","summary":"整理位运算的基础知识，包括常用内建函数、与或异或运算性质及常用技巧","title":"位运算: 基本知识"},{"content":"Vim 介绍 Vim的前身是Vi，是类Unix系统下一个很高效的文本编辑器 Vi本身舍弃了许多需要借助鼠标、Ctrl与Alt键的快捷键，转而设计了若干种模式以使双手尽可能的保持在键盘上的asdf与jkl;四个打字键上 Vim则是Vi的扩展，不过许多功能默认被设置为禁用，许多功能需要在.vimrc文件中启用 Vim内置了vimtutor命令，键入该命令会使用vim进入其教程文件，建议先熟悉一遍该教程，然后在平时编码中强迫使用Vim 基本用法 各种模式概览 Vim有多种模式，包括正常模式、写入模式、命令模式、选择模式等 正常模式是进入Vim后的初始模式，在其它模式下键入Esc可回到正常模式 写入模式是向文件中写入的主要模式，在正常模式下键入i、I、o、O、a、A、r、R可按不同方式进入写入模式 插入模式：插入模式指插入的字符不会覆盖已有的字符，而是插在光标上 按下i会在光标左侧进入插入模式(insert) 按下I会在行首进入插入模式 按下o会在光标下新开一行，在新行中进入插入模式 按下O会在光标上新开一行，在新行中进入插入模式 按下a会在光标右侧进入插入模式(append) 按下A会在行末进入插入模式 替换模式：替换模式指写入的字符会覆盖原本在光标上的字符 按下r仅替换一个字符，随后自动退回正常模式 按下R会进入替换模式，直至用户主动返回 命令模式可以运行Vim内置的命令、外部提供的命令等 调用内置命令：键入: 例如键入:help用Vim进入帮助文档 调用外部提供的命令，需要键入:! 选择模式可以配合移动选择一块文本，随后键入其它和正常模式一致的动作命令对选中的代码块进行操作 键入v可切换(进入或退出)选择模式 键入V可切换(进入或退出)行选择模式 光标移动 hjkl：分别为左下上右方向键 0：等价于Home，此外|和^也是Home $：等价于End G：移动到文件的最后一行 gg：移动到文件的第一行 正整数+G：移动到第正整数行 \u0026lt;Enter\u0026gt;：向下移动一行 w：跳转到下一个单词的开始，针对英文而言W与w没有区别，对中文可能有些许不同，但总而言之W并不常用 e：跳转到当前单词的结尾，若已处于当前单词结尾则跳转到下一单词结尾 b：跳转到上一单词的开始 %：匹配到光标所属的左/右括号，使光标在左/右括号间切换 Ctrl+f与Ctrl+b：Page Down与Page Up Ctrl+d与Ctrl+u：向下/向上移动半页 正整数+上述移动命令：重复执行正整数次该移动命令 撤销与恢复 u：撤销上一次修改命令造成的影响 U：撤销该行内所有的命令 Ctrl+r：撤销上一次“撤销”操作，即“恢复” 删除或粘贴字 x：删除光标所在的字符 X：删除光标的前一个字符 d?：其中?是移动命令，配合移动命令可快速删除字符 dd：删除光标所在行 D：删除至行尾，等价于d0 s：等价于x后键入i S：等价于dd后键入i c?：等价于d?后键入i cc：等价于dd后键入i C：等价于D后键入i y：复制光标所在的字符、或复制选中的内容 Y或yy：复制光标所在行 p：将上一次删除或复制的内容粘贴在光标的右侧(若为一整行则会在光标下方开启新行) P：将上一次删除或复制的内容粘贴在光标的左侧(若为一整行则会在光标下方开启新行) 搜索与替换 Vim中的搜索默认是正则表达式匹配(magic模式)，可以切换为nomagic与very magic模式 /：进入向后搜索模式，键入需要搜索的内容后键入回车即可 ?：进入向前搜索模式 n：在搜索模式中，跳转到下一个匹配项 N：在搜索模式中，跳转到上一个匹配项 :set ic：等价于:set ignorecase，设置搜索时无视大小写 :set hls：等价于:set hlsearch，设置搜索结果高亮 :set is：等价于:set incsearch，设置在搜索过程中搜索结果高亮 :s/old/new：将**本行第一个old**替换为new :s/old/new/g：将**本行的所有old**替换为new :%s/old/new/g：将**所有的old**替换为new :%s/old/new/gc：在:%s/old/new/g的基础上，每次替换前向用户发出确认(confirm) 分页 :sp：使当前文件上下分页 :sp 文件名：打开新文件并上下分页，空格等特殊字符需反斜杠转义 :vsp与:vsp 文件名：与上述命令类似，但是左右分页 Ctrl+w w：切换到下一个分页 Ctrl+w hjkl：切换到左、下、上、右分页 Ctrl+w c：关闭当前分页 Ctrl+w o：关闭其它所有分页 其它常用命令 :help：用Vim打开教程文件 :w：保存文件 :q：退出文件 :wq!：强制地保存并退出 :!cmd：运行一个由外部提供的名为cmd的命令 :set nu：显示行号 :set noxxx：取消某设置，例如:set nonu \u0026gt;\u0026gt;与\u0026lt;\u0026lt;：缩进与反缩进 自定义命令 之前说过在VS Code中使用json数据格式配置Vim的扩展功能，实际上在.vimrc文件配置才是最原始的方法，因为原生的Vim没有VS Code的editor那样默认有丰富的配置\nVim配置包含系统配置与用户配置，系统配置位于/usr/share/vim/vimXX/vimrc_example.vim，其中XX为版本，用户配置应位于~/.vimrc文件中，且用户配置优先于系统配置\n一份常用的配置如下：\nvimrcset encoding=utf-8 # 编码 syntax on # 语法高亮 set ruler # 显示光标所在行号与列号 set nu # 显示行号 set cursorline # 高亮光标所在行 set cursorcolumn # 高亮光标所在列 set autowrite # 自动保存 set is # 搜索时立刻执行而不等`\u0026lt;Enter\u0026gt;` set nocompatible # 不兼容`Vi` set showmatch # 高亮显示光标所在括号对 set tabstop=4 # 设置\\t的空格数 set shiftwidth=4 # 设置缩进的空格数 set noexpandtab # 设置按下Tab时不用空格替代\\t set laststatus=2 # 始终在下方显示文件名 ","permalink":"https://echo.ishian.me/2024/09/07/vim/","summary":"介绍Vim的模式体系、常用编辑命令与配置入门","title":"Vim: 极其高效的编辑器"},{"content":"代数系统 定义 SSS上的nnn元运算是一个特殊的函数f:Sn→Sf:S^n\\rightarrow Sf:Sn→S，具有封闭性 代数系统：由非空集合SSS和SSS上的运算组成的系统，记作\u0026lt;S,f1,f2,⋯ ,fn\u0026gt;\u0026lt;S,f_1,f_2,\\cdots,f_n\u0026gt;\u0026lt;S,f1​,f2​,⋯,fn​\u0026gt;，简称代数 代数的基数等于SSS的基数(因为运算具有封闭性) 同类型的代数：要求一一对应的各个运算的元数相同，和SSS无关 子代数：给定代数\u0026lt;S,f1,⋯ ,fn\u0026gt;\u0026lt;S,f_1,\\cdots,f_n\u0026gt;\u0026lt;S,f1​,⋯,fn​\u0026gt;，若非空集合T⊆ST\\subseteq ST⊆S，且TTT和f1,⋯ ,fnf_1,\\cdots,f_nf1​,⋯,fn​能够组成代数(即fff在TTT上封闭)，则\u0026lt;T,f1,⋯ ,fn\u0026gt; ⊆ \u0026lt;S,f1,⋯ ,fn\u0026gt;\u0026lt;T,f_1,\\cdots,f_n\u0026gt;\\,\\subseteq\\,\u0026lt;S,f_1,\\cdots,f_n\u0026gt;\u0026lt;T,f1​,⋯,fn​\u0026gt;⊆\u0026lt;S,f1​,⋯,fn​\u0026gt; 代数运算 代数运算的性质 设在SSS上的二元运算∘\\circ∘满足：\n结合律：∀x∀y∀z(x,y,z∈S→(x∘y)∘z=x∘(y∘z))\\forall x\\forall y\\forall z(x,y,z\\in S\\rightarrow (x\\circ y)\\circ z=x\\circ(y\\circ z))∀x∀y∀z(x,y,z∈S→(x∘y)∘z=x∘(y∘z)) 交换律：∀x∀y(x,y∈S→x∘y=y∘x)\\forall x\\forall y(x,y\\in S\\rightarrow x\\circ y=y\\circ x)∀x∀y(x,y∈S→x∘y=y∘x) 若同时满足结合律和交换律，则表达式可任意调换次序 分配律：设∘\\circ∘对SSS上的另一个运算×\\times×满足： 左分配律：∀x∀y∀z(x,y,z∈S→x∘(y×z)=(x∘y)×(x∘z))\\forall x\\forall y\\forall z(x,y,z\\in S\\rightarrow x\\circ(y\\times z)=(x\\circ y)\\times (x\\circ z))∀x∀y∀z(x,y,z∈S→x∘(y×z)=(x∘y)×(x∘z)) 右分配律：∀x∀y∀z(x,y,z∈S→(x×y)∘z=(x∘z)×(y∘z))\\forall x\\forall y\\forall z(x,y,z\\in S\\rightarrow (x\\times y)\\circ z=(x\\circ z)\\times (y\\circ z))∀x∀y∀z(x,y,z∈S→(x×y)∘z=(x∘z)×(y∘z)) 注意运算的对应关系，∘\\circ∘对×\\times×满足，则左式∘\\circ∘在括号外 同时满足左右分配律，才能称∘\\circ∘对×\\times×满足分配律 若∘\\circ∘满足交换律，且对×\\times×满足左或右分配律，则可证明它对×\\times×满足分配律 吸收律：类似于分配律的左右定义方式，∘\\circ∘对×\\times×满足左吸收律⇒∀x∀y(x,y∈S→x∘(x×y)=x)\\Rightarrow\\forall x\\forall y(x,y\\in S\\rightarrow x\\circ(x\\times y)=x)⇒∀x∀y(x,y∈S→x∘(x×y)=x) 幺元(单位元)：有左右幺元之分，若eee是关于∘\\circ∘的左幺元，则∀x(x∈S→e∘x=x)\\forall x(x\\in S\\rightarrow e\\circ x=x)∀x(x∈S→e∘x=x) 零元：有左右零元之分，若O\\OO是关于∘\\circ∘的左零元，则∀x(x∈S→O∘x=O)\\forall x(x\\in S\\rightarrow \\O\\circ x=\\O)∀x(x∈S→O∘x=O) 如果一个运算的左右幺元/零元存在，则左右分别相等，且这个幺元/零元是唯一的 如果一个运算同时存在幺元和零元，则它们不相等 等幂律：∀x(x∈S→x∘x=x)\\forall x(x\\in S\\rightarrow x\\circ x=x)∀x(x∈S→x∘x=x)，并定义xxx为∘\\circ∘的等幂元 若满足，则所有元素都为等幂元 一个运算的幺元和零元一定是等幂元 逆元：若eee为∘\\circ∘的幺元，有左右逆元之分，且有两种对应关系 xxx是关于∘\\circ∘的左逆元：∃y(y∈S∧x∘y=e)\\exist y(y\\in S\\wedge x\\circ y=e)∃y(y∈S∧x∘y=e) 称存在的这个yyy为xxx的右逆元，即x∘y=ex\\circ y=ex∘y=e 如果xxx和yyy互逆(xxx为yyy的左右逆元)，则xxx和yyy都是关于∘\\circ∘的逆元 一个运算的幺元一定是逆元 可约律：有左右可约律，若O\\OO为∘\\circ∘的零元，则∘\\circ∘是左可约的⇒∀x∀y∀z((x,y,z∈S∧x≠O∧x∘y=x∘z)→y=z)\\Rightarrow\\forall x\\forall y\\forall z((x,y,z\\in S\\wedge x\\ne\\O\\wedge x\\circ y=x\\circ z)\\rightarrow y=z)⇒∀x∀y∀z((x,y,z∈S∧x=O∧x∘y=x∘z)→y=z) 定义xxx为∘\\circ∘的左可约元 如果一个运算是可约的，那么所有非零元的元素都为可约元 存在一个可约元，不代表一个运算是可约的 ！如果∘\\circ∘满足结合律，同时x≠Ox\\ne\\Ox=O且xxx是∘\\circ∘的逆元，则xxx是∘\\circ∘的可约元 一个运算的幺元一定是可约元 性质在运算表上的体现 将二元运算的左元、右元分别作为行、列，将运算结果作为表中数据，生成的运算表(方阵)中：\n封闭性：表中数据一定都属于SSS，否则该运算不是SSS上的运算 可交换：该方阵是对称的 等幂：主对角线上元素和行头元素相等 左/右零元：左/右零元所在行/列元素和该元素相等 左/右幺元：左/右幺元所在行/列元素和对应的列/行元素相等 左/右逆元：左/右逆元所在行/列元素中必定有一个幺元 ！两元素互为逆元：两元素所在行和列的元素都是幺元 以上冒号左右两边的描述是等价的\n常见代数运算的性质 集合 运算 幺元 零元 等幂元 逆元 可约元 R 加法 0 无 0 相反数 任意元素 乘法 1 0 1和0 除0外的\n数的倒数 除0外的\n任意元素 P(S) 并集 空集 S 任意元素 空集 空集 交集 S 空集 任意元素 S S 命题 析取 F T 任意元素 F F 合取 T F 任意元素 T T 集合上的\n所有关系\n构成集合 合成 恒等关系 空关系 恒等关系 关系矩阵可逆\n的所有关系 关系矩阵可逆\n的所有关系 f:S-\u003eS\n所有双射函数\n构成的集合 合成 恒等函数 无 恒等函数 所有元素 所有元素 最后两条：\n一个集合上的所有双射函数都有其对应的反函数，且反函数也是该集合上的双射函数 一个集合上的双射函数和其反函数合成的结果是恒等函数 一个集合上的双射函数构成集合，是所有关系构成集合的子集 同类型的代数 以下关系，要求\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;和\u0026lt;T,×\u0026gt;\u0026lt;T,\\times\u0026gt;\u0026lt;T,×\u0026gt;是同类型的\n同态关系 记号：记\u0026lt;S,∘\u0026gt;≃\u0026lt;T,×\u0026gt;\u0026lt;S,\\circ\u0026gt;\\simeq\u0026lt;T,\\times\u0026gt;\u0026lt;S,∘\u0026gt;≃\u0026lt;T,×\u0026gt;表示前者同态于后者 定义：上式⇒∃f(f∈TS∧∀x∀y(x,y∈S→f(x∘y)=f(x)×f(y)))\\Rightarrow\\exist f(f\\in T^S\\wedge\\forall x\\forall y(x,y\\in S\\rightarrow f(x\\circ y)=f(x)\\times f(y)))⇒∃f(f∈TS∧∀x∀y(x,y∈S→f(x∘y)=f(x)×f(y)))，其中称f:S→Tf:S\\rightarrow Tf:S→T为\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;到\u0026lt;T,×\u0026gt;\u0026lt;T,\\times\u0026gt;\u0026lt;T,×\u0026gt;的同态映射 即先通过运算映射再通过fff映射等于先通过fff映射再通过运算映射 fff不唯一 在代数有多个二元运算时，要求fff是同一个 由fff的值域R(f)⊆TR(f)\\subseteq TR(f)⊆T，易得\u0026lt;R(f),×\u0026gt;⊆\u0026lt;T,×\u0026gt;\u0026lt;R(f),\\times\u0026gt;\\subseteq \u0026lt;T,\\times\u0026gt;\u0026lt;R(f),×\u0026gt;⊆\u0026lt;T,×\u0026gt; 同态映射的分类 根据fff的性质，对该映射进行分类：\nfff为满射⇒\\Rightarrow⇒从前者到后者的满同态映射 fff为单射⇒\\Rightarrow⇒从前者到后者的单一同态映射 fff为双射⇒\\Rightarrow⇒从前者到后者的同构映射 满同态映射的运算继承性质 只要fff是满射的(包括同构关系)，就有以下性质：\n两代数的对应运算的结合、交换、分配、等幂律等性质是一致的 前代数中运算的幺元、零元、逆元经fff后，等于后代数中对应运算的幺元、零元、逆元 同构关系 记号：\u0026lt;S,∘\u0026gt;≅\u0026lt;T,×\u0026gt;⇒\u0026lt;S,\\circ\u0026gt;\\cong\u0026lt;T,\\times\u0026gt;\\Rightarrow\u0026lt;S,∘\u0026gt;≅\u0026lt;T,×\u0026gt;⇒前者同构于后者 同构意味着，每个f(x)f(x)f(x)有其唯一对应的原像，因此两个代数可以互相映射，即同时存在同构关系\u0026lt;T,×\u0026gt;≅f−1\u0026lt;S,∘\u0026gt;\u0026lt;T,\\times\u0026gt;\\cong_{f^{-1}}\u0026lt;S,\\circ\u0026gt;\u0026lt;T,×\u0026gt;≅f−1​\u0026lt;S,∘\u0026gt;；而不是像同态一样，只能单向地映射系统 代数间的同构关系是等价关系 可通过同构关系，将所有代数形成的集合按其分类 同态、同构关系指的不是fff这个函数关系，而是由两代数作为第一、二元素的二元关系 称从\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;到自身的同态、同构关系为自同态、自同构关系 代数间的运算 同余关系 定义：若EEE是SSS上的等价关系，且满足∀x1∀y1∀x2∀y2((x1,y1,x2,y2∈S∧x1Ey1∧x2Ey2)→(x1∘x2)E(y1∘y2))\\forall x_1\\forall y_1\\forall x_2\\forall y_2((x_1,y_1,x_2,y_2\\in S\\wedge x_1Ey_1\\wedge x_2Ey_2)\\rightarrow(x_1\\circ x_2)E(y_1\\circ y_2))∀x1​∀y1​∀x2​∀y2​((x1​,y1​,x2​,y2​∈S∧x1​Ey1​∧x2​Ey2​)→(x1​∘x2​)E(y1​∘y2​))，则称EEE是\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;上的同余关系 称由EEE划分的等价类为同余类 EEE是特殊的等价关系，能保证元素运算后，仍能满足该关系 EEE的这种代换需要满足代数中的所有运算 同余关系和同态、同构不是一种概念 另一种表达：[x1]E=[y1]E∧[x2]E=[y2]E→[x1∘x2]E=[y1∘y2]E[x_1]_E=[y_1]_E\\wedge[x_2]_E=[y_2]_E\\rightarrow[x_1\\circ x_2]_E=[y_1\\circ y_2]_E[x1​]E​=[y1​]E​∧[x2​]E​=[y2​]E​→[x1​∘x2​]E​=[y1​∘y2​]E​ 商代数 若fff是\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;到\u0026lt;T,×\u0026gt;\u0026lt;T,\\times\u0026gt;\u0026lt;T,×\u0026gt;的同态映射，则SSS上的关系EfE_fEf​(满足xEfy:f(x)=f(y)xE_fy:f(x)=f(y)xEf​y:f(x)=f(y))一定是同余关系\n证明：任取x1,y1,x2,y2∈Sx_1,y_1,x_2,y_2\\in Sx1​,y1​,x2​,y2​∈S，若有x1Efy1∧x2Efy2x_1E_fy_1\\wedge x_2E_fy_2x1​Ef​y1​∧x2​Ef​y2​，则f(x1)=f(y1)∧f(x2)=f(y2)f(x_1)=f(y_1)\\wedge f(x_2)=f(y_2)f(x1​)=f(y1​)∧f(x2​)=f(y2​)；根据同态映射的定义，有{f(x1)×f(x2)=f(x1∘x2)f(y1)×f(y2)=f(y1∘y2)\\begin{cases}f(x_1)\\times f(x_2)=f(x_1\\circ x_2)\\\\f(y_1)\\times f(y_2)=f(y_1\\circ y_2)\\end{cases}{f(x1​)×f(x2​)=f(x1​∘x2​)f(y1​)×f(y2​)=f(y1​∘y2​)​；三式联立得f(x1∘x2)=f(y1∘y2)f(x_1\\circ x_2)=f(y_1\\circ y_2)f(x1​∘x2​)=f(y1​∘y2​)，即(x1∘x2)Ef(y1∘y2)(x_1\\circ x_2)E_f(y_1\\circ y_2)(x1​∘x2​)Ef​(y1​∘y2​)，得证 称EfE_fEf​为由fff诱导的同余关系 商代数：给定\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;及其上的一个同余关系EEE：\n以EEE导出的商集S/ES/ES/E为集合，构造满足同态定义的运算×\\times×：∀x∀y∈S→[x∘y]E=[x]E×[y]E\\forall x\\forall y\\in S\\rightarrow [x\\circ y]_E=[x]_E\\times [y]_E∀x∀y∈S→[x∘y]E​=[x]E​×[y]E​，则称\u0026lt;S/E,×\u0026gt;\u0026lt;S/E,\\times\u0026gt;\u0026lt;S/E,×\u0026gt;为它的商代数 商代数是人为构造的，满足\u0026lt;S,∘\u0026gt;≃\u0026lt;S/E,×\u0026gt;\u0026lt;S,\\circ\u0026gt;\\simeq\u0026lt;S/E,\\times\u0026gt;\u0026lt;S,∘\u0026gt;≃\u0026lt;S/E,×\u0026gt;，gE(x)=[x]Eg_E(x)=[x]_EgE​(x)=[x]E​ 易得gEg_EgE​为满同态映射，因此商代数的运算继承了本代数运算的性质 该映射被称为正则映射、自然同态 设\u0026lt;S,∘\u0026gt;≃\u0026lt;T,×\u0026gt;\u0026lt;S,\\circ\u0026gt;\\simeq\u0026lt;T,\\times\u0026gt;\u0026lt;S,∘\u0026gt;≃\u0026lt;T,×\u0026gt;，且fff为满同态映射，则\u0026lt;T,×\u0026gt;≅\u0026lt;S/Ef,∗\u0026gt;\u0026lt;T,\\times\u0026gt;\\cong\u0026lt;S/E_f,*\u0026gt;\u0026lt;T,×\u0026gt;≅\u0026lt;S/Ef​,∗\u0026gt;(后者为\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;的商代数)\n证明：构造函数{h:T→S/Efh(y)=[x]E\\begin{cases}h:T\\rightarrow S/E_f\\\\h(y)=[x]_E\\end{cases}{h:T→S/Ef​h(y)=[x]E​​，其中y=f(x)y=f(x)y=f(x)\n证其为同态映射\nh(y1×y2)=h(f(x1∘x2))=[x1∘x2]Eh(y_1\\times y_2)=h(f(x_1\\circ x_2))=[x_1\\circ x_2]_Eh(y1​×y2​)=h(f(x1​∘x2​))=[x1​∘x2​]E​，由商集定义，上式=[x1]E∗[x2]E=[x_1]_E*[x_2]_E=[x1​]E​∗[x2​]E​\n⇒∀y1∀y2(y1,y2∈T→[x1]E∗[x2]E=h(y1×y2))\\Rightarrow\\forall y_1\\forall y_2(y_1,y_2\\in T\\rightarrow [x_1]_E*[x_2]_E=h(y_1\\times y_2))⇒∀y1​∀y2​(y1​,y2​∈T→[x1​]E​∗[x2​]E​=h(y1​×y2​))\n⇒\u0026lt;T,×\u0026gt;≃\u0026lt;S/Ef,∗\u0026gt;\\Rightarrow \u0026lt;T,\\times\u0026gt;\\simeq\u0026lt;S/E_f,*\u0026gt;⇒\u0026lt;T,×\u0026gt;≃\u0026lt;S/Ef​,∗\u0026gt;，hhh为其同态映射\n证其为满射：f、gEff、g_{E_f}f、gEf​​为满射⇒h\\Rightarrow h⇒h为满射\n证其为单射：若h(y1)=h(y2)h(y_1)=h(y_2)h(y1​)=h(y2​)\n⇒[x1]E=[x2]E⇒x1Efx2⇒f(x1)=f(x2)⇒y1=y2\\Rightarrow [x_1]_E=[x_2]_E\\Rightarrow x_1E_fx_2\\Rightarrow f(x_1)=f(x_2)\\Rightarrow y_1=y_2⇒[x1​]E​=[x2​]E​⇒x1​Ef​x2​⇒f(x1​)=f(x2​)⇒y1​=y2​\n得证\n积代数 设有同类型的代数\u0026lt;S,∘\u0026gt;\u0026lt;S,\\circ\u0026gt;\u0026lt;S,∘\u0026gt;和\u0026lt;T,×\u0026gt;\u0026lt;T,\\times\u0026gt;\u0026lt;T,×\u0026gt;，则定义运算⊗\\otimes⊗：∀x1∀x2∀y1∀y2(x1,x2∈S∧y1,y2∈T→\u0026lt;x1∘x2,y1×y2\u0026gt;=\u0026lt;x1,y1\u0026gt;⊗\u0026lt;x2,y2\u0026gt;)\\forall x_1\\forall x_2\\forall y_1\\forall y_2(x_1,x_2\\in S\\wedge y_1,y_2\\in T\\rightarrow \u0026lt;x_1\\circ x_2,y_1\\times y_2\u0026gt;=\u0026lt;x_1,y_1\u0026gt;\\otimes\u0026lt;x_2,y_2\u0026gt;)∀x1​∀x2​∀y1​∀y2​(x1​,x2​∈S∧y1​,y2​∈T→\u0026lt;x1​∘x2​,y1​×y2​\u0026gt;=\u0026lt;x1​,y1​\u0026gt;⊗\u0026lt;x2​,y2​\u0026gt;)\n称\u0026lt;S×T,⊗\u0026gt;\u0026lt;S\\times T,\\otimes\u0026gt;\u0026lt;S×T,⊗\u0026gt;为上述两个代数的积代数；上述两个代数为该积代数的因子代数\n上述为两因子代数形成的积代数，多因子代数形成的积代数可递归定义 积代数和因子代数是同类型的 积代数的运算包含了所有因子代数的运算及其笛卡尔积顺序的信息 ","permalink":"https://echo.ishian.me/2024/08/07/discretemath-algebra-system/","summary":"讲解代数系统的定义、二元运算的性质及幺元、零元、逆元等特殊元素","title":"离散数学: 代数系统"},{"content":"HTML 简介 标记语言 HTML(超文本标记语言)，它不是一种编程语言，而是使用一个个标签来构建一个网页的框架，一个HTML文档就是一个WEB网页(而浏览器内含解析其内容的解析器)；CSS负责网页的美化，JS负责网页的动态交互，而它负责网页的架构，CSS以及脚本都要在这个框架里定义\n和编程语言的区别 Markdown就是一种标记语言，它们只是把一份文档通过标记来增加一些细节，然后展示出来，因此没有复杂的语法，只是定义了一个文档的结构、排布等。文档本身并没有实现部分，需要由解释器渲染才能有效展示。只有HTML代码的文档就是一静态网页，不会因为用户输入而发生实时变化(但可以将输入信息传输给服务器，及时更换一个新的网页，达到实时的效果)\n而编程语言有更复杂的语法来实现更高效、多样的逻辑与功能，例如最基本的输入输出、计算、内存管理等。源文件本身包含许多底层的实现，经过翻译后可由系统执行\n基本内容 输出标签 HTML不区分大小写，使用一条条元素来构建网页，每条元素一般由一对标签作为头尾，标签夹着的文本为内容，而标签内部的参数对为属性(其值一般不会显示)，解释器通过标签及其属性来展现不同样式的内容；很多属性都与样式有关，这里尽量只介绍框架有关的属性。以下是常用标签：\n\u0026lt;!DOCTYPE html\u0026gt;：这是一条声明，用于告诉浏览器使用特定的HTML版本解释器，这一条声明的是html5 \u0026lt;!-- message --\u0026gt;：用于注释，message只会在源代码中被看见 \u0026lt;html\u0026gt;\u0026lt;/html\u0026gt;：这是一份html文档的开头与结尾，定义了一整个html文档 \u0026lt;head\u0026gt;\u0026lt;/head\u0026gt;：定义文档的头部内容，包括编码方式(meta)、文档标题(title)、插入脚本(scripts块，可以在\u0026lt;body\u0026gt;中定义，但习惯在\u0026lt;head\u0026gt;中)、文档样式等，它们不呈现在网页主体中，而会被解释器接受。以下是常用的只在\u0026lt;head\u0026gt;中定义的元素 \u0026lt;title\u0026gt;\u0026lt;/title\u0026gt;：定义了在浏览器顶部的网页名称 \u0026lt;meta\u0026gt;：定义了文档信息的信息(即元数据)，例如属性charset指定了浏览器的编码方式，常见\u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt;指定编码方式保证中文不会出现乱码 \u0026lt;base\u0026gt;：定义基准链接，即其它所有使用相对链接的元素都会相对基准链接，只能定义一次 \u0026lt;link\u0026gt;：定义文档与外部资源的联系，例如链接外部CSS \u0026lt;body\u0026gt;\u0026lt;/body\u0026gt;：定义网页主体，其中内容将会展现给用户 \u0026lt;header\u0026gt;\u0026lt;/header\u0026gt;：定义文档简介 \u0026lt;h\u0026gt;\u0026lt;/h\u0026gt;：定义一行标题，从\u0026lt;h1\u0026gt;到\u0026lt;h6\u0026gt;标题等级递减，体现在文字加粗放大以及目录索引上 \u0026lt;p\u0026gt;\u0026lt;/p\u0026gt;：定义一片段落文字，前后会添加换行 \u0026lt;a\u0026gt;\u0026lt;/a\u0026gt;：定义一个链接，href属性指示链接地址链接 链接可以是一个网页、锚点、本地文件、图片等，锚点链接即\u0026rsquo;#\u0026lsquo;开头的一串标记，类似于Markdown，这个标记可以是id或name属性的值 \u0026lt;img\u0026gt;\u0026lt;/img\u0026gt;：定义一张图片，src属性指示图片地址，alt属性指示当图片无法显示时的可作替换的文本内容 \u0026lt;table\u0026gt;\u0026lt;/table\u0026gt;：定义一个表格，border属性指示其边框样式 \u0026lt;tr\u0026gt;\u0026lt;/tr\u0026gt;：定义表格的一行(table row) \u0026lt;th\u0026gt;\u0026lt;/th\u0026gt;：定义表格的表头单元格(table header) \u0026lt;td\u0026gt;\u0026lt;/td\u0026gt;：定义表格一般数据的单元格(table data) \u0026lt;ul\u0026gt;\u0026lt;/ul\u0026gt;、\u0026lt;ol\u0026gt;\u0026lt;/ol\u0026gt;、\u0026lt;dl\u0026gt;\u0026lt;/dl\u0026gt;：定义无序(unordered)、有序(ordered)、自定义列表(definition) \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt;：定义列表中的一项() \u0026lt;dt\u0026gt;\u0026lt;/dt\u0026gt;：定义自定义列表中的一项(definition term) \u0026lt;dd\u0026gt;\u0026lt;/dd\u0026gt;：定义列表上一项的注释(definition description) 文本格式化的常用标签有如下： \u0026lt;br\u0026gt;：定义一次换行，可在\u0026lt;p\u0026gt;等内容中使用 \u0026lt;hr\u0026gt;：定义一条水平分割线 \u0026lt;b\u0026gt;\u0026lt;/b\u0026gt;与\u0026lt;i\u0026gt;\u0026lt;/i\u0026gt;：定义文字的粗体与斜体，它们是样式标签，属于无意义的文字加粗与倾斜，两者应作为最后的选择 \u0026lt;strong\u0026gt;\u0026lt;/strong\u0026gt;与\u0026lt;em\u0026gt;\u0026lt;/em\u0026gt;：语义化标签，分别为特别强调与一般强调，可以做到\u0026lt;b\u0026gt;与\u0026lt;i\u0026gt;的效果，且在非视觉上(例如大声朗读)也有强调效果，这两者更常用 \u0026lt;mark\u0026gt;\u0026lt;/mark\u0026gt;：定义高亮强调文字 \u0026lt;small\u0026gt;\u0026lt;/small\u0026gt;：定义小号字体 \u0026lt;sub\u0026gt;\u0026lt;/sub\u0026gt;与\u0026lt;sup\u0026gt;\u0026lt;/sup\u0026gt;：定义下标与上标字体 \u0026lt;del\u0026gt;\u0026lt;/del\u0026gt;：定义划线字 其它类型字体渲染如下： \u0026lt;pre\u0026gt;\u0026lt;/pre\u0026gt;与\u0026lt;code\u0026gt;\u0026lt;/code\u0026gt;：定义代码字体，前者不会忽视制表符和回车 \u0026lt;abbr\u0026gt;\u0026lt;/abbr\u0026gt;：定义缩略词，赋予title属性后当光标悬浮其上时将会显示出来abbr \u0026lt;address\u0026gt;\u0026lt;/address\u0026gt;：其中应包含文档作者的相关信息如联系方式等，通常表现为斜体 \u0026lt;blockquote\u0026gt;\u0026lt;/blockquote\u0026gt;、\u0026lt;q\u0026gt;\u0026lt;/q\u0026gt;、\u0026lt;cite\u0026gt;\u0026lt;/cite\u0026gt;：分别定义长(一个段落)、短(一句话)引用内容、引用来源。\u0026lt;q\u0026gt;、\u0026lt;cite\u0026gt;，前两种标签含cite属性，用于指示引用来源(链接) 早期html标准存在一些无需加上关闭标签的元素，这在之后的XHTML标准(参考XML)中变为例如\u0026lt;br /\u0026gt;的自我封闭元素，而HTML5对XHTML兼容，所以\u0026lt;br\u0026gt;、\u0026lt;br/\u0026gt;都可以使用\n这些元素可以被分为区块元素与内联元素，前者与后者相比，会在内容开头加上换行符表示这是新的区块；为了更好地排版和优化框架，有如下标签：\n\u0026lt;div\u0026gt;\u0026lt;/div\u0026gt;：定义一块元素，即将其中的所有内容归纳为一个区块来管理，可以配合样式表进行整块的修饰 \u0026lt;span\u0026gt;\u0026lt;/span\u0026gt;：定义一个内联元素，即将其中的所有内容归纳为一个内联元素来管理，样式由内容决定 输入标签 html用表单来收集用户输入的信息，需要注意的是，html文档和解释器并不会处理这些信息，而是发送给服务器处理，处理后服务器会发送一个新的html文档给浏览器\n\u0026lt;form\u0026gt;\u0026lt;/form\u0026gt;：定义一个收集信息的区域，含有些许控件用于检测事件后传输收集到的表单，action属性指定目标地址，method属性指定传输的方法 \u0026lt;fieldset\u0026gt;\u0026lt;/fieldset\u0026gt;：定义表单的边框 \u0026lt;legend\u0026gt;\u0026lt;/legend\u0026gt;：定义边框的标题 \u0026lt;input\u0026gt;：定义一个输入控件，type属性指定其类型，id属性指定其关联的label元素，name属性指定这个表单项的名字，value指定这个选项的内容 \u0026lt;label\u0026gt;\u0026lt;/label\u0026gt;：定义一个表单项的标签，for属性指定这个表单项的id \u0026lt;select\u0026gt;\u0026lt;/select\u0026gt;：定义一个可下拉选项，name属性指定这个表单项的名字 \u0026lt;option\u0026gt;\u0026lt;/option\u0026gt;：定义一个选项，value属性指定这个选项的内容，selected属性指定这个下拉选项的预先选好的选项(否则为第一个option元素) \u0026lt;textarea\u0026gt;\u0026lt;/textarea\u0026gt;：定义一个用于输入多行文本的控件，cols和rows分别定义其宽高 html5提供的一些新控件如下：\n\u0026lt;button\u0026gt;\u0026lt;/button\u0026gt;：定义一个按钮控件，其与\u0026lt;input type=\u0026quot;button\u0026quot;\u0026gt;的区别为支持更丰富的内容(例如图像)。不要随意将button元素放在表单里，因为它相当于\u0026lt;input type=\u0026quot;submit\u0026quot;\u0026gt;(提交按钮) 属性的值 id：每个html元素只有唯一的id，因此这个属性用于查找唯一值、设置锚点等 name：name属性的一个值可以复用到多个html元素上，且会将它们绑定为一个整体传输数据；例如，单选框的所有选项的name属性应当一致，才能达到单选的效果。name属性是输入控件必不可少的一项，提交数据时只会提交含有name选项的元素 value：指定该元素的内容，方便传输数据，有时可以改变；一些控件会显示它的值 \u0026lt;option\u0026gt;的属性位：有**selected**(默认选中)、disabled(无法选中)、hidden(不显示该选项) \u0026lt;input\u0026gt;的type：其值有text(一般文本)、submit(提交按钮)、radio(单选框)、checkbox(复选框)、reset(重置按钮)、button(一般按钮)、password(密码文本，即隐藏文本) \u0026lt;input\u0026gt;的checked位：置位表示选中 ","permalink":"https://echo.ishian.me/2024/08/04/html-beginner/","summary":"介绍 HTML 标记语言的基本概念与常用输入输出标签","title":"HTML: 基本语法"},{"content":"机器学习基础 模型评价标准 模型超参数：指模型的较高维度的由人工调整的参数\n模型参数：指由模型自主学习并调整的学习函数的参数，例如权重、偏置等\n模型容量：用于描述模型的复杂程度，数学定义是模型的假设空间(所有可选函数的集合)的大小\n经验误差(训练误差)：指模型在训练集上的预测能力，模型在训练集上的预测能力越强，训练误差越小\n泛化误差(测试误差)：指模型在经过样本集训练后，对未知数据集进行预测的能力\n对未知数据的预测结果越准确，称泛化误差越小，泛化能力强\n过拟合：模型的训练误差很小，但泛化误差大，以至于显得像在对样本集死记硬背，称为过拟合\n原因：样本过少、样本噪音过大、模型容量过大\n欠拟合：模型的训练误差很大，导致泛化误差也很大，连给定的样本集都没学明白\n原因：模型容量过小而无法学习复杂的问题\n学习过程就是调整模型容量的过程，我们需要找到一个最优容量，使得泛化误差最小\n没有免费午餐(No Free Lunch，NFL)定理：没有一个算法能在所有的问题上都优于其它算法\n数据集 数据集(或样本集)是样本的集合，样本是特征的集合\n通常，可以用设计矩阵来表示数据集，在本系列笔记中，约定样本是由若干特征值构成的列向量，而每一行对应不同样本在同一个特征上的值\n根据用处的不同，数据集可以分为训练集、验证集、测试集\n训练集：用于训练模型的数据集\n验证集：用于初步评估模型的泛化能力，不直接调整模型参数，但可帮助开发者调整超参数\n每过一个训练周期，进行一次对验证集的计算\n当然，模型可以对训练集过拟合，肯定也有对验证集过拟合的情况\n测试集：模型定型后，用于检查它的最终泛化能力，必须保证计算过程不进行任何对模型的调整\n测试集必须是大量的、能反映真实环境复杂性的样本集\n在模型任何参数不变、输入的样本集也不变的情况下，结果理应是一样的，是可以完全复现的\n数据生成过程(Data Generating Process，DGP)：指数据从其来源到被收集、存储的整个过程\n数据集的假设：尽管上述训练集、验证集、测试集的用处不同，但通常遵循一些假设能使算法效果更好\n每个数据集中的不同样本是相互独立的 不同数据集是同分布的，即采样来自相同的分布，这个潜在的分布被称为数据生成分布(通常不能被精确表示) 学习算法 机器学习(ML)是实现人工智能的一种方法\n按结构特点可分为传统机器学习和深度学习(DL)\n按算法特点可分为无监督学习、监督学习、半监督学习、强化学习\n监督学习指样本集被人工贴上标签(label)，在模型得出预测值后和标签比较并反过来调整模型，即为有监督的学习过程\n监督学习中，一条样本拥有一个标签，所有样本的标签组成该数据集的标签向量y\\boldsymbol yy\n监督学习常用于分类、回归任务\n无监督学习指样本集没有提前贴上标签，完全靠分析(寻找隐藏的)数据集合的内部特点/结构来确定它们的标签，这类模型虽然没有样本集的指示，但仍然依赖梯度下降法\n无监督学习常用于聚类任务\n半监督学习指样本集中的部分样本点拥有标签，模型通过对有标签数据的学习后完成对剩下无标签数据贴标签的任务\n相比无监督学习，它通过消耗少部分人力，得到更好的预测效果\n强化学习的样本集同样没有标签，但它不倾向于分析样本集，而是由反馈信号引导(反馈信号由环境给出)，模型追求奖励最大化，最终得出全局的策略\n开发模型的过程 训练模型的目标：调整模型的参数以及超参数，使其泛化误差最小 数据准备： 数据收集 数据清洗：处理缺失、损坏、重复数据 特征化：将数据整合为样本集，每条样本包括选定的特征 数据划分：将数据集划分为训练集、验证集、测试集 设计模型： 选择使用的模型，包括设定模型某层类型、模型层数等 根据不同的任务选择合适的损失函数 选择合适的优化算法 反复地训练模型、验证并调整超参数 ","permalink":"https://echo.ishian.me/2024/08/04/deeplearning-beginner/","summary":"介绍机器学习基础概念，包括模型评价标准、过拟合与欠拟合、数据集划分等","title":"机器学习: 基本概念"},{"content":"Effective Modern C++ 关于类 初始化 C++丰富的语法中提供了三种对象的初始化：\nc\u0026#43;\u0026#43;int a = 0; int a(0); int a{ 0 };一般来说，对数组的初始化通常只用花括号，忽略等号\n这样的初始化会带来对类类型的疑惑，这里复习一下：\nc\u0026#43;\u0026#43;Class a = ClassObj;\t// 调用复制构造函数. a = ClassObj;\t// 调用重载赋值运算符. C++通过花括号来实现统一初始化，只有花括号能在任何地点使用，既不会被看作函数声明，也可用在不可拷贝类上，你也可以自定义初始化列表构造函数，编译器会优先调用它而不是其它构造函数，只有在这种情况下，可以考虑使用圆括号调用其它构造函数，但如果需要自定义，一定需要三思，因为用户可能因这个自定义而疑惑\n一种边缘情况是，在一个类既有默认ctor又有自定义初始化列表ctor的时候，传递空花括号时编译器会选择默认ctor\n使用花括号唯一的限制是不能对内部表达式进行变窄转换(而其它两种因向下兼容问题不会检查这种转换)\n总而言之，无论是作为库开发者还是库使用者，应该注意是否含有初始化列表ctor，事实上()与{}各有优劣，但后者使用更广\n成员函数的引用限定符 在C++11中，增加了两种限制调用成员函数的情况，即调用左/右值的成员函数，与const一样，可以作为签名区分两个重名函数：\nc\u0026#43;\u0026#43;class A { public:\t// 这两个限定符要放在const后. void member() const \u0026amp;;\t// 左值限定符,只有左值能够调用它. void member() const \u0026amp;\u0026amp;;\t// 右值限定符,只有右值能够调用它. };对于容器优先使用常量迭代器和非成员迭代器函数 优先考虑常量迭代器不难理解，优先使用非成员迭代器函数的原因是：\n原生C数组不能使用成员函数，可能还有更多情况 自由函数拥有更大的通用性，因为可以随时添加对某个特定容器的特例化版本，而不用对这个数据结构进行改动 构造函数、析构函数、虚函数、赋值运算符 多态性决定了，可以通过用基类指针来使用子类方法，对于这种基类，应将析构函数声明为虚拟的；因为使用的是子类的数据，就应该使用子类的析构函数\nc\u0026#43;\u0026#43;class Base { public: virtual ~Base(); }; class Son : public Base { public: ~Son(); }; int main() { Base* Obj = new Son;\t// 应该调用Son的析构函数. } 不在构造、析构函数中使用虚函数，原因很简单，编译器会先构造父类，再构造子类，而一旦在构造父类对象时调用虚函数，那么就会调用一个尚未构造的子类对象的成员函数，这是危险的；析构函数同理，先析构子类对象再析构父类对象，在调用虚函数时子类对象早已销毁了\n永不从析构函数中抛出异常，就算需要抛出异常，也应由析构函数捕获并终止程序\n在实现使用堆内存类的赋值运算符重载时，需要考虑自我赋值的情况，一般不能先释放自身内存再拷贝右值资源：\nc\u0026#43;\u0026#43;Class\u0026amp; operator=(const Class\u0026amp; rhs) { // delete data;\t// 错误!万一rhs是自己呢? if (rhs == this) {return *this; }\t// 正确做法一:检查,但会使代码量增加. Class* tmp {data};\t// 正确做法二:先记住原先data,不会增加代码,但有暂时空间开销. data = new type(rhs.data);\t// 复制,这里已经创建新副本. delete tmp;\t// 删除原先data. return *this; } 保证常成员函数的线程安全 线程安全是指，需要在并发环境下，保证获取、改变资源的安全：\n一般来说，同时调用多个只有读操作的常成员函数不会造成线程安全问题 当多线程同时工作时，如果含有写线程，则可能引起数据竞争，即有不同线程同时修改共享数据，这破坏了线程安全，例如： 多窗口同时售票情景，需要保证已取走的共享资源不会被其它线程访问 C++11引入mutable，它只能修饰成员变量，表示数据一直可变(即使在const成员函数中)。例如，它可用于缓存已计算的值，这使得const成员函数只需要计算第一次，而在之后都访问这个结果。由于它能在常成员函数中被改变，所以即使是const成员函数，也引发了线程安全问题 有以下几种方式保证线程安全：\n使用std::mutex，即互斥量：\nc\u0026#43;\u0026#43;#include \u0026lt;mutex\u0026gt; class A { public: void Func() const { lock_guard\u0026lt;mutex\u0026gt; lg(m);\t// 锁住互斥量m,使其它线程无法访问. if (!Value) {\t// 如果没有记录. Compute;\t// 计算并记录. } return Value;\t// 返回结果. }\t// 解锁m. private: mutable mutex m;\t// 由于lock_guard调用非常成员函数,所以要声明multable. mutable Type Value; }; 使用互斥量开销较大，可使用std::atomic：\nc\u0026#43;\u0026#43;#include \u0026lt;atomic\u0026gt; class A { private: mutable atomic\u0026lt;unsigned\u0026gt; cnt;\t// 只是用于记录调用次数. }; 当然，如果能保证一个常成员函数不会发生在并发环境或者不会发生写操作，那么它的线程安全是无关紧要的\n为类型信息添加类型萃取类type_traits 假设需要统一不同类的接口，而这些类的一些类型在实现上有区别，最好使用类型萃取来管理这些类型信息\n例如，C++ STL的迭代器类型就依靠这种技术：\n链表与数组的迭代器是不同的，前者是一个具体类，而后者只是原生指针 使用部分模板特化，对类型信息进行萃取，并使算法端通过这个萃取类来访问迭代器 萃取类尝试获取容器的迭代器类，如果没有，则返回原生指针 这样就分开了算法与容器，使得算法不用考虑迭代器的实现，而总是获取到想要的迭代器 使不同的类型包括自定义类、基础数据类型等有统一的类型名，实现统一的接口，这就是类型萃取的任务\n智能指针 使用智能指针需要包含头文件\u0026lt;memory\u0026gt;\nstd::unique_ptr管理独占资源 在以前的C++版本中，智能指针由std::auto_ptr担任，但auto_ptr是在C++11之后被废弃的，它有如下问题：\n智能指针是类，设计原意为拥有原生指针的功能，并且能避开原生指针的坑，例如使用的是栈空间还是堆空间？如果使用堆空间，那么它是单个对象还是数组(决定使用delete还是delete[])？ 用拷贝方法来实现资源转移，但为了保证资源所有权唯一，原本的副本会被赋值nullptr，对于其它指向这块副本的智能指针来说是危险的；在语义上，给人\u0026quot;拷贝\u0026quot;的错觉，但其实并非拷贝 仍然需要手动delete 而unique_ptr拥有如下优势：\n用移动方法来实现资源转移，不允许拷贝，这在语义上是很清晰的 与智能指针的初衷一样，每个unique_ptr拥有唯一的资源(除非它是nullptr)，每份资源对应唯一的unique_ptr，不会导致多次delete 非nullptr指针析构时自动释放指向的资源，能安全地delete 所以对于独占资源，最好使用unique_ptr来管理，如果使用原生指针，则很有可能因多次delete而出错\nstd::shared_ptr管理共享资源 通过shared_ptr，C++拥有一套自动垃圾回收，且可预测回收时机的系统。它使用的垃圾回收策略是引用计数，事实上很多语言并不使用这套策略，因为它有一些问题：\n只有在最后一个引用对象析构时，shared_ptr会delete掉这部分资源，但对循环引用的资源无效 性能上的问题：它需要同时维护一个引用计数，而且这个引用计数是动态分配的 每个shared_ptr只对应一个控制块，即一份资源，但每个shared_ptr控制块互相不关联，这导致下面这个问题： 通过原始指针创建一个shared_ptr，将同时创建一个控制块，接下来正常应该只通过它来管理，但如果再次用同一原始指针创建新的shared_ptr，尽管它们指向的内存相同，它依旧会创建一个新的控制块，这将导致重复delete 用shared_ptr管理的资源应该在堆上，因为它总想使用delete来释放这份资源 尽量养成以下习惯：\n使用make_shared来创建控制块而不是传递一个原始指针，它保证在堆上创建新对象，而这个新对象肯定不会与其它控制块关联 通过传递shared_ptr或weak_ptr来创建新引用，这样不会创建新的控制块 由于有额外开销，尽量只在共享资源上使用它 使用weak_ptr辅助可能悬空的shared_ptr ``weak_ptr是shared_ptr的升级，当它悬空时，expired函数返回1，但是，它无法解引用，通常来说，weak_ptr`用于观测一份资源，或用在循环引用中打破循环的引用计数 不混用智能指针和原始指针 使用make函数而不是new 常用的make函数有make_unique(C++14)和make_shared(C++11)，这有以下好处：\n两种方式的区别：\nc\u0026#43;\u0026#43;#include \u0026lt;memory\u0026gt; auto upObj{std::make_unique\u0026lt;Class\u0026gt;()};\t// 调用函数申请一份Class资源. std::unique_ptr\u0026lt;Class\u0026gt; upObj {new Class}; 光是这样看似乎没有区别，但后者隐含了异常安全问题，因为在调用new和调用unique_ptr构造函数间有一层空隙，如果在这时出现异常，那么已new的资源无法被unique_ptr管理，造成内存泄漏；而前者安全性更高\n但是，make函数不支持自定义删除器和花括号初始化，在这些情况下make函数不好用\n优先考虑的关键字与场景 优先使用nullptr而不是0或NULL NULL是0LL的宏定义，与0都被看作数值，只有在必须转换成指针时才会转换，这样的优先度使得当遇到重载的数值参数函数和指针参数函数时，调用func(NULL)可能不是你想要的 nullptr增加了类型安全的保险，它只能隐式转换成任意的指针类型，编译器不会将它视为数值 nullptr是向后兼容的，可以完美替换掉老代码中的NULL nullptr可以直接使用，那么应该是某种类型的实例化，标准定义为decltype(nullptr)=std::nullptr_t 优先使用using而不是typedef进行重命名 typedef TypeName OtherName与using OtherName = TypeName都是将TypeName重命名为OtherName，某些情况下using可读性更高，例如：\nc\u0026#43;\u0026#43;void func(int a); typedef void (*OtherName)func(int);\t// 将这个函数指针类型重命名为OtherName. using OtherName = void (*)func(int);// 效果相同. 在模板类型的重命名上，using更简洁实用，例如：\nc\u0026#43;\u0026#43;template \u0026lt;typename T\u0026gt; class basic_List\u0026lt;T, Alloc\u0026lt;T\u0026gt;\u0026gt; {}; // 为了让用户声明List\u0026lt;T\u0026gt;时就像声明basic_List\u0026lt;T, Alloc\u0026lt;T\u0026gt;\u0026gt;时一样. template \u0026lt;typename T\u0026gt;\t// typedef是做不到的,最多只能嵌套在结构体内. struct ListType { typedef basic_List\u0026lt;T, Alloc\u0026lt;T\u0026gt;\u0026gt; type; }; // 并且由于这个类依赖于T, 在其它模板类使用时需要在前面加typename让编译器将它视为类型. template \u0026lt;typename T\u0026gt; class UseList { typename ListType\u0026lt;T\u0026gt;::type _M_List; }; // 而using则简单很多. template \u0026lt;typename T\u0026gt; using List = basic_List\u0026lt;T, Alloc\u0026lt;T\u0026gt;\u0026gt;; template \u0026lt;typename T\u0026gt; class UseList { List\u0026lt;T\u0026gt; _M_List;\t// 正常使用,达成目的. }; 优先使用限域enum而不是非限域 非限域enum指，一个枚举类型的所有内含名在这个enum所在的作用域都不可再使用，如果没有限定在命名空间里或限域，这些内含名就会泄露到整个文件中\n限域指C++11的一个新特性：\nc\u0026#43;\u0026#43;enum class ENUM { A, B, C };// 在枚举类型前加class,将枚举名限定在花括号内,且总可以前置声明. auto a = ENUM::A;\t// 通过枚举类型访问枚举名. 优先考虑auto而不是显式声明类型 优先使用delete而不是未定义的私有函数 当不希望用户使用一个类的构造函数，而编译器又会偷偷提供时，以前的做法是在私有域内声明，且不定义它们，但这样会使友元或成员函数有潜在的使用风险，道理很简单，应该使用delete关键字：\nc\u0026#43;\u0026#43;class A { public: A(const A\u0026amp;) = delete; };delete不止可用于成员函数，它能用于任何函数以处理隐式转换的弊端：\nc\u0026#43;\u0026#43;void func(int);\t// 如果希望只接受int,而不是char,bool,double,就删除它们. void func(char) = delete; void func(bool) = delete; void func(double) = delete;\t// float会先转换成double,所以这条同时删除了float. 它也能用于删除特化模板的实例化\n优先使用noexcept只要函数不抛出异常 C++11引入的noexcept在以前是throw()，但拥有更多的优化灵活\n对于一个绝对不会抛出异常的函数来说，可以在后面添加noexcept来承诺，允许编译器极尽所能优化生成的代码\n而且，noexcept可视条件优化，即：\nc\u0026#43;\u0026#43;template \u0026lt;typename T\u0026gt; void swap(T\u0026amp; a, T\u0026amp; b) noexcept(noexcept(swap(*a, *b)));即只要提供不抛异常的swap(*,*)，那么swap(\u0026amp;,\u0026amp;)就是不抛异常的\n使用except需要考虑改动，因为只要承诺了，就要考虑用户的使用，未来一旦改动，那么向下的代码都有可能出现问题，一般而言，移动、交换、内存释放等相关函数能靠noexcept得到相当大的优化\n尽可能使用constexpr constexpr是C++11引入的关键字，从文字上来看，是const与expr的组合，即常量表达式：\n对于constexpr对象而言，与const对象不同的是它需要在编译期就决定，即编译期常量 对于constexpr函数而言，如果传递constexpr对象，函数会返回一个编译期常量，否则将在运行时计算；注意这不表示函数返回一个const值，而是\u0026quot;如果传递编译期常量参数，则返回值可作为编译期常量\u0026quot; 注意在C++11中，只能用1条return语句，而在C++14中限制减小了 注意constexpr与const、noexcept同为接口的一部分，如果未来可能修改，那么最好不要使用它们 类型推导细则 模板类型推导 通常通过定义模板类或模板函数来减少代码量，使用模板所要求的是，每个使用过它的.cpp都能直接看见它的定义，即声明与定义应该放在同一份头文件内\n因为不同参数类型的模板作为头文件在展开时需要实例化，而展开过程在预处理期，所以当声明与定义分开时，将因头文件找不到定义而展开失败，导致无法编译\n而一般头文件的展开只是复制粘贴而已，不影响编译，那么整个过程就不会出错\n放在一起时编译器会丢弃重复的实例化，最后随机保留一份；事实上inline、module等新特性正在致力于将所有定义放进头文件内或直接不使用头文件\n通常来说，对于一个模板，编译器会对T与ParamType推导类型：\nc\u0026#43;\u0026#43;template \u0026lt;typename T\u0026gt; void func(ParamType Param);ParamType可以有一些修饰符，但终究ParamT会被推导为非引用非指针类型，例如：\nc\u0026#43;\u0026#43;template \u0026lt;typename T\u0026gt; void func(const T\u0026amp; Param);这里函数签名内的T会推导成模板参数T，这是最简单的情形：当传入的ParamType是一个指针或一个非通用引用时(只能作为左值或右值之一的引用)，T就会是非引用非指针类型\n如果ParamType为通用引用(既可能是左值，也可能是右值)，在作为右值时当然与上述情况相符，但作为左值时，考虑以下模板：\nc\u0026#43;\u0026#43;template \u0026lt;typename T\u0026gt; void func(T\u0026amp;\u0026amp; Param); int x = 1; func(x);\t// 此时Param为左值,推导出不正常的T = int\u0026amp;. func(1);\t// 此时Param为右值,推导出T为正常的T = int, ParamType为int\u0026amp;\u0026amp;. 只有在引入了右值引用的C++11中才出现通用引用，在类型推导时会区分引用的左值与右值\nauto类型推导 一般进行auto的情况有例如申请迭代器等场合，事实上除了推导花括号的初始化外，其它情况均与模板推导相同，而对于内部元素类型相同的花括号，auto能够成功推出它是一个初始化列表，以及它的模板参数\nauto声明变量一定要求初始化，这在很多情况下比不用auto要安全；除此之外，使用它也可以解决某些移植性问题\nauto也可用于函数返回类型的自动推导，但单独使用auto推导返回类型比较少用，由于与模板推导是及其类似的，推出的类型大部分情况下是非引用非指针类型的，这在很多情况下会造成麻烦\n使用auto时需注意一些特殊情况，例如隐藏的代理类调用：\n例如对一个vector容器，它在实现bool时采取1位存储，由于C++不允许对位的引用，导致vector\u0026lt;bool\u0026gt;的operator[]只能返回一个代理类vector\u0026lt;bool\u0026gt;::reference对象来模仿引用的行为，这时使用auto推导出的对象是实实在在会改变容器的(而不像其它类型返回的是值)\n在上述情况下，可以考虑使用显式类型转换器，这能保证既进行了初始化，又进行了类型转换：\nc\u0026#43;\u0026#43;vector\u0026lt;bool\u0026gt; vec; auto value = static_cast\u0026lt;bool\u0026gt;(vec[0]);\t// 结果为bool. auto reference = vec[0];\t// 结果为vector\u0026lt;bool\u0026gt;::reference. 当然，一般的bool空间是八位，一个字节，可以用bool\u0026amp;\ndecltype decltype是C++11新增的关键字，用于在编译期推导出表达式内的类型，并用这个类型作为后面变量的类型\n它与auto强制要求初始化不同，它只是帮你推导出你提供的表达式最终的类型而已，而且它与模板类型推导规则是不同的，对非直接变量名进行decltype，通常返回它的引用\ndecltype通常与尾置返回类型相匹配使用，尾置返回类型这个C++11引入的新特性在声明复杂函数时有奇效：\nc\u0026#43;\u0026#43;// 定义返回一个一维数组类型的函数. int (* func(int arr[][3], int n) )[3];\t// 一般声明. auto func(int arr[][3], int n) -\u0026gt; int(*)[3];\t// 尾置返回类型声明. 这里的auto不代表让编译器自动推导类型，而是告诉编译器使用尾置类型\n但当函数复杂度极高时，例如定义一个返回上述func()的指针的函数时：\nc\u0026#43;\u0026#43;auto ptf() -\u0026gt; int (*func(int[][3],int))[3]; auto ptf() -\u0026gt; decltype(func);这并没有快捷多少，所以decltype就派上用场了\n还有一个场景，设想一个operator[]函数，我们需要它返回元素的引用：\nauto会去除引用 decltype将推导出引用类型 那么组合起来，就像下面这样：\nc\u0026#43;\u0026#43;auto func(int arr[], int n) -\u0026gt; decltype(arr[n]); // 或: decltype(auto) func(int arr[], int n);decltype(auto)是指，让编译器自动推导类型，并且使用decltype的规则\n将数组推广为容器，那么通常传的是它的引用，这意味着只能传左值引用，事实上，虽然传入临时对象返回它的元素的引用是未定义的，但极限情况是有用的，如果需要支持它，可以使用通用引用：\nc\u0026#43;\u0026#43;decltype(auto) func(Container\u0026amp;\u0026amp; c, Index i);","permalink":"https://echo.ishian.me/2024/07/15/cpp-effective-moderncpp-reaction/","summary":"记录Effective Modern C++中初始化、引用限定符、智能指针与移动语义等要点","title":"Cpp: Effective Modern C++阅读笔记"},{"content":"数据库 介绍 数据库分两种，关系型(Relational Database)和非关系型(Not Only SQL)，它们的区别：\nRDBMS：使用表格存储数据；具有强大的查询性能；但只能存储在硬盘，读写性能较差 NoSQL：提供多种存储数据的结构；不支持SQL，查询方面差；但可使用RAM，数据量大时有优势 数据库必须符合ACID：\nAtomicity：事务若执行失败，数据库能恢复到事务开始前的状态 Consistency：写入的数据必须符合预设规则，否则事务执行失败 Isolation：允许并发事务的存在，是线程安全的 Durability：数据及其修改是永久存在的(体现在保存于硬盘上) SQL(Structured Query Language，结构化查询语言)是用于操作数据库的语言\n不同数据库提供的SQL不一样，所以常出现在数据库的名字里\n目前常用的开源数据库有：\nSQLite：轻量的数据库 MySQL PostegreSQL Navicat是一款能直观地操作数据库的应用\nSQL标准 杂碎知识 虽然不同数据库的SQL语法有差别(实现上也不同)，但它们都遵循SQL标准 SQL是不区分大小写的 称表的一行为一条记录 通过CREATE DATABASE dbName创建数据库 文本数据需要添加单引号或双引号；当文本数据是一个数字时，需要仔细检查 常用数据类型 文本类型： CHAR(N)：长度固定为N的字符串，文本长度不足则用空格补充，N最长一般为65535 VARCHAR(N)：长度可变的字符串，最长为N TEXT：适用于长文本 数值类型： INT/INTEGER：4字节整型 SMALLINT：2字节整型 BIGINT：8字节整型 FLOAT(D)：小数位最多为D的浮点数 DECIMAL(M, D)：总位数为M，小数位数为D的浮点数 时间类型： DATE：存储日期YYYY-MM-DD TIME：存储时间HH:MM:SS DATETIME：存储日期时间YYYY-MM-DD HH:MM:SS 其它： BLOB：存储大量二进制数据(如图片、音频等) ENUM：枚举类型 查询语句 查询结果是类似于表的结构，对数据库的许多操作需要配合查询语句一起使用\nSELECT系列语句用于确定列，WHERE系列语句用于确定行\nsql# 查询列 SELECT * FROM tableName; # 查询整个表 SELECT columnNameList FROM tableName; # 查询特定列, 列间用逗号隔开 # 查询时过滤记录 SELECT DISTINCT ... ; # DISTINCT: 查询后,自动去除完全重复的记录 SELECT ... WHERE condition; # WHERE condition: 按condition过滤记录 # condition的编写: 在查询时, 若表达式返回真, 则加入查询结果 # 格式 返回真的情况 columnName \u0026gt;/=/\u0026lt; value # 和值比较 columnName IS NULL # 空值 columnName BETWEEN val1 AND val2 # 介于val1, val2间的值 columnName IN (val1, ...) # 等于括号内任意值 columnName LIKE val # LIKE模糊查询 columnName REGEXP val # 正则表达式匹配 columnName GLOB val # SQLite支持GLOB匹配 # condition语句编写 # 括号优先级最高; 若不熟悉运算符的优先级, 可以直接打括号 exp1 AND exp2 # 逻辑与 exp1 OR exp2 # 逻辑或 NOT exp # exp的否定 # 在condition后可添加ORDER BY语句, 表示按列排序 # 默认为ASC(上到下升序), DESC为上到下降序 # 若有复数个列, 则按顺序排序(可认为是先排最后列, 再排第一列) SELECT ... FROM ... WHERE ... ORDER BY columnNames ASC / DESC; # 例: 对下表查询SELECT * FROM table ORDER BY name, id; (先排id, 再排name) id | name ---\u0026gt; id | name ---\u0026gt; id | name 3 a 1 b 2 a 1 b 2 a 3 a 2 a 3 a 1 b # 起别名: # 在查询语句中, 所有涉及表名、列名的语句, 可在名字后添加 AS 别名, 查询结果将使用别名 SELECT * FROM a AS newName;LIKE通配符：\n%：匹配任意数目的字符 _：匹配单个字符 若字段中含通配符，可通过ESCAPE转义字符：LIKE 'a\\_b' ESCAPE '\\' 对表内容的操作 对表内容的操作一定记得联合查询语句一起使用，否则可能会修改所有记录\nsql# 更改记录内容 # WHERE指定要修改的行, SET指定要修改的列 UPDATE tableName SET column1=value1,...,columnN=valueN WHERE condition; # 删除具体记录(配合查询语句) DELETE FROM tableName WHERE condition; # 插入新记录 INSERT tableName (columnList) VALUES (valueList); # 以上为对列进行修改的语句, 而更新表的列需要用到ALTER系列语句 # 添加列 ALTER TABLE tableName ADD columnName valueType # 删除列(SQLite不支持) ALTER TABLE tableName DROP COLUMN columnName # 更改列的数据类型及其约束 ALTER TABLE tableName MODIFY COLUMN columnName valueType # 修改列名 ALTER TABLE tableName CHANGE columnName newName; # 需要检查的情况(需要设置主键约束): # 记录不存在则插入、存在则更新 INSERT OR REPLACE INTO ... # SQLite REPLACE INTO ... # MySQL INSERT IGNORE INTO ... # 记录不存在则插入、存在则忽略操作对表整体的操作 sql# 创建表 # 列名不是数据, 不用加引号 CREATE TABLE tableName ( columnName dataType, # 列名 该列的数据类型 逗号分隔开不同列 ... ); # 用oldTable的查询结果作为内容, 创建新表 CREATE TABLE newTable AS SELECT * FROM oldTable; # 用oldTable的查询结果作为内容, 插入 已创建 的表里 INSERT INTO exTable (columnList) SELECT * FROM oldTable; # 修改表名 ALTER TABLE tableName RENAME TO newName; # 删除表 DROP TABLE tableName; # 删除表的所有数据, 保留表 TRUNCATE TABLE tableName; # 一次性删除所有记录 DELETE FROM tableName; # 找出所有行, 逐个删除(DELETE还可用于删除特定行)部分关键字 sql# 创建/删除表时, 判断表是否不存在/存在: CREATE TABLE IF NOT EXISTS ...; # 若表不存在, 才创建 DROP TABLE IF EXISTS ...; # 若表存在, 才删除 # 数据的约束(创建表时, 在数据类型后指定; 或者使用ALTER ... MODIFY修改) NOT NULL # 该列数据一定非空 UNIQUE # 该列数据各不相同 PRIMARY KEY # 主键约束:该列数据各不相同, 且不为空 DEFAULT value # 插入记录时, 若未指定该列的值, 则使用默认值value AUTO_INCREMENT # 用于整型的自动增长 COMMENT # 注释(放在最后) # 格式特殊的外键约束: # 用于绑定两表的两列, 本表是从表, otherTable是主表 # 从表添加记录时, 外键约束列的数据必须存在于主表内 # 从表删除记录时, 主表对应的记录不删除 FOREIGN KEY columnName REFERENCES otherTable(columnName) dataType; # 外键约束的关键字: CASCADE # 主表修改/删除记录时, 从表对应外键字段随之修改/删除, 常用 RESTRICT # 和CASCADE相反 SET NULL # 主表删除记录时, 从表对应外键字段设置为空 # 也可将修改和删除拆开: ON UPDATE CASCADE / ON DELETE CASCADE / ON UPDATE RESTRICT / ON DELETE RESTRICT # 可以在数据类型后指定, 也可以在后面批量设置约束: CREATE ... ( ..., CONSTRAINT 约束名 UNIQUE (要添加的列) );Qt Sql 前言 Qt提供了两种通过C++修改数据库的方法：一是模型/视图套件；二是提供查询接口，直接输入SQL语句\n它对SQLite的支持是最好的，可直接使用；而有些数据库的驱动需要自行编译，例如MySQL\nMySQL驱动编译\nQSqlDatabase 该类的对象是将数据从硬盘加载到内存上的桥梁\n连接数据库：\nc\u0026#43;\u0026#43;// 以连接SQLite为例 QSqlDatabase db = QSqlDatabase::addDatabase(\u0026#34;QSQLITE\u0026#34;); // 创建数据库对象 db.setHostName(\u0026#34;127.0.0.1\u0026#34;); // 设置主机名 db.setPort(3306); // 设置端口 db.setUserName(\u0026#34;root\u0026#34;); // 设置拥有者名 db.setPassword(\u0026#34;1\u0026#34;); // 设置密码 db.setDatabase(\u0026#34;mdb.db\u0026#34;); // 设置数据库路径; SQLite是一个.db文件 db.open(); // 打开数据库 // 含lastError()方法:返回该对象最近发生的错误, 若一切正常则返回空串 Qt Query 在Qt中，可直接利用QSqlQuery对象操作数据库\nc\u0026#43;\u0026#43;QSqlQuery* query {new QSqlQuery(db)}; // 绑定QSqlDatabase对象即可 query-\u0026gt;exec(\u0026#34;SQL语句\u0026#34;); // 遍历查询结果 while (query-\u0026gt;next()) { query-\u0026gt;value(列名); // 返回QVariant, 存储第i条记录中指定列的数据 } // 也含lastError()方法 Qt Model/View/Delegate 模型是为了操作数据库而提供的接口，视图能让用户直观地操作数据库，有两种常用模型和视图\n所有模型都有lastError()方法\n委托是模型和视图的中间层，用于渲染视图和编辑模型\nQSqlTableModel/QTableView 以表为结构的模型/以表形式展现的视图\nc\u0026#43;\u0026#43;// QTableView控件一般在QD中绘制, 这里假设它是ui-\u0026gt;tv // 准备 QSqlTableModel* model {new QSqlTableModel(this, db); } // 指定父组件和数据库 model-\u0026gt;setEditStrategy(QSqlTableModel::OnManualSubmit); // 一般要设置手动提交 model-\u0026gt;setTable(\u0026#34;tableName\u0026#34;); // 模型要设置数据库的表的名称 model-\u0026gt;select(); // 将数据加载进模型 ui-\u0026gt;tv-\u0026gt;setModel(model); // 视图要绑定模型:表-\u0026gt;模型-\u0026gt;视图 // 通过模型修改数据库 model-\u0026gt;index(int, int) // 返回QModelIndex对象(0开始) rowCount() / columnCount() // 返回当前行/列数 data(QModelIndex) // 返回索引指向的数据(类型为QVariant) removeRow(int)/removeColumn(int) // 删除某行/列 submitAll() // 提交所有修改(真正修改数据库) revertAll() // 撤销所有修改(停止事务) // 通过视图修改模型 ui-\u0026gt;tv-\u0026gt;currentIndex() // 返回QModelIndex, 表示用户选中的格子 setCurrentIndex(QModelIndex)// 设置选中格 // 模型调用方法后, 当前选中的信息可能丢失 // 关于QVariant和QModelIndex: // QVariant含toString()和toInt()等方法, 能将字段转换成QString和int等数据类型 // QModelIndex含row()和column()方法, 返回对象包含的行/列信息 关于视图单元格的选中模式(枚举QAbstractItemView::SelectionMode)：\nNoSelection：无法选中 SingleSelection：只能单选 MultiSelection：允许多选 ExtendedSelection：允许配合Ctrl或Shift键进行选择 ContiguousSelection：允许配合Ctrl+Shift键进行批量选择 如果需要设置某列不可选中，可使用自定义委托：\nc\u0026#43;\u0026#43;class ReadOnlyDelegate: public QItemDelegate { public: ReadOnlyDelegate(QWidget *parent = nullptr) {} QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem \u0026amp;option, const QModelIndex \u0026amp;index) const override { Q_UNUSED(parent) Q_UNUSED(option) Q_UNUSED(index) return nullptr; } }; // 令委托无视掉指定列的选中 ReadOnlyDelegate* readOnlyDelegate = new ReadOnlyDelegate(this); for (auto i = 1; i \u0026lt; 3; ++i) { // 无视 ui-\u0026gt;matrixTable-\u0026gt;setItemDelegateForColumn(i, readOnlyDelegate); }QStandardItemModel/QTreeView QStandardItemModel是以项为基础的模型，主从结构分明，很适合展示树形结构\n它的编辑要比QSqlTableView复杂，不能直接通过select()(也没有setTable()方法)完成数据的加载，而要使用QSqlQuery，因此单纯的矩阵结构应该用后者绘制\nc\u0026#43;\u0026#43;// 准备 ... // 构造函数、设置手动提交、视图和模型绑定 与上述一致 ManageModel-\u0026gt;setColumnCount(2); // 设置模型共有两列 ManageModel-\u0026gt;setHorizontalHeaderLabels({\u0026#34;用户组\u0026#34;, \u0026#34;组权限\u0026#34;}); // 设置这两列的名称 // 例如要绘制二层的树结构, 表group和member如下: group: gid grpname power memberCnt member: uid grp_id[含group(gid)的外键约束] username pwd // 加载, 假设有QStandardItemModel对象的指针model: QSqlQuery group{db}, member{db}; group.exec(\u0026#34;SELECT gid, grpname FROM group\u0026#34;); while (group.next()) { QList\u0026lt;QStandardItem*\u0026gt; rowItems; // 因设置了两列, 故插入的是链表 auto tmpItem {new QStandardItem(qroup.value(\u0026#34;grpname\u0026#34;).toString())}; // 树的第一层是 组名 和 组权限 rowItems \u0026lt;\u0026lt; tmpItem \u0026lt;\u0026lt; new QStandardItem(group.value(\u0026#34;power\u0026#34;).toInt()); // 查询当前组的组员 member.exec(\u0026#34;SELECT username FROM member WHERE grp_id=\u0026#34; + QString::number(group.value(\u0026#34;gid\u0026#34;).toInt())); while (member.next()) { // 增加子项(树的第二层 用户名) tmpItem-\u0026gt;appendRow(new QStandardItem(member.value(\u0026#34;username\u0026#34;).toString())); } model-\u0026gt;appendRow(rowItems); } // 项一旦加入模型, 所有权将移交, 模型内存释放时所有项一同释放 // 树形视图的方法: tv-\u0026gt;expandAll() // 展开所有项 collapseAII() // 折叠所有项 树形视图的更多方法\n更多模型/视图\n","permalink":"https://echo.ishian.me/2024/06/30/cpp-qt-database/","summary":"介绍数据库基础概念、SQL常用语法及使用Qt进行数据库连接与操作的方法","title":"Qt: 数据库连接"},{"content":"Qt基础 在Qt5后，Qt不断开发QML和Qt Quick，同时在抛弃传统的基于C++的Qt Widgets，致力于更快捷、直观地绘制应用\n当然，Qt的优势在于极致的跨平台和前后端的顺畅结合，Qt Widgets和Qt Quick的设计思路是相似的；多是QML/C++混合开发，纯粹的Qt Widgets在前端开发上是较为低效的\n本文并不想介绍QML，而是介绍Qt Widgets\n元对象系统 简介 元对象系统(Meta-Object System, MOS)是Qt的核心机制，元对象即用于描述对象的对象；它提供以下功能：\n信号-槽：允许对象间安全的通信，方便响应事件 反射系统：允许程序自己获取(运行时获取)对象的信息，在java、dotNet中也有类似的概念 动态属性：允许程序在运行时添加属性 所有继承QObject的类中，只要私有声明了宏Q_OBJECT，就可使用MOS\n信号与槽 关键字：借助MOC(元对象编译器)，Qt为了实现信号槽机制，添加了slots,signals关键字(宏)\n信号和槽都是成员函数，一般声明如下：\nc\u0026#43;\u0026#43;class Class : public QObject { Q_OBJECT signals: ... private slots: ... };signals宏定义为public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)，一定是公有的\nslots则可以在任意域中，但一般是保护或私有的\n一般，每个QObject的子类中的信号都应该有对应的用于发送该信号的成员函数(Qt自带类中一般定义为公有槽函数)\n一般控件有继承自父类的Qt实现的信号-槽和发送-响应机制\n自定义信号：\n无返回值，可以有参数 只需声明，无需实现(MOC自动实现) 自定义槽：\n返回值及参数必须和相连接的信号一致 一旦声明，必须实现 发送信号：\n通常在成员函数中，可以使用emit关键字发送自定义信号，同时传递参数：\nc\u0026#43;\u0026#43;emit mySignal(...); 需要主动发送信号时，通常调用封装好的发送函数，而不是直接使用emit\nc\u0026#43;\u0026#43;// 官方命名习惯(例如按钮类): public slots: click(); // 发送信号. signals: clicked(); // 信号. 连接方式：\n手动连接：调用connect()，四个必须填写的参数依次为发送者指针、信号、接收者指针、槽\n有三种输入信号-槽参数的方式：\n旧版本：调用宏函数SIGNAL()和SLOT()，处理输入的函数名后返回特定字符串\n连接失败后无反馈，例：\nc\u0026#43;\u0026#43;// 参数不用添加限定符. connect(ui-\u0026gt;lineEdit, SIGNAL(textChanged(QString)), this, SLOT(doSomething(QString))); 新版本：传递函数指针，好处是连接失败后会报错，如：\nc\u0026#43;\u0026#43;connect(ui-\u0026gt;lineEdit, \u0026amp;QLineEdit::textChanged, this, \u0026amp;MainWindow::doSomething); lambda表达式(匿名函数)：好处是不用额外声明槽函数\nc\u0026#43;\u0026#43;connect(ui-\u0026gt;button, SIGNAL(textChanged(QString)), [this](const QString\u0026amp;) { ... }); 连接函数的第五个参数表示连接方式，默认为自动关联，常用的有：\nAutoConnection：信号槽在同一线程则为DirectionConnection，否则为QueuedConnection DirectionConnection：信号发射后直接调用槽，槽函数执行完毕后再执行emit语句后的代码 QueuedConnection：槽在接收者的线程中执行(若在不同线程，则发送线程继续进行) 默认连接：Qt编译器会尝试让所有名为on_objName_signalName()的槽函数进行连接\n类似于调用：\nc\u0026#43;\u0026#43;connect(\u0026amp;objName, \u0026amp;SenderClass::signalName, this, \u0026amp;thisClass::on_objName_signalName);注：objName命名不能太长，否则连接失败；连接失败后不会终止程序，但会打印错误信息\n常用控件的信号：\npythonQPushButton: # 按钮 clicked() # 被点击 QLineEdit: # 行编辑器 textChanged(const QString\u0026amp;) # 文本被修改, 同时发送修改后的文本 returnPressed() # 焦点在控件上时, 按下回车 cursorPositionChanged() # 光标移动 editingFinished() # 失去焦点/焦点在该控件上时, 按下回车 selectionChanged() # 选中区域被修改 QDialog: # 对话框 accepted()/rejected() # 因用户确认/拒绝对话框而被关闭 finished() # 被关闭 QStackedWidget/QToolBox: # 堆栈窗口(多窗口器)/工具箱 currentChanged(int idx) # 当前窗口/选项修改, 同时发送新窗口的索引 可能用到的控件的信号：\npythonQSplitter: # 窗口分割器 splitterMoved(int pos, int idx) # 分割条被移动, 同时发送移动后的位置、被移动条的索引 QAction/QToolButton: # 动作/工具按钮 triggered() # 被触发 QSpinBox/QDoubleSpinBox: # 整数旋钮/浮点数旋钮 valueChanged(int/double val) # 值被修改, 同时发送新的值 事件系统 事件和信号槽的区别：\n信号本身没有和底层硬件、操作系统交流的能力，部分信号还需要通过事件发出(如clicked()) 关注点： 信号槽：发出、接收信号的对象 事件处理：发生的是哪种事件，并不关注对象间的通信 来源： 信号：程序里的函数，由对象发出 事件：系统的底层消息 本质： 信号槽：成员函数 事件：对象 Qt的事件循环：\n产生：应用启动(QApplication::exec())后，Qt即开启事件循环，不断检测底层消息并将它们转换为事件 分发：事件通过QApplication::notify()传入到事件队列，按队列的顺序取出事件，调用事件分发函数bool event(QEvent* event) 处理：event()会分情况调用不同的事件处理函数 事件循环里，事件的产生和处理是异步的 事件也可手动发送，它们不经过事件循环\n有些类(如按钮类)的处理函数已经封装好了，而有些类(如窗口类)的键鼠事件处理函数没有语句，因此有需要时，需要重写它们，一般有两种处理方式：\n发送信号，将事件的处理解耦成多个槽函数，可扩展性强，便于跨对象、跨线程通信 直接在函数内部处理，可用于事件处理较为简单的情况 常见的事件处理函数：\npython# 启用某种属性的函数,某些事件需要特定属性才能产生 setAttribute(Qt::WidgetAttribute, bool) # 以下函数返回值为void 键盘: keyPressEvent(QKeyEvent* event) # 按键按下 keyReleaseEvent(QKeyEvent* event) # 按键松开 鼠标: mousePressEvent(QMouseEvent* event) # 鼠标按下 mouseReleaseEvent(QMouseEvent* event) # 鼠标松开 mouseDoubleClickEvent(QMouseEvent* event) # 鼠标双击 wheelEvent(QWheelEvent* event) # 鼠标滚轮滑动 enterEvent(QEvent* event) # 鼠标移进 leaveEvent(QEvent* event) # 鼠标移出 hoverMoveEvent(QHoverEvent* event) # 鼠标悬停(需启用WA_Hover) 焦点: focusInEvent(QFocusEvent* event) # 获得焦点 focusOutEvent(QFocusEvent* event) # 失去焦点 拖动: setAcceptDrops(bool) # 设置控件能否拖动 dragEnterEvent(QDragEnterEvent* event) # 被拖动的物体进入可放置区域 dragMoveEvent(QDragMoveEvent* event) # 被拖动的物体移动 dragLeaveEvent(QDragLeaveEvent* event) # 被拖动的物体离开可放置区域 dropEvent(QDropEvent* event) # 放下传入的事件本身是带有信息的，例如：\npythonQKeyEvent: key() # 返回Qt::Key枚举值, 表示按下的是哪个键 QMouseEvent: pos() # 返回QPoint, 表示鼠标相对于该控件的位置 globalPos() # 返回QPoint, 表示鼠标在屏幕上的位置关于事件分发函数event()的重写：\nc\u0026#43;\u0026#43;// event()是事件分发函数, 是有返回值的(表示事件是否已处理) // 重写event()的目的是实现 事件过滤 或 想在事件处理前执行一些代码 或 分发自定义事件 bool event(QEvent* event) override { switch (event-\u0026gt;type()) { // 想处理的事件 case QEvent::KeyPress: ... keyPressEvent((QKeyEvent*)event); // 标记为已处理(Qt默认调用accepted, 可以在处理函数里手动调用ignore()) if (((QKeyEvent *)event)-\u0026gt;isAccepted()) return true; // 则不传递 break; case 想忽略的事件: return true; // 虽然未处理, 但可以直接返回true表示已处理 } return QWidget::event(event); // 未处理, 交给父类处理 }项目初始化 通过项目初始化来介绍Qt在控件上的一些机制和默认\n.ui文件 在创建Qt项目时，应该自选生成.ui文件，能轻易地在Qt Designer中修改它，并定义窗口和控件对象、以及它们的初始化\n该文件使用xml语言，在Qt Designer上修改比直接编辑快的多\n其能在C++程序中起作用的原因是，Qt编译器会通过uic(ui编译器)将name.ui翻译成头文件ui_name.h，其中定义了类Ui_name：\n该类定义了所有在QD设计的、除窗口类外的控件\n定义了成员函数setupUi(QMainWindow*)，用于初始化各种控件\n自动生成的项目主窗口类中，默认定义Ui_name类的指针ui\n所有构造函数的第一行应调用**ui-\u0026gt;setupUi(this)**，以确保能安全调用ui中的控件\n主窗口类及内存机制 学习自动创建的主窗口头文件格式，便于学会自定义控件类\n设创建项目时定义的主窗口类为MainWindow，则应有头文件定义：\nc\u0026#43;\u0026#43;class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget* parent = nullptr); ~MainWindow(); }; Q_OBJECT：Qt定义的宏，只有声明了它，才能使用元对象系统\n任何继承了QObject类的子类都应私有声明该宏\n简单窗口类可直接继承自QWidget\n复杂窗口类(如包含菜单栏、对话框)应继承QMainWindow，才能正常显示\n构造函数的实现至少是这样的：\nc\u0026#43;\u0026#43;MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui_MainWindow) { ui-\u0026gt;setupUi(this); }作为参数传入的parent和this是有必要的，因为Qt有自己的一套回收内存的机制：\n半自动回收：所有QObject类及其子类对象都定义了parent和链表(用于存储子控件的指针)\n习惯上，所有控件类对象都使用堆内存，并在构造时指定父控件\n当释放父控件的内存时，会先释放所有子控件的内存，因此只要记得释放根控件的内存，就不会出现内存泄露\n而释放子控件的内存后，它会从其父控件的链表中删去，因此不会出现重复释放内存的情况\n所有这些对象都有**QObject::setParent()**方法，用于设置父控件\n手动释放：对于父控件在不同线程、或非QObject系的占用堆内存的对象，无法半自动回收，应在其所在线程里调用**deleteLater()方法，调用后会在线程结束时释放其内存**\n外部资源管理 Qt中通过.qrc(Qt Resource)文件管理外部资源，该文件使用xml语言，在QC中能更直观地使用\nxml\u0026lt;!DOCTYPE RCC\u0026gt; \u0026lt;RCC\u0026gt; \u0026lt;qresource prefix=\u0026#34;/Icons\u0026#34;\u0026gt; \u0026lt;file\u0026gt;images/name.jpg\u0026lt;/file\u0026gt; \u0026lt;/qresource\u0026gt; \u0026lt;/RCC\u0026gt;直接编码也不难理解，这些标签的含义是：\nRCC：其内容会被rcc(Qt的资源编译器)识别，并把资源编译到可执行文件里 qresource：用于指定其所属文件的前缀，前缀默认为\u0026quot;/\u0026quot; file：其内容为资源的真正的路径，例如上述语句引入了处于./images/name.jpg路径(相对于该资源文件)的图片 .qrc文件通过独特的相对路径管理资源，即冒号+前缀+文件路径，例如通过\u0026quot;:/Icons/images/name.jpg\u0026quot;访问上述语句中的文件\n使用资源的方式：\nc\u0026#43;\u0026#43;setWindowIcon(QIcon(路径)) // 设置窗口图标，仅窗口类可使用 setIcon(QIcon(路径)) // 设置控件图标，仅支持图标的控件可使用 QFile qssFile(\u0026#34;:/path/name.css\u0026#34;); // 引入外部样式表 qssFile.open(QFile::ReadOnly); qApp-\u0026gt;setStyleSheet(qssFile.readAll()); // 加载到整个应用程序(所有QWidget类对象都可加载) QSound::play(\u0026#34;:/path/name.wav\u0026#34;); // 播放wav音频文件(静态播放,只能在主线程中调用) QSound* musicPlayer {new QSound(路径)}; // 加载进内存后播放 musicPlayer-\u0026gt;play(); QMediaPlayer* musicPlayer {new QMediaPlayer}; // 可播放wav、mp3、mp4等 musicPlayer-\u0026gt;setMedia(QUrl::fromLocalFile(路径)); musicPlayer-\u0026gt;play();样式 关于样式，推荐通过QD进行初始化，或一律通过外部样式表统一，而不是通过C++编码修改\n如果有CSS基础，许多属性会很熟悉\n控件 所有QWidget系对象都有(由以下类组合而成)：\nfont：存储字体族、字体大小、粗体、斜体等(构造函数的参数就是这四个属性)\n对象可调用setFont(QFont)方法修改\nsizePolicy：存储大小和伸缩的策略\n伸展策略：实质是枚举QSizePolicy::Policy，可通过setSizePolicy()修改\n基于sizeHint有不同的策略，以下称其为默认值\nFixed：控件大小始终为默认值\nMinimun/Maximun：控件的默认值为最小/最大尺寸，大小可以大于/小于默认值\n最小/最大尺寸可通过setMinimunSize()/setMaximunSize()设置\nPreferred：控件大小是默认值，但可以放大/缩小\nExpanding：控件可以随意放大/缩小\n当Preferred和Expanding的控件同时存在于布局中时，前者会把空间让给后者且尽量保持默认值\n伸展因子：一般通过布局来管理，伸展因子越大，拉伸控件时改变的空间越多\nfocusPolicy：存储获取焦点的策略，实质是枚举Qt::FocusPolicy\nStrongFocus：默认值，也最常用，可通过鼠标点击、Tab键获取焦点 TabFocus/ClickFocus：Tab键/鼠标点击获取焦点 NoFocus：不可通过键鼠获取焦点 geometry：不重要，一般通过布局来控制它\n布局 Qt提供以下几种布局管理器：\nQHBoxLayout/QVBoxLayout：水平/垂直布局 QGridLayout：网格布局 QFormLayout：表单布局 布局有助于控件随窗口拉伸而自动拉伸，以下操作可在QD快速实现：\n窗口调用setCentralWidget()设置中心控件 该中心控件调用setLayout()设置布局 窗口 如要限制窗口的放大/缩小键，可通过setWindowFlags()设置，参数为枚举Qt::WindowType\nWindowMaximunButtonHint/WindowMinimunButtonHint：最大化/最小化 WindowCloseButtonHint：关闭键 更多常用方法 pythonQWidget: resize() # 重置大小 setFocus() # 主动吸引焦点 setHidden() # 设置是否隐藏控件 QMainWindow: setWindowTitle() # 设置标题 show() # 显示窗口 close() # 关闭窗口 QDoubleSpinBox: setDecimals() # 设置浮点数精度(实质是字符串) 文本编辑/显示器: setText() # 设置文本, 会解析HTML内容 setPlainText() # 设置纯文本 text() # (QLineEdit)获取文本 toPlainText() # (其它)获取文本 按钮类: setEnabled() # 设置是否启用 布局管理器: addWidget() # 添加控件 columnCount()/rowCount() # 列/行数 QStackedWidget: setCurrentIndex() # 设置当前窗口 QString: number() # 静态成员, 将整型/浮点型转化为QString QMessageBox: information() # 静态成员, 呼出简单的小窗口(可添加Ok/Cancel按钮并返回用户的选择) qDebug() \u0026lt;\u0026lt; Data # 打印信息","permalink":"https://echo.ishian.me/2024/06/27/cpp-qt-beginner/","summary":"介绍Qt Widgets基础、元对象系统、信号与槽机制及事件处理等核心概念","title":"Qt: 基本用法"},{"content":"使用Git进行版本管理 介绍 Git是一个分布式版本管理工具，它诞生的故事很有意思-\u0026gt;Git的历史 版本控制器：当我们需要修改代码但又担心无法回退时，我们需要创建副本。当修改次数多、修改规模大时，我们很难记住这些副本实现了什么功能或是修复了哪些bug，也不知道改动了哪里，而这只是一人修改的情况，如果是一个需要互相沟通的团队呢？因此，版本控制器应运而生，它方便我们查看历史版本、快速回退、创建版本分支等。总而言之，一个合格的程序员应学会熟练使用Git这个高效强大且开源的版本控制工具 基本概念 Git是分布式版本控制系统，即除了中心仓库以外，每个开发者在本地有完整的仓库副本\nGit在本地的工作目录中，通过.git目录存储所有的数据\nGit在本地分为三个区域\n工作区：当前、即时的内容，但修改没有保存在.git里\n暂存区：通过git add保存的修改会被保存到暂存区，即.git里\n暂存区又被称为index\n本地仓库：通过git commit将暂存区的修改提交到本地仓库\n当前指向的提交能通过HEAD指针访问\n文件的状态：\noutput?? file.txt # 未跟踪 (Untracked) A file.txt # 新增到暂存区 (Added to index) M file.txt # 已修改，未暂存 (M前有空格) M file.txt # 已修改，已暂存 MM file.txt # 已修改，暂存后又修改了 D file.txt # 已删除，已暂存 D file.txt # 已删除，未暂存 (D前有空格) R old -\u0026gt; new # 重命名 C file.txt # 复制 UU file.txt # 冲突 (Unmerged) Git会将一次提交生成一个哈希值来标记，并使用**HEAD**来指向当前所在的提交\nGit将新增行和删除行都视为文件的修改，即视为M\n将修改行视为删除行和新增行的组合\nGit还支持一个文件内的部分修改块进行单独暂存，这一部分修改默认使用Myers算法求得，结果被称为hunk\nGit提供分支管理，分支名称指向该分支的最新提交，就像HEAD指向当前所在提交类似的\nGit对远程仓库在本地的跟踪：支持使用自定义名称指向，例如origin和upstream\n注意这仍是在本地的，只不过是跟踪了远程仓库，它们不会自动更新\n可以通过origin/xxx访问本地存储的origin仓库的xxx分支\n命令行符号 --：用于消除歧义，在--后跟文件名不会有歧义\n~x：相对引用，HEAD~x指向HEAD之前的沿着主分支的第x代父提交，x表示向上回溯的代数\nHEAD等价于HEAD~0、HEAD~等价于HEAD~1，~通常用于HEAD，但是也完全支持对分支名使用，支持链式调用HEAD~1~1\ntext B---C---D [branch1] J [branch2] / \\ / \\ [main] A---E---F---G---H---I---K例如对于K这类合并提交，K~1指向I、K~2等价于I~1(非合并提交)因此指向H、K~4等价于G~1指向F\n^x：相对引用，HEAD~x指向HEAD之前的第x个父提交\nHEAD^等价于HEAD^1等价于HEAD~1、HEAD^^等价于HEAD^2，支持混合的链式调用HEAD^~\n同样是上面的例子，K^1指向I、K^2指向J\n@{x}：引用日志的相对引用，表示某分支在x次操作之前的位置，即当前指向的是HEAD@{0}\n可以通过git reflog查看引用日志\n:!\u0026lt;pattern\u0026gt;：用于排除\u0026lt;pattern\u0026gt;匹配的文件\nA..B：两点语法，用于查看B相对于A不同的地方，可用于diff、log等子命令\n哈希值、HEAD、分支名（包括和远程仓库关联的分支）都可以\n默认，A或B省略时，使用HEAD\nA...B：三点语法，用于查看A和B相对于LCA(A, B)不同的地方，可用于diff、log等子命令\n基本配置(config) git config子命令用于修改项目配置，也可以通过修改.git/config文件\ngit config --global [xxx]：修改全局配置，文件一般位于$HOME/.gitconfig\ngit config --global user.name \u0026quot;user name\u0026quot;：提交时的用户名，带空格时需要\ngit config --global user.email xxx@xxx.com：提交时使用的邮箱\n由于github等平台以邮箱为索引用户唯一依据，所以user.email是必须设置好的，否则上传到远程仓库的话贡献就不是自己的了\ngit config --global core.editor nvim：使用git进入编辑器界面时，会使用系统默认的编辑器，也可以通过core.editor配置修改\ngit config --global core.pager bat：使用git log或git diff时，输出将默认交给系统分页器如less，可配置修改\ngit config --global url.\u0026lt;base\u0026gt;.insteadof \u0026lt;alias\u0026gt;：对url中的\u0026lt;base\u0026gt;起别名\u0026lt;alias\u0026gt;\ngit config --global url.git@github.com:.insteadof https://github.com/：例如将https替换为ssh前缀，可以做到每次都直接使用ssh访问github的效果\n还可以替换为自己搭建的github镜像\ngit config --global alias.\u0026lt;alias\u0026gt; \u0026lt;base\u0026gt;：对\u0026lt;base\u0026gt;（如checkout等）起别名\u0026lt;alias\u0026gt;\ngit config --list：查看所有配置\n--show-origin：查看所有配置，同时显示来源 --local/--global：指定查看本地/全局 git config --get xxx：查看配置\ngit config --get-regexp xxx：模糊查询，使用简易的通配符匹配\ngit config --unset [xxx]：重置某个设置\n基本使用 创建仓库 创建本地仓库\nshgit init # 新建空仓库 git init [dir] # 指定目录作为仓库 git clone [href] # 克隆别人的仓库, href即资源位置新建后，该文件夹内就会出现.git隐藏文件夹，可以进行提交等操作\nhref在github中，你可以在一个项目的根目录下找到一个绿色的Code按钮，里面就有url\nhref的一般格式：https://github.com/用户名/仓库名.git、git@github.com:用户名/仓库名.git\n暂存与提交(add与commit) 暂存部分或全部文件的修改到暂存区：\nshgit add file1 file2 ... git add *.java git add . # 暂存所有修改和新增 git add . -- \u0026#34;:!target/\u0026#34; # 暂存除了`target/`以外的 git add -A # 暂存所有修改、新增、删除 git add -u [-- \u0026#34;xxx\u0026#34;] # 暂存所有或部分文件的修改、删除，即不暂存未跟踪文件 git add -i：打开交互式暂存\ngit add -p [xxx]：对全部文件或部分文件打开选择式暂存修改(hunk)，会针对每一个hunk询问\ny - stage this hunk n - do not stage this hunk q - quit; do not stage this hunk or any of the remaining ones a - stage this hunk and all later hunks in the file d - do not stage this hunk or any of the later hunks in the file j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk g - select a hunk to go to / - search for a hunk matching the given regex e - manually edit the current hunk ? - print help git status：检查暂存区内的内容，这些内容和commit时的那些注释内容是一致的\n-s：简短查看，使用M、A、D、??等标志 将暂存区内容提交到仓库：\nshgit commit -m \u0026#34;explanation\u0026#34; # -m后为注释, 一般只适用于只有标题的提交 git commit # 进入编辑器编辑提交信息一般来说，每次更新或修复一个功能，就需要提交一次，因此，上述命令是很常用的\ngit commit --amend：追加提交，将暂存区提交到上一个提交，这种操作会产生新的哈希值\ngit commit --allow-empty：空提交，通常用于触发CI/CD\ngit commit -p：补丁式提交\ngit commit --no-edit：不进入编辑器直接提交\n分支管理(branch与switch) 分支在此前基本概念一节已经介绍过了，Git将本地分支存储在.git/refs/heads/下，远程分支存储在.git/remtoes/中\ngit branch子命令用于管理分支\n查看分支：\n-a：查看所有分支，包括远程分支，远程分支指的是本地中跟踪了远程仓库的分支，仍然存储在本地 -v：查看所有本地分支同时显示最新提交 -vv：在-v的基础上额外显示最新提交上是否有上游的远程分支 --merged：查看已经合并的分支 --no-merged：查看未合并的分支 -r：查看所有远程分支 创建分支：\ngit branch \u0026lt;branchname\u0026gt;：基于当前分支创建新分支\u0026lt;branchname\u0026gt; git branch \u0026lt;newb\u0026gt; \u0026lt;commit/branch\u0026gt;：基于指定提交/分支创建新分支 迁移分支：\ngit branch -d \u0026lt;bname\u0026gt;：删除某分支，无法删除所在分支\ngit branch -D \u0026lt;bname\u0026gt;：强制删除某分支\ngit branch -m \u0026lt;newname\u0026gt;：重命名当前分支\ngit branch -m \u0026lt;oldname\u0026gt; \u0026lt;newname\u0026gt;：重命名指定分支\ngit branch -u \u0026lt;remotename\u0026gt;/\u0026lt;bname\u0026gt;：设置当前分支的上游分支\n等价于--set-upstream-to=\u0026lt;rname\u0026gt;/\u0026lt;bname\u0026gt;\n也可以在创建分支的同时使用--track参数直接设置上游分支\n在git 2.23之前，使用checkout进行切换分支、恢复文件，因此网上的教程多有使用checkout的，但此后checkout被分割成switch和restore\ngit switch用于切换分支\n-c \u0026lt;newb\u0026gt; [\u0026lt;base\u0026gt;]：切换时若分支不存在则创建 -f：默认若本分支存在未暂存/未提交的更改，则无法切换，-f表示丢弃本地修改（包括工作区和暂存区） -m：携带所有未提交的修改带到下一个分支 -t：创建分支时指定跟踪远程分支 -：切换到上一个分支 恢复更改(restore与reset与rm与reflog) git restore专注于工作区、暂存区和本地仓库HEAD指向提交之间的文件恢复，替代了checkout与reset的部分功能\ngit restore \u0026lt;file\u0026gt;：丢弃工作区的修改恢复到HEAD状态，暂存区仍保留\nPS：老版本中为git checkout -- \u0026lt;file\u0026gt;，--消除歧义，但checkout这种方式不会提供操作日志，较为危险\n--worktree：就是上述命令所默认使用的参数\n--staged：将暂存区的文件移出到工作区\nPS：等价于git reset HEAD \u0026lt;file\u0026gt;\n--patch：补丁式恢复\n--source=\u0026lt;commit\u0026gt;：指定恢复的源，默认是HEAD\ngit reset \u0026lt;commit\u0026gt; [\u0026lt;file\u0026gt;]专注于不同提交之间的文件恢复，如果不跟文件则默认对所有文件执行，reset只能在本分支移动HEAD\n--mixed：是默认模式，移动分支指针，将暂存区移动到工作区，不修改工作区 --soft：只移动分支指针 --hard：移动分支指针、清空工作区和暂存区 git rm \u0026lt;file\u0026gt;用于快捷地删除工作区内容和删除误暂存的内容\n默认情况下，删除工作区的文件同时停止跟踪\n使用--cached参数只停止跟踪，保留在工作区而不删除\n使用rm --cached配合commit --amend可以删除被误提交的二进制文件等（工作区不变）：\nshgit rm --cached target/ git commit --amend --no-edit但这种方式只适用于最新一次提交中误提交的情况，若历史提交中存在，则只能使用filter-repo\ngit reflog用于查看操作日志\n查看差异(diff) diff子命令用于精确地查看不同区之间的差异\ndiff输出的结构：\ndiffdiff --git a/source/_posts/git.md b/source/_posts/git.md index b4a4d11..301e28f 100644 --- a/source/_posts/git.md +++ b/source/_posts/git.md @@ -117,6 +117,8 @@ tags: - e - manually edit the current hunk - ? - print help +- `git status`：检查暂存区内的内容，这些内容和`commit`时的那些注释内容是一致的 + - `-s`：简短查看，使用`M`、`A`、`D`、`??`等标志 - 将暂存区内容提交到仓库： ```sh 第一行：a/xxx表示修改前的文件（即暂存区），b/xxx表示工作区文件\n第二行表示版本哈希值和文件权限\n第三、四行表示旧文件、新文件的路径\n第五行表示块头，@@ -旧起始行号,旧行数 +新起始行号,新行数 @@\n默认带有上下三行上下文，可以用git diff -Ux来显示x行\ngit diff：查看工作区相对于暂存区的区别\ngit diff --staged：查看暂存区相对于本地仓库之间的区别\n旧版本中为git diff --cached\ngit diff HEAD：\ngit diff commitA commitB，等价于..\ngit diff xxx...xxx：三点运算符比较\n一些参数：\n--stat：显示每个文件的变更统计 --shortstat：显示每个文件的简短变更统计 --name-only：仅显示文件名称 --name-status：显示文件状态符号和文件名称 --numstat：显示变更行数统计 合并提交(merge与rebase与cherry-pick) git merge用于将两个分支合并，创建一个新的合并提交\ngit merge xxx：使xxx分支合并到当前分支\n快进合并：合并者是被合并者的直接祖先，则合并者直接移动到被合并者，不产生合并提交\ntext[main] A---B---C \\ [branch1] D---E---F (main) git merge branch1 [main] A---B---C---D---E---F [branch1] | 三方合并：合并者本身有一定修改，则创建一个合并提交\ntext[main] A---B---C \\ [branch1] D---E---F (main) git merge branch1 [main] A---B---C-------G \\ / [branch1] D---E---F 默认情况下，会保留被合并者的所有提交记录\n--squash：将被合并者的所有提交合并成一个提交，此时需要手动commit\ngit rebase用于变基，即重新设置基准点而不是创建合并提交\n发起者会提取基准分支的额外提交（如下图的C）并以基准分支的最新提交作为新的基准点，然后在这个新的基准点的基础上应用本分支的历史提交（DEF），它们的提交哈希值会更改\ntext[main] A---B---C \\ [branch1] D---E---F (branch1) git rebase main [main] A---B---C \\ [branch1] D\u0026#39;---E\u0026#39;---F\u0026#39;注意rebase通常是在非主分支上执行的，和merge、cherry-pick不同，rebase通常用于整理这个分支的提交，使其看起来是线性的\n如果一个分支已经push上去了，就不要用rebase了，因为这会导致大量的更改，使得其它在这个分支上工作的协作者全都要额外处理，而且rebase产生冲突的概率是最大的\ngit cherry-pick xxx ...用于复制提交，也不是创建合并提交，而是在源分支中自定义选择一些提交，复制到当前分支上，创建若干等量的新提交\ntext[main] A---B---C \\ [branch1] D---E---F (main) git cherry-pick branch1~..branch1~2 [main] A---B---C---D\u0026#39;---E\u0026#39; \\ [branch1] D---E---F 在使用上面三条命令时都有可能遇到合并冲突，冲突发现后使用任何命令都会失败并提醒\ngit merge/rebase/cherry-pick --abort：中止合并\ngit merge/rebase/cherry-pick --continue：解决冲突后继续合并\n冲突内容：会是下面的格式\ndiff\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; HEAD ours ======= theirs \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; branchname 可以手动编辑文件直到没有冲突为止，也可以通过checkout --ours和checkout --theirs完全采纳我们的和它们的\n这里的ours始终是对于当前分支所说的，theirs始终是对于被合并分支来说的\n解决冲突永远修改的都是当前分支中发生冲突的提交，不会修改被合并分支\n查看提交(log与show) git log用于查看提交历史 --oneline：单行查看，只显示title --graph：显示分支结构 -x：显示最近x个提交 -p：显示每次提交的差异 --stat：显示统计 git show [commit/branch/tag]用于查看提交详情 存储更改(stash与worktree) git stash用于暂时存储工作区+暂存区，从而能够切换分支而既不提交也不丢弃\ngit stash等价于git stash push，将当前index包括工作区更改存储起来\n-u：这里不是update而是untracked，存储未跟踪文件 -a：包括所有文件，包括被.gitignore忽略的 git stash list：查看存储列表，每个存储项的格式：\nstash@{x}: WIP on branch_name: commit_hash commit_title\nWIP就是半成品的意思\ngit stash pop [stash@{x}]：恢复最近的存储，可以指定存储记录，同时将存储记录删除\ngit stash apply [stash@{x}]：同pop但不会删除存储记录\ngit stash drop [stash@{x}]：删除指定存储记录\ngit stash clear：删除所有记录\ngit stash show [stash@{x}] [-p]：查看存储内容，-p表示是否详细diff\ngit worktree用于创建一个子目录，是另一种解决无法灵活切换分支的办法\n它将创建一个指定的新的目录，其中的.git/符号链接指向当前目录的.git/，则在新目录下的提交可以直接关联到原目录\ngit worktree add \u0026lt;dir\u0026gt; \u0026lt;branch_name\u0026gt;：新增，此后可以用cd来等效切换分支了 lock与unlock：加锁与解锁，用于防止意外删除 git worktree remove \u0026lt;dir\u0026gt;：删除 git worktree list：查看工作树 其它命令 git tag \u0026lt;tagname\u0026gt;：标记本提交为一个自定义tag git clean：清理未跟踪的文件 git bisect：用于二分查找引入bug的第一个提交 git bisect start：开始二分查找 git bisect bad：标记当前为坏提交 git bisect good \u0026lt;tag\u0026gt;：标记一个已知的好提交，即没有bug的，随后将自动跳转到中间的某个提交 git bisect bad/good：标记当前提交为坏/好，然后自动跳转到中间 git bisect reset：提前结束查找 远程仓库相关 管理远程仓库(remote) git remote子命令用于管理远程仓库\n空本地仓库关联远程仓库\nshgit remote add [alias] [url] # 关联到某个远程仓库, alias是这个仓库的别名 git remote rename/remove/set-url/show等\n常见的工作流为：先fork源仓库，然后关联自己的这个远程仓库，此后fetch上游仓库后合并到本地仓库中\nshgit remote add origin [https://github.com/me/xxx.git] # fork 下来的远程仓库通常命名为 origin git remote add upstream [https://github.com/other/xxx.git] # 源仓库通常命名为 upstream (上游) git pull origin # 直接 pull 下来 # 本地完成功能后, 先合并上游的新更新再push到origin git fetch upstream git merge upstream/main git push origin 已有本地仓库关联远程仓库\nshgit remote add origin [https://github.com/me/xxx.git] git push origin # 直接 push 即可 下载(fetch与pull与push) git fetch remote_name [branch_name]用于下载指定的远程仓库并存储到本地的远程分支中，默认下载全部分支 git fetch --prune：清理已删除的远程分支引用 git pull用于一键式地fetch+merge，可以使用--rebase表示fetch+rebase git push remote_name localbr:remotebr用于将本地提交推送 git push rname abc等价于git push rname abc:abc，即默认推送到同名分支\n这里是真正的推送到了远程仓库的分支里，而不是本地仓库的远程分支\n-u：推送并设置上游跟踪\n设置上游分支后，此后只需要git push即可\n--delete xxx：删除远程仓库的对应分支\n或者git push origin :xxx\ngit push origin \u0026lt;tag\u0026gt;：推送某个tag，使用--tags推送所有tag\n安全事项 不要对已经push的提交进行修改，比如： push了一个提交后amend push了一个分支后尝试rebase，导致修改了这个分支上的所有提交 被merge后的分支应该删除，而不是继续在上面提交 从安全的角度上，pull缺少了审查步骤，应尽量使用分步骤地fetch+merge而不是pull .gitignore .gitignore中匹配的文件会被git忽略，它们甚至不会成为未跟踪文件 #开头是注释 *匹配任意字符（非目录分隔符） ?匹配单个字符 **匹配任意字符 []匹配字符类 以/结尾表示忽略目录 .gitattribute .gitattribute是一系列预定义的处理规则，可以对不同文件进行不同的处理，包括设置处理工具、合并策略、换行符等 语法在这里不多赘述 轶闻 diff的默认算法Myers 如图，myers通过层次遍历的方法，寻找(0, 0)到(n, m)的最短路径\n横轴的每个点代表原文件的每一行，纵轴的每个点代表着新文件的每一行，因此n是原文件的总行数，m是新文件的总行数\n允许的操作是向右、向下、向右下移动\n向右移动(x-\u0026gt;x+1)意味着删除原文件的第x+1行，向下移动(y-\u0026gt;y+1)意味着新增新文件的第y+1行，它们的消耗都为一\n向右下移动((x,y)-\u0026gt;(x+1,y+1))意味着原文件的第x+1行和新文件的第y+1行匹配成功，花费为零但有条件(两文件的对应行一致)\ndiff的目标就是找到人类习惯上的好的差异：差异尽可能紧凑、尽可能少，不变的行尽可能多\n而自然，最短路径将是右下操作最多的，刚好和好的差异相匹配\n寻找最短路径的过程就是宽度优先遍历并找到最先到达(n, m)的路径然后回溯，自然根据规则就能得到新增、删除的对比补丁了\n那么一个hunk自然就是连续的修改块了\ngit集成插件 lazygit是一个命令行工具，提供更好的UI界面和快捷键 neogit是一个在nvim中的另一个集成插件，也是提供了更好的UI和快捷键 vimdiff是一个能够更直观地查看diff以及修改冲突的工具 ","permalink":"https://echo.ishian.me/2024/06/12/git/","summary":"介绍Git版本管理的基本概念、三个区域、文件状态及常用命令操作","title":"Git: 使用git进行版本管理"},{"content":"矩阵的秩 矩阵判别 秩的性质 r(Am×n)≤min⁡(m,n)r(A_{m\\times n})\\le\\min(m,n)r(Am×n​)≤min(m,n)，换句话说，如果确定r(A)≥mr(A)\\ge mr(A)≥m或nnn，则r(A)=mr(A)=mr(A)=m或nnn 通过将矩阵化为行阶梯型或行最简型求具体矩阵的秩，如果求线性方程组系数阵的秩，应该只进行初等行变换；有时也通过定义求解 秩与其它运算 r(lA)={r(A)l≠00l=0r(lA)=\\begin{cases}r(A)\u0026amp;l\\ne0\\\\0\u0026amp;l=0\\end{cases}r(lA)={r(A)0​l=0l=0​ r(A−1)=r(A)=n、r(A){\u0026lt;n∣A∣=0=n∣A∣≠0r(A^{-1})=r(A)=n、r(A)\\begin{cases}\u0026lt;n\u0026amp;|A|=0\\\\=n\u0026amp;|A|\\ne0\\end{cases}r(A−1)=r(A)=n、r(A){\u0026lt;n=n​∣A∣=0∣A∣=0​ P、QP、QP、Q可逆⇔r(PAQ)=r(A)\\Leftrightarrow r(PAQ)=r(A)⇔r(PAQ)=r(A) r(A±B)≤r(A)+r(B)r(A\\pm B)\\le r(A)+r(B)r(A±B)≤r(A)+r(B)，通常使用右→\\rightarrow→左限定范围 r(A)+r(B)−n≤r(Am×nBn×m)≤min⁡(r(A),r(B))\\begin{align}r(A)+r(B)-n\\le r(A_{m\\times n}B_{n\\times m})\\le \\min\\Big(r(A),r(B)\\Big)\\end{align}r(A)+r(B)−n≤r(Am×n​Bn×m​)≤min(r(A),r(B))​​，乘积秩不增加 r(A∗)={nr(A)=n1r(A)=n−10r(A)\u0026lt;n−1r(A^*)=\\begin{cases}n\u0026amp;r(A)=n\\\\1\u0026amp;r(A)=n-1\\\\0\u0026amp;r(A)\u0026lt;n-1\\end{cases}r(A∗)=⎩⎨⎧​n10​r(A)=nr(A)=n−1r(A)\u0026lt;n−1​ r(AAT)=r(ATA)=r(A)=r(AT)r(AA^T)=r(A^TA)=r(A)=r(A^T)r(AAT)=r(ATA)=r(A)=r(AT) ATAx=0A^TAx=0ATAx=0与Ax=0Ax=0Ax=0同解⇒k−r(ATA)=k−r(A)⇒r(ATA)=r(A)\\Rightarrow k-r(A^TA)=k-r(A)\\Rightarrow r(A^TA)=r(A)⇒k−r(ATA)=k−r(A)⇒r(ATA)=r(A) AATx=0AA^Tx=0AATx=0与ATx=0A^Tx=0ATx=0同解⇒k−r(AAT)=k−r(AT)⇒r(AAT)=r(AT)=r(A)\\Rightarrow k-r(AA^T)=k-r(A^T)\\Rightarrow r(AA^T)=r(A^T)=r(A)⇒k−r(AAT)=k−r(AT)⇒r(AAT)=r(AT)=r(A) 分块矩阵： r(ACOB)≥r(A)+r(B)r\\left(\\begin{matrix}A\u0026amp;C\\\\O\u0026amp;B\\end{matrix}\\right)\\ge r(A)+r(B)r(AO​CB​)≥r(A)+r(B)，若C=OC=OC=O，则=r(A)+r(B)=r(A)+r(B)=r(A)+r(B) 增广矩阵max⁡(r(A),r(B))≤r(A,B)≤r(A)+r(B)\\max(r(A),r(B))\\le r(A,B)\\le r(A)+r(B)max(r(A),r(B))≤r(A,B)≤r(A)+r(B) r(A,B)=r(B,A)r(A,B)=r(B,A)r(A,B)=r(B,A) 向量组 线性相关 定义：若存在一组不全为000的数k1,k2,⋯ ,knk_1,k_2,\\cdots,k_nk1​,k2​,⋯,kn​，使000能够被向量组α1,α2,⋯ ,αn\\alpha_1,\\alpha_2,\\cdots,\\alpha_nα1​,α2​,⋯,αn​线性表示，则称该向量组线性相关 性质： 线性无关组删去向量仍无关、线性相关组添加向量仍相关 线性无关组添加分量仍无关、线性相关组删去分量仍相关 向量组A(α1,α2,⋯ ,αn)A(\\alpha_1,\\alpha_2,\\cdots,\\alpha_n)A(α1​,α2​,⋯,αn​)线性无关，B(A,β)B(A,\\beta)B(A,β)线性相关⇔β\\Leftrightarrow\\beta⇔β可由AAA唯一线性表示 一组向量线性相关⇔\\Leftrightarrow⇔不满秩⇔\\Leftrightarrow⇔极大无关组个数小于nnn⇔\\Leftrightarrow⇔至少有一个向量能被其余向量表示 一组向量线性无关⇔\\Leftrightarrow⇔定义式中系数kkk全为且只能为000，故可用待定系数法求解线性无关问题 极大无关组 向量组中任意一个向量都能被极大无关组表示 等价矩阵的极大无关组 秩与等价 秩的判别： 定义：存在rrr个向量线性无关，任意r+1r+1r+1个向量线性相关 极大线性无关组不唯一 化为矩阵判别更高效 若两个向量组能互相线性表示，称为等价，等价性质： 等价矩阵对应的向量组也等价，且极大无关组的序号一致，线性相关性也一致 子向量组可由父向量组线性表示 向量组与其极大无关组等价、向量组的两个极大无关组等价 AAA可由BBB线性表示⇔r(B)=r(B,A)≤r(A)+r(B)⇔r(A)≤r(B)\\Leftrightarrow r(B)=r(B,A)\\le r(A)+r(B)\\Leftrightarrow r(A)\\le r(B)⇔r(B)=r(B,A)≤r(A)+r(B)⇔r(A)≤r(B)⇔\\Leftrightarrow⇔存在KKK，使A=BKA=BKA=BK，其中设{α1=Bk⃗1⋯αn=Bk⃗n\\begin{cases}\\alpha_1=B\\vec k_1\\\\\\cdots\\\\\\alpha_n=B\\vec k_n\\end{cases}⎩⎨⎧​α1​=Bk1​⋯αn​=Bkn​​，则K=(k1,k2,⋯ ,kn)K=\\Big(k_1,k_2,\\cdots,k_n\\Big)K=(k1​,k2​,⋯,kn​)，kik_iki​为列向量 若AAA可由BBB线性表示且两向量组的向量个数相同，则证明A≡BA\\equiv BA≡B就是证明KKK可逆 A、BA、BA、B等价⇔r(A)=r(A,B)=r(B)\\Leftrightarrow r(A)=r(A,B)=r(B)⇔r(A)=r(A,B)=r(B) 反推，如果AAA不可由BBB线性表示⇔r(B)\u0026lt;r(B,A)⇔r(B)\u0026lt;r(A)\\Leftrightarrow r(B)\u0026lt;r(B,A)\\Leftrightarrow r(B)\u0026lt;r(A)⇔r(B)\u0026lt;r(B,A)⇔r(B)\u0026lt;r(A) 线性方程组中的应用 齐次线性方程组 设nnn为未知量的个数 齐次线性方程组一定有解，即r(A)≡r(A,β)r(A)\\equiv r(A,\\beta)r(A)≡r(A,β)，{r(A)=n有唯一零解r(A)\u0026lt;n有无穷解\\begin{cases}r(A)=n\u0026amp;有唯一零解\\\\r(A)\u0026lt;n\u0026amp;有无穷解\\end{cases}{r(A)=nr(A)\u0026lt;n​有唯一零解有无穷解​ 若有无穷解： 基础解系为解向量组的极大无关组，且个数为n−r(A)n-r(A)n−r(A) 求基础解系的过程就是找出n−r(A)n-r(A)n−r(A)个线性无关的解组 通解为 ∑i=1n−r(A)kixi\\begin{align}\\sum_{i=1}^{n-r(A)}k_i x_i\\end{align}i=1∑n−r(A)​ki​xi​​​ 在齐次方程组中，找另一个基础解系的过程就是找和已知基础解系等价的向量组 非齐次线性方程组 {r(A)≠r(A,β)无解r(A)=r(A,β)=n有唯一解r(A)=r(A,β)\u0026lt;n有无穷解\\begin{cases}r(A)\\ne r(A,\\beta)\u0026amp;无解\\\\r(A)=r(A,\\beta)=n\u0026amp;有唯一解\\\\r(A)=r(A,\\beta)\u0026lt;n\u0026amp;有无穷解\\end{cases}⎩⎨⎧​r(A)=r(A,β)r(A)=r(A,β)=nr(A)=r(A,β)\u0026lt;n​无解有唯一解有无穷解​ 若有无穷解： 基础解系仍是解向量组的极大无关组，且个数为n−r(A)+1n-r(A)+1n−r(A)+1 对应齐次线性方程组的基础解系，再加上本身的一个特解，不是基础解系，但构成它的通解 由于β≠0\\beta\\ne0β=0，方程组的两个不同解一定线性无关 通解为 X+∑i=1n−r(A)kixi\\begin{align}X+\\sum_{i=1}^{n-r(A)}k_i x_i\\end{align}X+i=1∑n−r(A)​ki​xi​​​，其中xix_ixi​为齐次线性方程组的解 无论Ax=βAx=\\betaAx=β是否有解，ATAx=ATβA^TAx=A^T\\betaATAx=ATβ一定有解 r(ATA)≤r(ATA,ATβ)=r(AT(A,β))≤r(AT)=r(ATA)r(A^TA)\\le r(A^TA,A^T\\beta)=r(A^T(A,\\beta))\\le r(A^T)=r(A^TA)r(ATA)≤r(ATA,ATβ)=r(AT(A,β))≤r(AT)=r(ATA) 求解过程 对一个非齐次线性方程组，有Ax=bAx=bAx=b，先找Ax=0Ax=0Ax=0的通解，再找Ax=bAx=bAx=b的一个特解 将AAA化为行最简型，找出自由变量(非阶梯处，个数一定为n−r(A)n-r(A)n−r(A)个)，依次令其中一个为111，同时其余自由变量为000，解出n−r(A)n-r(A)n−r(A)个解向量，即为基础解系 回到Ax=bAx=bAx=b，令所有自由变量为000，解出的解向量即为特解 齐次线性方程组解的线性组合仍是该方程组的解 一个非齐次线性方程组的解和多个对应齐次线性方程组的解的线性组合仍是该非齐次线性方程组的解 非齐次线性方程组的解的线性组合仍为该方程组的解⇔\\Leftrightarrow⇔系数和为111 非齐次线性方程组的解的差是对应齐次线性方程组的解 条件推理复习 1.由线性方程组Ax=b解的情况推出秩(共有n个未知量):1).无解:r(A)≠r(A,b)2).有唯一解:r(A)=r(A,b)=n3).有无穷解:r(A)=r(A,b)\u0026lt;n; 其中基础解系个数为 n−r(A)2.三个空间平面方程的关系:1).重合⇒有无穷解且可化为1条方程⇒r(A)=r(A,b)=12).交于一条直线⇒有无穷解且只能化为2条方程⇒r(A)=r(A,b)=23).交于一点⇒有唯一解⇒r(A)=r(A,b)=34).平行⇒无解⇒r(A)≠r(A,b)3.给出齐次线性方程组的一个基础解系A,同时给出可由A表出的B,判断B是否也是一个基础解系− B可由A表出⇒B也是解⇒当B与A等价时,也为基础解系⇒写出B=AK,K可逆时,结论成立\\begin{align}\u0026amp;1.由线性方程组Ax=b解的情况推出秩(共有n个未知量):\\\\\u0026amp;1).无解:r(A)\\ne r(A,b)\\\\\u0026amp;2).有唯一解:r(A)=r(A,b)=n\\\\\u0026amp;3).有无穷解:r(A)=r(A,b)\u0026lt;n;\\ 其中基础解系个数为\\ n-r(A)\\\\\\\\\u0026amp;2.三个空间平面方程的关系:\\\\\u0026amp;1).重合\\Rightarrow有无穷解且可化为1条方程\\Rightarrow r(A)=r(A,b)=1\\\\\u0026amp;2).交于一条直线\\Rightarrow有无穷解且只能化为2条方程\\Rightarrow r(A)=r(A,b)=2\\\\\u0026amp;3).交于一点\\Rightarrow有唯一解\\Rightarrow r(A)=r(A,b)=3\\\\\u0026amp;4).平行\\Rightarrow无解\\Rightarrow r(A)\\ne r(A,b)\\\\\\\\\u0026amp;3.给出齐次线性方程组的一个基础解系A,同时给出可由A表出的B,判断B是否也是一个基础解系\\\\\u0026amp;-\\ B可由A表出\\Rightarrow B也是解\\Rightarrow当B与A等价时,也为基础解系\\Rightarrow写出B=AK,K可逆时,结论成立\\end{align}​1.由线性方程组Ax=b解的情况推出秩(共有n个未知量):1).无解:r(A)=r(A,b)2).有唯一解:r(A)=r(A,b)=n3).有无穷解:r(A)=r(A,b)\u0026lt;n; 其中基础解系个数为 n−r(A)2.三个空间平面方程的关系:1).重合⇒有无穷解且可化为1条方程⇒r(A)=r(A,b)=12).交于一条直线⇒有无穷解且只能化为2条方程⇒r(A)=r(A,b)=23).交于一点⇒有唯一解⇒r(A)=r(A,b)=34).平行⇒无解⇒r(A)=r(A,b)3.给出齐次线性方程组的一个基础解系A,同时给出可由A表出的B,判断B是否也是一个基础解系− B可由A表出⇒B也是解⇒当B与A等价时,也为基础解系⇒写出B=AK,K可逆时,结论成立​​","permalink":"https://echo.ishian.me/2024/05/31/linearalgebra-rank/","summary":"介绍矩阵秩的性质及其在向量组与线性方程组中的应用","title":"线性代数: 矩阵的秩"},{"content":"向量与解析几何 概述 向量的线性表示 在右手空间直角坐标系中，向量可表示为a⃗=a1i⃗+a2j⃗+a3k⃗\\vec a=a_1\\vec i+a_2\\vec j+a_3\\vec ka=a1​i+a2​j​+a3​k或a⃗=(a1,a2,a3)\\vec a=(a_1,a_2,a_3)a=(a1​,a2​,a3​)\n两点形成的向量PQ→=(q1−p1, q2−p2, q3−p3)\\overrightarrow {PQ}=(q_1-p_1,\\ q_2-p_2,\\ q_3-p_3)PQ​=(q1​−p1​, q2​−p2​, q3​−p3​)\n向量的运算与关系 加减法：平行四边形法则，线性表示为a⃗+b⃗=(a1+b1, a2+b2, a3+b3)\\vec a+\\vec b=(a_1+b_1,\\ a_2+b_2,\\ a_3+b_3)a+b=(a1​+b1​, a2​+b2​, a3​+b3​) 数乘：线性表示为λa⃗=(λa1, λa2, λa3)\\lambda\\vec a=(\\lambda a_1,\\ \\lambda a_2,\\ \\lambda a_3)λa=(λa1​, λa2​, λa3​)，模长∣λa⃗∣=∣λ∣∣a⃗∣|\\lambda\\vec a|=|\\lambda||\\vec a|∣λa∣=∣λ∣∣a∣ 求模：线性表示为∣a⃗∣=a12+a22+a32|\\vec a|=\\sqrt{a_1^2+a_2^2+a_3^2}∣a∣=a12​+a22​+a32​​ 点乘(数量积)：a⃗⋅b⃗=∣a⃗∣∣b⃗∣⋅cos⁡θ=a1b1+a2b2+a3b3\\vec a·\\vec b=|\\vec a||\\vec b|·\\cos\\theta=a_1b_1+a_2b_2+a_3b_3a⋅b=∣a∣∣b∣⋅cosθ=a1​b1​+a2​b2​+a3​b3​，点乘满足交换、数乘结合、分配律 平行：线性表示为 a⃗//b⃗ ⟺ a1b1=a2b2=a3b3\\begin{align}\u0026amp;\\vec a//\\vec b\\iff \\frac{a_1}{b_1}=\\frac{a_2}{b_2}=\\frac{a_3}{b_3}\\end{align}​a//b⟺b1​a1​​=b2​a2​​=b3​a3​​​​，即叉乘的模长等于000，由行列式知识可知，平行时两向量各坐标值成比例 垂直：线性表示为a⃗⊥b⃗ ⟺ a1b1+a2b2+a3b3=0\\vec a\\perp\\vec b\\iff a_1b_1+a_2b_2+a_3b_3=0a⊥b⟺a1​b1​+a2​b2​+a3​b3​=0，即点乘等于000 夹角：由点乘， cos⁡θ=a⃗⋅b⃗∣a⃗∣∣b⃗∣\\begin{align}\\cos \\theta=\\frac{\\vec a·\\vec b}{|\\vec a||\\vec b|}\\end{align}cosθ=∣a∣∣b∣a⋅b​​​。一个向量的三个方向余弦cos⁡2θ1+cos⁡2θ2+cos⁡2θ3=1\\cos^2\\theta_1+\\cos^2\\theta_2+\\cos^2\\theta_3=1cos2θ1​+cos2θ2​+cos2θ3​=1 单位向量： e⃗a⃗=a⃗∣a⃗∣\\begin{align}\\vec e_{\\vec a}=\\frac{\\vec a}{|\\vec a|}\\end{align}ea​=∣a∣a​​​，方向余弦 cos⁡θx,y,z=a⃗x,y,z∣a⃗x,y,z∣\\begin{align}\\cos\\theta_{x,y,z}=\\frac{\\vec a_{x,y,z}}{|\\vec a_{x,y,z}|}\\end{align}cosθx,y,z​=∣ax,y,z​∣ax,y,z​​​​ 投影：a⃗\\vec aa在b⃗\\vec bb上的投影为 (a⃗)b⃗=∣a⃗∣cos⁡θ\\begin{align}(\\vec a)_{\\vec b}=|\\vec a|\\cos\\theta\\end{align}(a)b​=∣a∣cosθ​​，投影向量为 (a⃗)b⃗b⃗∣b⃗∣\\begin{align}(\\vec a)_{\\vec b}\\frac{\\vec b}{|\\vec b|}\\end{align}(a)b​∣b∣b​​​ 叉乘(向量积)：方向为右手四指从a⃗\\vec aa到b⃗\\vec bb握后大拇指的方向，模长为∣a⃗∣∣b⃗∣sin⁡θ|\\vec a||\\vec b|\\sin\\theta∣a∣∣b∣sinθ。叉乘满足分配、数乘结合律，交换后结果方向相反。可以视a⃗×b⃗\\vec a\\times\\vec ba×b为三阶行列式∣i⃗j⃗k⃗a1a2a3b1b2b3∣\\left|\\begin{matrix}\\vec i\u0026amp;\\vec j\u0026amp;\\vec k\\\\a_1\u0026amp;a_2\u0026amp;a_3\\\\b_1\u0026amp;b_2\u0026amp;b_3\\end{matrix}\\right|​ia1​b1​​j​a2​b2​​ka3​b3​​​，整理后的线性表示为按第一行展开的结果，即a⃗×b⃗=(∣a2a3b2b3∣, ∣a1a3b1b3∣, ∣a1a2b1b2∣)\\vec a\\times\\vec b=(\\left|\\begin{matrix}a_2\u0026amp;a_3\\\\b_2\u0026amp;b_3\\end{matrix}\\right|,\\ \\left|\\begin{matrix}a_1\u0026amp;a_3\\\\b_1\u0026amp;b_3\\end{matrix}\\right|,\\ \\left|\\begin{matrix}a_1\u0026amp;a_2\\\\b_1\u0026amp;b_2\\end{matrix}\\right|)a×b=(​a2​b2​​a3​b3​​​, ​a1​b1​​a3​b3​​​, ​a1​b1​​a2​b2​​​)，两个向量的叉乘的模长等于平行四边形的面积 混合积：称(a⃗, b⃗, c⃗)(\\vec a,\\ \\vec b,\\ \\vec c)(a, b, c)为(a⃗×b⃗)⋅c⃗(\\vec a\\times\\vec b)·\\vec c(a×b)⋅c或a⃗⋅(b⃗×c⃗)\\vec a·(\\vec b\\times\\vec c)a⋅(b×c)，叉乘运算必然优先。可表示为三阶行列式∣a1a2a3b1b2b3c1c2c3∣\\left|\\begin{matrix}a_1\u0026amp;a_2\u0026amp;a_3\\\\b_1\u0026amp;b_2\u0026amp;b_3\\\\c_1\u0026amp;c_2\u0026amp;c_3\\end{matrix}\\right|​a1​b1​c1​​a2​b2​c2​​a3​b3​c3​​​，由行列式性质得它等于偶数次交换行/列，或转置后的结果。混合积的几何意义为它的绝对值等于平行六面体的体积 自乘：a⃗⋅a⃗=∣a⃗∣2, a⃗×a⃗=0⃗\\vec a·\\vec a=|\\vec a|^2,\\ \\vec a\\times\\vec a=\\vec0a⋅a=∣a∣2, a×a=0 若a⃗⋅b⃗=0\\vec a\\sdot\\vec b=0a⋅b=0，则两向量垂直；若a⃗×b⃗=0\\vec a\\times\\vec b=0a×b=0，则两向量平行；若(a⃗, b⃗, c⃗)=0(\\vec a,\\ \\vec b,\\ \\vec c)=0(a, b, c)=0，则三向量共面 平面方程 点法式： x−x0A=y−y0B=z−z0C\\begin{align}\\frac{x-x_0}{A}=\\frac{y-y_0}{B}=\\frac{z-z_0}{C}\\end{align}Ax−x0​​=By−y0​​=Cz−z0​​​​，法向量为(A,B,C)(A,B,C)(A,B,C)，平面必过点(x0,y0,z0)(x_0,y_0,z_0)(x0​,y0​,z0​) 一般式：Ax+By+Cz+D=0Ax+By+Cz+D=0Ax+By+Cz+D=0，法向量为(A,B,C)(A,B,C)(A,B,C) 截距式： xA+yB+zC=1\\begin{align}\\frac xA+\\frac yB+\\frac zC=1\\end{align}Ax​+By​+Cz​=1​​ 两个相交平面确定一条直线，这条直线的同轴平面束为(A1x+B1y+C1z+D1)+λ(A2x+B2y+C2z+D2)(A_1x+B_1y+C_1z+D_1)+\\lambda(A_2x+B_2y+C_2z+D_2)(A1​x+B1​y+C1​z+D1​)+λ(A2​x+B2​y+C2​z+D2​)\n平面与平面的关系 两平面相交，则 A1:B1:C1≠A2:B2:C2\\begin{align}A_1:B_1:C_1\\ne A_2:B_2:C_2\\end{align}A1​:B1​:C1​=A2​:B2​:C2​​​ 两平面重合，则 A1A2=B1B2=C1C2=D1D2\\begin{align}\\frac{A_1}{A_2}=\\frac{B_1}{B_2}=\\frac{C_1}{C_2}=\\frac{D_1}{D_2}\\end{align}A2​A1​​=B2​B1​​=C2​C1​​=D2​D1​​​​ 两平面平行，则 A1A2=B1B2=C1C2≠D1D2\\begin{align}\\frac{A_1}{A_2}=\\frac{B_1}{B_2}=\\frac{C_1}{C_2}\\ne\\frac{D_1}{D_2}\\end{align}A2​A1​​=B2​B1​​=C2​C1​​=D2​D1​​​​ 平行平面间的距离：将A,B,CA,B,CA,B,C化为相同后，距离为 ∣D2−D1∣A2+B2+C2\\begin{align}\\frac{|D_2-D_1|}{\\sqrt{A^2+B^2+C^2}}\\end{align}A2+B2+C2​∣D2​−D1​∣​​​，由于距离公式含绝对值，需要注意有多个解 两平面间夹角：求两法向量间夹角的余弦值的绝对值，得到平面间夹角的余弦值 空间直线方程 点向式(对称式)： x−x0A=y−y0B=z−z0C\\begin{align}\\frac{x-x_0}A=\\frac{y-y_0}B=\\frac{z-z_0}C\\end{align}Ax−x0​​=By−y0​​=Cz−z0​​​​，方向向量为(A,B,C)(A,B,C)(A,B,C)，直线必过点(x0,y0,z0)(x_0,y_0,z_0)(x0​,y0​,z0​) 参数式：{x=x0+Aty=y0+Btz=z0+Ct\\begin{cases}x=x_0+At\\\\y=y_0+Bt\\\\z=z_0+Ct\\end{cases}⎩⎨⎧​x=x0​+Aty=y0​+Btz=z0​+Ct​，方向向量为(A,B,C)(A,B,C)(A,B,C)，直线必过点(x0,y0,z0)(x_0,y_0,z_0)(x0​,y0​,z0​) 两点式： x−x0x1−x0=y−y0y1−y0=z−z0z1−z0\\begin{align}\\frac{x-x_0}{x_1-x_0}=\\frac{y-y_0}{y_1-y_0}=\\frac{z-z_0}{z_1-z_0}\\end{align}x1​−x0​x−x0​​=y1​−y0​y−y0​​=z1​−z0​z−z0​​​​，方向向量为P0P1→\\overrightarrow{P_0P_1}P0​P1​​ 一般式：{A1x+B1y+C1z+D1=0A2x+B2y+C2z+D2=0\\begin{cases}A_1x+B_1y+C_1z+D_1=0\\\\A_2x+B_2y+C_2z+D_2=0\\end{cases}{A1​x+B1​y+C1​z+D1​=0A2​x+B2​y+C2​z+D2​=0​，即两个平面的交线，方向向量为(A1,B1,C1)×(A2,B2,C2)(A_1,B_1,C_1)\\times(A_2,B_2,C_2)(A1​,B1​,C1​)×(A2​,B2​,C2​) 空间直线方程间的转换 由于点向式与参数式容易互相转换，在这里只讲述一般式与参数式、两点式的互换：\n一般式化为参数式：联立两平面，令x、y、zx、y、zx、y、z任意一者为ttt，即得到参数式方程 快捷地，如果只需求一般式直线的方向向量，只需对两平面法向量进行叉乘即可 一般式化为两点式：令x、y、zx、y、zx、y、z任意两者为000，求出必过的两点即可 参数式化为一般式：三个方程两两联立除去ttt，求出两条关系式即可，点向式同理等式间两两联立整理即可 直线与直线的关系 两直线异面：首先判断方向向量是否平行，若平行(叉乘为0⃗\\vec 00)则两直线共面，否则应在直线中分别找出两点，得到向量P1P2→\\overrightarrow{P_1P_2}P1​P2​​，与叉乘结果进行点乘运算，若不为000，则为异面；完整行为是求混合积，但有时可以不求P1P2→\\overrightarrow{P_1P_2}P1​P2​​，并更容易判断共面情况 两直线共面且相交：在上述混合积为000的情况下，两方向向量不平行 两直线平行：两方向向量平行且都不与P1P2→\\overrightarrow {P_1P_2}P1​P2​​平行 两直线重合：三个向量互相平行 平行直线间的距离： d=∣s1⃗×P1P2→∣∣s1⃗∣\\begin{align}d=\\frac{|\\vec {s_1}\\times\\overrightarrow {P_1P_2}|}{|\\vec {s_1}|}\\end{align}d=∣s1​​∣∣s1​​×P1​P2​​∣​​​，其中s1⃗\\vec{s_1}s1​​为某直线的方向向量，即利用了叉乘的正弦值 异面直线间的距离： d=∣(s1⃗,s2⃗,P1P2→)∣∣s1⃗×s2⃗∣\\begin{align}d=\\frac{|(\\vec{s_1},\\vec{s_2},\\overrightarrow{P_1P_2})|}{|\\vec{s_1}\\times\\vec{s_2}|}\\end{align}d=∣s1​​×s2​​∣∣(s1​​,s2​​,P1​P2​​)∣​​​，即利用混合积求出垂直于两条直线的公垂线 直线与平面的关系 直线平行于平面：若直线非一般式，则方向向量与法向量垂直，且直线上点不在平面上，可利用P0P_0P0​判断；若为一般式，可直接观察其中一条平面方程是否与该平面平行 直线在平面上：若直线非一般式，则方向向量与法向量垂直，且P0P_0P0​应在平面上；若为一般式，可直接观察其中一条平面方程是否可与该平面化为一致 直线与平面相交于一点：若直线非一般式，则方向向量与法向量点乘不为000；若为一般式，可直接观察两条平面方程是否都不与该平面平行 直线与平面的夹角：利用方向向量与法向量的点乘求出直线与平面夹角的正弦值 平行直线离平面的距离：任意找出直线上一点， d=∣Ax0+By0+Cz0+D∣A2+B2+C2\\begin{align}d=\\frac{|Ax_0+By_0+Cz_0+D|}{\\sqrt{A^2+B^2+C^2}}\\end{align}d=A2+B2+C2​∣Ax0​+By0​+Cz0​+D∣​​​，由于距离公式含绝对值，需要注意有多个解 二次曲面 一个二元函数就是一个面，若自变量与因变量均为111次，则这个面是一个平面；若含有任意一个变量为二次，则称其为二次曲面\n类似空间直线，空间曲线由两个空间曲面相交而成，截痕法通过曲面在三个坐标平面上的空间曲线来研究曲面的形状，例如令z=0z=0z=0，可得到平面OxyOxyOxy上的截痕\n常用二次曲面 球面：(x−x0)2+(y−y0)2+(z−z0)2=r2(x-x_0)^2+(y-y_0)^2+(z-z_0)^2=r^2(x−x0​)2+(y−y0​)2+(z−z0​)2=r2，其中球心为(x0,y0,z0)(x_0,y_0,z_0)(x0​,y0​,z0​)，球半径为rrr 椭球面：若球面中各系数存在不同，则为椭球面，化简为 (x−x0)2a2+(y−y0)2b2+(z−z0)2c2=1\\begin{align}\\frac{(x-x_0)^2}{a^2}+\\frac{(y-y_0)^2}{b^2}+\\frac{(z-z_0)^2}{c^2}=1\\end{align}a2(x−x0​)2​+b2(y−y0​)2​+c2(z−z0​)2​=1​​ 在三个坐标平面上的空间曲线均为椭圆(或圆) 柱面：柱面由母线绕**定曲线(即准线)平行于定直线lll**移动而成，一个只有两个变量的曲面一定是柱面，且平行于缺失变量所在坐标轴，以下方程中，(x0,y0)(x_0,y_0)(x0​,y0​)表示准线的中心 椭圆柱面：准线为椭圆， (x−x0)2a2+(y−y0)2b2=1\\begin{align}\\frac{(x-x_0)^2}{a^2}+\\frac{(y-y_0)^2}{b^2}=1\\end{align}a2(x−x0​)2​+b2(y−y0​)2​=1​​表示该柱面以一个椭圆为准线、且l//zl//zl//z轴；特别地，当a2=b2a^2=b^2a2=b2时，表示一个圆柱面 双曲柱面：准线为双曲线， (x−x0)2a2−(y−y0)2b2=1\\begin{align}\\frac{(x-x_0)^2}{a^2}-\\frac{(y-y_0)^2}{b^2}=1\\end{align}a2(x−x0​)2​−b2(y−y0​)2​=1​​，l//zl//zl//z轴 抛物柱面：准线为抛物线， (x−x0)2−2py=0\\begin{align}(x-x_0)^2-2py=0\\end{align}(x−x0​)2−2py=0​​，l//zl//zl//z轴 二次锥面： (x−x0)2a2+(y−y0)2b2−(z−z0)2c2=0\\begin{align}\\frac{(x-x_0)^2}{a^2}+\\frac{(y-y_0)^2}{b^2}-\\frac{(z-z_0)^2}{c^2}=0\\end{align}a2(x−x0​)2​+b2(y−y0​)2​−c2(z−z0​)2​=0​​表示在x=x0x=x_0x=x0​与y=y0y=y_0y=y0​上的截痕均为一对直线，而在z=z0z=z_0z=z0​平面上截痕为一点，该点为(x0,y0,z0)(x_0,y_0,z_0)(x0​,y0​,z0​)(两≥0\\ge0≥0的数相加=0=0=0只有一个解) 单叶双曲面： (x−x0)2a2+(y−y0)2b2−(z−z0)2c2=1\\begin{align}\\frac{(x-x_0)^2}{a^2}+\\frac{(y-y_0)^2}{b^2}-\\frac{(z-z_0)^2}{c^2}=1\\end{align}a2(x−x0​)2​+b2(y−y0​)2​−c2(z−z0​)2​=1​​表示在x=x0x=x_0x=x0​与y=y0y=y_0y=y0​上的截痕均为双曲线，而在z=z0z=z_0z=z0​上截痕为椭圆，其中心为(x0,y0,z0)(x_0,y_0,z_0)(x0​,y0​,z0​) 双叶双曲面： (x−x0)2a2−(y−y0)2b2−(z−z0)2c2=1\\begin{align}\\frac{(x-x_0)^2}{a^2}-\\frac{(y-y_0)^2}{b^2}-\\frac{(z-z_0)^2}{c^2}=1\\end{align}a2(x−x0​)2​−b2(y−y0​)2​−c2(z−z0​)2​=1​​表示在y=y0y=y_0y=y0​和z=z0z=z_0z=z0​上的截痕为双曲线， (x1−x0)2a2−1=1\\begin{align}\\frac{(x_1-x_0)^2}{a^2}-1=1\\end{align}a2(x1​−x0​)2​−1=1​​时，在x=x1x=x_1x=x1​上的截痕为椭圆；若x1\u0026gt;x0x_1\u0026gt;x_0x1​\u0026gt;x0​，因在x0\u0026lt;x\u0026lt;x1x_0\u0026lt;x\u0026lt;x_1x0​\u0026lt;x\u0026lt;x1​间没有定义，故称双叶 椭圆抛物面： ±(z−z0)=(x−x0)2a2+(y−y0)2b2\\begin{align}\\pm(z-z_0)=\\frac{(x-x_0)^2}{a^2}+\\frac{(y-y_0)^2}{b^2}\\end{align}±(z−z0​)=a2(x−x0​)2​+b2(y−y0​)2​​​表示在x=x0x=x_0x=x0​与y=y0y=y_0y=y0​上截痕为抛物线，而在z=z0z=z_0z=z0​上截痕为椭圆 双曲抛物面(马鞍面)： z−z0=(x−x0)2a2−(y−y0)2b2\\begin{align}z-z_0=\\frac{(x-x_0)^2}{a^2}-\\frac{(y-y_0)^2}{b^2}\\end{align}z−z0​=a2(x−x0​)2​−b2(y−y0​)2​​​表示在x=x0x=x_0x=x0​与y=y0y=y_0y=y0​上截痕为抛物线，而在z=z1≠z0z=z_1\\ne z_0z=z1​=z0​上截痕为双曲线，在z=z0z=z_0z=z0​上为一对直线 类似的，许多常见曲面应用截痕法记忆\n由截痕法所求的在某个平面上的截痕方程，就是曲面在这个平面上的交线\n而求曲线在坐标平面上的投影曲线，就是消去该方向上的坐标变量而合并成一条曲面方程后，和该平面方程组合而成的曲线方程\n一般形化简为标准形 假设一个曲面方程的一般形为a11x2+a22y2+a33z2+2a12xy+2a13xz+2a23yz+b1x+b2y+b3z+c=0a_{11}x^2+a_{22}y^2+a_{33}z^2+2a_{12}xy+2a_{13}xz+2a_{23}yz+b_1x+b_2y+b_3z+c=0a11​x2+a22​y2+a33​z2+2a12​xy+2a13​xz+2a23​yz+b1​x+b2​y+b3​z+c=0，要将它化为λ1(x′−x0)2+λ2(y′−y0)2+λ3(z′−z0)2+c=0\\lambda_1(x\u0026#x27;-x_0)^2+\\lambda_2(y\u0026#x27;-y_0)^2+\\lambda_3(z\u0026#x27;-z_0)^2+c=0λ1​(x′−x0​)2+λ2​(y′−y0​)2+λ3​(z′−z0​)2+c=0的形式，需要消除xy,xz,yzxy,xz,yzxy,xz,yz项，则需：\n将它的系数化为对称矩阵：A=[a11a12a13a12a22a23a13a23a33]A=\\left[\\begin{matrix}a_{11}\u0026amp;a_{12}\u0026amp;a_{13}\\\\a_{12}\u0026amp;a_{22}\u0026amp;a_{23}\\\\a_{13}\u0026amp;a_{23}\u0026amp;a_{33}\\end{matrix}\\right]A=​a11​a12​a13​​a12​a22​a23​​a13​a23​a33​​​，通过λE−A=0\\lambda E-A=0λE−A=0，求出它的特征值λ1,2,3\\lambda_{1,2,3}λ1,2,3​ 若b1=b2=b3=0b_1=b_2=b_3=0b1​=b2​=b3​=0，则不需要作平移变换，且x0=y0=z0=0x_0=y_0=z_0=0x0​=y0​=z0​=0 否则，需要平移变换，即找出正交阵XXX，使XTAX=diag(λ1,λ2,λ3)X^TAX=\\rm diag(\\lambda_1,\\lambda_2,\\lambda_3)XTAX=diag(λ1​,λ2​,λ3​) 则(b1b2b3)X=(d1d2d3)\\left(\\begin{matrix}b_1\u0026amp;b_2\u0026amp;b_3\\end{matrix}\\right)X=\\left(\\begin{matrix}d_1\u0026amp;d_2\u0026amp;d_3\\end{matrix}\\right)(b1​​b2​​b3​​)X=(d1​​d2​​d3​​) 求解未知曲面方程 在实际求解方程时，先设所求曲面上一点M(x,y,z)M(x,y,z)M(x,y,z)，然后根据条件求其一般形F(x,y,z)=0F(x,y,z)=0F(x,y,z)=0，根据需要进行正交变换转换为标准方程，再判断其形状\n能利用的条件有许多：\nMMM距球面(球心为OOO)距离：d=∣∣MO→∣−R∣d=\\Big||\\overrightarrow{MO}|-R\\Big|d=​∣MO∣−R​ 给出双参数方程，则联立消去参数，可转化为一般形 给出旋转轴和动曲线，先找到这条动曲线上任意点的表示形式，如M0(x0,y0,z0)M_0(x_0,y_0,z_0)M0​(x0​,y0​,z0​)，然后通过与旋转轴的关系(即在某一方向上，动曲线的值k0k_0k0​与待求曲面的值kkk相同、以及M0M_0M0​与轴的距离不变)，求出M0M_0M0​与MMM的关系，再由M0M_0M0​内部的关系得出MMM内部的关系 具体来说，假设要求动直线LLL绕zzz轴旋转后的曲面方程，其中LLL方向向量为s⃗\\vec ss；(1)(1)(1)则M0M_0M0​的三个坐标值与s⃗\\vec ss相关；(2)(2)(2)由于绕zzz轴，则在zzz轴方向上，z=z0z=z_0z=z0​；(3)(3)(3)在同一个z0z_0z0​上，有r2=x2+y2=x02+y02r^2=x^2+y^2=x_0^2+y_0^2r2=x2+y2=x02​+y02​；由(1),(2),(3)(1),(2),(3)(1),(2),(3)得到的式子联立，可求出未知曲面方程 练习 1.求过点M1(−1,0,4),且与平面π1:3x−4y+z−10=0平行,又与直线L1:x+13=y−31=z2相交的直线L的方程.解:设L方向向量为(A,B,C),由π1法向量为(3,−4,1),得3A−4B+C=0又L1的方向向量为(3,1,2),其中一点为M2(−1,3,0),由它们相交得(sL⃗,sL1⃗,M1M2→)=0即10A−12B−9C=0,令C=4,得A=48,B=37,即L:x+148=y37=x−44\\begin{align}\u0026amp;1.求过点M_1(-1,0,4),且与平面\\pi_1:3x-4y+z-10=0平行,又与直线L_1:\\frac{x+1}3=\\frac{y-3}1=\\frac z2\\\\\u0026amp;相交的直线L的方程.\\\\\u0026amp;解:\\\\\u0026amp;设L方向向量为(A,B,C),由\\pi_1法向量为(3,-4,1),得3A-4B+C=0\\\\\u0026amp;又L_1的方向向量为(3,1,2),其中一点为M_2(-1,3,0),由它们相交得(\\vec {s_{L}},\\vec{s_{L_1}},\\overrightarrow{M_1M_2})=0\\\\\u0026amp;即10A-12B-9C=0,令C=4,得A=48,B=37,即L:\\frac{x+1}{48}=\\frac{y}{37}=\\frac{x-4}4\\end{align}​1.求过点M1​(−1,0,4),且与平面π1​:3x−4y+z−10=0平行,又与直线L1​:3x+1​=1y−3​=2z​相交的直线L的方程.解:设L方向向量为(A,B,C),由π1​法向量为(3,−4,1),得3A−4B+C=0又L1​的方向向量为(3,1,2),其中一点为M2​(−1,3,0),由它们相交得(sL​​,sL1​​​,M1​M2​​)=0即10A−12B−9C=0,令C=4,得A=48,B=37,即L:48x+1​=37y​=4x−4​​​ 2.设两直线L1:{x−3y+z=02x−4y+z+1=0;L2:x=y+13=z−24(1).证明异面.(2).求距离.(3).求过L1且平行于L2的平面方程解:(1).令z=t,化L1为{x=32+t2y=12+t2z=t,s1⃗=(1,1,2),过点M1(32,12,0)则(s1⃗,s2⃗,M1M2→)=2≠0,故异面.(2).d=∣(s1⃗,s2⃗,M1M2→)∣s1⃗×s2⃗∣∣=13(3).由两方向向量叉乘,且平面过M1,得方程为x+y−z+2=0\\begin{align}\u0026amp;2.设两直线L_1:\\begin{cases}x-3y+z=0\\\\2x-4y+z+1=0\\end{cases};L_2:x=\\frac{y+1}3=\\frac{z-2}4\\\\\u0026amp;(1).证明异面.(2).求距离.(3).求过L_1且平行于L_2的平面方程\\\\\u0026amp;解:\\\\\u0026amp;(1).令z=t,化L_1为\\begin{cases}x=\\frac32+\\frac t2\\\\y=\\frac12+\\frac t2\\\\z=t\\end{cases},\\vec {s_1}=(1,1,2),过点M_1(\\frac32,\\frac12,0)\\\\\u0026amp;则(\\vec {s_1},\\vec{s_2},\\overrightarrow{M_1M_2})=2\\ne 0,故异面.\\\\\u0026amp;(2).d=\\Bigg|\\frac{(\\vec {s_1},\\vec{s_2},\\overrightarrow{M_1M_2})}{|\\vec{s_1}\\times\\vec{s_2}|}\\Bigg|=\\frac1{\\sqrt3}\\\\\u0026amp;(3).由两方向向量叉乘,且平面过M_1,得方程为x+y-z+2=0\\end{align}​2.设两直线L1​:{x−3y+z=02x−4y+z+1=0​;L2​:x=3y+1​=4z−2​(1).证明异面.(2).求距离.(3).求过L1​且平行于L2​的平面方程解:(1).令z=t,化L1​为⎩⎨⎧​x=23​+2t​y=21​+2t​z=t​,s1​​=(1,1,2),过点M1​(23​,21​,0)则(s1​​,s2​​,M1​M2​​)=2=0,故异面.(2).d=​∣s1​​×s2​​∣(s1​​,s2​​,M1​M2​​)​​=3​1​(3).由两方向向量叉乘,且平面过M1​,得方程为x+y−z+2=0​​ 3.已知点A,B的坐标分别为(1,0,0),(0,1,1).线段AB绕z轴旋转一周所成的曲面为S.求S及平面z=0,z=1围成的立体体积.解:线段AB所在直线的方向向量为(1,−1,−1),设其上任意一点为(x0,y0,z0)则LAB:x0−1=−y0=−z0(1).设所求曲面任意一点为(x,y,z),绕z轴旋转,故z=z0(2).由r2=x2+y2=x02+y02与上式联立得,x2+y2=(1−z)2+z2即x2+y2−2z2+2z−1=0.故在平行于Oxy平面上的截面面积为S=π(2z2−2z+1),V=∫01Sdz=23π\\begin{align}\u0026amp;3.已知点A,B的坐标分别为(1,0,0),(0,1,1).线段AB绕z轴旋转一周所成的曲面为S.\\\\\u0026amp;求S及平面z=0,z=1围成的立体体积.\\\\\u0026amp;解:\\\\\u0026amp;线段AB所在直线的方向向量为(1,-1,-1),设其上任意一点为(x_0,y_0,z_0)\\\\\u0026amp;则L_{AB}:x_0-1=-y_0=-z_0\u0026amp;(1).\\\\\u0026amp;设所求曲面任意一点为(x,y,z),绕z轴旋转,故z=z_0\u0026amp;(2).\\\\\u0026amp;由r^2=x^2+y^2=x_0^2+y_0^2与上式联立得,x^2+y^2=(1-z)^2+z^2\\\\\u0026amp;即x^2+y^2-2z^2+2z-1=0.\\\\\u0026amp;故在平行于Oxy平面上的截面面积为S=\\pi(2z^2-2z+1),\\\\\u0026amp;V=\\int_0^1Sdz=\\frac23\\pi\\end{align}​3.已知点A,B的坐标分别为(1,0,0),(0,1,1).线段AB绕z轴旋转一周所成的曲面为S.求S及平面z=0,z=1围成的立体体积.解:线段AB所在直线的方向向量为(1,−1,−1),设其上任意一点为(x0​,y0​,z0​)则LAB​:x0​−1=−y0​=−z0​设所求曲面任意一点为(x,y,z),绕z轴旋转,故z=z0​由r2=x2+y2=x02​+y02​与上式联立得,x2+y2=(1−z)2+z2即x2+y2−2z2+2z−1=0.故在平行于Oxy平面上的截面面积为S=π(2z2−2z+1),V=∫01​Sdz=32​π​(1).(2).​​","permalink":"https://echo.ishian.me/2024/05/20/linearalgebra-analytic-geometry/","summary":"介绍向量的线性表示与运算、平面/空间直线方程及二次曲面","title":"向量与解析几何入门"},{"content":"行列式 结论 ∣AT∣=∣A∣|A^T|=|A|∣AT∣=∣A∣ 一般矩阵一次对调变换后，∣A∣|A|∣A∣取反 一般矩阵或分块矩阵一次倍加变换后，∣A∣|A|∣A∣不变 ∣kA∣=kn∣A∣|kA|=k^n|A|∣kA∣=kn∣A∣ 分块矩阵一次对调变换后，∣Am×jBm×kCn×jD∣=(−1)m×n∣Cn×jDAm×jBm×k∣=(−1)j×k∣Bm×kAm×jDCn×j∣\\left|\\begin{matrix}A_{m\\times j}\u0026amp;B_{m\\times k}\\\\C_{n\\times j}\u0026amp;D\\end{matrix}\\right|=(-1)^{m\\times n}\\left|\\begin{matrix}C_{n\\times j}\u0026amp;D\\\\A_{m\\times j}\u0026amp;B_{m\\times k}\\end{matrix}\\right|=(-1)^{j\\times k}\\left|\\begin{matrix}B_{m\\times k}\u0026amp;A_{m\\times j}\\\\D\u0026amp;C_{n\\times j}\\end{matrix}\\right|​Am×j​Cn×j​​Bm×k​D​​=(−1)m×n​Cn×j​Am×j​​DBm×k​​​=(−1)j×k​Bm×k​D​Am×j​Cn×j​​​ ∣A−1∣=∣A∣−1|A^{-1}|=|A|^{-1}∣A−1∣=∣A∣−1 若A、B都为方阵, 则∣AB∣=∣A∣∣B∣若A、B都为方阵,\\ 则|AB|=|A||B|若A、B都为方阵, 则∣AB∣=∣A∣∣B∣ 若A可逆, 则A∗=∣A∣A−1若A可逆,\\ 则A^*=|A|A^{-1}若A可逆, 则A∗=∣A∣A−1 无论A是否可逆, ∣A∗∣=∣∣A∣A−1∣=∣A∣n−1,(方便记忆,实际推导分情况)无论A是否可逆,\\ |A^*|=\\bigg||A|A^{-1}\\bigg|=|A|^{n-1},(方便记忆,实际推导分情况)无论A是否可逆, ∣A∗∣=​∣A∣A−1​=∣A∣n−1,(方便记忆,实际推导分情况) 若A、C为方阵, 则∣ABOC∣=∣A∣∣C∣若A、C为方阵,\\ 则\\left|\\begin{matrix}A\u0026amp;B\\\\O\u0026amp;C\\end{matrix}\\right|=|A||C|若A、C为方阵, 则​AO​BC​​=∣A∣∣C∣ 若A、B为同阶方阵, 则∣ABBA∣=∣A+B∣∣A−B∣若A、B为同阶方阵,\\ 则\\left|\\begin{matrix}A\u0026amp;B\\\\B\u0026amp;A\\end{matrix}\\right|=|A+B||A-B|若A、B为同阶方阵, 则​AB​BA​​=∣A+B∣∣A−B∣ ∣A∣≠0⇔A可逆⇔A为非奇异矩阵⇔对应线性方程组有唯一解⇔r(A)=n|A|\\ne0\\Leftrightarrow A可逆\\Leftrightarrow A为非奇异矩阵\\Leftrightarrow 对应线性方程组有唯一解\\Leftrightarrow r(A)=n∣A∣=0⇔A可逆⇔A为非奇异矩阵⇔对应线性方程组有唯一解⇔r(A)=n 上条的部分内容为克莱姆法则的一部分，若任一nnn个未知量、nnn条方程的线性方程组中，由系数组成的方阵的行列式不为000，则有唯一解，且 x=(D1D,D2D,⋯ ,DnD)\\begin{align}x=(\\frac{D_1}D,\\frac{D_2}D,\\cdots,\\frac{D_n}D)\\end{align}x=(DD1​​,DD2​​,⋯,DDn​​)​​，其中DjD_jDj​为将原行列式中的第jjj列元素替换成列向量bbb后形成的行列式 特别地，若b=Ob=Ob=O，即齐次线性方程组中，{有唯一零解,∣A∣≠0一定含有非零解，也即无穷多个解,∣A∣=0\\begin{cases}有唯一零解,\u0026amp;|A|\\ne0\\\\一定含有非零解，也即无穷多个解,\u0026amp;|A|=0\\end{cases}{有唯一零解,一定含有非零解，也即无穷多个解,​∣A∣=0∣A∣=0​ 行列式某一行(列)元素与本身的代数余子式的乘积为该行列式的值，与另一行(列)对应元素的代数余子式的乘积为000 运算技巧 特殊行列式 对角或上三角或下三角：\n∣A∣=∏n≥i≥1aii\\begin{align}|A|=\\prod_{n\\ge i\\ge1}a_{ii}\\end{align}∣A∣=n≥i≥1∏​aii​​​类对角或类上三角或类下三角：\n∣A∣=(−1)n(n−1)2∏n≥i≥1aii\\begin{align}|A|=(-1)^{\\frac{n(n-1)}2}\\prod_{n\\ge i\\ge1}a_{ii}\\end{align}∣A∣=(−1)2n(n−1)​n≥i≥1∏​aii​​​范德蒙德行列式：\n∣1…1x1…xnx12…xn2⋮⋮x1n−1…xnn−1∣=∏n≥i\u0026gt;j≥1(xi−xj)\\begin{align}\\left|\\begin{matrix}1\u0026amp;\\dots\u0026amp;1\\\\x_1\u0026amp;\\dots\u0026amp;x_n\\\\x_1^2\u0026amp;\\dots\u0026amp;x_n^2\\\\\\vdots\u0026amp;\u0026amp;\\vdots\\\\x_1^{n-1}\u0026amp;\\dots\u0026amp;x_n^{n-1}\\end{matrix}\\right|=\\prod_{n\\ge i\u0026gt;j\\ge1}(x_i-x_j)\\end{align}​1x1​x12​⋮x1n−1​​…………​1xn​xn2​⋮xnn−1​​​=n≥i\u0026gt;j≥1∏​(xi​−xj​)​​例：\n∣111…122223…2n33233…3n⋮⋮⋮⋮nn2n3…nn∣\\begin{align}\\left|\\begin{matrix}1\u0026amp;1\u0026amp;1\u0026amp;\\dots\u0026amp;1\\\\2\u0026amp;2^2\u0026amp;2^3\u0026amp;\\dots\u0026amp;2^n\\\\3\u0026amp;3^2\u0026amp;3^3\u0026amp;\\dots\u0026amp;3^n\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\n\u0026amp;n^2\u0026amp;n^3\u0026amp;\\dots\u0026amp;n^n\\end{matrix}\\right|\\end{align}​123⋮n​12232⋮n2​12333⋮n3​…………​12n3n⋮nn​​​​，虽然不是范德蒙德行列式，但可以转化为这种形式：每行均可以提出公因子，得原式\n=n!∣111…11222…2n−11332…3n−1⋮⋮⋮⋮1nn2…nn−1∣=n!(n−1)!⋯2!\\begin{align}=n!\\left|\\begin{matrix}1\u0026amp;1\u0026amp;1\u0026amp;\\dots\u0026amp;1\\\\1\u0026amp;2\u0026amp;2^2\u0026amp;\\dots\u0026amp;2^{n-1}\\\\1\u0026amp;3\u0026amp;3^2\u0026amp;\\dots\u0026amp;3^{n-1}\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\1\u0026amp;n\u0026amp;n^2\u0026amp;\\dots\u0026amp;n^{n-1}\\end{matrix}\\right|=n!(n-1)!\\cdots2!\\end{align}=n!​111⋮1​123⋮n​12232⋮n2​…………​12n−13n−1⋮nn−1​​=n!(n−1)!⋯2!​​简单行列式消零 一：\n大部分元素相同，而且每行、每列只有一个不同的元素，而且这些元素相同时，例如：\n∣ba…aab…a⋮⋮⋮aa…b∣\\begin{align}\\left|\\begin{matrix}b\u0026amp;a\u0026amp;\\dots\u0026amp;a\\\\a\u0026amp;b\u0026amp;\\dots\u0026amp;a\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\a\u0026amp;a\u0026amp;\\dots\u0026amp;b\\end{matrix}\\right|\\end{align}​ba⋮a​ab⋮a​………​aa⋮b​​​​将每一行(列)加到第一行(列)，提出公因子b−(n−1)ab-(n-1)ab−(n−1)a，再通过倍加变换消掉剩余的aaa，可得到大部分元素为000的行列式，特别地，在这个例子里，∣A∣=(b−(n−1)a)(b−a)n−1|A|=\\Big(b-(n-1)a\\Big)(b-a)^{n-1}∣A∣=(b−(n−1)a)(b−a)n−1\n若观察到每行(列)元素的和相同，也可以采用这种方法\n若观察发现类似范德蒙德行列式，却有某几行有区别，也可采用这种方法\n二：三对角行列式\n形如\nDn=∣ab0…0cab…00ca…0⋮⋮⋮⋮000…a∣\\begin{align}D_n=\\left|\\begin{matrix}a\u0026amp;b\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\c\u0026amp;a\u0026amp;b\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;c\u0026amp;a\u0026amp;\\dots\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\0\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;a\\end{matrix}\\right|\\end{align}Dn​=​ac0⋮0​bac⋮0​0ba⋮0​…………​000⋮a​​​​的三对角元分别相同的行列式，可采用递推法\n在这个例子里，Dn=aDn−1−bcDn−2D_n=aD_{n-1}-bcD_{n-2}Dn​=aDn−1​−bcDn−2​\n设Dn−xDn−1=y(Dn−1−xDn−2)D_n-xD_{n-1}=y(D_{n-1}-xD_{n-2})Dn​−xDn−1​=y(Dn−1​−xDn−2​)，解得\n{x=2bca+a2−4bcy=a+a2−4bc2\\begin{align}\\begin{cases}x=\\large\\frac{2bc}{a+\\sqrt{a^2-4bc}}\\\\y=\\large\\frac{a+\\sqrt{a^2-4bc}}2\\end{cases}\\end{align}{x=a+a2−4bc​2bc​y=2a+a2−4bc​​​​​或\n{x=2bca−a2−4bcy=a−a2−4bc2\\begin{align}\\begin{cases}x=\\large\\frac{2bc}{a-\\sqrt{a^2-4bc}}\\\\y=\\large\\frac{a-\\sqrt{a^2-4bc}}2\\end{cases}\\end{align}{x=a−a2−4bc​2bc​y=2a−a2−4bc​​​​​递归代入原方程后可得到两条关于DnD_nDn​和Dn−1D_{n-1}Dn−1​的式子，通过消元即可得到DnD_nDn​\n这种行列式很特殊，事实上三对角行列式都可以采用递推法\n三：递推变种\n(1).(1).(1).形如\nDn=∣ab0…00ab…000a…0⋮⋮⋮⋮ccc…a∣\\begin{align}D_n=\\left|\\begin{matrix}a\u0026amp;b\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;a\u0026amp;b\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;0\u0026amp;a\u0026amp;\\dots\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\c\u0026amp;c\u0026amp;c\u0026amp;\\dots\u0026amp;a\\end{matrix}\\right|\\end{align}Dn​=​a00⋮c​ba0⋮c​0ba⋮c​…………​000⋮a​​​​或\nDn=∣a00…bca0…b0ca…b⋮⋮⋮⋮000…a∣\\begin{align}D_n=\\left|\\begin{matrix}a\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;b\\\\c\u0026amp;a\u0026amp;0\u0026amp;\\dots\u0026amp;b\\\\0\u0026amp;c\u0026amp;a\u0026amp;\\dots\u0026amp;b\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\0\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;a\\end{matrix}\\right|\\end{align}Dn​=​ac0⋮0​0ac⋮0​00a⋮0​…………​bbb⋮a​​​​的行列式也可用递推法\n例如，在左式例子中，Dn=aDn−1+(−1)n+1cbn−1D_n=aD_{n-1}+(-1)^{n+1}cb^{n-1}Dn​=aDn−1​+(−1)n+1cbn−1\n又例如，\nDn=∣a−10…00a−1…000a…0⋮⋮⋮⋮x1x2x3…a+xn∣=aDn−1+x1\\begin{align}D_n=\\left|\\begin{matrix}a\u0026amp;-1\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;a\u0026amp;-1\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;0\u0026amp;a\u0026amp;\\dots\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\x_1\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;a+x_n\\end{matrix}\\right|=aD_{n-1}+x_1\\end{align}Dn​=​a00⋮x1​​−1a0⋮x2​​0−1a⋮x3​​…………​000⋮a+xn​​​=aDn−1​+x1​​​∴Dn=an+an−1xn+⋯+ax2+x1\\therefore D_n=a^n+a^{n-1}x_n+\\dots+ax_2+x_1∴Dn​=an+an−1xn​+⋯+ax2​+x1​\n若上式a=ba=ba=b，还可以采用逐列倍加法，即把第iii列的元素的−1-1−1倍加到第i+1i+1i+1列上\n(2).(2).(2).形如D2n=∣abab⋱…abcd…dc⋱cd∣D_{2n}=\\left|\\begin{matrix}a\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;b\\\\\u0026amp;a\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;b\\\\\u0026amp;\u0026amp;\\ddots\u0026amp;\u0026amp;\u0026amp;\\dots\\\\\u0026amp;\u0026amp;\u0026amp;a\u0026amp;b\\\\\u0026amp;\u0026amp;\u0026amp;c\u0026amp;d\\\\\u0026amp;\u0026amp;\\dots\u0026amp;\u0026amp;\u0026amp;d\\\\\u0026amp;c\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;\\ddots\\\\c\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;\u0026amp;d\\end{matrix}\\right|D2n​=​ac​ac​⋱…​ac​bd​…d​b⋱​bd​​的2n2n2n阶行列式也可用递推法\nD2n=(ad−bc)D2n−2、D2=ad−bc⇒D2n=(ad−bc)nD_{2n}=(ad-bc)D_{2n-2}、D_2=ad-bc\\Rightarrow D_{2n}=(ad-bc)^nD2n​=(ad−bc)D2n−2​、D2​=ad−bc⇒D2n​=(ad−bc)n\n四：升阶法\n当行列式每行(列)中有大部分元素相同，其中有部分元素不同时，可采用升阶法\n例如\n∣ax2x3…xnx1bx3…xnx1x2c…xn⋮⋮⋮⋮x1x2x3…y∣\\begin{align}\\left|\\begin{matrix}a\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\x_1\u0026amp;b\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\x_1\u0026amp;x_2\u0026amp;c\u0026amp;\\dots\u0026amp;x_n\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\x_1\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;y\\end{matrix}\\right|\\end{align}​ax1​x1​⋮x1​​x2​bx2​⋮x2​​x3​x3​c⋮x3​​…………​xn​xn​xn​⋮y​​​​，由于各行、各列和不相同，不能提出公因子\n可以转化为\n∣1x1x2x3…xn0ax2x3…xn0x1bx3…xn0x1x2c…xn⋮⋮⋮⋮⋮0x1x2x3…y∣\\begin{align}\\left|\\begin{matrix}1\u0026amp;x_1\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\0\u0026amp;a\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\0\u0026amp;x_1\u0026amp;b\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\0\u0026amp;x_1\u0026amp;x_2\u0026amp;c\u0026amp;\\dots\u0026amp;x_n\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\0\u0026amp;x_1\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;y\\end{matrix}\\right|\\end{align}​1000⋮0​x1​ax1​x1​⋮x1​​x2​x2​bx2​⋮x2​​x3​x3​x3​c⋮x3​​……………​xn​xn​xn​xn​⋮y​​​​，利用第一行消去其它行的大部分元素：\n∣1x1x2x3…xn−1a−x100…0−10b−x20…0−100c−x3…0⋮⋮⋮⋮⋮−1000…y−xn∣\\begin{align}\\left|\\begin{matrix}1\u0026amp;x_1\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\-1\u0026amp;a-x_1\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\-1\u0026amp;0\u0026amp;b-x_2\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\-1\u0026amp;0\u0026amp;0\u0026amp;c-x_3\u0026amp;\\dots\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\-1\u0026amp;0\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;y-x_n\\end{matrix}\\right|\\end{align}​1−1−1−1⋮−1​x1​a−x1​00⋮0​x2​0b−x2​0⋮0​x3​00c−x3​⋮0​……………​xn​000⋮y−xn​​​​​再消去第一列的−1-1−1：\n∣1+x1a−x1+⋯+xny−xnx1x2x3…xn0a−x100…000b−x20…0000c−x3…0⋮⋮⋮⋮⋮0000…y−xn∣\\begin{align}\\left|\\begin{matrix}{\\large1+\\frac{x_1}{a-x_1}+\\dots+\\frac{x_n}{y-x_n}}\u0026amp;x_1\u0026amp;x_2\u0026amp;x_3\u0026amp;\\dots\u0026amp;x_n\\\\0\u0026amp;a-x_1\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;0\u0026amp;b-x_2\u0026amp;0\u0026amp;\\dots\u0026amp;0\\\\0\u0026amp;0\u0026amp;0\u0026amp;c-x_3\u0026amp;\\dots\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\0\u0026amp;0\u0026amp;0\u0026amp;0\u0026amp;\\dots\u0026amp;y-x_n\\end{matrix}\\right|\\end{align}​1+a−x1​x1​​+⋯+y−xn​xn​​000⋮0​x1​a−x1​00⋮0​x2​0b−x2​0⋮0​x3​00c−x3​⋮0​……………​xn​000⋮y−xn​​​​​化为上三角行列式\n五：逐行(列)倍加法\n若大部分相邻两行(列)间的大部分一一对应的元素差距相似，可考虑该方法，例：\nDn=∣012⋯n−2n−1101⋯n−3n−2210⋯n−4n−3⋮⋮⋮⋮⋮n−2n−3n−4⋯01n−1n−2n−3⋯10∣\\begin{align}D_n=\\left|\\begin{matrix}0\u0026amp;1\u0026amp;2\u0026amp;\\cdots\u0026amp;n-2\u0026amp;n-1\\\\1\u0026amp;0\u0026amp;1\u0026amp;\\cdots\u0026amp;n-3\u0026amp;n-2\\\\2\u0026amp;1\u0026amp;0\u0026amp;\\cdots\u0026amp;n-4\u0026amp;n-3\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\u0026amp;\\vdots\\\\n-2\u0026amp;n-3\u0026amp;n-4\u0026amp;\\cdots\u0026amp;0\u0026amp;1\\\\n-1\u0026amp;n-2\u0026amp;n-3\u0026amp;\\cdots\u0026amp;1\u0026amp;0\\end{matrix}\\right|\\end{align}Dn​=​012⋮n−2n−1​101⋮n−3n−2​210⋮n−4n−3​⋯⋯⋯⋯⋯​n−2n−3n−4⋮01​n−1n−2n−3⋮10​​​​行和不相同，列和也不相同，但观察到第i−1i-1i−1行的大部分元素为第iii行对应元素减111，则逐行向后倍加(第i+1i+1i+1行乘−1-1−1倍加到第iii行，iii从111开始)：\nDn=∣−111⋯11−1−11⋯11−1−1−1⋯11⋮⋮⋮⋮⋮−1−1−1⋯−11n−1n−2n−3⋯10∣=∣022⋯21002⋯21000⋯21⋮⋮⋮⋮⋮000⋯01n−1n−2n−3⋯10∣=(−1)1+n(n−1)2n−2\\begin{align}\u0026amp;D_n=\\left|\\begin{matrix}-1\u0026amp;1\u0026amp;1\u0026amp;\\cdots\u0026amp;1\u0026amp;1\\\\-1\u0026amp;-1\u0026amp;1\u0026amp;\\cdots\u0026amp;1\u0026amp;1\\\\-1\u0026amp;-1\u0026amp;-1\u0026amp;\\cdots\u0026amp;1\u0026amp;1\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\u0026amp;\\vdots\\\\-1\u0026amp;-1\u0026amp;-1\u0026amp;\\cdots\u0026amp;-1\u0026amp;1\\\\n-1\u0026amp;n-2\u0026amp;n-3\u0026amp;\\cdots\u0026amp;1\u0026amp;0\\end{matrix}\\right|=\\left|\\begin{matrix}0\u0026amp;2\u0026amp;2\u0026amp;\\cdots\u0026amp;2\u0026amp;1\\\\0\u0026amp;0\u0026amp;2\u0026amp;\\cdots\u0026amp;2\u0026amp;1\\\\0\u0026amp;0\u0026amp;0\u0026amp;\\cdots\u0026amp;2\u0026amp;1\\\\\\vdots\u0026amp;\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\u0026amp;\\vdots\\\\0\u0026amp;0\u0026amp;0\u0026amp;\\cdots\u0026amp;0\u0026amp;1\\\\n-1\u0026amp;n-2\u0026amp;n-3\u0026amp;\\cdots\u0026amp;1\u0026amp;0\\end{matrix}\\right|\\\\\u0026amp;=(-1)^{1+n}(n-1)2^{n-2}\\end{align}​Dn​=​−1−1−1⋮−1n−1​1−1−1⋮−1n−2​11−1⋮−1n−3​⋯⋯⋯⋯⋯​111⋮−11​111⋮10​​=​000⋮0n−1​200⋮0n−2​220⋮0n−3​⋯⋯⋯⋯⋯​222⋮01​111⋮10​​=(−1)1+n(n−1)2n−2​​","permalink":"https://echo.ishian.me/2024/05/14/linearalgebra-determinant/","summary":"介绍行列式的结论与特殊行列式、消零等运算技巧","title":"线性代数: 行列式"},{"content":"矩阵 概念 形如\n[a11a12...a1na21a22...a2n⋮⋮⋮am1am2...amn]\\begin{align}\\left[\\begin{matrix}a_{11}\u0026amp;a_{12}\u0026amp;...\u0026amp;a_{1\\rm n}\\\\a_{21}\u0026amp;a_{22}\u0026amp;...\u0026amp;a_{2\\rm n}\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\a_{\\rm m1}\u0026amp;a_{\\rm m2}\u0026amp;...\u0026amp;a_{\\rm mn}\\end{matrix}\\right]\\end{align}​a11​a21​⋮am1​​a12​a22​⋮am2​​.........​a1n​a2n​⋮amn​​​​​的矩阵称为m×nm\\times nm×n矩阵，记为Am×nA_{m\\times n}Am×n​\n行矩阵/列矩阵：只有一行/一列的矩阵 nnn阶矩阵：An×nA_{n\\times n}An×n​ 三角形矩阵(000一般省略不写)：三角形矩阵一定是方阵 对角矩阵： [a110...00a22...0⋮⋮⋮00...ann]\\begin{align}\\left[\\begin{matrix}a_{11}\u0026amp;0\u0026amp;...\u0026amp;0\\\\0\u0026amp;a_{22}\u0026amp;...\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\0\u0026amp;0\u0026amp;...\u0026amp;a_{\\rm nn}\\end{matrix}\\right]\\end{align}​a11​0⋮0​0a22​⋮0​.........​00⋮ann​​​​​，记为diag(a11,a22,⋯ ,ann)\\rm diag(a_{11},a_{22},\\cdots,a_{nn})diag(a11​,a22​,⋯,ann​) 上三角矩阵： [a11a12...a1n0a22...a2n⋮⋮⋮00...ann]\\begin{align}\\left[\\begin{matrix}a_{11}\u0026amp;a_{12}\u0026amp;...\u0026amp;a_{1\\rm n}\\\\0\u0026amp;a_{22}\u0026amp;...\u0026amp;a_{2\\rm n}\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\0\u0026amp;0\u0026amp;...\u0026amp;a_{\\rm nn}\\end{matrix}\\right]\\end{align}​a11​0⋮0​a12​a22​⋮0​.........​a1n​a2n​⋮ann​​​​​ 下三角矩阵： [a110...0a21a22...0⋮⋮⋮am1am2...ann]\\begin{align}\\left[\\begin{matrix}a_{11}\u0026amp;0\u0026amp;...\u0026amp;0\\\\a_{21}\u0026amp;a_{22}\u0026amp;...\u0026amp;0\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\a_{\\rm m1}\u0026amp;a_{\\rm m2}\u0026amp;...\u0026amp;a_{\\rm nn}\\end{matrix}\\right]\\end{align}​a11​a21​⋮am1​​0a22​⋮am2​​.........​00⋮ann​​​​​ 单位矩阵：diag(1,1,⋯ ,1)\\rm diag(1,1,\\cdots,1)diag(1,1,⋯,1)，记为EEE或III 同型矩阵：行、列数相同的两矩阵 运算 基本运算 加/减法运算：A+BA+BA+B即把各对应的元素相加/减，其中A、BA、BA、B必须是同型矩阵 数乘运算：kAk\\bf AkA即每个元素都乘上kkk 乘法运算：Am×kBk×m=Cm×nA_{\\rm m\\times k}B_{\\rm k\\times m}=C_{\\rm m\\times n}Am×k​Bk×m​=Cm×n​， cij=∑a=1kaiabaj\\begin{align}c_{ij}=\\sum_{a=1}^ka_{\\rm ia}b_{\\rm aj}\\end{align}cij​=a=1∑k​aia​baj​​​ 运算的性质 加减法与数乘运算的性质不赘述\n一般AB≠BAAB\\ne BAAB=BA，若AB=BAAB=BAAB=BA，则A、BA、BA、B为同阶方阵 A≠OA\\ne OA=O时，若AB=ACAB=ACAB=AC，一般B≠CB\\ne CB=C 若AB=OAB=OAB=O，一般A≠OA\\ne OA=O或B≠OB\\ne OB=O；若A≠OA\\ne OA=O且B≠OB\\ne OB=O，ABABAB可能等于OOO A(BC)=(AB)CA(BC)=(AB)CA(BC)=(AB)C k(AB)=(kA)B=A(kB)k(AB)=(kA)B=A(kB)k(AB)=(kA)B=A(kB) A(B+C)=AB+ACA(B+C)=AB+ACA(B+C)=AB+AC；(A+B)C=AC+BC(A+B)C=AC+BC(A+B)C=AC+BC AE1=E2A=AAE_1=E_2A=AAE1​=E2​A=A，若AAA为方阵，则E1=E2E_1=E_2E1​=E2​ 两个同阶且相同类型的三角形矩阵相乘还是对应类型的三角形矩阵，且新矩阵对角元等于两矩阵对角元相乘 线性方程组的矩阵形式 形如\n{a11x1+a12x2+⋯+a1nxn=b1,a21x1+a22x2+⋯+a2nxn=b2, ⋯⋯⋯⋯am1x1+am2x2+⋯+amnxn=bm\\begin{align}\\begin{cases}a_{11}x_1+a_{12}x_2+\\cdots+a_{1\\rm n}x_n=b_1,\\\\a_{21}x_1+a_{22}x_2+\\cdots+a_{2\\rm n}x_n=b_2,\\\\\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\cdots\\cdots\\cdots\\cdots\\\\a_{\\rm m1}x_1+a_{\\rm m2}x_2+\\cdots+a_{\\rm mn}x_n=b_m\\end{cases}\\end{align}⎩⎨⎧​a11​x1​+a12​x2​+⋯+a1n​xn​=b1​,a21​x1​+a22​x2​+⋯+a2n​xn​=b2​, ⋯⋯⋯⋯am1​x1​+am2​x2​+⋯+amn​xn​=bm​​​​的mmm条nnn元一次方程组称为m×nm\\times nm×n型方程组的一般形式\n令\nA=[a11a12...a1na21a22...a2n⋮⋮⋮am1am2...amn],x=[x1x2⋮xn],b=[b1b2⋮bn]\\begin{align}A=\\left[\\begin{matrix}a_{11}\u0026amp;a_{12}\u0026amp;...\u0026amp;a_{1\\rm n}\\\\a_{21}\u0026amp;a_{22}\u0026amp;...\u0026amp;a_{2\\rm n}\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\a_{\\rm m1}\u0026amp;a_{\\rm m2}\u0026amp;...\u0026amp;a_{\\rm mn}\\end{matrix}\\right],x=\\left[\\begin{matrix}x_1\\\\x_2\\\\\\vdots\\\\x_n\\end{matrix}\\right],b=\\left[\\begin{matrix}b_1\\\\b_2\\\\\\vdots\\\\b_n\\end{matrix}\\right]\\end{align}A=​a11​a21​⋮am1​​a12​a22​⋮am2​​.........​a1n​a2n​⋮amn​​​,x=​x1​x2​⋮xn​​​,b=​b1​b2​⋮bn​​​​​则Ax=bAx=bAx=b，这是它的矩阵形式，其中AAA称为系数矩阵，bbb称为常数向量\n增广矩阵：[A,b]=[a11a12...a1nb1a21a22...a2nb2⋮⋮⋮⋮am1am2...amnbm][A,b]=\\left[\\begin{matrix}a_{11}\u0026amp;a_{12}\u0026amp;...\u0026amp;a_{1\\rm n}\u0026amp;b_1\\\\a_{21}\u0026amp;a_{22}\u0026amp;...\u0026amp;a_{2\\rm n}\u0026amp;b_2\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\u0026amp;\\vdots\\\\a_{\\rm m1}\u0026amp;a_{\\rm m2}\u0026amp;...\u0026amp;a_{\\rm mn}\u0026amp;b_m\\end{matrix}\\right][A,b]=​a11​a21​⋮am1​​a12​a22​⋮am2​​.........​a1n​a2n​⋮amn​​b1​b2​⋮bm​​​\n当b=Ob=Ob=O时，称为齐次线性方程组\n转置 定义 An×mA_{n\\times m}An×m​称为Am×nA_{m\\times n}Am×n​的转置矩阵，记为ATA^{\\rm T}AT\n转置的性质 (A+B)T=AT+BT(A+B)^{\\rm T}=A^{\\rm T}+B^{\\rm T}(A+B)T=AT+BT (AB)T=BTAT(AB)^{\\rm T}=B^{\\rm T}A^{\\rm T}(AB)T=BTAT 对称矩阵/反称矩阵：设AAA为方阵，若AT=AA^{\\rm T}=AAT=A，则为对称矩阵；若AT=−AA^{\\rm T}=-AAT=−A，则为反称矩阵 正交矩阵：若ATA=AAT=EA^{\\rm T}A=AA^{\\rm T}=EATA=AAT=E，称AAA为正交矩阵 幂零矩阵：若存在正整数mmm，使Am=OA^m=OAm=O，则称AAA为幂零矩阵 幂等矩阵：若A2=AA^2=AA2=A，称AAA为幂等矩阵 对合矩阵：若A2=EA^2=EA2=E，称AAA为对合矩阵 分块矩阵 分块后矩阵的运算与一般矩阵类似，若A=[A11⋯A1n⋮⋮Am1⋯Amn]A=\\left[\\begin{matrix}A_{11}\u0026amp;\\cdots\u0026amp;A_{1n}\\\\\\vdots\u0026amp;\u0026amp;\\vdots\\\\A_{m1}\u0026amp;\\cdots\u0026amp;A_{mn}\\end{matrix}\\right]A=​A11​⋮Am1​​⋯⋯​A1n​⋮Amn​​​，则AT=[A11T⋯A1nT⋮⋮Am1T⋯AmnT]A^{\\rm T}=\\left[\\begin{matrix}A_{11}^{\\rm T}\u0026amp;\\cdots\u0026amp;A^{\\rm T}_{1n}\\\\\\vdots\u0026amp;\u0026amp;\\vdots\\\\A^{\\rm T}_{m1}\u0026amp;\\cdots\u0026amp;A^{\\rm T}_{mn}\\end{matrix}\\right]AT=​A11T​⋮Am1T​​⋯⋯​A1nT​⋮AmnT​​​\n常用分块方法 按行或按列分为mmm个nnn阶行向量或nnn个mmm阶列向量 分为2×22\\times22×2的分块矩阵，其中应利用EEE或OOO或三角形矩阵 矩阵的初等变换 对一个线性方程组进行操作：\n调换两条方程的位置 方程两边同乘非零常数 某行乘以一非零常数后加到另一行 不改变该方程组的解，这些操作称为线性方程组的初等变换\n对应到增广矩阵上，这些操作为：\n对调行变换/对调列变换：对调两行/两列，记为ri↔rjr_i\\leftrightarrow r_jri​↔rj​或ci↔cjc_i\\leftrightarrow c_jci​↔cj​ 倍乘行变换/倍乘列变换：第iii行/列乘上一个非零常数，记为ri×kr_i\\times kri​×k或ri×kr_i\\times kri​×k 倍加行变换/倍加列变换：第iii行/列乘上一个非零常数后加到第jjj行/列上，记为rj+krir_j+kr_irj​+kri​或cj+kcic_j+kc_icj​+kci​ 称为矩阵的初等变换，有限次变换后可逆，称原矩阵和得到的矩阵等价或相抵\n初等矩阵 由EEE进行一次初等变换后的矩阵称为初等矩阵(k≠0)(k\\ne 0)(k=0)：\n对调矩阵：Ei,jE_{i,j}Ei,j​，即第iii行/列与第jjj行/列对调，行或列对调得到的矩阵相同 倍乘矩阵：Ei(k)E_i(k)Ei​(k)，即第iii行/列元素乘上kkk，行或列倍乘得到的矩阵相同 倍加矩阵：Ei,j(k)E_{i,j}(k)Ei,j​(k)，即第jjj行/列乘上kkk后加到第iii行/列上，Er=EcTE_r=E_c^{\\rm T}Er​=EcT​ 初等矩阵的性质 Ei,jT=Ei,j、EiT(k)=Ei(k)、Ei,jT(k)=Ej,i(k)E_{i,j}^{\\rm T}=E_{i,j}、E_i^{\\rm T}(k)=E_i(k)、E^{\\rm T}_{i,j}(k)=E_{j,i}(k)Ei,jT​=Ei,j​、EiT​(k)=Ei​(k)、Ei,jT​(k)=Ej,i​(k) Ei,jEj,i=Ei(k)Ei(k−1)=Ei,j(k)Ei,j(−k)=E，k≠0E_{i,j}E_{j,i}=E_i(k)E_i(k^{-1})=E_{i,j}(k)E_{i,j}(-k)=E，k\\ne0Ei,j​Ej,i​=Ei​(k)Ei​(k−1)=Ei,j​(k)Ei,j​(−k)=E，k=0 等价标准形 F=[EsOOO]F=\\left[\\begin{matrix}E_s\u0026amp;O\\\\O\u0026amp;O\\end{matrix}\\right]F=[Es​O​OO​]称为AAA的等价标准形，AAA通过有限次初等变换可以变成FFF\nFFF有特例：[EsO]、[EsO]、[Es]\\left[\\begin{matrix}E_s\u0026amp;O\\end{matrix}\\right]、\\left[\\begin{matrix}E_s\\\\O\\end{matrix}\\right]、\\left[\\begin{matrix}E_s\\end{matrix}\\right][Es​​O​]、[Es​O​]、[Es​​]\n一个矩阵的等价标准形是唯一的\n矩阵的行列式 当一个矩阵是方阵时，才有行列式，记为∣A∣|A|∣A∣ ∣AT∣=∣A∣|A^{\\rm T}|=|A|∣AT∣=∣A∣ ∣λA∣=λn∣A∣|\\lambda A|=\\lambda^n|A|∣λA∣=λn∣A∣ ∣AB∣=∣A∣∣B∣|AB|=|A||B|∣AB∣=∣A∣∣B∣ ∣AB∣=∣BA∣|AB|=|BA|∣AB∣=∣BA∣ ","permalink":"https://echo.ishian.me/2024/05/11/linearalgebra-matrix/","summary":"介绍矩阵的概念、运算、转置、分块与初等变换","title":"线性代数: 矩阵"},{"content":"模拟电路 电路分析方法 基本概念 两个结点构成一条支路，支路可构成一个回路 基尔霍夫定律： KCL：流入结点电流等于流出结点电流 KVL：延回路绕行一周，电压降之和等于电压升之和 \u0026lt;img src=\u0026ldquo;end{align}$，电流相等 \u0026lt;img src=\u0026ldquo;end{align}$，电压相等 理想电压源：输出电阻越小，带负载能力越高，对外输出恒定电压U=E−IR0U=E-IR_0U=E−IR0​ 理想电流源：R0R_0R0​越大，输出电流越稳定 I=Is−UR0\\begin{align}I=I_s-\\frac{U}{R_0}\\end{align}I=Is​−R0​U​​​ 叠加原理：对于线性电路，某一结点的电流或电压可视为所有电源单独作用后(其它电流源看作断路、电压源看作短路)，在该点产生的电流或电压的代数和(参考方向相同时即相加) 戴维宁定理与诺顿定理：任何有源二端线性网络可看作一个等效电压源或等效电流源，等效电源的内阻等于将电压源短路、电流源断路后的无源二段网络的总电阻 四种受控源：Voltage/Current Controlled Voltage/Current Source\\rm Voltage/Current\\ \\ Controlled\\ \\ Voltage/Current\\ \\ SourceVoltage/Current Controlled Voltage/Current Source VCVSVCVSVCVS：电压控制电压源 VCCSVCCSVCCS：电压控制电流源 CCVSCCVSCCVS：电流控制电压源 CCCSCCCSCCCS：电流控制电流源 电容的容抗为 1ωC\\begin{align}\\frac1{\\omega C}\\end{align}ωC1​​​，阻抗为 1jωC\\begin{align}\\frac1{j\\omega C}\\end{align}jωC1​​​，jjj表示相位偏移，因为在通交流电时，电容的电压与流过电容的电流的相位相差909090度 模拟电路元件 半导体 纯净半导体掺入五价元素后变为NNN型半导体，自由电子为多数载流子；掺入三价元素后变为PPP型半导体，空穴为多数载流子；它们本身都不带电\n多数载流子受掺杂浓度影响，少数载流子易受温度影响，温度越高、少子越多\n在PNPNPN结里，多子扩散、少子漂移\n一般二极管 形成PNPNPN结后，接入正向电压时，在克服内电场后，多子扩散加强，正向导通；接入反向电压后，内电场加强，少子漂移加强，但形成电流很小，反向截止\n克服内电场的阶段称为死区，在反向击穿电压内电流基本保持不变，反向击穿后一般二极管即损坏，不可逆\n正向电压更大的二极管优先导通\n稳压二极管 稳压二极管正常工作时接入反向击穿电压，需要接入限流电阻，过程可逆；此时该支路电压保持在UZU_ZUZ​不变\n经典稳压电路：\n设UI∈[UImin,UImax]U_I\\in[U_{\\rm Imin}, U_{\\rm Imax}]UI​∈[UImin​,UImax​]，RL∈[RLmin,RLmax]R_L\\in[R_{\\rm Lmin},R_{\\rm Lmax}]RL​∈[RLmin​,RLmax​]，稳压二极管的击穿电压为UZU_ZUZ​，限制流入二极管的电流I∈[Imin,Imax]I\\in[I_{\\rm min},I_{\\rm max}]I∈[Imin​,Imax​]，则RRR的选取范围为：\nIL=UZRL, IR=UI−UZR, I=IR−IL, ∴R∈[UImin−UZImax+UZRLmin, UImax−UZImin+UZRLmax]\\begin{align}I_L=\\frac{U_Z}{R_L},\\ I_R=\\frac{U_I-U_Z}{R},\\ I=I_R-I_L,\\ \\therefore R\\in\\Bigg[\\frac{U_{\\rm Imin}-U_Z}{I_{\\rm max}+\\frac{U_Z}{R_{\\rm Lmin}}},\\ \\frac{U_{\\rm Imax}-U_Z}{I_{\\rm min}+\\frac{U_Z}{R_{\\rm Lmax}}}\\Bigg]\\end{align}IL​=RL​UZ​​, IR​=RUI​−UZ​​, I=IR​−IL​, ∴R∈[Imax​+RLmin​UZ​​UImin​−UZ​​, Imin​+RLmax​UZ​​UImax​−UZ​​]​​三极管 常在放大电路中使用NPNNPNNPN型三极管，分为集电极、基极、发射极\n工作状态：\n当VE\u0026lt;VB\u0026lt;VCV_E\u0026lt;V_B\u0026lt;V_CVE​\u0026lt;VB​\u0026lt;VC​时，处于放大状态，工作在放大区，也称线性区 当VB\u0026gt;VCV_B\u0026gt;V_CVB​\u0026gt;VC​时，处于饱和状态，工作在饱和区(输入电压大到放大不了) 当VE\u0026gt;VBV_E\u0026gt;V_BVE​\u0026gt;VB​时，处于截止状态，工作在截止区(放大了，但无法发射) 工作在线性区时：\nIE=(1+β)IBIC=βIB\\begin{align}\u0026amp;I_E=(1+\\beta)I_B\u0026amp;I_C=\\beta I_B\\end{align}​IE​=(1+β)IB​​IC​=βIB​​​ 若集电极电流上升，则β\\betaβ会下降 三极管可等效为rber_{be}rbe​与一个CCCSCCCSCCCS rbe=(200+26 mVIB)Ω\\begin{align}r_{be}=(200+\\frac{26\\ mV}{I_B})\\Omega\\end{align}rbe​=(200+IB​26 mV​)Ω​​ 基本放大电路 对优秀的放大电路来讲，AuA_uAu​要够用，rir_iri​尽可能大，ror_oro​尽可能小\n共发射极放大电路 在集电极取输出电压，称为共发射极放大电路\n静态分析：\n只观察两电容中间的电路，并采用左路支路计算 VB=UBE\\begin{align}V_B=U_{BE}\\end{align}VB​=UBE​​​ IB=VCC−VBRb\\begin{align}I_B=\\frac{V_{CC}-V_B}{R_b}\\end{align}IB​=Rb​VCC​−VB​​​​ UCE=VCC−βIBRc\\begin{align}U_{CE}=V_{CC}-\\beta I_BR_c\\end{align}UCE​=VCC​−βIB​Rc​​​ (IB,UBE)(I_B,U_{BE})(IB​,UBE​)和(IC,UCE)(I_C,U_{CE})(IC​,UCE​)分别对应输入、输出曲线上的一点，称为静态工作点QQQ 放大后，电流相位不变，电压相位相反 若QQQ点过高(IB、VBI_B、V_BIB​、VB​过高，反相后表现在UOU_OUO​下部分波形失真)，发生饱和失真 若QQQ点过低(IB、VBI_B、V_BIB​、VB​过低，反相后表现在UOU_OUO​上部分波形失真)，发生截止失真 动态分析：\n画出交流通路后，化为等效微变电路\nAu=uoui=−ic(Rc//RL)ibrbe\\begin{align}\u0026amp;A_u=\\frac{u_o}{u_i}=\\frac{-i_c(R_c//R_L)}{i_br_{be}}\\end{align}​Au​=ui​uo​​=ib​rbe​−ic​(Rc​//RL​)​​​ 输入电阻\nri=Rb//rbe\\begin{align}r_i=R_b//r_{be}\\end{align}ri​=Rb​//rbe​​​ 输出电阻ro=Rcr_o=R_cro​=Rc​\n分压式偏置电路 温度升高时，QQQ点升高，分压式偏置电路能够在温度升高时自动降低QQQ点：\nIC≈IE=VB−UBERE\\begin{align}\u0026amp;I_C\\approx I_E=\\frac{V_B-U_{BE}}{R_E}\\end{align}​IC​≈IE​=RE​VB​−UBE​​​​ 一般来说，UBE\u0026lt;\u0026lt;VBU_{BE}\u0026lt;\u0026lt;V_BUBE​\u0026lt;\u0026lt;VB​时，基极电流保持稳定，QQQ点不变；所以RB1\u0026gt;RB2R_{B1}\u0026gt;R_{B2}RB1​\u0026gt;RB2​，提高VBV_BVB​(但不能过高) 静态分析：\n一般认为IB→0I_B\\rightarrow0IB​→0， ∴VB≈RB2RB1+RB2VCC\\begin{align}\\therefore V_B\\approx\\frac{R_{B2}}{R_{B1}+R_{B2}}V_{CC}\\end{align}∴VB​≈RB1​+RB2​RB2​​VCC​​​ IC≈IE=VB−UBERE\\begin{align}\u0026amp;I_C\\approx I_E=\\frac{V_B-U_{BE}}{R_E}\\end{align}​IC​≈IE​=RE​VB​−UBE​​​​ UCE=VCC−ICRC−IERE\\begin{align}U_{CE}=V_{CC}-I_CR_C-I_ER_E\\end{align}UCE​=VCC​−IC​RC​−IE​RE​​​ 动态分析：\nAu=uoui=−ic(RC//RL)ib(rbe+(1+βRE))\\begin{align}\u0026amp;A_u=\\frac{u_o}{u_i}=\\frac{-i_c(R_C//R_L)}{i_b(r_{be}+(1+\\beta R_E))}\\end{align}​Au​=ui​uo​​=ib​(rbe​+(1+βRE​))−ic​(RC​//RL​)​​​，相比未分压时减小了 ri=RB//(rbe+(1+βRE)), RB=RB1//RB2\\begin{align}r_i=R_B//(r_{be}+(1+\\beta R_E)),\\ R_B=R_{B1}//R_{B2}\\end{align}ri​=RB​//(rbe​+(1+βRE​)), RB​=RB1​//RB2​​​，相比未分压时增大了 ro=RC\\begin{align}r_o=R_C\\end{align}ro​=RC​​​，不变 共发射极放大电路能做到有较大的电压放大倍数、有较大的输入电阻\n共集电极放大电路 也称射极输出器\n静态分析：\nVCC=VB+IBRB=UBE+IBRB+IEREIB=VCC−UBERB+(1+β)RE\\begin{align}\u0026amp;V_{CC}=V_B+I_BR_B=U_{BE}+I_BR_B+I_ER_E\u0026amp;I_B=\\frac{V_{CC}-U_{BE}}{R_B+(1+\\beta)R_E}\\end{align}​VCC​=VB​+IB​RB​=UBE​+IB​RB​+IE​RE​​IB​=RB​+(1+β)RE​VCC​−UBE​​​​ UCE=VCC−IERE\\begin{align}U_{CE}=V_{CC}-I_ER_E\\end{align}UCE​=VCC​−IE​RE​​​ 动态分析：\nAu=uoui=ie(RE//RL)ib(rbe+(1+β)(RE//RL))≈1\\begin{align}\u0026amp;A_u=\\frac{u_o}{u_i}=\\frac{i_e(R_E//R_L)}{i_b(r_{be}+(1+\\beta)(R_E//R_L))}\\approx1\\end{align}​Au​=ui​uo​​=ib​(rbe​+(1+β)(RE​//RL​))ie​(RE​//RL​)​≈1​​，故也称电压跟随器 ri=RB//(rbe+(1+β)(RE//RL))\\begin{align}\u0026amp;r_i=R_B//(r_{be}+(1+\\beta)(R_E//R_L))\\end{align}​ri​=RB​//(rbe​+(1+β)(RE​//RL​))​​ ro=RE//(RS//RB+rbe)1+β\\begin{align}\u0026amp;r_o=R_E//\\frac{(R_S//R_B+r_{be})}{1+\\beta}\\end{align}​ro​=RE​//1+β(RS​//RB​+rbe​)​​​ 它的输出电阻很小，带负载能力强，且输入电阻很大\n差动放大电路 差动放大电路是抑制零点漂移的最有效的电路\n理想情况下，T1,2T_{1,2}T1,2​的静态工作点相同，可以有效抑制共模信号\n大小相同，但方向相反的信号称为差模信号，对差模信号有放大能力：uo=−2ΔVu_o=-2\\Delta Vuo​=−2ΔV\n即，uo=ui2−ui1u_o=u_{\\rm i2}-u_{\\rm i1}uo​=ui2​−ui1​，是uou_ouo​负级端信号和正级端信号的差值\n该电路多被用于集成放大电路的第一级\n互补对称功率放大电路 效率\nη=负载交流功率电源直流功率\\begin{align}\\eta=\\frac{负载交流功率}{电源直流功率}\\end{align}η=电源直流功率负载交流功率​​​该放大电路由两个三极管组成，在此电路中，为了减小电源直流功率，增大效率，常采用甲乙类工作状态的静态工作点，集甲类(整个周期都导通)和乙类(只有半个周期导通)的优点，能减小失真同时提高效率，此时静态工作点应略高于截止点，即IBI_BIB​略大于000\n此电路产生的波形会发生交越失真，因为晶体管在输入电压000附近会进入死区，导通性差，无法输出\n引入两个二极管，使得在任意半个周期时，总有一个三极管处于良好工作状态，另一个三极管工作点略高于截止点，使两个三极管交替地处在甲乙类状态工作，又能减少交越失真现象\n该电路多被用于放大电路的输出级\n多级放大电路 阻容耦合：\n使用电容耦合时，各级的静态工作点互相独立，可分别单独计算\n动态分析时，AuA_uAu​等于各级AuA_uAu​的乘积\n但由于电容不容易集成，这种方式不常用\n直接耦合：\n直接耦合直接用导线连接上下两级，因此各级的静态工作点会相互影响\n而且由于温度的影响，在静态工作时会发生零点漂移，即电压缓慢、无规则地变化现象\n直接耦合的电路具有良好的低频特性(当在某频率上\n∣Au∣\u0026lt;12∣Aumax∣\\begin{align}|A_u|\u0026lt;\\frac1{\\sqrt2}|A_{umax}|\\end{align}∣Au​∣\u0026lt;2​1​∣Aumax​∣​​时，称为无效频率)\n运算放大电路 集成放大电路 最大输出电压(UoppU_{\\rm opp}Uopp​)：保持输入输出不失真的最大输出电压，若计算后输出电压的绝对值大于此值，将造成失真，输出电压的绝对值将始终等于UoppU_{\\rm opp}Uopp​\n理想的集成放大电路如果没有接入负反馈，则始终工作在饱和区，输出{+Uo(sat),u+\u0026gt;u−−Uo(sat),u+\u0026lt;u−\\begin{cases}+U_{\\rm o(sat)},\u0026amp;u_+\u0026gt;u_-\\\\-U_{\\rm o(sat)},\u0026amp;u_+\u0026lt;u_-\\end{cases}{+Uo(sat)​,−Uo(sat)​,​u+​\u0026gt;u−​u+​\u0026lt;u−​​，是一个电压比较器\n由于rid→∞r_{\\rm id}\\rightarrow\\inftyrid​→∞，理想的集成放大电路一定存在虚拟断路现象，即输入端不取电流\n反馈信号 设输入信号为XiX_{\\rm i}Xi​，反馈信号为XfX_{\\rm f}Xf​，叠加后的净输入信号为XdX_{\\rm d}Xd​，输出信号为XoX_{\\rm o}Xo​，则：\nXd=Xi−XfX_{\\rm d}=X_{\\rm i}-X_{\\rm f}Xd​=Xi​−Xf​ 开环放大倍数 A=XoXd\\begin{align}A=\\frac{X_{\\rm o}}{X_{\\rm d}}\\end{align}A=Xd​Xo​​​​ 反馈系数 F=XfXo\\begin{align}F=\\frac{X_{\\rm f}}{X_{\\rm o}}\\end{align}F=Xo​Xf​​​​ 若AF\u0026lt;0AF\u0026lt;0AF\u0026lt;0，即输入信号与反馈信号反相，则称反馈为负反馈；反之为正反馈；若Xf=0X_{\\rm f}=0Xf​=0，即RiR_{\\rm i}Ri​短路或RfR_{f}Rf​断路，则AF≥0AF\\ge0AF≥0，没有负反馈 负反馈使放大倍数降低，用于运算电路；正反馈使放大倍数增大，用于产生自激振荡，产生各种波形 瞬时极性法判断反馈类型：若uiu_{\\rm i}ui​接入同相端，则输出信号与它极性相同，否则不同；若反馈信号和uiu_{\\rm i}ui​接入同一极，极性相同为正反馈，否则为负反馈；若反馈信号和uiu_{\\rm i}ui​分别接入不同极，则与接入同一极的情况相反 经验判断：只跨一个集成放大电路的反馈信号，若接入同相端肯定为正反馈，否则肯定为负反馈 接入反馈时的放大倍数AfA_{f}Af​称为闭环放大倍数， Af=XoXi=A1+AF\\begin{align}A_{f}=\\frac{X_{\\rm o}}{X_{\\rm i}}=\\frac A{1+AF}\\end{align}Af​=Xi​Xo​​=1+AFA​​​ 要求静态时，同、反相端对地电阻相同，所以AuA_uAu​一定可以化简为某一单独相端的电阻的表达式，也可以化简为由反馈电阻和输入端支路电阻组成的表达式；但若题目给出了所有电阻的具体阻值，应按流程计算，因为不一定是理想的运算电路 负反馈的性质 根据采样不同，负反馈分为电压反馈和电流反馈，一般来说，电压反馈直接从uou_{\\rm o}uo​取电压 电压负反馈能稳定输出电压，减小输出电阻(从输出端向内看，相当于和一条支路并联) 电流负反馈能稳定输出电流，增大输出电阻(从输出端向内看，相当于和一条支路串联) 根据与输入信号叠加形式的不同，负反馈分为串联反馈和并联反馈，一般来说，并联反馈和输入信号连接在同一极 串联负反馈能增大输入电阻(从输入端向外看，相当于和一条支路串联) 并联负反馈能减小输入电阻(从输入端向外看，相当于和一条支路并联) 反馈支路能通交流成分就可称为交流负反馈，能通直流就可称为直流负反馈，一般负反馈属于交、直流反馈；以上都属于交流类型负反馈的作用，而直流负反馈能够稳定静态工作点 为了达成输入电阻高，输出电阻低，一般采用串联电压负反馈 为了使其成为恒电流源，一般采用电流负反馈 负反馈可减小波形失真，但不能完全消除 认为只要接入负反馈，就处于深度负反馈，一定工作在线性区，此时不仅满足虚拟断路，也满足虚拟短路，即同相与反相输入端的电位相等\n且认为\nAf=uoui≈1F\\begin{align}A_{f}=\\frac{u_{\\rm o}}{u_{\\rm i}}\\approx\\frac1F\\end{align}Af​=ui​uo​​≈F1​​​，此时可对输入信号进行各种运算\n比例运算电路 反相比例运算：输入信号接在反相端，放大倍数为负数\nV−=RFR1+RFui+R1R1+RFuo=V+=0⇒Auf=−RFR1\\begin{align}V_-=\\frac{R_F}{R_1+R_F}u_{\\rm i}+\\frac{R_1}{R_1+R_F}u_{\\rm o}=V_+=0\\Rightarrow A_{uf}=-\\frac{R_F}{R_1}\\end{align}V−​=R1​+RF​RF​​ui​+R1​+RF​R1​​uo​=V+​=0⇒Auf​=−R1​RF​​​​同相比例运算：输入信号接在同相端，放大倍数一定≥1\\ge1≥1\nV−=R1R1+RFuo=V+=ui⇒Auf=1+RFR1=RFR2\\begin{align}V_-=\\frac{R_1}{R_1+R_F}u_{\\rm o}=V_+=u_{\\rm i}\\Rightarrow A_{uf}=1+\\frac{R_F}{R_1}=\\frac{R_F}{R_2}\\end{align}V−​=R1​+RF​R1​​uo​=V+​=ui​⇒Auf​=1+R1​RF​​=R2​RF​​​​电压跟随器：在同相比例运算电路的基础上，当R1=∞R_1=\\inftyR1​=∞或Rf=0R_f=0Rf​=0时，Auf=1A_{uf}=1Auf​=1，uo=uiu_{\\rm o}=u_{\\rm i}uo​=ui​\n加减法运算电路 反相加法：\nV+=0=V−=(R2//R3//Rf)ui1R1+R2//R3//Rf+(R1//R3//Rf)ui2R2+R1//R3//Rf+(R1//R2//Rf)ui3R3+R1//R2//Rf+(R1//R2//R3)uoRf+R1//R2//R3在此图中,R1//R2//R3//Rf=0,但假设同相端有电阻R+,则R1//R2//R3//Rf=R+解得:uo=(−R+R1ui1−R+R2ui2−R+R3ui3)RfR+=−(RfR1ui1+RfR2ui2+RfR3ui3)\\begin{align}\u0026amp;V_+=0=V_-=\\\\\u0026amp;\\frac{(R_2//R_3//R_f)u_{\\rm i1}}{R_1+R_2//R_3//R_f}+\\frac{(R_1//R_3//R_f)u_{\\rm i2}}{R_2+R_1//R_3//R_f}+\\frac{(R_1//R_2//R_f)u_{\\rm i3}}{R_3+R_1//R_2//R_f}+\\frac{(R_1//R_2//R_3)u_{\\rm o}}{R_f+R_1//R_2//R_3}\\\\\u0026amp;在此图中,R_1//R_2//R_3//R_f=0,但假设同相端有电阻R_+,则R_1//R_2//R_3//R_f=R_+\\\\\u0026amp;解得:u_{\\rm o}=(-\\frac{R_+}{R_1}u_{\\rm i1}-\\frac{R_+}{R_2}u_{\\rm i2}-\\frac{R_+}{R_3}u_{\\rm i3})\\frac{R_f}{R_+}=-(\\frac{R_f}{R_1}u_{\\rm i1}+\\frac{R_f}{R_2}u_{\\rm i2}+\\frac{R_f}{R_3}u_{\\rm i3})\\end{align}​V+​=0=V−​=R1​+R2​//R3​//Rf​(R2​//R3​//Rf​)ui1​​+R2​+R1​//R3​//Rf​(R1​//R3​//Rf​)ui2​​+R3​+R1​//R2​//Rf​(R1​//R2​//Rf​)ui3​​+Rf​+R1​//R2​//R3​(R1​//R2​//R3​)uo​​在此图中,R1​//R2​//R3​//Rf​=0,但假设同相端有电阻R+​,则R1​//R2​//R3​//Rf​=R+​解得:uo​=(−R1​R+​​ui1​−R2​R+​​ui2​−R3​R+​​ui3​)R+​Rf​​=−(R1​Rf​​ui1​+R2​Rf​​ui2​+R3​Rf​​ui3​)​​同相加法：\n易得，\nuo=RFRi1ui1+RFRi2ui2\\begin{align}u_{\\rm o}=\\frac{R_F}{R_{\\rm i1}}u_{\\rm i1}+\\frac{R_F}{R_{\\rm i2}}u_{\\rm i2}\\end{align}uo​=Ri1​RF​​ui1​+Ri2​RF​​ui2​​​减法运算：\n易得，\nuo=RFR2ui2−RFR1ui1\\begin{align}u_{\\rm o}=\\frac{R_F}{R_2}u_{\\rm i2}-\\frac{R_F}{R_1}u_{\\rm i1}\\end{align}uo​=R2​RF​​ui2​−R1​RF​​ui1​​​结论：加减法电路基于比例运算电路，输出电压是各个输入电压单独作用时(其它输入支路断路)产生的输出电压之和，化简后的输出电压的表达式里，没有接地端的电阻，只含有反馈电阻和含输入信号的支路的电阻时，是最简的\n积分微分运算电路 积分运算：\n常见反相积分运算电路，反馈元件是一个电容\nV+=0=V−=uo−ucV_+=0=V_-=u_{\\rm o}-u_{\\rm c}V+​=0=V−​=uo​−uc​\n电容的电流有效值为：\nic=Cducdt=i1=−uiR1\\begin{align}i_c=C\\frac{du_{c}}{dt}=i_1=\\frac{-u_{\\rm i}}{R_1}\\end{align}ic​=Cdtduc​​=i1​=R1​−ui​​​​联立解得\nuo=−1R1C∫uidt\\begin{align}u_{\\rm o}=-\\frac1{R_1C}\\int u_{\\rm i}dt\\end{align}uo​=−R1​C1​∫ui​dt​​微分运算：\n微分运放中，反馈元件仍是电阻，但输入端由电阻变为电容\n根据积分运算电路的分析，\nCduidt=−uoRF\\begin{align}C\\frac{du_{\\rm i}}{dt}=-\\frac{u_{\\rm o}}{R_F}\\end{align}Cdtdui​​=−RF​uo​​​​即\nuo=−RFCduidt\\begin{align}u_{\\rm o}=-R_FC\\frac{du_{\\rm i}}{dt}\\end{align}uo​=−RF​Cdtdui​​​​信号处理 电压比较器 之前提到过，不加负反馈的理想集放Au=∞A_u=\\inftyAu​=∞，始终处于饱和失真，工作在饱和区，门限值为uRu_RuR​(输入在反相端)或−uR-u_R−uR​(输入在同相端)\n但不加反馈的电压比较器的门限值不变，这使得输入电压在门限值处反复跳变时，输出电压不准确，故引入正反馈，使其成为滞回比较器：\n只满足虚拟断路，\nu+=R1R1+R2uo,u−=ui当ui进行从小于u+到大于u+的转变时,uo由+Uo(sat)变为−Uo(sat),门限值突变为−u+\\begin{align}\u0026amp;u_+=\\frac{R_1}{R_1+R_2}u_{\\rm o},u_-=u_{\\rm i}\\\\\u0026amp;当u_{\\rm i}进行从小于u_+到大于u_+的转变时,u_{\\rm o}由+U_{\\rm o(sat)}变为-U_{\\rm o(sat)},门限值突变为-u_+\\end{align}​u+​=R1​+R2​R1​​uo​,u−​=ui​当ui​进行从小于u+​到大于u+​的转变时,uo​由+Uo(sat)​变为−Uo(sat)​,门限值突变为−u+​​​若参考电压为000，两门限值互为相反数；当它不为000时，两门限值以它为中心\n称两门限值之差的绝对值为回差电压ΔU\\Delta UΔU，uou_{\\rm o}uo​与uiu_{\\rm i}ui​的图像称为电压传输特性曲线\n有源滤波器 滤波电路分为低通、高通、带通、带阻，以下为低通滤波器：\n由于电容支路的电流相位超前ui90u_{\\rm i}90ui​90度，所以引入复数jjj来表示相位运算，复数jjj只表示相位关系，在具体求值时使用其模长\n由电容阻抗为\nRc=1jωC=1j2πfC\\begin{align}R_c=\\frac1{j\\omega C}=\\frac1{j2\\pi fC}\\end{align}Rc​=jωC1​=j2πfC1​​​，\nV−=R1R1+RFuo=V+=uC=RCR+RCui\\begin{align}V_-=\\frac{R_1}{R_1+R_F}u_{\\rm o}=V_+=u_C=\\frac{R_C}{R+R_C}u_{\\rm i}\\end{align}V−​=R1​+RF​R1​​uo​=V+​=uC​=R+RC​RC​​ui​​​解得\nuo=R1+RFR1(1+j2πfRC)ui\\begin{align}u_{\\rm o}=\\frac{R_1+R_F}{R_1(1+j2\\pi fRC)}u_{\\rm i}\\end{align}uo​=R1​(1+j2πfRC)R1​+RF​​ui​​​，取AuA_uAu​模长，故\nAu=R1+RFR1(1+2πfRC)2\\begin{align}A_u=\\frac{R_1+R_F}{R_1\\sqrt{(1+2\\pi fRC)^2}}\\end{align}Au​=R1​(1+2πfRC)2​R1​+RF​​​​当f→0f\\rightarrow0f→0时，uou_{\\rm o}uo​最大；fff上升，∣Au∣|A_u|∣Au​∣逐渐下降；当(1+2πfRC)2\u0026gt;2\\sqrt{(1+2\\pi fRC)^2}\u0026gt;\\sqrt2(1+2πfRC)2​\u0026gt;2​时，称这些频率无法被有效放大\n高通滤波器的计算与其相似，电路设计上，RRR与电容CCC互换位置，不再赘述\n为了使滤波器在∣Au∣=∣Au(max)∣|A_u|=\\sqrt{|A_{u(max)}|}∣Au​∣=∣Au(max)​∣​处衰减更快，通常在输入端再接一块电容，构成二阶有源滤波电路\n正弦波发生器 自激振荡的条件： 幅度平衡： ∣AuF∣=ufui=1⇒有足够的反馈量\\begin{align}|A_uF|=\\frac{u_f}{u_{\\rm i}}=1\\Rightarrow有足够的反馈量\\end{align}∣Au​F∣=ui​uf​​=1⇒有足够的反馈量​​ 相位平衡：φA+φF=±2nπ⇒正反馈\\varphi_A+\\varphi_F=\\pm 2n\\pi\\Rightarrow正反馈φA​+φF​=±2nπ⇒正反馈 起振过程： 切换开关使其产生一系列含不同交流成分的交流量，通过选频网络选出特定交流电 uo\u0026lt;U,(U为稳定时的预期幅度)u_{\\rm o}\u0026lt;U,(U为稳定时的预期幅度)uo​\u0026lt;U,(U为稳定时的预期幅度)，故∣AuF∣\u0026gt;1|A_uF|\u0026gt;1∣Au​F∣\u0026gt;1，要引入正反馈 稳幅过程： 使∣AuF∣→1|A_uF|\\rightarrow1∣Au​F∣→1的过程 通过含非线性伏安特性曲线的元件，例如负温度系热敏电阻或二极管，使电路在起振时，反馈量略大于输入量；稳幅时RFR_FRF​电阻下降，反馈量等于输入量 直流稳压电源 过程 直流稳压过程包括：\n变压，降压 整流，使交流电变为直流电 滤波，减少直流电中的交流成分 稳压，进一步减少交流成分，且减少输出的直流电受之前部分电路的影响 整流电路 整流电路包括：半波、全波等\n评估整流电路的指数： 整流电压平均值UO(AV)U_{O(AV)}UO(AV)​：对一个周期的电压积分后除以它的周期 整流电流平均值IO(AV)I_{O(AV)}IO(AV)​：UO(AV)U_{O(AV)}UO(AV)​除以负载的阻值 脉动系数SSS：越低越好 选取二极管的指数： 流过每个二极管的电流平均值IDI_DID​：应大于IO(AV)I_{O(AV)}IO(AV)​除以导通状态的二极管的个数 每管承受的最高反向电压UDRMU_{DRM}UDRM​：应大于电压峰值除以截止状态的二极管的个数，最高反向电压往往是反向击穿电压的一半或三分之一 正弦波电压峰值UMAX=2U有效值U_{MAX}=\\sqrt2U_{有效值}UMAX​=2​U有效值​ 测量交流电时，直流表测量平均值，交流表测量有效值 半波整流：只留下正方向的波形，完全消除负方向的电流\n一个二极管就可以实现半波整流，其参数满足ID\u0026gt;IO(AV),UDRM\u0026gt;UMAXI_D\u0026gt;I_{O(AV)},U_{DRM}\u0026gt;U_{MAX}ID​\u0026gt;IO(AV)​,UDRM​\u0026gt;UMAX​\n其它参数：\nS=1.57,UO(AV)=0.45U有效值,IL有效值=IMAX2\\begin{align}S=1.57,U_{O(AV)}=0.45U_{有效值},I_{L有效值}=\\frac {I_{MAX}}2\\end{align}S=1.57,UO(AV)​=0.45U有效值​,IL有效值​=2IMAX​​​​桥式整流：含有完整的波形，因为始终有其中两个二极管导通，另外两个二极管截止\n二极管参数满足ID\u0026gt;0.5IO(AV),UDRM\u0026gt;UMAXI_D\u0026gt;0.5I_{O(AV)},U_{DRM}\u0026gt;U_{MAX}ID​\u0026gt;0.5IO(AV)​,UDRM​\u0026gt;UMAX​\n其它参数：\nS=0.67,UO(AV)=0.9U有效值,IL有效值=IMAX2\\begin{align}S=0.67,U_{O(AV)}=0.9U_{有效值},I_{L有效值}=\\frac{I_{MAX}}{\\sqrt2}\\end{align}S=0.67,UO(AV)​=0.9U有效值​,IL有效值​=2​IMAX​​​​滤波与稳压 直流稳压电路采用无源滤波器，使用电容、电感实现\n给负载并联电容，或串联电感，可有效减少交流成分\n稳压部分采用稳压二极管\n集成稳压电源：78XX78XX78XX代表输出大小为XXXXXX的正向电压；79XX79XX79XX代表输出大小为XXXXXX的反向电压\n前者三个端口：1−输入端、2−输出端、3−公共端1-输入端、2-输出端、3-公共端1−输入端、2−输出端、3−公共端\n后者三个端口：1−公共端、2、输出端、3−输入端1-公共端、2、输出端、3-输入端1−公共端、2、输出端、3−输入端\n","permalink":"https://echo.ishian.me/2024/05/04/analog-circuit/","summary":"整理模拟电路基础，涵盖电路分析方法、二极管三极管及放大电路等元件特性","title":"模拟电路入门"},{"content":"OVERTRUE OVERTRUE是最简单的图灵完备的处理器，即能够读取指令、分情况进行计算或跳转或转移数据，接下来从基础逻辑门开始搭建该架构\n基础逻辑电路 基础逻辑门 NAND与非门是最为基础的逻辑门，根据德·摩根定理，能够用它和若干个**NOT非门**搭建其它三个基础门\n它的真值表为010100111110\\begin{matrix}0\u0026amp;1\u0026amp;0\u0026amp;1\\\\0\u0026amp;0\u0026amp;1\u0026amp;1\\\\1\u0026amp;1\u0026amp;1\u0026amp;0\\end{matrix}001​101​011​110​，即非11\\begin{matrix}1\\\\1\\end{matrix}11​得111，否则得000，画法为\n通过它可以制造NOT，真值表为0110\\begin{matrix}0\u0026amp;1\\\\1\u0026amp;0\\end{matrix}01​10​，画法为\n对NAND门的输出取反，得到**AND与门**，逻辑为**11\\begin{matrix}1\\\\1\\end{matrix}11​得111，否则得000**，真值表为NAND输出按位取反，画法为\n对NAND门的输入取反，得到**OR或门**，逻辑为非00\\begin{matrix}0\\\\0\\end{matrix}00​得111，否则得000，真值表为NAND输出左右颠倒，画法为\n对OR门的输出取反(或对AND的输入取反)，得到**NOR或非门**，逻辑为**00\\begin{matrix}0\\\\0\\end{matrix}00​得111，否则得000**，真值表为OR输出按位取反，画法为\n逻辑门扩展 各个基础逻辑门互相组合，能得到所有24=162^4=1624=16种真值表，在这里扩展**XOR异或门与XNOR同或门**：\n对OR门与NAND门的输出AND，得到XOR，逻辑为输入不同得111，否则得000，真值表为010100110110\\begin{matrix}0\u0026amp;1\u0026amp;0\u0026amp;1\\\\0\u0026amp;0\u0026amp;1\u0026amp;1\\\\0\u0026amp;1\u0026amp;1\u0026amp;0\\end{matrix}000​101​011​110​，画法为 对XOR门的输出取反(或对NOR门与AND门的输出OR)，得到XNOR，逻辑为输入相同得111，否则得000，真值表为XOR输出按位取反，画法为 其它 通过OR与AND，可实现始终输出高/低电平的电路**ON与OFF**\n通过两个相同类型的基础逻辑门组合，可得到三路输入的对应逻辑门\n通过三个XOR门组合，可实现在四路输入奇数个信号时，输出111\n算术运算 二进制信号计数与二进制补码 通过三个XOR门组合，使得当四路输入奇数个信号时，接入代表1的二进制输出上 枚举四路输入，使得当四路输入222或333个信号时，接入代表2的二进制输出上 通过三个AND门组合，使得当四路输入全为111时，接入代表444的二进制输出上 总而言之，通过繁琐的排列组合，实现接受255255255路输入后，输出数值上最多表示255255255的单字节八位信号\n为了方便，依然将这个信号用一点表示，使用分线器解散这个点，使用集线器构建这个点\n二进制补码：将一个字节的最后一位视为−128-128−128后，即有按位取反加111后变为其相反数的特性\n半加器与全加器与八位加法器 半加器指对两路111位输入求和，并用两路输出本位和与进位：\n一个XOR门得到正确的本位和输出，即在1+0=11+0=11+0=1或0+1=10+1=10+1=1时输出111 一个AND门得到正确的进位输出，即在1+1=101+1=101+1=10时进位 全加器指对三路111位输入求和，并用两路输出本位和与进位：\n通过两个XOR门，当输入信号为奇数个时在本位和处输出111 对两路输入通过半加器，当进位为111时，或本位和为111且第三路输入为111时，在全加器进位处输出111 八位加法器接受一个一位输入用于继承上一个八位加法器的进位；接受两个字节输入表示两个参数；输出一个一位信号表示三个数的进位；输出一个字节信号表示除进位外的和：\n通过八个全加器与一个集线器即可得到八位加法器 附录中展现了高速八位加法器，能达到616161逻辑门、202020延迟 通过对第二个输入取相反数，实现减法\n数据选择 根据一个111位信号输入来选择两路输入是很有价值的，所以需要开关：\n当开关关闭时，既不输出高电平也不输出低电平，处于高阻态 两个开关即可实现数据选择，开关保证了在一路输出时不会受影响另一路影响 除了选择输入外，也可以用开关来选择输出端 数据存储 一位存储器 通过延迟线，能够将该刻的信号延迟到下一刻输出，实现存储器功能：\n接受一个代表写入开关的输入和代表待写入数据的输入，且总是输出当前存储的数据 当表示是否写入的输入为000时，选择延迟线的输出，否则选择待写入数据 将这个信号传递给延迟线后进入下一刻，则存储器能通过读取输入判断是否要覆写数据，并在覆写前将旧数据输出 寄存器 通过八个111位存储器，实现寄存器，它额外接受一个代表读取的输入并拥有一个额外的输出端，只有当其为111时，将该刻数据输出到这个输出端上\n计时器 想让一个处理器逐个读取指令，需要一个计时器：\n本身处于计时模式，每过一刻钟自增固定数值；接受一个代表启动擦写的输入和一个代表待写入数值的输入；始终输出当前时刻的值 通过ON与数据选择器即可实现步进为111的计时器 解码器 所谓解码器，即将所有输入的所有组合输出，例如三位解码器有八位输出，这使得处理器能够处理不同的指令\n通过解码器、开关与数据选择器，能够实现根据输入来选择不同运算或直接输出数据\n总结 OVERTRUE架构将一条指令看作复制、运算、条件判断、立即数的任意一种，通过解码能够简单地实现读取地址、区分运算等功能，在本架构中通过两位信号四种可能来区分它们，接下来介绍如何实现该架构\n在OVERTRUE架构中，能够操作的条件种类较少，主要是与000对比，或总是跳转，如果满足条件，将跳转到某一寄存器所指的地址\n这个架构虽然具备自行处理指令的功能，但指令之间是隔离的，即运算功能使用固定的两个寄存器，跳转功能只判断一个固定的寄存器与一个固定的数值，立即数只能传入一个固定的寄存器，都需要通过复制来处理数据，对编写程序是极不友好的，而在之后的LEG框架能够有效提升性能\n附录 基础逻辑门 NOT\nNOR\nOR\nAND\nXOR\n算数运算 一位半加器\n一位全加器\n高速八位加法器\n时序逻辑电路基本元件 数据选择器\n计时器\n三位解码器\n","permalink":"https://echo.ishian.me/2024/05/03/games-turingcomplete/","summary":"记录数电游戏图灵完备的通关笔记，从基础逻辑门逐步搭建处理器架构","title":"Turing Complete: 图灵完备，一款数电小游戏"},{"content":"图 图的基本概念 图由结点集合VVV，边集合EEE，映射关系φ\\varphiφ构成，其中φ:E→V×V/{V,V}\\varphi:E\\rightarrow V\\times V/\\{V,V\\}φ:E→V×V/{V,V}为由边到结点序偶/两元素集合的函数 当φ\\varphiφ映射到序偶时，称为有向图，反之为无向图 每条边关联的两个点相互点邻接，同一结点上的各条边相互边邻接 若eee关联的两点是同一结点，称为自回路/环 若φ(e1)=φ(e2)\\varphi(e_1)=\\varphi(e_2)φ(e1​)=φ(e2​)，称这两条边互为平行边，在有向图中需注意方向 图的分类(侧重点不同的领域定义可能有所不同)： 简单图：既没有自回路也没有平行边 多重边图：没有自回路，有平行边 伪图：有自回路和平行边 图的同构：含双射函数f:V→V′,g:E→E′f:V\\rightarrow V\u0026#x27;,g:E\\rightarrow E\u0026#x27;f:V→V′,g:E→E′，新的映射关系φ′\\varphi\u0026#x27;φ′，满足φ(e)={v1,v2}→φ′(g(e))={f(v1),f(v2)}\\varphi(e)=\\{v_1,v_2\\}\\rightarrow\\varphi\u0026#x27;(g(e))=\\{f(v_1),f(v_2)\\}φ(e)={v1​,v2​}→φ′(g(e))={f(v1​),f(v2​)} 结点的度数及相关结论： 所有结点度数之和d(G)d(G)d(G)等于两倍的边数 一定含偶数个奇度节点 ddd度正则图：所有结点度数为ddd度，若结点数为d+1d+1d+1，则称其为**d+1d+1d+1阶完全无向图**，记为kd+1k_{d+1}kd+1​ knk_nkn​为同阶图中边数最多的图，且 ∣E∣=n(n−1)2=Cn2\\begin{align}|E|=\\frac{n(n-1)}2=C_n^2\\end{align}∣E∣=2n(n−1)​=Cn2​​​ 子图的定义： 若子图的三个构成部分都包含于母图，称为子图 在子图基础上，若边、映射关系真包含于母图，称为真子图 在子图基础上，若结点集合和母图一致，称为生成子图，即对母图只删除边(或不删除) 导出子图：分为由边导出和由结点导出 路径、回路： 路径由若干结点及连接它们的边组成，若起点和终点重合，称其为回路 若一条路径/回路中边互不相同，称为简单路径/回路 若一条路径结点互不相同/回路除起点外结点互不相同，称为基本路径/回路(圈) 若简单回路包含图中所有边，称为欧拉回路；若基本回路包含图中所有点，称为哈密尔顿回路 基本路径一定是简单路径，反之不成立；基本回路也不一定是简单回路 nnn阶图中，基本路径长度小于等于n−1n-1n−1 连通性： 若两结点间有路径，则称它们可达，称最短路径为测地线 定义图的直径为最长路径的长度 无向图：若任意两结点可达，称GGG是连通的 有向图： 若其对应的无向图(也叫基础图)连通，称为弱连通 若任意两结点有单向路径，称为单向连通 若任意两结点互相可达，称为强连通 无向图连通分支：由可达关系(是一个等价类)划分出的等价类导出的子图为连通分支 每个连通分支都是一个连通子图 若通过删去边/点能使一个连通分支变为两个或以上，称其为割边/割点 e是割边⇔e不在任何简单回路上e是割边\\Leftrightarrow e不在任何简单回路上e是割边⇔e不在任何简单回路上 邻接矩阵：注意结点没有自回路，结点和自身不可达 邻接矩阵的转置：表示其逆图，所有边反向 邻接矩阵的幂AnA^nAn：aija_{ij}aij​表示由iii到jjj的nnn度路径长度 可达性矩阵PPP：是布尔矩阵，表示可达性 AATAA^TAAT：aija_{ij}aij​表示以i/ji/ji/j为起点，共同终于某些结点的边的个数 ATAA^TAATA：aija_{ij}aij​表示以某些结点为起点，终于i/ji/ji/j的边的个数 ","permalink":"https://echo.ishian.me/2024/04/29/discretemath-graph/","summary":"讲解图的基本概念、度数、路径回路、连通性及欧拉图与哈密尔顿图等图论基础","title":"离散数学: 图论基础"},{"content":"多元函数微分学 多元函数极限 判别方法 多元函数的极限考虑一组自变量同时趋近一组常量，体现在二元函数上，是一个二重极限\n任何方式趋近时极限都存在且相同，例如直线(y=kx)(y=kx)(y=kx)、曲线方式(y=x2、y=sin⁡x...)(y=x^2、y=\\sin x...)(y=x2、y=sinx...) 由具有不同变量的初等函数的四则运算复合而成的二元函数在定义域上连续，若在该点连续，则在该点极限等于函数值 δ−ε\\delta-\\varepsilonδ−ε语言定义： 对任意ε\u0026gt;0\\varepsilon\u0026gt;0ε\u0026gt;0，存在δ\u0026gt;0\\var\u0026gt;0δ\u0026gt;0，当{∣x−x0∣\u0026lt;δ∣y−y0∣\u0026lt;δ\\begin{cases}|x-x_0|\u0026lt;\\delta\\\\|y-y_0|\u0026lt;\\delta\\end{cases}{∣x−x0​∣\u0026lt;δ∣y−y0​∣\u0026lt;δ​且(x,y)≠(x0,y0)(x,y)\\ne(x_0,y_0)(x,y)=(x0​,y0​)时(去心邻域内)，有∣f(x,y)−A∣\u0026lt;ε|f(x,y)-A|\u0026lt;\\varepsilon∣f(x,y)−A∣\u0026lt;ε，则极限为AAA 这种语言通常通过放缩来证明二重极限存在，即找到**g(δ)g(\\delta)g(δ)，使其满足f(x,y)−A\u0026lt;g(δ)f(x,y)-A\u0026lt;g(\\delta)f(x,y)−A\u0026lt;g(δ)**，令ε=g(δ)\\varepsilon=g(\\delta)ε=g(δ)即可 常见二重极限 lim⁡(x,y)→(0,0)xyx2+y2=0\\begin{align}\u0026amp;\\lim_{(x,y)\\rightarrow(0,0)}\\frac{xy}{\\sqrt{x^2+y^2}}=0\\end{align}​(x,y)→(0,0)lim​x2+y2​xy​=0​​，幂次方、分子继续乘x、yx、yx、y的极限都为000 lim⁡(x,y)→(0,0)xyx2+y2\\begin{align}\u0026amp;\\lim_{(x,y)\\rightarrow(0,0)}\\frac{xy}{x^2+y^2}\\end{align}​(x,y)→(0,0)lim​x2+y2xy​​​不存在 lim⁡(x,y)→(0,0)xyx+y\\begin{align}\u0026amp;\\lim_{(x,y)\\rightarrow(0,0)}\\frac{xy}{x+y}\\end{align}​(x,y)→(0,0)lim​x+yxy​​​不存在 二元函数连续性 若 lim⁡(x,y)→(x0,y0)f(x,y)=f(x0,y0)\\begin{align}\u0026amp;\\lim_{(x,y)\\rightarrow(x_0,y_0)}f(x,y)=f(x_0,y_0)\\end{align}​(x,y)→(x0​,y0​)lim​f(x,y)=f(x0​,y0​)​​，称在该点连续 当二重极限不存在、或在该点无定义、或二重极限存在但与该点定义不等时，该点为间断点 f(x,y)f(x,y)f(x,y)在(x0,y0)(x_0,y_0)(x0​,y0​)处连续 ⇔lim⁡(x,y)→(0,0)Δz=0\\begin{align}\\Leftrightarrow\\lim_{(x,y)\\rightarrow(0,0)}\\Delta z=0\\end{align}⇔(x,y)→(0,0)lim​Δz=0​​ 多元连续函数符合有界性、介值定理 多元函数全微分 求偏导 将其它变量视为常量，将整个函数看作一元函数求导 与一元函数求导一样，导数间断点但用定义、导数连续点用求导法则 求某点的偏导，可先行代入其它变量的值 偏导函数的变量结构可化为与原函数一致，但实际求出一阶偏导后，往往会多出几种变量 含二阶连续偏导的二元函数的二阶混合偏导相等 求全微分 Δz=f(x+Δx,y+Δy)−f(x,y)(1).\\begin{align}\u0026amp;\\Delta z=f(x+\\Delta x,y+\\Delta y)-f(x,y)\u0026amp;(1).\\end{align}​Δz=f(x+Δx,y+Δy)−f(x,y)​(1).​​ 如果函数在(x0,y0)(x_0,y_0)(x0​,y0​)处可微，则Δz=dz+o(ρ)\\Delta z=dz+o(\\rho)Δz=dz+o(ρ)，且 dz=fx′dx+fy′dy(2).全微分公式\\begin{align}\u0026amp;dz=f_x\u0026#x27;dx+f_y\u0026#x27;dy\u0026amp;(2).全微分公式\\end{align}​dz=fx′​dx+fy′​dy​(2).全微分公式​​ 换句话说，要证明函数在该点可微，就是证明Δz−dz\\Delta z-dzΔz−dz是ρ\\rhoρ的高阶无穷小： lim⁡(x,y)→(0,0)o(ρ)ρ=0,其中o(ρ)=(1)−(2)=f(x0+x,y0+y)−f(x0,y0)−fx′(x0,y0)x−fy′(x0,y0)yρ=x2+y2\\begin{align}\u0026amp;\\lim_{(x,y)\\rightarrow(0,0)}\\frac{o(\\rho)}{\\rho}=0,其中o(\\rho)=(1)-(2)\\\\\u0026amp;=f(x_0+x,y_0+y)-f(x_0,y_0)-f_x\u0026#x27;(x_0,y_0)x-f_y\u0026#x27;(x_0,y_0)y\\\\\u0026amp;\\rho=\\sqrt{x^2+y^2}\\end{align}​(x,y)→(0,0)lim​ρo(ρ)​=0,其中o(ρ)=(1)−(2)=f(x0​+x,y0​+y)−f(x0​,y0​)−fx′​(x0​,y0​)x−fy′​(x0​,y0​)yρ=x2+y2​​​ 全微分的四则运算法则与一元函数相同 若已知全增量，容易得到偏导是否存在 复合函数全微分 假设对xxx求偏导，若所有外层函数在此点可微，最内层函数对xxx可偏导(或可导)，则符合： 找到所有含xxx的路径 分叉路用偏导符号，单路用微分符号 ∂f∂x=∂f∂u⋅∂u∂x+⋯\\begin{align}\u0026amp;\\frac{\\part f}{\\part x}=\\frac{\\part f}{\\part u}·\\frac{\\part u}{\\part x}+\\cdots\\end{align}​∂x∂f​=∂u∂f​⋅∂x∂u​+⋯​​，即相同路径求导相乘，不同路径导数相加 若符号重复，可改写为f(x,y,z)f(x,y,z)f(x,y,z)的形式，并用下标1,2,⋯1,2,\\cdots1,2,⋯指代偏导 一阶全微分形式不变性：复合函数一阶全微分仍可套用全微分公式 隐函数存在定理 对nnn元函数方程确定的n−1n-1n−1元隐函数，若在点P0P_0P0​处具有连续偏导、函数值为000、对变量zzz偏导不为000，则有唯一确定的隐函数z=f(其它n−1个变量)z=f(其它n-1个变量)z=f(其它n−1个变量) 该隐函数的偏导 ∂z∂i=−fifz\\begin{align}\\frac{\\part z}{\\part i}=-\\frac{f_i}{f_z}\\end{align}∂i∂z​=−fz​fi​​​​ 由方程组确定的隐函数：将方程转换为隐函数的全微分后，联立消去某变量 方向导数 定义： ∂f∂l=lim⁡ρ→0f(x+Δx,y+Δy)−f(x,y)ρ=lim⁡t→0+f(x+tcos⁡α,y+tcos⁡β)−f(x,y)t\\begin{align}\\frac{\\part f}{\\part l}=\\lim_{\\rho\\rightarrow0}\\frac{f(x+\\Delta x,y+\\Delta y)-f(x,y)}\\rho=\\lim_{t\\rightarrow0^+}\\frac{f(x+t\\cos\\alpha,y+t\\cos\\beta)-f(x,y)}t\\end{align}∂l∂f​=ρ→0lim​ρf(x+Δx,y+Δy)−f(x,y)​=t→0+lim​tf(x+tcosα,y+tcosβ)−f(x,y)​​​ 方向导数是沿某一射线方向切片上的导数，实质上是单侧极限 若在一点可微，则(以二元函数为例)： 在该点各个方向的方向导数存在，且满足： ∂f∂l=fx(x,y)cos⁡α+fy(x,y)cos⁡β\\begin{align}\\frac{\\part f}{\\part l}=f_x(x,y)\\cos\\alpha+f_y(x,y)\\cos\\beta\\end{align}∂l∂f​=fx​(x,y)cosα+fy​(x,y)cosβ​​ 向量(fx(x,y),fy(x,y))\\Big(f_x(x,y),f_y(x,y)\\Big)(fx​(x,y),fy​(x,y))称为梯度，记为∇f(x,y)\\grad f(x,y)∇f(x,y) 方向导数最大值为∣(fx(x,y),fy(x,y))∣\\bigg|\\Big(f_x(x,y),f_y(x,y)\\Big)\\bigg|​(fx​(x,y),fy​(x,y))​，即梯度方向的方向导数 一个点的梯度方向，就是等值线在该点的法线方向 条件关系图 graph LR A(偏导连续) --\u0026gt; B(可微) B --\u0026gt; F(方向偏导数都存在) F --\u0026gt; C(可偏导) B --\u0026gt; D(连续) F --\u0026gt; E(所有方向导数都存在) AA(高阶偏导连续) ---\u0026gt; BB(低阶偏导连续) --\u0026gt; CC(原函数可微)可偏导与连续无蕴涵关系，都是可微的必要条件\n方向偏导数指在某一直线方向切片上的偏导数存在\n空间几何 对空间曲面来说，由该曲面方程形成的三元函数的三个偏导所构成的向量n⃗\\vec nn就是在该点处，切平面的法向量，也即法线的方向向量 对以完整的参数方程形式给出的空间曲线来说，即{x=φ(t)y=ψ(t)z=ω(t)\\begin{cases}x=\\varphi(t)\\\\y=\\psi(t)\\\\z=\\omega(t)\\end{cases}⎩⎨⎧​x=φ(t)y=ψ(t)z=ω(t)​，其中φ(t),ψ(t),ω(t)\\varphi(t),\\psi(t),\\omega(t)φ(t),ψ(t),ω(t)导数存在且不全为000，则切线的方向向量就是它们对ttt的导数组成的向量，也即该点法平面的法向量 对不完整的参数方程，例如{y=φ(x)z=ω(x)\\begin{cases}y=\\varphi(x)\\\\z=\\omega(x)\\end{cases}{y=φ(x)z=ω(x)​，则可改写为完整的参数方程{x=xy=φ(x)z=ω(x)\\begin{cases}x=x\\\\y=\\varphi(x)\\\\z=\\omega(x)\\end{cases}⎩⎨⎧​x=xy=φ(x)z=ω(x)​，此时方向向量为(1,y′,z′)(1,y\u0026#x27;,z\u0026#x27;)(1,y′,z′) 对一般式空间曲线，由隐函数存在定理，可转化为上述情况，法平面方程由此可转化为行列式∣x−x0y−y0z−z0FxFyFzGxGyGz∣=0\\left|\\begin{matrix}x-x_0\u0026amp;y-y_0\u0026amp;z-z_0\\\\F_x\u0026amp;F_y\u0026amp;F_z\\\\G_x\u0026amp;G_y\u0026amp;G_z\\end{matrix}\\right|=0​x−x0​Fx​Gx​​y−y0​Fy​Gy​​z−z0​Fz​Gz​​​=0，此时法向量(即切线方向向量)为 (∂(F,G)∂(y,z),−∂(F,G)∂(x,z),∂(F,G)∂(x,y))\\begin{align}\\left(\\frac{\\part(F,G)}{\\part(y,z)},{\\bf -}\\frac{\\part(F,G)}{\\part(x,z)},\\frac{\\part(F,G)}{\\part(x,y)}\\right)\\end{align}(∂(y,z)∂(F,G)​,−∂(x,z)∂(F,G)​,∂(x,y)∂(F,G)​)​​，也就是两曲面法向量的叉乘积 无条件极值 二元函数的二阶连续偏导点为极值点的充分条件：一阶偏导为000，海森阵行列式大于000 二元函数海森阵：[FxxFxyFyxFyy]\\left[\\begin{matrix}F_{xx}\u0026amp;F_{xy}\\\\F_{yx}\u0026amp;F_{yy}\\end{matrix}\\right][Fxx​Fyx​​Fxy​Fyy​​]，由于二阶偏导连续时，Fxy=FyxF_{xy}=F_{yx}Fxy​=Fyx​，故此时不存在Fxx=0F_{xx}=0Fxx​=0而行列式大于000的情况 若行列式大于000，则是极值点，且{Fxx\u0026gt;0,极小值点Fxx\u0026lt;0,极大值点\\begin{cases}F_{xx}\u0026gt;0,\u0026amp;极小值点\\\\F_{xx}\u0026lt;0,\u0026amp;极大值点\\end{cases}{Fxx​\u0026gt;0,Fxx​\u0026lt;0,​极小值点极大值点​ 若行列式小于000，则不是极值点，而是鞍点 若行列式等于000，则都有可能，无法判断 多元函数F(x,y,⋯ ,z)F(x,y,\\cdots,z)F(x,y,⋯,z)的二阶连续偏导点为极值点：一阶偏导为000，考虑海森阵行列式： 海森阵[FxxFxy⋯FxzFyxFyy⋯Fyz⋮⋮⋮FzxFzy⋯Fzz]\\left[\\begin{matrix}F_{xx}\u0026amp;F_{xy}\u0026amp;\\cdots\u0026amp;F_{xz}\\\\F_{yx}\u0026amp;F_{yy}\u0026amp;\\cdots\u0026amp;F_{yz}\\\\\\vdots\u0026amp;\\vdots\u0026amp;\u0026amp;\\vdots\\\\F_{zx}\u0026amp;F_{zy}\u0026amp;\\cdots\u0026amp;F_{zz}\\end{matrix}\\right]​Fxx​Fyx​⋮Fzx​​Fxy​Fyy​⋮Fzy​​⋯⋯⋯​Fxz​Fyz​⋮Fzz​​​ 若为正定矩阵(特征值都大于000，或顺序主子式都大于000)：为极小值点 若为负定矩阵(特征值都小于000，或{奇数阶顺序主子式小于0偶数阶顺序主子式大于0\\begin{cases}奇数阶顺序主子式小于0\\\\偶数阶顺序主子式大于0\\end{cases}{奇数阶顺序主子式小于0偶数阶顺序主子式大于0​)：为极大值点 若为不定矩阵(特征值含大于、小于000)：不是极值点 若为半正定矩阵、半负定矩阵(特征值≥0\\ge0≥0、≤0\\le 0≤0)：无法判断 二元函数的不可导点也可能是极值点，但没有系统的方法可求解，只能根据图形判断 条件极值 在求多元函数某闭区域内的最值时，需要求出所有可疑的内部极值点及边界上的极值点，求边界极值时往往会附带条件，采用拉格朗日乘数法解决：\n求f(x,y,⋯ ,z)f(x,y,\\cdots,z)f(x,y,⋯,z)在φ(x,y,⋯ ,z)=0、⋯、ψ(x,y,⋯ ,z)=0\\varphi(x,y,\\cdots,z)=0、\\cdots、\\psi(x,y,\\cdots,z)=0φ(x,y,⋯,z)=0、⋯、ψ(x,y,⋯,z)=0条件约束下的极值 构造函数F(x,y,⋯ ,z,λ,⋯ ,u)=f+λφ+⋯+uψF(x,y,\\cdots,z,\\lambda,\\cdots,u)=f+\\lambda\\varphi+\\cdots+u\\psiF(x,y,⋯,z,λ,⋯,u)=f+λφ+⋯+uψ 令FFF的所有偏导等于000，解方程组得到条件极值点(x0,y0,⋯ ,z0)(x_0,y_0,\\cdots,z_0)(x0​,y0​,⋯,z0​)及对应的λ、⋯、u\\lambda、\\cdots、uλ、⋯、u 练习 简单判断 1.讨论f(x,y)={(x2+y2)ln⁡(x2+y2),x2+y2≠00,x2+y2=0在(0,0)处的连续性解:lim⁡(x,y)→(0,0)f(x,y)=y=ρcos⁡tx=ρsin⁡tlim⁡ρ→0ρ2ln⁡ρ2=lim⁡ρ→0ln⁡ρ21ρ2=0=f(0,0)\\begin{align}\u0026amp;1.讨论f(x,y)=\\begin{cases}(x^2+y^2)\\ln(x^2+y^2),\u0026amp;x^2+y^2\\ne0\\\\0,\u0026amp;x^2+y^2=0\\end{cases}在(0,0)处的连续性\\\\\u0026amp;解:\\lim_{(x,y)\\rightarrow(0,0)}f(x,y)\\xlongequal[y=\\rho\\cos t]{x=\\rho \\sin t}\\lim_{\\rho\\rightarrow0}\\rho^2\\ln\\rho^2=\\lim_{\\rho\\rightarrow0}\\frac{\\ln\\rho^2}{\\frac1{\\rho^2}}=0=f(0,0)\\end{align}​1.讨论f(x,y)={(x2+y2)ln(x2+y2),0,​x2+y2=0x2+y2=0​在(0,0)处的连续性解:(x,y)→(0,0)lim​f(x,y)x=ρsinty=ρcost​ρ→0lim​ρ2lnρ2=ρ→0lim​ρ21​lnρ2​=0=f(0,0)​​ 2.讨论lim⁡(x,y)→(0,0)xyx+y是否存在.沿y=kx,原极限=0;沿y=x2−x,原极限=−1,故不存在\\begin{align}\u0026amp;2.讨论\\lim_{(x,y)\\rightarrow(0,0)}\\frac{xy}{x+y}是否存在.\\\\\u0026amp;沿y=kx,原极限=0;沿y=x^2-x,原极限=-1,故不存在\\end{align}​2.讨论(x,y)→(0,0)lim​x+yxy​是否存在.沿y=kx,原极限=0;沿y=x2−x,原极限=−1,故不存在​​ 3.讨论lim⁡(x,y)→(0,0)xy2sin⁡kyx2+y4是否存在(k≠0).0≤∣xy2sin⁡kyx2+y4∣≤∣kxy3x2+y4∣≤∣12(x2+y4)x2+y4ky∣=12∣ky∣=0\\begin{align}\u0026amp;3.讨论\\lim_{(x,y)\\rightarrow(0,0)}\\frac{xy^2\\sin ky}{x^2+y^4}是否存在(k\\ne0).\\\\\u0026amp;0\\le \\left|\\frac{xy^2\\sin ky}{x^2+y^4}\\right|\\le\\left|\\frac{kxy^3}{x^2+y^4}\\right|\\le\\left|\\frac{\\frac12(x^2+y^4)}{x^2+y^4}ky\\right|=\\frac12|ky|=0\\end{align}​3.讨论(x,y)→(0,0)lim​x2+y4xy2sinky​是否存在(k=0).0≤​x2+y4xy2sinky​​≤​x2+y4kxy3​​≤​x2+y421​(x2+y4)​ky​=21​∣ky∣=0​​ 4.f(x,y)={1xysin⁡x2y,xy≠00,xy=0,则fx′(0,1)的值为?分段点,定义求导fx′(0,1)=lim⁡x→01xsin⁡x2x−0=1\\begin{align}\u0026amp;4.f(x,y)=\\begin{cases}\\frac1{xy}\\sin x^2y,\u0026amp;xy\\ne0\\\\0,\u0026amp;xy=0\\end{cases},则f_x\u0026#x27;(0,1)的值为?\\\\\u0026amp;分段点,定义求导f_x\u0026#x27;(0,1)=\\lim_{x\\rightarrow0}\\frac{\\frac1x\\sin x^2}{x-0}=1\\end{align}​4.f(x,y)={xy1​sinx2y,0,​xy=0xy=0​,则fx′​(0,1)的值为?分段点,定义求导fx′​(0,1)=x→0lim​x−0x1​sinx2​=1​​ 5.f(x,y)=ex2+y4在(0,0)处的偏导.在看不出是否分段时,用定义求导一定正确fx(0,0)=lim⁡x→0ex2−1x−0=lim⁡x→0∣x∣x,不存在fy(0,0)=lim⁡y→0ey2−1y−0=lim⁡y→0y=0\\begin{align}\u0026amp;5.f(x,y)=e^{\\sqrt{x^2+y^4}}在(0,0)处的偏导.\\\\\u0026amp;在看不出是否分段时,用定义求导一定正确\\\\\u0026amp;f_x(0,0)=\\lim_{x\\rightarrow0}\\frac{e^{\\sqrt{x^2}}-1}{x-0}=\\lim_{x\\rightarrow0}\\frac{|x|}x,不存在\\\\\u0026amp;f_y(0,0)=\\lim_{y\\rightarrow0}\\frac{e^{y^2}-1}{y-0}=\\lim_{y\\rightarrow0}y=0\\end{align}​5.f(x,y)=ex2+y4​在(0,0)处的偏导.在看不出是否分段时,用定义求导一定正确fx​(0,0)=x→0lim​x−0ex2​−1​=x→0lim​x∣x∣​,不存在fy​(0,0)=y→0lim​y−0ey2−1​=y→0lim​y=0​​ 6.证明f(x,y)在(0,0)处可微的一个充分条件为lim⁡(x,y)→(0,0)f(x,y)−f(0,0)x2+y2=0证:由条件得lim⁡x→0f(x,0)−f(0,0)∣x∣=0,两边极限都等于0,故fx(0,0)=0同理fy(0,0)=0,考虑lim⁡(x,y)→(0,0)Δf−fx(0,0)x−fy(0,0)yx2+y2=lim⁡(x,y)→(0,0)f(x,y)−f(0,0)x2+y2=0故Δf(0,0)=fx(0,0)dx+fy(0,0)dy+o(x2+y2),故可微\\begin{align}\u0026amp;6.证明f(x,y)在(0,0)处可微的一个充分条件为\\lim_{(x,y)\\rightarrow(0,0)}\\frac{f(x,y)-f(0,0)}{\\sqrt{x^2+y^2}}=0\\\\\u0026amp;证:由条件得\\lim_{x\\rightarrow0}\\frac{f(x,0)-f(0,0)}{|x|}=0,两边极限都等于0,故f_x(0,0)=0\\\\\u0026amp;同理f_y(0,0)=0,\\\\\u0026amp;考虑\\lim_{(x,y)\\rightarrow(0,0)}\\frac{\\Delta f-f_x(0,0)x-f_y(0,0)y}{\\sqrt{x^2+y^2}}=\\lim_{(x,y)\\rightarrow(0,0)}\\frac{f(x,y)-f(0,0)}{\\sqrt{x^2+y^2}}=0\\\\\u0026amp;故\\Delta f(0,0)=f_x(0,0)dx+f_y(0,0)dy+o(\\sqrt{x^2+y^2}),故可微\\end{align}​6.证明f(x,y)在(0,0)处可微的一个充分条件为(x,y)→(0,0)lim​x2+y2​f(x,y)−f(0,0)​=0证:由条件得x→0lim​∣x∣f(x,0)−f(0,0)​=0,两边极限都等于0,故fx​(0,0)=0同理fy​(0,0)=0,考虑(x,y)→(0,0)lim​x2+y2​Δf−fx​(0,0)x−fy​(0,0)y​=(x,y)→(0,0)lim​x2+y2​f(x,y)−f(0,0)​=0故Δf(0,0)=fx​(0,0)dx+fy​(0,0)dy+o(x2+y2​),故可微​​ 7.证明f(x,y)在(0,0)处可微的一个充分条件为lim⁡(x,y)→(0,0)f(x,y)−f(0,0)+2x−yx2+y2=0与前者不同在于求偏导的过程.fx(0,0)=lim⁡x→0(f(x,0)−f(0,0)+2x∣x∣⋅∣x∣x−2)=−2同理fy(0,0)=1,其余过程相同\\begin{align}\u0026amp;7.证明f(x,y)在(0,0)处可微的一个充分条件为\\lim_{(x,y)\\rightarrow(0,0)}\\frac{f(x,y)-f(0,0)+2x-y}{\\sqrt{x^2+y^2}}=0\\\\\u0026amp;与前者不同在于求偏导的过程.\\\\\u0026amp;f_x(0,0)=\\lim_{x\\rightarrow0}(\\frac{f(x,0)-f(0,0)+2x}{|x|}·\\frac{|x|}{x}-2)=-2\\\\\u0026amp;同理f_y(0,0)=1,其余过程相同\\end{align}​7.证明f(x,y)在(0,0)处可微的一个充分条件为(x,y)→(0,0)lim​x2+y2​f(x,y)−f(0,0)+2x−y​=0与前者不同在于求偏导的过程.fx​(0,0)=x→0lim​(∣x∣f(x,0)−f(0,0)+2x​⋅x∣x∣​−2)=−2同理fy​(0,0)=1,其余过程相同​​ 8.xy−zln⁡y+exz=1在(0,1,1)的一个邻域内可确定哪几个隐函数?代入F(0,1,1)=0,看偏导求导:Fx=y+exzz;Fy=x−zy;Fz=−ln⁡y+exzx代入Fx(0,1,1)=2;Fy(0,1,1)=−1;Fz(0,1,1)=0故可确定唯一的x(y,z)和y(x,z)\\begin{align}\u0026amp;8.xy-z\\ln y+e^{xz}=1在(0,1,1)的一个邻域内可确定哪几个隐函数?\\\\\u0026amp;代入F(0,1,1)=0,看偏导\\\\\u0026amp;求导:F_x=y+e^{xz}z;F_y=x-\\frac zy;F_z=-\\ln y+e^{xz}x\\\\\u0026amp;代入F_x(0,1,1)=2;F_y(0,1,1)=-1;F_z(0,1,1)=0\\\\\u0026amp;故可确定唯一的x(y,z)和y(x,z)\\end{align}​8.xy−zlny+exz=1在(0,1,1)的一个邻域内可确定哪几个隐函数?代入F(0,1,1)=0,看偏导求导:Fx​=y+exzz;Fy​=x−yz​;Fz​=−lny+exzx代入Fx​(0,1,1)=2;Fy​(0,1,1)=−1;Fz​(0,1,1)=0故可确定唯一的x(y,z)和y(x,z)​​ 9.求函数z=1−(x2a2+y2b2)在点(a2,b2)处沿曲线x2a2+y2b2=1在这点的内法线方向的方向导数.解:该曲线可确定唯一的y=y(x),补充x=x,即为参数方程故切向量为(1,−fxfy∣(a2,b2))=(1,−ba)两个相反方向的法向量为±(b,a),内法线方向为(−b,−a)故方向导数为zxcos⁡α+zycos⁡β=2(a2+b2)ab\\begin{align}\u0026amp;9.求函数z=1-\\left(\\frac{x^2}{a^2}+\\frac{y^2}{b^2}\\right)在点\\left(\\frac a{\\sqrt2},\\frac b{\\sqrt2}\\right)处\\\\\u0026amp;沿曲线\\frac{x^2}{a^2}+\\frac{y^2}{b^2}=1在这点的内法线方向的方向导数.\\\\\u0026amp;解:该曲线可确定唯一的y=y(x),补充x=x,即为参数方程\\\\\u0026amp;故切向量为\\left(1,-\\frac{f_x}{f_y}\\Bigg|_{\\left(\\frac a{\\sqrt2},\\frac b{\\sqrt2}\\right)}\\right)=(1,-\\frac ba)\\\\\u0026amp;两个相反方向的法向量为\\pm(b,a),内法线方向为(-b,-a)\\\\\u0026amp;故方向导数为z_x\\cos\\alpha+z_y\\cos\\beta=\\frac{\\sqrt{2(a^2+b^2)}}{ab}\\end{align}​9.求函数z=1−(a2x2​+b2y2​)在点(2​a​,2​b​)处沿曲线a2x2​+b2y2​=1在这点的内法线方向的方向导数.解:该曲线可确定唯一的y=y(x),补充x=x,即为参数方程故切向量为​1,−fy​fx​​​(2​a​,2​b​)​​=(1,−ab​)两个相反方向的法向量为±(b,a),内法线方向为(−b,−a)故方向导数为zx​cosα+zy​cosβ=ab2(a2+b2)​​​​ 10.若f(x,y)在有界闭区域内有二阶连续偏导,且∂2f∂x∂y≠0,∂2f∂x2+∂2f∂y2=0,求其最大、最小值的位置令A=∂2f∂x2,B=∂2f∂x∂y,C=∂2f∂y2,易得AC−B2\u0026lt;0,内部没有极值点故最大、最小值只能在边界上\\begin{align}\u0026amp;10.若f(x,y)在有界闭区域内有二阶连续偏导,且\\frac{\\part^2f}{\\part x\\part y}\\ne0,\\frac{\\part^2f}{\\part x^2}+\\frac{\\part^2f}{\\part y^2}=0,求其最大、最小值的位置\\\\\u0026amp;令A=\\frac{\\part^2f}{\\part x^2},B=\\frac{\\part^2f}{\\part x\\part y},C=\\frac{\\part^2f}{\\part y^2},易得AC-B^2\u0026lt;0,内部没有极值点\\\\\u0026amp;故最大、最小值只能在边界上\\end{align}​10.若f(x,y)在有界闭区域内有二阶连续偏导,且∂x∂y∂2f​=0,∂x2∂2f​+∂y2∂2f​=0,求其最大、最小值的位置令A=∂x2∂2f​,B=∂x∂y∂2f​,C=∂y2∂2f​,易得AC−B2\u0026lt;0,内部没有极值点故最大、最小值只能在边界上​​ 11.设函数f(x,y)在(0,0)处连续,且lim⁡(x,y)→(0,0)f(x,y)1−cos⁡x2+y2=−2,则在该点是否为极值?由1−cos⁡x2+y2∼x2+y22,易得f(x,y)是−(x2+y2)的等价无穷小即lim⁡(x,y)→(0,0)f(x,y)=0,连续⇒f(0,0)=0fx(0,0)=lim⁡x→0f(x,0)−0x=0,同理fy(0,0)=0,是可疑极值点f(x,y)=−(x2+y2)(1+α),其中lim⁡(x,y)→(0,0)α=0,故f(x,y)在邻域内\u0026lt;0故是极值点,且为极大值点\\begin{align}\u0026amp;11.设函数f(x,y)在(0,0)处连续,且\\lim_{(x,y)\\rightarrow(0,0)}\\frac{f(x,y)}{1-\\cos{\\sqrt{x^2+y^2}}}=-2,则在该点是否为极值?\\\\\u0026amp;由1-\\cos\\sqrt{x^2+y^2}\\sim\\frac{x^2+y^2}2,易得f(x,y)是-(x^2+y^2)的等价无穷小\\\\\u0026amp;即\\lim_{(x,y)\\rightarrow(0,0)}f(x,y)=0,连续\\Rightarrow f(0,0)=0\\\\\u0026amp;f_x(0,0)=\\lim_{x\\rightarrow0}\\frac{f(x,0)-0}x=0,同理f_y(0,0)=0,是可疑极值点\\\\\u0026amp;f(x,y)=-(x^2+y^2)(1+\\alpha),其中\\lim_{(x,y)\\rightarrow(0,0)}\\alpha=0,故f(x,y)在邻域内\u0026lt;0\\\\\u0026amp;故是极值点,且为极大值点\\end{align}​11.设函数f(x,y)在(0,0)处连续,且(x,y)→(0,0)lim​1−cosx2+y2​f(x,y)​=−2,则在该点是否为极值?由1−cosx2+y2​∼2x2+y2​,易得f(x,y)是−(x2+y2)的等价无穷小即(x,y)→(0,0)lim​f(x,y)=0,连续⇒f(0,0)=0fx​(0,0)=x→0lim​xf(x,0)−0​=0,同理fy​(0,0)=0,是可疑极值点f(x,y)=−(x2+y2)(1+α),其中(x,y)→(0,0)lim​α=0,故f(x,y)在邻域内\u0026lt;0故是极值点,且为极大值点​​ 12.设f(x,y)在(0,0)某邻域内连续,且lim⁡(x,y)→(0,0)f(x,y)−xy(x2+y2)2=1,证明(0,0)不是f的极值点由x2+y2→0,得f(x,y)−xy→0,得lim⁡(x,y)→(0,0)f(x,y)=0,由连续得f(0,0)=0[举特例说明]:再由条件得lim⁡x→0f(x,0)−0x4=1,即在y=0路径上为极小值点在y=−x路径上,lim⁡x→0f(x,−x)+x24x4=1,∴0\u0026gt;lim⁡x→0f(x,−x)\u0026gt;−x2,是极大值点故不是极值点[不通过路径说明]:f(x,y)xy+(x2+y2)2=1+α,其中lim⁡(x,y)→(0,0)α=0即f(x,y)=xy+(1+α)(x2+y2)2故f(x,y)符号取决于xy,当x、y异号时为极大值点,当x、y同号时为极小值点\\begin{align}\u0026amp;12.设f(x,y)在(0,0)某邻域内连续,且\\lim_{(x,y)\\rightarrow(0,0)}\\frac{f(x,y)-xy}{(x^2+y^2)^2}=1,证明(0,0)不是f的极值点\\\\\u0026amp;由x^2+y^2\\rightarrow0,得f(x,y)-xy\\rightarrow0,得\\lim_{(x,y)\\rightarrow(0,0)}f(x,y)=0,由连续得f(0,0)=0\\\\\u0026amp;[举特例说明]:再由条件得\\lim_{x\\rightarrow0}\\frac{f(x,0)-0}{x^4}=1,即在y=0路径上为极小值点\\\\\u0026amp;在y=-x路径上,\\lim_{x\\rightarrow0}\\frac{f(x,-x)+x^2}{4x^4}=1,\\therefore0\u0026gt;\\lim_{x\\rightarrow0}f(x,-x)\u0026gt;-x^2,是极大值点\\\\\u0026amp;故不是极值点\\\\\u0026amp;[不通过路径说明]:\\frac{f(x,y)}{xy+(x^2+y^2)^2}=1+\\alpha,其中\\lim_{(x,y)\\rightarrow(0,0)}\\alpha=0\\\\\u0026amp;即f(x,y)=xy+(1+\\alpha)(x^2+y^2)^2\\\\\u0026amp;故f(x,y)符号取决于xy,当x、y异号时为极大值点,当x、y同号时为极小值点\\end{align}​12.设f(x,y)在(0,0)某邻域内连续,且(x,y)→(0,0)lim​(x2+y2)2f(x,y)−xy​=1,证明(0,0)不是f的极值点由x2+y2→0,得f(x,y)−xy→0,得(x,y)→(0,0)lim​f(x,y)=0,由连续得f(0,0)=0[举特例说明]:再由条件得x→0lim​x4f(x,0)−0​=1,即在y=0路径上为极小值点在y=−x路径上,x→0lim​4x4f(x,−x)+x2​=1,∴0\u0026gt;x→0lim​f(x,−x)\u0026gt;−x2,是极大值点故不是极值点[不通过路径说明]:xy+(x2+y2)2f(x,y)​=1+α,其中(x,y)→(0,0)lim​α=0即f(x,y)=xy+(1+α)(x2+y2)2故f(x,y)符号取决于xy,当x、y异号时为极大值点,当x、y同号时为极小值点​​大题练习 1.通过{x=euy=ev,变换2x2∂2z∂x2+xy∂2z∂x∂y+y2∂2z∂y2=0∂z∂u=∂z∂xeu; ∂z∂v=∂z∂yev∂2z∂u2=eu(∂z∂x+∂2z∂x2eu),对v二阶偏导、混合偏导同理最终答案:2∂2z∂u2+∂2z∂u∂v+∂2z∂v2−2∂z∂u−∂z∂v=0\\begin{align}\u0026amp;1.通过\\begin{cases}x=e^u\\\\y=e^v\\end{cases},变换2x^2\\frac{\\part^2z}{\\part x^2}+xy\\frac{\\part^2z}{\\part x\\part y}+y^2\\frac{\\part^2z}{\\part y^2}=0\\\\\u0026amp;\\frac{\\part z}{\\part u}=\\frac{\\part z}{\\part x}e^u;\\ \\ \\frac{\\part z}{\\part v}=\\frac{\\part z}{\\part y}e^v\\\\\u0026amp;\\frac{\\part^2z}{\\part u^2}=e^u\\left(\\frac{\\part z}{\\part x}+\\frac{\\part^2z}{\\part x^2}e^u\\right),对v二阶偏导、混合偏导同理\\\\\u0026amp;最终答案:2\\frac{\\part^2z}{\\part u^2}+\\frac{\\part^2z}{\\part u\\part v}+\\frac{\\part^2z}{\\part v^2}-2\\frac{\\part z}{\\part u}-\\frac{\\part z}{\\part v}=0\\end{align}​1.通过{x=euy=ev​,变换2x2∂x2∂2z​+xy∂x∂y∂2z​+y2∂y2∂2z​=0∂u∂z​=∂x∂z​eu; ∂v∂z​=∂y∂z​ev∂u2∂2z​=eu(∂x∂z​+∂x2∂2z​eu),对v二阶偏导、混合偏导同理最终答案:2∂u2∂2z​+∂u∂v∂2z​+∂v2∂2z​−2∂u∂z​−∂v∂z​=0​​ 2.讨论f(x,y)={x2y2(x2+y2)32,x2+y2≠00,x2+y2=0在(0,0)处是否连续、偏导数存在性、以及是否可微1).0≤lim⁡(x,y)→(0,0)f(x,y)≤lim⁡(x,y)→(0,0)x2y2x2+y2=0,故连续2).分段函数用定义,fx(0,0)=lim⁡x→0f(x,0)−f(0,0)x−0=lim⁡x→0xx3⋅0=0fy(0,0)=lim⁡y→0f(0,y)−f(0,0)y−0=0,偏导存在3).lim⁡(x,y)→(0,0)x2y2(x2+y2)32−0−0x−0yρ=lim⁡(x,y)→(0,0)x2y2(x2+y2)2=lim⁡(x,y)→(0,0)(xyx2+y2)2不存在,故不可微\\begin{align}\u0026amp;2.讨论f(x,y)=\\begin{cases}\\large\\frac{x^2y^2}{(x^2+y^2)^{\\frac32}},\u0026amp;x^2+y^2\\ne0\\\\0,\u0026amp;x^2+y^2=0\\end{cases}在(0,0)处是否连续、偏导数存在性、以及是否可微\\\\\u0026amp;1).0\\le\\lim_{(x,y)\\rightarrow(0,0)}f(x,y)\\le\\lim_{(x,y)\\rightarrow(0,0)}\\frac{x^2y^2}{x^2+y^2}=0,故连续\\\\\u0026amp;2).分段函数用定义,f_x(0,0)=\\lim_{x\\rightarrow0}\\frac{f(x,0)-f(0,0)}{x-0}=\\lim_{x\\rightarrow0}\\frac{x}{x^3}·0=0\\\\\u0026amp;f_y(0,0)=\\lim_{y\\rightarrow0}\\frac{f(0,y)-f(0,0)}{y-0}=0,偏导存在\\\\\u0026amp;3).\\lim_{(x,y)\\rightarrow(0,0)}\\frac{\\frac{x^2y^2}{(x^2+y^2)^{\\frac32}}-0-0x-0y}{\\rho}=\\lim_{(x,y)\\rightarrow(0,0)}\\frac{x^2y^2}{(x^2+y^2)^2}=\\lim_{(x,y)\\rightarrow(0,0)}\\left(\\frac{xy}{x^2+y^2}\\right)^2不存在,\\\\\u0026amp;故不可微\\end{align}​2.讨论f(x,y)=⎩⎨⎧​(x2+y2)23​x2y2​,0,​x2+y2=0x2+y2=0​在(0,0)处是否连续、偏导数存在性、以及是否可微1).0≤(x,y)→(0,0)lim​f(x,y)≤(x,y)→(0,0)lim​x2+y2x2y2​=0,故连续2).分段函数用定义,fx​(0,0)=x→0lim​x−0f(x,0)−f(0,0)​=x→0lim​x3x​⋅0=0fy​(0,0)=y→0lim​y−0f(0,y)−f(0,0)​=0,偏导存在3).(x,y)→(0,0)lim​ρ(x2+y2)23​x2y2​−0−0x−0y​=(x,y)→(0,0)lim​(x2+y2)2x2y2​=(x,y)→(0,0)lim​(x2+y2xy​)2不存在,故不可微​​","permalink":"https://echo.ishian.me/2024/04/23/calculus-multivar-diff/","summary":"讲解多元函数的二重极限、连续性、偏导与全微分及其应用","title":"微积分: 多元函数微分"},{"content":"集合论 约定 小写字母表示元素，大写字母表示集合 元素可指代变元、集合、元组等任何离散结构 集合基本概念 集合表示 离散结构大厦的地基！\n集合是若干元素的无序聚集，且元素不重复 集合构造法：V={x ∣ P(x),Q(x),⋯ }V=\\{x\\ |\\ P(x),Q(x),\\cdots\\}V={x ∣ P(x),Q(x),⋯}，其中谓词P,QP,QP,Q表示xxx的性质 属于：x∈Vx\\in Vx∈V表示一个元素属于一个集合，在构造法限定元素所属父集时可写在竖线左方 包含关系： V1⊆V2V_1\\subseteq V_2V1​⊆V2​表示集合**V1V_1V1​包含于V2V_2V2​，或V2V_2V2​包含V1V_1V1​，称V1V_1V1​是V2V_2V2​的子集**，与V2⊇V1V_2\\supseteq V_1V2​⊇V1​等价 逻辑表示：V1⊆V2⇔∀x(x∈V1→x∈V2)V_1\\subseteq V_2\\Leftrightarrow\\forall x(x\\in V_1\\rightarrow x\\in V_2)V1​⊆V2​⇔∀x(x∈V1​→x∈V2​) 所有集合都包含空集和它本身 包含是严格区分离散结构的，变元即变元，集合即集合，例如{1}⊈{{1}}\\{1\\}\\nsubseteq\\{\\{1\\}\\}{1}⊈{{1}} 相等： 若V1⊆V2V_1\\subseteq V_2V1​⊆V2​且V2⊆V1V_2\\subseteq V_1V2​⊆V1​，称V1=V2V_1=V_2V1​=V2​ 逻辑表示：V1=V2⇔∀x(x∈V1↔x∈V2)V_1=V_2\\Leftrightarrow\\forall x(x\\in V_1\\leftrightarrow x\\in V_2)V1​=V2​⇔∀x(x∈V1​↔x∈V2​) 基数：∣V∣|V|∣V∣表示集合的基数，即集合中元素的个数 常用集合约定记号： NNN：所有自然数的集合 ZZZ：所有整数的集合 Z+Z^+Z+：所有正整数的集合，和{x∈N ∣ x≠0}\\{x\\in N\\ |\\ x\\ne0\\}{x∈N ∣ x=0}相等 QQQ：{p/q ∣ p∈Z∧q∈Z∧q≠0}\\{p/q\\ |\\ p\\in Z\\wedge q\\in Z\\wedge q\\ne0\\}{p/q ∣ p∈Z∧q∈Z∧q=0}，即所有有理数的集合 RRR：所有实数的集合 R+R^+R+：所有正实数的集合 CCC：所有复数的集合 集合分类及特殊集合 元素个数分类：有限集、无限集 空集：∅\\varnothing∅，表示不含任何元素的集合 空集不显式写在集合内，但若出现在集合内，证明是一个结构是集合的元素 区分：空集单独作为集合写作∅\\varnothing∅，作为其它集合的一个元素写作{...,∅,...}\\{...,\\varnothing,...\\}{...,∅,...} 逻辑表示：{x ∣ P(x)∧¬P(x)}\\{x\\ |\\ P(x)\\wedge\\neg P(x)\\}{x ∣ P(x)∧¬P(x)} 幂集：P(V)\\mathcal P(V)P(V)表示集合VVV的幂集，是VVV所有子集的集合 ∣P(V)∣=2∣V∣|\\mathcal P(V)|=2^{|V|}∣P(V)∣=2∣V∣，若一个集合每取一次幂集，基数就取一次222的幂 所有集合一定包含空集和本身，而∅=∅\\varnothing=\\varnothing∅=∅，故P(∅)={∅}\\mathcal P(\\varnothing)=\\{\\varnothing\\}P(∅)={∅} 空集作为元素的边缘情况：P(P(∅))=P({∅})={∅,{∅}}\\mathcal P(\\mathcal P(\\varnothing))=\\mathcal P(\\{\\varnothing\\})=\\{\\varnothing,\\{\\varnothing\\}\\}P(P(∅))=P({∅})={∅,{∅}} 集合运算 集合运算的结果也是集合 并集：V1∪V2={x ∣ x∈V1∨x∈V2}V_1\\cup V_2=\\{x\\ |\\ x\\in V_1\\vee x\\in V_2\\}V1​∪V2​={x ∣ x∈V1​∨x∈V2​} 交集：V1∩V2={x ∣ x∈V1∧x∈V2}V_1\\cap V_2=\\{x\\ |\\ x\\in V_1\\wedge x\\in V_2\\}V1​∩V2​={x ∣ x∈V1​∧x∈V2​}，V1∩V2=∅⇔V1和V2不相交V_1\\cap V_2=\\varnothing\\Leftrightarrow V_1和V_2不相交V1​∩V2​=∅⇔V1​和V2​不相交 相对补集：V1−V2={x ∣ x∈V1∧x∉V2}V_1-V_2=\\{x\\ |\\ x\\in V_1\\wedge x\\notin V_2\\}V1​−V2​={x ∣ x∈V1​∧x∈/V2​}，称为V2V_2V2​相对于V1V_1V1​的补集，也称V1V_1V1​和V2V_2V2​的差集 对称差集：V1⊕V2=(V1−V2)∪(V2−V1)V_1\\oplus V_2=(V_1-V_2)\\cup(V_2-V_1)V1​⊕V2​=(V1​−V2​)∪(V2​−V1​)，即V1∪V2−V1∩V2V_1\\cup V_2-V_1\\cap V_2V1​∪V2​−V1​∩V2​ 绝对补集： 通常假定所有集合都包含于一个称为全集UUU的大集合，通常所说VVV的补集就是VVV相对于UUU的补集，记为∼V\\sim V∼V或Vˉ\\bar{V}Vˉ x∈Vˉ⇔x∉Vx\\in\\bar V\\Leftrightarrow x\\notin Vx∈Vˉ⇔x∈/V 集合的并集、交集运算满足布尔和、布尔积的各种恒等式，其中空集∅\\varnothing∅对应FFF，全集UUU对应TTT 恒包含及补集恒等式 V1∩V2⊆ V1V2⊆V1∪V2V_1\\cap V_2\\subseteq\\ \\begin{matrix}V_1\\\\V_2\\end{matrix}\\subseteq V_1\\cup V_2V1​∩V2​⊆ V1​V2​​⊆V1​∪V2​ V1−V2=V1∩V2ˉ=V1−V1∩V2⊆V1V_1-V_2=V_1\\cap\\bar {V_2}=V_1-V_1\\cap V_2\\subseteq V_1V1​−V2​=V1​∩V2​ˉ​=V1​−V1​∩V2​⊆V1​，相对补集满足吸收律，且最终包含于前集合 V1∩V2−V1∩V3=V1∩(V2−V3)V_1\\cap V_2-V_1\\cap V_3=V_1\\cap(V_2-V_3)V1​∩V2​−V1​∩V3​=V1​∩(V2​−V3​)，即相对补集和交集满足分配律 V1∪V2−V1∪V3⊆V1∪(V2−V3)V_1\\cup V_2-V_1\\cup V_3\\subseteq V_1\\cup(V_2-V_3)V1​∪V2​−V1​∪V3​⊆V1​∪(V2​−V3​) V1⊕V1=∅V_1\\oplus V_1=\\varnothingV1​⊕V1​=∅，自身对称补集为空集 V1⊆V2⇔V1−V2=∅V_1\\subseteq V_2\\Leftrightarrow V_1-V_2=\\varnothingV1​⊆V2​⇔V1​−V2​=∅ 二元关系 元组 单纯的集合结构聚集的元素是无序的，无法表示元素的聚集顺序，故通过元组来反映次序 元组表示：用圆括号或尖括号括起具有固定排列顺序的元素，称为一个元组 二元组：含有两个元素的元组，也称序偶，第一个元素称为第一元素 元组相等：两个元组的所有元素对应相等 笛卡尔积 笛卡尔积从多个集合中构造出能够反映聚集顺序的元素全为元组的新集合，不满足交换律 V1×V2×⋯×Vn={(x1,x2,⋯ ,xn) ∣ x1∈V1∧x2∈V2∧⋯∧xn∈Vn} V_1\\times V_2\\times\\cdots\\times V_n=\\{(x_1,x_2,\\cdots,x_n)\\ |\\ x_1\\in V_1\\wedge x_2\\in V_2\\wedge\\cdots\\wedge x_n\\in V_n\\} V1​×V2​×⋯×Vn​={(x1​,x2​,⋯,xn​) ∣ x1​∈V1​∧x2​∈V2​∧⋯∧xn​∈Vn​} 二元笛卡尔积即构造出以V1V_1V1​元素为第一元素，以V2V_2V2​元素为第二元素的所有序偶，并放在一个新集合里\n边缘情况：若其中一个参数集合是空集，那么新集合为空集\n不难得到，\n∣V1×V2×⋯×Vn∣=∏1≤i≤n∣Vi∣\\begin{align}|V_1\\times V_2\\times\\cdots\\times V_n|=\\prod_{1\\le i\\le n}|V_i|\\end{align}∣V1​×V2​×⋯×Vn​∣=1≤i≤n∏​∣Vi​∣​​映射函数 ","permalink":"https://echo.ishian.me/2024/04/22/discretemath-set/","summary":"讲解集合的基本概念、运算、笛卡尔积、关系与函数等集合论知识","title":"离散数学: 集合论"},{"content":"微分方程 概述 称\nf(x)+f0(x)g0(y)+...+fn(x)gn(y(n))=0\\begin{align}f(x)+f_0(x)g_0(y)+...+f_n(x)g_n(y^{(n)})=0\\end{align}f(x)+f0​(x)g0​(y)+...+fn​(x)gn​(y(n))=0​​为一个nnn阶常微分方程，有如下概念：\n当所有g(y(t))=y(t)g(y^{(t)})=y^{(t)}g(y(t))=y(t)时，称其为线性微分方程 当所有ft(x)=Cf_t(x)=Cft​(x)=C时，称其为常系数微分方程 当f(x)=0f(x)=0f(x)=0时，称方程齐次 称含相互独立的、一共nnn个CCC的解为通解，由特定条件(称其为定解条件)求出CCC的值的解为特解，含有CCC的解为隐式通解(也称通积分) 称通解的图形为积分曲线族，特解的图形为积分曲线 初等积分法 分离变量法 称\ndydx=h(x)g(y)\\begin{align}\\frac{dy}{dx}=h(x)g(y)\\end{align}dxdy​=h(x)g(y)​​为一阶可分离变量方程，求解过程：\n分离变量：将x、yx、yx、y相关分别分为左右两部分，由于相除过程中g(y)g(y)g(y)不能等于000，所以需判断g(y)=0g(y)=0g(y)=0是否为它的特解 两边积分，得到隐式通解 如遇到f(x、y)f(x、y)f(x、y)，应先换元得到 dudx=h(x)g(u)\\begin{align}\\frac{du}{dx}=h(x)g(u)\\end{align}dxdu​=h(x)g(u)​​ eg1:\n求y′=sin⁡2(x−y+1)的通解解:令u=x−y+1. 得dudx=1−y′∴1−dudx=sin⁡2u∴ducos⁡2u=dx∴tan⁡u=tan⁡(x−y+1)=x+C\\begin{align}\u0026amp;求y\u0026#x27;=\\sin^2{(x-y+1)}的通解\\\\\u0026amp;解:\\\\\u0026amp;令u=x-y+1.\\ 得\\frac{du}{dx}=1-y\u0026#x27;\\\\\u0026amp;\\therefore 1-\\frac{du}{dx}=\\sin^2u\\\\\u0026amp;\\therefore\\frac{du}{\\cos^2u}=dx\\\\\u0026amp;\\therefore \\tan u=\\tan(x-y+1)=x+C\\end{align}​求y′=sin2(x−y+1)的通解解:令u=x−y+1. 得dxdu​=1−y′∴1−dxdu​=sin2u∴cos2udu​=dx∴tanu=tan(x−y+1)=x+C​​ eg2:\n求解初值问题:cos⁡ydx+(1+e−x)sin⁡ydy=0,y(0)=π4解:整理得tan⁡ydy=−dx1+e−x积分得ln⁡∣cos⁡y∣=ln⁡(e−x+1)+C1∴∣cos⁡y∣=(e−x+1)eC1∴Ccos⁡y=e−x+1, (C=±1eC1)故该初值问题的解为22cos⁡y=e−x+1\\begin{align}\u0026amp;求解初值问题:\\cos ydx+(1+e^{-x})\\sin ydy=0,y(0)=\\frac\\pi4\\\\\u0026amp;解:\\\\\u0026amp;整理得\\tan ydy=-\\frac{dx}{1+e^{-x}}\\\\\u0026amp;积分得\\ln|\\cos y|=\\ln(e^{-x}+1)+C_1\\\\\u0026amp;\\therefore |\\cos y|=(e^{-x}+1)e^{C_1}\\\\\u0026amp;\\therefore C\\cos y=e^{-x}+1,\\ (C=\\pm \\frac{1}{e^{C_1}})\\\\\u0026amp;故该初值问题的解为2\\sqrt2\\cos y=e^{-x}+1\\end{align}​求解初值问题:cosydx+(1+e−x)sinydy=0,y(0)=4π​解:整理得tanydy=−1+e−xdx​积分得ln∣cosy∣=ln(e−x+1)+C1​∴∣cosy∣=(e−x+1)eC1​∴Ccosy=e−x+1, (C=±eC1​1​)故该初值问题的解为22​cosy=e−x+1​​ eg3:\n已知∫01f(αx)dα=12f(x)+1, 且f′(x)存在, 求f(x).解:令αx=u, 得1x∫0xf(u)du=12f(x)+1∴12xf(x)+x=∫0xf(u)du∴12(f(x)+2+xf′(x))=f(x)∴f′(x)=f(x)−2x∴f(x)=Cx+2\\begin{align}\u0026amp;已知\\int_0^1f(\\alpha x)d\\alpha=\\frac12f(x)+1,\\ 且f\u0026#x27;(x)存在,\\ 求f(x).\\\\\u0026amp;解:\\\\\u0026amp;令\\alpha x=u,\\ 得\\frac1x\\int_0^xf(u)du=\\frac12f(x)+1\\\\\u0026amp;\\therefore \\frac12xf(x)+x=\\int_0^xf(u)du\\\\\u0026amp;\\therefore \\frac12\\big(f(x)+2+xf\u0026#x27;(x)\\big)=f(x)\\\\\u0026amp;\\therefore f\u0026#x27;(x)=\\frac{f(x)-2}x\\\\\u0026amp;\\therefore f(x)=Cx+2\\end{align}​已知∫01​f(αx)dα=21​f(x)+1, 且f′(x)存在, 求f(x).解:令αx=u, 得x1​∫0x​f(u)du=21​f(x)+1∴21​xf(x)+x=∫0x​f(u)du∴21​(f(x)+2+xf′(x))=f(x)∴f′(x)=xf(x)−2​∴f(x)=Cx+2​​常数变易法 教材先讲的不完整的线性方程的求法，实际上就是常系数线性方程\n一阶齐次线性方程也是一个一阶可分离变量方程，对于一阶非齐次线性方程，即\ndydx+f(x)y=g(x)\\begin{align}\\frac{dy}{dx}+f(x)y=g(x)\\end{align}dxdy​+f(x)y=g(x)​​，由于所有非齐次线性方程的通解等于与其对应的齐次线性方程通解加上它的一个特解，所以首先也得求齐次通解，求解步骤为：\n求对应一阶齐次线性方程的通解，得到y=Ch(x)y=Ch(x)y=Ch(x) 视CCC为u(x)u(x)u(x)，两边求导得到 dydx\\begin{align}\\frac{dy}{dx}\\end{align}dxdy​​​ 把yyy和 dydx\\begin{align}\\frac{dy}{dx}\\end{align}dxdy​​​代入原方程，得到u′(x)u\u0026#x27;(x)u′(x)后积分得到u(x)u(x)u(x)，注意不定积分后的u(x)u(x)u(x)含CCC，得到答案应检查是否含CCC 由推导得u(x)u(x)u(x)会被消掉，所以将二、三步合为一步：\n令u′(x)h(x)=g(x)u\u0026#x27;(x)h(x)=g(x)u′(x)h(x)=g(x)，积分求出u(x)u(x)u(x)，即CCC，得到齐次通解 只需要整理成\ndydx\\begin{align}\\frac{dy}{dx}\\end{align}dxdy​​​系数为111的形式，就可以快速得到答案\neg1:\n已知连续函数f(x)满足f(x)=∫03xf(t3)dt+e2x, 求f(x).解:∫03xf(t3)dt=3∫0xf(u)du两边求导得, f′(x)=3f(x)+2e2x先求对应齐次方程通解, 当f′(x)=3f(x)时, ln⁡∣f(x)∣=3x+C0∴f(x)=C1e3x, 令C1=u(x)∴u′(x)⋅e3x=2e2x∴u(x)=−2e−x+C, f(x)=(−2e−x+C)e3x由于f(0)=1, ∴f(x)=3e3x−2e2x\\begin{align}\u0026amp;已知连续函数f(x)满足f(x)=\\int_0^{3x}f(\\frac t3)dt+e^{2x},\\ 求f(x).\\\\\u0026amp;解:\\\\\u0026amp;\\int_0^{3x}f(\\frac t3)dt=3\\int_0^xf(u)du\\\\\u0026amp;两边求导得,\\ f\u0026#x27;(x)=3f(x)+2e^{2x}\\\\\u0026amp;先求对应齐次方程通解,\\ 当f\u0026#x27;(x)=3f(x)时,\\ \\ln\\Big|f(x)\\Big|=3x+C_0\\\\\u0026amp;\\therefore f(x)=C_1e^{3x},\\ 令C_1=u(x)\\\\\u0026amp;\\therefore u\u0026#x27;(x)·e^{3x}=2e^{2x}\\\\\u0026amp;\\therefore u(x)=-2e^{-x}+C,\\ f(x)=(-2e^{-x}+C)e^{3x}\\\\\u0026amp;由于f(0)=1,\\ \\therefore f(x)=3e^{3x}-2e^{2x}\\end{align}​已知连续函数f(x)满足f(x)=∫03x​f(3t​)dt+e2x, 求f(x).解:∫03x​f(3t​)dt=3∫0x​f(u)du两边求导得, f′(x)=3f(x)+2e2x先求对应齐次方程通解, 当f′(x)=3f(x)时, ln​f(x)​=3x+C0​∴f(x)=C1​e3x, 令C1​=u(x)∴u′(x)⋅e3x=2e2x∴u(x)=−2e−x+C, f(x)=(−2e−x+C)e3x由于f(0)=1, ∴f(x)=3e3x−2e2x​​这道题是隐含了特解的\n伯努利方程 称形如\ndydx+f(x)y=g(x)yn, (n≠0, 1)\\begin{align}\\frac{dy}{dx}+f(x)y=g(x)y^n,\\ (n\\ne 0,\\ 1)\\end{align}dxdy​+f(x)y=g(x)yn, (n=0, 1)​​的微分方程为伯努利方程，求解步骤如下：\n两边除以yny^nyn，得y−ny′+f(x)y1−n=g(x)y^{-n}y\u0026#x27;+f(x)y^{1-n}=g(x)y−ny′+f(x)y1−n=g(x)，因为(1−n)y−ny′=(y1−n)′(1-n)y^{-n}y\u0026#x27;=(y^{1-n})\u0026#x27;(1−n)y−ny′=(y1−n)′，故令u(x)=y1−nu(x)=y^{1-n}u(x)=y1−n u′1−n+f(x)u=g(x), u=y1−n\\begin{align}\\frac{u\u0026#x27;}{1-n}+f(x)u=g(x),\\ u=y^{1-n}\\end{align}1−nu′​+f(x)u=g(x), u=y1−n​​ 视为一阶线性方程求解 将uuu替换为y1−ny^{1-n}y1−n eg1:\n求微分方程3(1+x2)y′+2xy=2xy4的通解.解:令u=y−3, 得−(1+x2)u′+2xu=2x∵(1+x2)u′=2xu的通解为:ln⁡u=ln⁡(1+x2)+C0即u=C1(1+x2)令C1=v(x), 得v′(x)(1+x2)=−2x(1+x2)−1∴v(x)=11+x2+C, y−3=(C+11+x2)(1+x2)\\begin{align}\u0026amp;求微分方程3(1+x^2)y\u0026#x27;+2xy=2xy^4的通解.\\\\\u0026amp;解:\\\\\u0026amp;令u=y^{-3},\\ 得-(1+x^2)u\u0026#x27;+2xu=2x\\\\\u0026amp;\\because (1+x^2)u\u0026#x27;=2xu的通解为:\\\\\u0026amp;\\ln u=\\ln(1+x^2)+C_0\\\\\u0026amp;即u=C_1(1+x^2)\\\\\u0026amp;令C_1=v(x),\\ 得v\u0026#x27;(x)(1+x^2)=-2x(1+x^2)^{-1}\\\\\u0026amp;\\therefore v(x)=\\frac{1}{1+x^2}+C,\\ y^{-3}=(C+\\frac{1}{1+x^2})(1+x^2)\\end{align}​求微分方程3(1+x2)y′+2xy=2xy4的通解.解:令u=y−3, 得−(1+x2)u′+2xu=2x∵(1+x2)u′=2xu的通解为:lnu=ln(1+x2)+C0​即u=C1​(1+x2)令C1​=v(x), 得v′(x)(1+x2)=−2x(1+x2)−1∴v(x)=1+x21​+C, y−3=(C+1+x21​)(1+x2)​​齐次微分方程 当x、yx、yx、y次数相吻合，即\ny′=f(yx)\\begin{align}y\u0026#x27;=f(\\frac yx)\\end{align}y′=f(xy​)​​时：\n令 u(x)=yx\\begin{align}u(x)=\\frac yx\\end{align}u(x)=xy​​​，则y′=(ux)′=u′x+u=f(u)y\u0026#x27;=(ux)\u0026#x27;=u\u0026#x27;x+u=f(u)y′=(ux)′=u′x+u=f(u)，即u′x=g(u)u\u0026#x27;x=g(u)u′x=g(u) 通过分离变量法求解 将uuu替换为 yx\\begin{align}\\frac yx\\end{align}xy​​​ 化为齐次微分方程 对于形如\ndydx=f(a1x+b1y+c1a2x+b2y+c2)\\begin{align}\\frac{dy}{dx}=f\\Big(\\frac{a_1x+b_1y+c_1}{a_2x+b_2y+c_2}\\Big)\\end{align}dxdy​=f(a2​x+b2​y+c2​a1​x+b1​y+c1​​)​​的方程：\n若c1=c2=0c_1=c_2=0c1​=c2​=0，则显然可以上下同除xxx，构造齐次 否则，对{a1x+b1y+c1=0a2x+b2y+c2=0\\begin{cases}a_1x+b_1y+c_1=0\\\\a_2x+b_2y+c_2=0\\end{cases}{a1​x+b1​y+c1​=0a2​x+b2​y+c2​=0​进行求解，如果有解，设X=x−x0, Y=y−y0X=x-x_0,\\ Y=y-y_0X=x−x0​, Y=y−y0​，构造齐次 如果无解，即∣a1b1a2b2∣=0\\left|\\begin{matrix}a_1\u0026amp;b_1\\\\a_2\u0026amp;b_2\\end{matrix}\\right|=0​a1​a2​​b1​b2​​​=0时，令 λ=a1a2=b1b2, u=a2x+b2y\\begin{align}\\lambda=\\frac{a_1}{a_2}=\\frac{b_1}{b_2},\\ u=a_2x+b_2y\\end{align}λ=a2​a1​​=b2​b1​​, u=a2​x+b2​y​​，构造可分离变量方程 y′=(dudx−a2)1b2=f(ku+c1u+c2)\\begin{align}y\u0026#x27;=\\Big(\\frac{du}{dx}-a_2\\Big)\\frac1{b_2}=f\\Big(\\frac{ku+c_1}{u+c_2}\\Big)\\end{align}y′=(dxdu​−a2​)b2​1​=f(u+c2​ku+c1​​)​​ 可降阶的高阶微分方程 假设关于y(x)y(x)y(x)的微分方程：\n若y(n)=f(x)y^{(n)}=f(x)y(n)=f(x)，逐层积分即可 若y′′=f(x,y′)y\u0026#x27;\u0026#x27;=f(x,y\u0026#x27;)y′′=f(x,y′)，即含有xxx，但不含yyy，可令y′=u(x)、y′′=u′(x)y\u0026#x27;=u(x)、y\u0026#x27;\u0026#x27;=u\u0026#x27;(x)y′=u(x)、y′′=u′(x)，代入后求出u(x)u(x)u(x)，积分后为y(x)y(x)y(x) 若y′′=f(y,y′)y\u0026#x27;\u0026#x27;=f(y,y\u0026#x27;)y′′=f(y,y′)，即含有yyy，但不含xxx，可令 y′=u(y)、y′′=dudx=u′y′=u′⋅u\\begin{align}y\u0026#x27;=u(y)、y\u0026#x27;\u0026#x27;=\\frac{du}{dx}=u\u0026#x27;y\u0026#x27;=u\u0026#x27;·u\\end{align}y′=u(y)、y′′=dxdu​=u′y′=u′⋅u​​，代入后求出 u(y)\\begin{align}u(y)\\end{align}u(y)​​，再通过y′=u(y)y\u0026#x27;=u(y)y′=u(y)和分离变量法求解即可 若既不含xxx，也不含yyy，则上述两种方法均可，但令y′=u(x)y\u0026#x27;=u(x)y′=u(x)更快捷 一些技巧 常规方法求解时，一定先将 dydx\\begin{align}\\frac{dy}{dx}\\end{align}dxdy​​​整理出来 将xxx视为yyy，yyy视为xxx，可能变成可以求解的一阶线性方程。当整理式子发现y′=单项式多项式y\u0026#x27;=\\frac{单项式}{多项式}y′=多项式单项式​时，可以考虑化为x′(y)x\u0026#x27;(y)x′(y) 记住一些常见导数，将整体化为u(x)u(x)u(x)： xy′+y=(xy)′xy\u0026#x27;+y=(xy)\u0026#x27;xy′+y=(xy)′ y−xy′y2=(xy)′\\begin{align}\\frac{y-xy\u0026#x27;}{y^2}=(\\frac xy)\u0026#x27;\\end{align}y2y−xy′​=(yx​)′​​ eg1:\n求微分方程(x−2xy−y2)dydx+y2=0的通解.解:dxdy=y2+2xy−xy2=1+x2y−1y2若dxdy=x2y−1y2, 则dxx=2y−1y2dy∴ln⁡∣x∣=ln⁡y2+1y+C0∴x=C1y2e1y, 令C1=u(y)得u′(y)⋅y2e1y=1∴u(y)=e−1y+C∴x=y2+Cy2e1y\\begin{align}\u0026amp;求微分方程(x-2xy-y^2)\\frac{dy}{dx}+y^2=0的通解.\\\\\u0026amp;解:\\\\\u0026amp;\\frac{dx}{dy}=\\frac{y^2+2xy-x}{y^2}=1+x\\frac{2y-1}{y^2}\\\\\u0026amp;若\\frac{dx}{dy}=x\\frac{2y-1}{y^2},\\ 则\\frac{dx}x=\\frac{2y-1}{y^2}dy\\\\\u0026amp;\\therefore \\ln|x|=\\ln y^2+\\frac1y+C_0\\\\\u0026amp;\\therefore x=C_1y^2e^{\\Large\\frac1y},\\ 令C_1=u(y)\\\\\u0026amp;得u\u0026#x27;(y)·y^2e^{\\Large\\frac1y}=1\\\\\u0026amp;\\therefore u(y)=e^{\\Large-\\frac1y}+C\\\\\u0026amp;\\therefore x=y^2+Cy^2e^{\\Large \\frac1y}\\end{align}​求微分方程(x−2xy−y2)dxdy​+y2=0的通解.解:dydx​=y2y2+2xy−x​=1+xy22y−1​若dydx​=xy22y−1​, 则xdx​=y22y−1​dy∴ln∣x∣=lny2+y1​+C0​∴x=C1​y2ey1​, 令C1​=u(y)得u′(y)⋅y2ey1​=1∴u(y)=e−y1​+C∴x=y2+Cy2ey1​​​高阶线性微分方程 常系数齐次方程 对于齐次线性微分方程，它的特解会是eλxe^{\\lambda x}eλx的形式，故对于A0y(n)+A1y(n−1)+⋯+Any=0A_0y^{(n)}+A_1y^{(n-1)}+\\dots+A_ny=0A0​y(n)+A1​y(n−1)+⋯+An​y=0，它的特征方程为：\nA0λn+A1yn−1+⋯+An=0A_0\\lambda^n+A_1y^{n-1}+\\dots+A_n=0A0​λn+A1​yn−1+⋯+An​=0，求出λ\\lambdaλ即得到它的一个特解 得出λn\\lambda_nλn​为一重根：通解为y=C1eλ1x+C2eλ2x+⋯+Cneλnxy=C_1e^{\\lambda_1 x}+C_2e^{\\lambda_2 x}+\\dots+C_ne^{\\lambda_n x}y=C1​eλ1​x+C2​eλ2​x+⋯+Cn​eλn​x 得出λ∗\\lambda_*λ∗​为二重根：通解为y=(C1+C2x)eλ∗x+...y=(C_1+C_2x)e^{\\lambda_* x}+...y=(C1​+C2​x)eλ∗​x+... 得出λ∗\\lambda_*λ∗​为kkk重根：通解为y=(C1+C2x+C3x2+⋯+Ckxk−1)eλ∗xy=(C_1+C_2x+C_3x^2+\\dots+C_{k}x^{k-1})e^{\\lambda_*x}y=(C1​+C2​x+C3​x2+⋯+Ck​xk−1)eλ∗​x 根据欧拉公式eix=cos⁡x+isin⁡xe^{ix}=\\cos x+i\\sin xeix=cosx+isinx，得出λ∗\\lambda_*λ∗​为一对共轭复根α±βx\\alpha\\pm\\beta xα±βx：通解为y=eαx(C1cos⁡βx+C2sin⁡βx)y=e^{\\alpha x}(C_1\\cos\\beta x+C_2\\sin\\beta x)y=eαx(C1​cosβx+C2​sinβx) 得出λ∗\\lambda_*λ∗​为一对共轭复根各kkk重±α±βx\\pm\\alpha\\pm\\beta x±α±βx：通解为y=eαx((C1+C2x+⋯+Ckxk−1)cos⁡βx+(C1′+C2′x+⋯+Ck′xk−1)sin⁡βx)y=e^{\\alpha x}\\big((C_1+C_2x+\\dots+C_kx^{k-1})\\cos\\beta x+(C_1\u0026#x27;+C_2\u0026#x27;x+\\dots+C_k\u0026#x27;x^{k-1})\\sin\\beta x\\big)y=eαx((C1​+C2​x+⋯+Ck​xk−1)cosβx+(C1′​+C2′​x+⋯+Ck′​xk−1)sinβx) 常系数非齐次方程 对于一个右边为单项式的非齐次线性微分方程，需要先求出它对应齐次微分方程的通解，再根据特征根求出原方程的一个特解(假设Pm(x)P_m(x)Pm​(x)为系数确定的一元mmm次线性函数，Qm(x)Q_m(x)Qm​(x)为系数待定的一元mmm次线性函数)：\n右式为eαxPm(x)e^{\\alpha x}P_m(x)eαxPm​(x)形式，如果α\\alphaα是kkk重特征根，则特解形式为：xkeαxQm(x)x^ke^{\\alpha x}Q_m(x)xkeαxQm​(x) 右式为eαx(Pm1(x)cos⁡βx+Pm2(x)sin⁡βx)e^{\\alpha x}\\big(P_{m_1}(x)\\cos \\beta x+P_{m_2}(x)\\sin \\beta x\\big)eαx(Pm1​​(x)cosβx+Pm2​​(x)sinβx)形式，如果α+βx\\alpha+\\beta xα+βx是kkk重特征根，则特解形式为：xkeαx(Qm(x)cos⁡βx+Qm′(x)sin⁡βx)x^ke^{\\alpha x}\\big(Q_m(x)\\cos\\beta x+Q_m\u0026#x27;(x)\\sin \\beta x\\big)xkeαx(Qm​(x)cosβx+Qm′​(x)sinβx)，其中m=max⁡(m1, m2)m=\\max{(m_1,\\ m_2)}m=max(m1​, m2​) 求出形式后，代入原方程求解待定的系数即可\n对于多项式，可以拆分成多个单项式方程的特解之和求解\n欧拉方程 形如A1xny(n)+A2xn−1y(n−1)+⋯+Anxy′+An+1y=f(x)A_1x^{n}y^{(n)}+A_2x^{n-1}y^{(n-1)}+\\dots+A_nxy\u0026#x27;+A_{n+1}y=f(x)A1​xny(n)+A2​xn−1y(n−1)+⋯+An​xy′+An+1​y=f(x)的方程称为欧拉方程\n令t=ln⁡xt=\\ln xt=lnx，导数算子 D(y)=dydt\\begin{align}D(y)=\\frac{dy}{dt}\\end{align}D(y)=dtdy​​​，得到 xy′=D(y)=y′(t)、x2y′′=D(D−1)(y)=y′′(t)−y′(t)、…、xny(n)=D(D−1)(D−2)…(D−n+1)(y)\\begin{align}\u0026amp;xy\u0026#x27;=D(y)=y\u0026#x27;(t)、x^2y\u0026#x27;\u0026#x27;=D(D-1)(y)=y\u0026#x27;\u0026#x27;(t)-y\u0026#x27;(t)、\\dots、\\\\\u0026amp;x^ny^{(n)}=D(D-1)(D-2)\\dots(D-n+1)(y)\\end{align}​xy′=D(y)=y′(t)、x2y′′=D(D−1)(y)=y′′(t)−y′(t)、…、xny(n)=D(D−1)(D−2)…(D−n+1)(y)​​ 代入方程得到的是关于y(t)y(t)y(t)的微分方程 解结构降阶法 对解的结构的应用，类似于线性代数中线性方程组的解，以下所指非齐次解均指同一个非齐次线性方程的解、齐次解均指该非齐次线性方程对应的齐次线性方程的解：\n齐次解+齐次解=齐次解 非齐次解+齐次解=非齐次解 非齐次解-非齐次解=齐次解 已知齐次通解yyy，则非齐次通解为y+y∗y+y^*y+y∗，其中y∗y^*y∗为非齐次特解 假设一个关于y(x)y(x)y(x)的齐次线性微分方程，且已知一个特解y∗y^*y∗，则Cy∗Cy^*Cy∗也是解，视CCC为u(x)u(x)u(x)，代入原方程解出u(x)u(x)u(x)即可得到通解，需注意解出的u(x)u(x)u(x)含CCC 降阶在于，代入原方程后u(x)u(x)u(x)会被消掉，只剩u(n)(x)u^{(n)}(x)u(n)(x)\n","permalink":"https://echo.ishian.me/2024/04/20/calculus-diff-equation/","summary":"讲解常微分方程的基本概念与分离变量法等初等解法，并附例题","title":"微积分: 常微分方程"},{"content":"数理逻辑 命题逻辑 基本概念 命题：只有真、假两种状态的陈述语句，在命题逻辑里，原子命题不提供任何除真假以外的信息，这也导致命题逻辑的局限性 逻辑等价(p≡qp\\equiv qp≡q)：表示**ppp等价于qqq**，即有相同的真值表 否定(¬p\\neg p¬p)：表示**ppp的否定** 析取(p∨qp\\vee qp∨q)：表示**ppp或者qqq** 合取(p∧qp\\wedge qp∧q)：表示**ppp并且qqq** 异或(p⊕qp\\oplus qp⊕q)：表示**ppp异或qqq** 条件(p→qp\\rightarrow qp→q)：表示若ppp，则qqq 从定义上看，只有当p=T∧q=Fp=T\\wedge q=Fp=T∧q=F时违反了语句，故其它情况均人为定义为TTT 双条件(p↔qp\\leftrightarrow qp↔q)：表示**ppp当且仅当qqq**，与p⊙qp\\odot qp⊙q等价 若p↔qp\\leftrightarrow qp↔q为永真式，称p≡qp\\equiv qp≡q或p⇔qp\\Leftrightarrow qp⇔q 对条件语句p→qp\\rightarrow qp→q： 反命题：¬p→¬q\\neg p\\rightarrow\\neg q¬p→¬q，即对输入取反，真值表上下颠倒 逆命题：q→pq\\rightarrow pq→p，真值表上下颠倒 逆否命题：¬q→¬p\\neg q\\rightarrow \\neg p¬q→¬p，和原命题等价 优先级：¬\u0026gt;∧\u0026gt;∨\u0026gt;→\u0026gt;↔\\neg\u0026gt;\\wedge\u0026gt;\\vee\u0026gt;\\rightarrow\u0026gt;\\leftrightarrow¬\u0026gt;∧\u0026gt;∨\u0026gt;→\u0026gt;↔ 与其它逻辑系统的对应 命题逻辑 逻辑代数 位运算符 集合论 自然语言描述 ¬p\\neg p¬p pˉ\\bar ppˉ​ NOT\\rm NOTNOT 否定/取反 ∨\\vee∨ +++ OR\\rm OROR ∪\\cup∪ 析取/或/逻辑和/并 ∧\\wedge∧ ⋅·⋅ AND\\rm ANDAND ∩\\cap∩ 合取/与/逻辑积/交 p→qp \\rightarrow qp→q ppp蕴涵qqq p⇒qp\\Rightarrow qp⇒q ppp永真蕴涵qqq/ppp是qqq的充分条件 p↔qp\\leftrightarrow qp↔q ⊙\\odot⊙ XNOR\\rm XNORXNOR ppp当且仅当qqq/ppp同或qqq p⇔qp\\Leftrightarrow qp⇔q ppp是qqq的充要条件 $ $ NAND\\rm NANDNAND ↓\\downarrow↓ NOR\\rm NORNOR 或非 基本等价公式 满足交换律、结合律、分配律 德⋅\\cdot⋅摩根律： ¬(p∧q)≡¬p∨¬q\\neg(p\\wedge q)\\equiv \\neg p\\vee\\neg q¬(p∧q)≡¬p∨¬q ¬(p∨q)≡¬p∧¬q\\neg(p \\vee q)\\equiv\\neg p\\wedge\\neg q¬(p∨q)≡¬p∧¬q 支配律：p∨T≡T、p∧F≡Fp\\vee T\\equiv T、p\\wedge F\\equiv Fp∨T≡T、p∧F≡F 自等律：p∨p≡p、p∧p≡pp\\vee p\\equiv p、p\\wedge p\\equiv pp∨p≡p、p∧p≡p 恒等律：p∨F≡p、p∧T≡pp\\vee F\\equiv p、p\\wedge T\\equiv pp∨F≡p、p∧T≡p 否定律：p∨¬p≡T、p∧¬p≡Fp\\vee \\neg p\\equiv T、p\\wedge \\neg p\\equiv Fp∨¬p≡T、p∧¬p≡F 吸收律： p∨(p∧q)≡pp\\vee(p\\wedge q)\\equiv pp∨(p∧q)≡p 由分配律，p∧(p∨q)≡pp\\wedge(p\\vee q)\\equiv pp∧(p∨q)≡p，也即上式的对偶 消解律： (p∧q)∨(¬p∧r)≡q∧r(p\\wedge q)\\vee (\\neg p\\wedge r)\\equiv q\\wedge r(p∧q)∨(¬p∧r)≡q∧r 取对偶，(p∨q)∧(p∨r)≡q∨r(p\\vee q)\\wedge(p\\vee r)\\equiv q\\vee r(p∨q)∧(p∨r)≡q∨r 条件命题的等价 p→q≡¬q→¬p≡¬p∨qp\\rightarrow q\\equiv \\neg q\\rightarrow\\neg p\\equiv \\neg p\\vee qp→q≡¬q→¬p≡¬p∨q (p→q)∧(p→r)≡p→(q∧r)(p\\rightarrow q)\\wedge(p\\rightarrow r)\\equiv p\\rightarrow(q\\wedge r)(p→q)∧(p→r)≡p→(q∧r) (p→r)∧(q→r)≡(p∨q)→r(p\\rightarrow r)\\wedge(q\\rightarrow r)\\equiv(p\\vee q)\\rightarrow r(p→r)∧(q→r)≡(p∨q)→r (p→q)∨(p→r)≡p→(q∨r)(p\\rightarrow q)\\vee(p\\rightarrow r)\\equiv p\\rightarrow(q\\vee r)(p→q)∨(p→r)≡p→(q∨r) (p→r)∨(q→r)≡(p∧q)→r(p\\rightarrow r)\\vee(q\\rightarrow r)\\equiv(p\\wedge q)\\rightarrow r(p→r)∨(q→r)≡(p∧q)→r 逻辑函数 对偶规则 恒等式通常成对出现，称其为对偶，以FdF^dFd表示FFF的对偶函数，对一个恒等式两边取对偶不影响恒等性 Fd≢F(一般)、(Fd)d≡FF^d\\not\\equiv F(一般)、(F^d)^d\\equiv FFd≡F(一般)、(Fd)d≡F 对偶函数由原函数交换逻辑与、逻辑或，交换000和111得到 反演规则 一个函数的反函数可以由原函数交换逻辑与、逻辑或，交换000和111，同时原变量变反变量、反变量变原变量而得，其中反变量特指单个变量上的非号，跨两个及以上变量的非号不能动 同样可借Fˉ\\bar FFˉ来求FFF 谓词逻辑 命题逻辑的局限 命题逻辑的最小单元是命题，所以命题逻辑无法表示那些不真不假的语句，因为它们没有指定变量的值\n因此也无法从若干简单命题中得到新命题，例如亚里士多德三段论：\n三个原子命题：p:p:p:所有人都要死、q:q:q:苏格拉底是人、r:r:r:所以苏格拉底要死 根据逻辑推理，由ppp且qqq一定可以得到rrr，即要证明(p∧q)⇒r(p\\wedge q)\\Rightarrow r(p∧q)⇒r 尽管p∧q=Tp\\wedge q=Tp∧q=T，在命题逻辑的系统里，仍然可以把FFF指派给rrr 换句话说就是，给命题赋予真值的同时，也掩盖掉了具体的信息，你只知道ppp是对的，qqq是对的，然后无法得知任何其它信息，也就无法得知rrr的真值 如果能变成假言三段论的形式，是可以通过命题逻辑表示的，但原子命题已经是最小的命题了 谓词逻辑解决这样的事情：提取出命题中的信息，并提供一套符号用于产生新的、适用更广的命题\n谓词与量词 通过谓词可以从这些语句中生成命题，谓词逻辑允许对命题进行更细的划分\n个体：命题所描述的对象，约定用小写字母表示 谓词：命题赋予主体的属性或动作，约定用大写字母P、Q、RP、Q、RP、Q、R表示 命题的谓词形式：由谓词和若干个体常元组合成P(a1,a2,⋯ ,an)P(a_1,a_2,\\cdots,a_n)P(a1​,a2​,⋯,an​)的形式 称P(x1,x2,⋯ ,xn)P(x_1,x_2,\\cdots,x_n)P(x1​,x2​,⋯,xn​)为一个nnn元谓词，其中xxx为个体变元，称它能够表示的所有个体的集合为它的个体域 量词：通过量词来限定个体域在谓词中的不确定性，例如所有、至少等 全称量词(∀x)(\\forall x)(∀x)：表达所有 存在量词(∃x)(\\exist x)(∃x)：表达存在 存在唯一量词(∃ ! x)(\\exist\\ !\\ x)(∃ ! x)：表达存在唯一 后接谓词P(x)P(x)P(x)表示该量词的辖域 谓词逻辑公式 德⋅·⋅摩根律： ¬(∃x)P(x)≡(∀x)¬P(x)\\neg(\\exist x)P(x)\\equiv (\\forall x)\\neg P(x)¬(∃x)P(x)≡(∀x)¬P(x) ¬(∀x)P(x)≡(∃x)¬P(x)\\neg (\\forall x)P(x)\\equiv(\\exist x)\\neg P(x)¬(∀x)P(x)≡(∃x)¬P(x) 量词的辖域扩张： (∀x)P(x)∨Q≡(∀x)(P(x)∨Q)(\\forall x)P(x)\\vee Q\\equiv (\\forall x)(P(x)\\vee Q)(∀x)P(x)∨Q≡(∀x)(P(x)∨Q)，其中QQQ不含变元 同理，将析取换为合取、将全称量词换位存在量词也成立 (∀x)(P(x)→Q)≡(∃x)P(x)→Q(\\forall x)(P(x)\\rightarrow Q)\\equiv(\\exist x)P(x)\\rightarrow Q(∀x)(P(x)→Q)≡(∃x)P(x)→Q，将∀\\forall∀和∃\\exist∃互换同理 证明：左式=(∀x)(¬P(x)∨Q)=(∀x)¬P(x)∨Q=¬(∃x)P(x)∨Q==(\\forall x)(\\neg P(x)\\vee Q)=(\\forall x)\\neg P(x)\\vee Q=\\neg(\\exist x)P(x)\\vee Q==(∀x)(¬P(x)∨Q)=(∀x)¬P(x)∨Q=¬(∃x)P(x)∨Q=右式 (∀x)(Q→P(x))≡Q→(∀x)P(x)(\\forall x)(Q\\rightarrow P(x))\\equiv Q\\rightarrow(\\forall x)P(x)(∀x)(Q→P(x))≡Q→(∀x)P(x)，将∀\\forall∀换成∃\\exist∃同理 量词的分配律： ∀x\\forall x∀x对∧\\wedge∧、∃x\\exist x∃x对∨\\vee∨满足分配律 ∀x\\forall x∀x对∨\\vee∨、∃x\\exist x∃x对∧\\wedge∧不满足分配律，只满足(∀x)P(x)∨(∀x)Q(x)⇒(∀x)(P(x)∨Q(x))(∃x)(P(x)∧Q(x))⇒(∃x)P(x)∧(∃x)Q(x)(\\forall x)P(x)\\vee (\\forall x)Q(x)\\Rightarrow(\\forall x)(P(x)\\vee Q(x))\\\\(\\exist x)(P(x)\\wedge Q(x))\\Rightarrow(\\exist x)P(x)\\wedge (\\exist x)Q(x)(∀x)P(x)∨(∀x)Q(x)⇒(∀x)(P(x)∨Q(x))(∃x)(P(x)∧Q(x))⇒(∃x)P(x)∧(∃x)Q(x) 永真蕴涵式：(∃x)P(x)→(∀x)Q(x)⇒(∀x)(P(x)→Q(x))⇒(∀x)P(x)→(∀x)Q(x)(\\exist x)P(x)\\rightarrow(\\forall x)Q(x)\\Rightarrow(\\forall x)(P(x)\\rightarrow Q(x))\\Rightarrow (\\forall x)P(x)\\rightarrow(\\forall x)Q(x)(∃x)P(x)→(∀x)Q(x)⇒(∀x)(P(x)→Q(x))⇒(∀x)P(x)→(∀x)Q(x) 嵌套量词 嵌套量词的顺序： (∀x)(∀y)≡(∀y)(∀x)(∃x)(∃y)≡(∃y)(∃x)(\\forall x)(\\forall y)\\equiv (\\forall y)(\\forall x)\\\\(\\exist x)(\\exist y)\\equiv (\\exist y)(\\exist x)(∀x)(∀y)≡(∀y)(∀x)(∃x)(∃y)≡(∃y)(∃x) (∀x)(∀y)⇒(∃x)(∀y)⇒(∀y)(∃x)⇒(∃x)(∃y)(∀x)(∀y)⇒(∃y)(∀x)⇒(∀x)(∃y)⇒(∃x)(∃y)(\\forall x)(\\forall y)\\Rightarrow(\\exist x)(\\forall y)\\Rightarrow(\\forall y)(\\exist x)\\Rightarrow(\\exist x)(\\exist y)\\\\(\\forall x)(\\forall y)\\Rightarrow(\\exist y)(\\forall x)\\Rightarrow(\\forall x)(\\exist y)\\Rightarrow(\\exist x)(\\exist y)(∀x)(∀y)⇒(∃x)(∀y)⇒(∀y)(∃x)⇒(∃x)(∃y)(∀x)(∀y)⇒(∃y)(∀x)⇒(∀x)(∃y)⇒(∃x)(∃y) 嵌套量词的否定： 层层套用德⋅·⋅摩根律，结果为∀\\forall∀和∃\\exist∃互换，非号在最里层，例如： ¬(∀x)(∃y)P(x,y)≡(∃x)(∀y)¬P(x,y)\\neg(\\forall x)(\\exist y)P(x,y)\\equiv(\\exist x)(\\forall y)\\neg P(x,y)¬(∀x)(∃y)P(x,y)≡(∃x)(∀y)¬P(x,y) 推理规则 命题逻辑规则 PPP：引入一个前件 TTT：引用公式SSS CPCPCP：P∧Q→R≡P→(Q→R)P\\wedge Q\\rightarrow R\\equiv P\\rightarrow(Q\\rightarrow R)P∧Q→R≡P→(Q→R) 假言推理：表示为永真式(p∧(p→q))→q(p\\wedge(p\\rightarrow q))\\rightarrow q(p∧(p→q))→q 假言三段论：由一个大条件和一个小条件可得出，表示为永真式((p→q)∧(q→r))→(p→r)((p\\rightarrow q)\\wedge(q\\rightarrow r))\\rightarrow (p\\rightarrow r)((p→q)∧(q→r))→(p→r) 利用永真式化简命题，其实就是在使用规则TTT 谓词逻辑规则 谓词逻辑是命题逻辑的推广，同样可使用命题逻辑的推理规则 全称特指(US)(\\rm US)(US)：(∀x)P(x)⇒P(y)(\\forall x)P(x)\\Rightarrow P(y)(∀x)P(x)⇒P(y) 存在特指(ES)(\\rm ES)(ES)：(∃x)P(x)⇒P(a)(\\exist x)P(x)\\Rightarrow P(a)(∃x)P(x)⇒P(a) 存在推广(EG)(\\rm EG)(EG)：P(y)⇒(∃x)P(x)P(y)\\Rightarrow(\\exist x)P(x)P(y)⇒(∃x)P(x)，yyy属于xxx的个体域 全称推广(UG)(\\rm UG)(UG)：P(y)⇒(∀x)P(x)P(y)\\Rightarrow(\\forall x)P(x)P(y)⇒(∀x)P(x)，xxx属于yyy的个体域 ","permalink":"https://echo.ishian.me/2024/04/19/discretemath-mathamatic-logic/","summary":"讲解命题逻辑与谓词逻辑的基本概念、等价公式及推理规则","title":"离散数学: 数理逻辑"},{"content":"级数练习 求常数项级数敛散性 对所有收敛的常数项级数，unu_nun​严格不为000 在此基础上，尝试对∣un∣|u_n|∣un​∣进行比值判别法或根值判别法 均为ρ\u0026lt;1\\rho\u0026lt;1ρ\u0026lt;1收敛、ρ\u0026gt;1\\rho\u0026gt;1ρ\u0026gt;1发散、ρ=1\\rho=1ρ=1未定 当出现阶乘、双阶乘、多项式乘积、nnn次方等时考虑比值判别法 当出现**nαn^\\alphanα次方、多项式的幂次方**时考虑根值判别法 lim⁡n→∞nn=1\\begin{aligned}\\lim_{n\\rightarrow\\infty}\\sqrt[n]n=1\\end{aligned}n→∞lim​nn​=1​、1+nn=e\\sqrt[n]{1+n}=en1+n​=e，两者均单调递减 若为交错级数，通过这两种方法判断出来一定是绝对收敛或发散 在上述基础上，考虑与它相似的函数，根据比较判别法的极限形式进行判断(敛散性相同) 常用比较有：ppp级数、等比级数、三角→1\\rightarrow1→1、积分→\\rightarrow→可求积分、含参→\\rightarrow→常数 放缩还可以：移项、拆项......... lim⁡n→∞nA+BnCA∼n,(A,B,C为常数,A\u0026gt;C)\\begin{align}\\lim_{n\\rightarrow\\infty}\\sqrt[A]{n^A+Bn^C}\\sim n,(A,B,C为常数,A\u0026gt;C)\\end{align}n→∞lim​AnA+BnC​∼n,(A,B,C为常数,A\u0026gt;C)​​ 若为交错级数，考虑莱布尼兹判别法 ∣un∣|u_n|∣un​∣单调递减且趋于000 上述方案需要复习极限，在此基础上，根据直接求SnS_nSn​极限或单调有界收敛原理考虑SnS_nSn​的敛散性 在上述基础上，考虑柯西收敛原理 尝试让∣Sn+p−Sn∣\u0026lt;f(n)|S_{n+p}-S_n|\u0026lt;f(n)∣Sn+p​−Sn​∣\u0026lt;f(n)，且f(n)f(n)f(n)足够小，则ϵ=f(N)、n\u0026gt;N\\epsilon=f(N)、n\u0026gt;Nϵ=f(N)、n\u0026gt;N时级数收敛 常见级数敛散性： ppp级数：p\u0026gt;1p\u0026gt;1p\u0026gt;1收敛，否则发散 un=1np(ln⁡n)q\\begin{align}u_n=\\frac1{n^p(\\ln n)^q}\\end{align}un​=np(lnn)q1​​​：p\u0026lt;1,发散p\u0026gt;1,收敛p=1,q≤1,发散p=1,q\u0026gt;1,收敛\\begin{matrix}p\u0026lt;1,\u0026amp;发散\\\\p\u0026gt;1,\u0026amp;收敛\\\\p=1,q\\le1,\u0026amp;发散\\\\p=1,q\u0026gt;1,\u0026amp;收敛\\end{matrix}p\u0026lt;1,p\u0026gt;1,p=1,q≤1,p=1,q\u0026gt;1,​发散收敛发散收敛​ 常数项级数的一般性结论 利用收敛±\\pm±收敛===收敛、收敛±\\pm±发散===发散、发散±\\pm±发散===未定的性质 若一般项趋于000，则添加无限括号与去除无限括号后级数敛散性相同；若未强调一般项，则收敛级数不可随意去掉无限括号、发散级数不可随意添加无限括号 利用基本不等式进行放缩 根据第一条性质，在收敛级数的一般项上加上例如 1n\\begin{align}\\frac{1}{n}\\end{align}n1​​​的发散级数一般项，从而使原级数发散 求幂级数和函数 求解RRR的方法：\n若不缺项， R=lim⁡n→∞∣unun+1∣\\begin{align}\u0026amp;R=\\lim_{n\\rightarrow\\infty}\\Bigg|\\frac{u_n}{u_{n+1}}\\Bigg|\\end{align}​R=n→∞lim​​un+1​un​​​​​ 若缺项，可尝试换元，使其不缺项 若无法换元，可用根值判别法，将xxx视为常数，再判断范围 得到收敛区间后，分别判断两个端点值，求出收敛域 若第一项或若干项为000，可进行移位以便看出和函数 对一个幂级数，首先要求其收敛域，才能求和函数\n一般通过逐项定积分或求导，化为最简的\n∑n=0∞xn\\begin{align}\u0026amp;\\sum_{n=0}^\\infty x^n\\end{align}​n=0∑∞​xn​​的形式，利用它的\nS(x)=11−x\\begin{align}S(x)=\\frac{1}{1-x}\\end{align}S(x)=1−x1​​​来求解；有时也会借助其它的麦克劳林级数判断\n注意在抽取出xαx^\\alphaxα后，s(x)s(x)s(x)定义域可能改变，需要分别讨论\n求函数的幂级数展开 记住常见的麦克劳林级数： 11−x=∑n=0∞xn,x∈(−1,1)11+x=∑n=0∞(−1)nxn,x∈(−1,1)\\begin{align}\u0026amp;\\frac1{1-x}=\\sum_{n=0}^\\infty x^n,\u0026amp;x\\in(-1,1)\\\\\u0026amp;\\frac1{1+x}=\\sum_{n=0}^\\infty(-1)^nx^n,\u0026amp;x\\in(-1,1)\\end{align}​1−x1​=n=0∑∞​xn,1+x1​=n=0∑∞​(−1)nxn,​x∈(−1,1)x∈(−1,1)​​ ex=∑n=0∞xnn!,x∈Rln⁡(1+x)=∑n=1∞(−1)n+1xnn,x∈(−1,1]\\begin{align}\u0026amp;e^x=\\sum_{n=0}^\\infty\\frac{x^n}{n!},\u0026amp;x\\in R\\\\\u0026amp;\\ln (1+x)=\\sum_{n=1}^\\infty\\frac{(-1)^{n+1}x^n}{n},\u0026amp;x\\in(-1,1]\\end{align}​ex=n=0∑∞​n!xn​,ln(1+x)=n=1∑∞​n(−1)n+1xn​,​x∈Rx∈(−1,1]​​ sin⁡x=∑n=1∞(−1)n+1x2n−1(2n−1)!,x∈Rcos⁡x=∑n=0∞(−1)nx2n(2n)!,x∈Rarctan⁡x=∫0x11+x2dx+1,x∈[−1,1],(在两点分别判断收敛)\\begin{align}\u0026amp;\\sin x=\\sum_{n=1}^\\infty\\frac{(-1)^{n+1}x^{2n-1}}{(2n-1)!},\u0026amp;x\\in R\\\\\u0026amp;\\cos x=\\sum_{n=0}^\\infty\\frac{(-1)^nx^{2n}}{(2n)!},\u0026amp;x\\in R\\\\\u0026amp;\\arctan x=\\int_0^x\\frac1{1+x^2}dx+1,\u0026amp;x\\in[-1,1],(在两点分别判断收敛)\\end{align}​sinx=n=1∑∞​(2n−1)!(−1)n+1x2n−1​,cosx=n=0∑∞​(2n)!(−1)nx2n​,arctanx=∫0x​1+x21​dx+1,​x∈Rx∈Rx∈[−1,1],(在两点分别判断收敛)​​ (1+x)α=1+∑n=1∞α(α−1)…(α−n+1)xnn!,R=111−x2=1+∑n=1∞(−1)n(2n−1)!!⋅x2n(2n)!!,x∈(−1,1)11+x2=1+∑n=1∞(2n−1)!!⋅x2n(2n)!!,x∈[−1,1]\\begin{align}\u0026amp;(1+x)^\\alpha=1+\\sum_{n=1}^\\infty\\frac{\\alpha(\\alpha-1)\\dots(\\alpha-n+1)x^n}{n!},\u0026amp;R=1\\\\\u0026amp;\\frac1{\\sqrt{1-x^2}}=1+\\sum_{n=1}^\\infty(-1)^n\\frac{(2n-1)!!·x^{2n}}{(2n)!!},\u0026amp;x\\in(-1,1)\\\\\u0026amp;\\frac1{\\sqrt{1+x^2}}=1+\\sum_{n=1}^\\infty\\frac{(2n-1)!!·x^{2n}}{(2n)!!},\u0026amp;x\\in[-1,1]\\end{align}​(1+x)α=1+n=1∑∞​n!α(α−1)…(α−n+1)xn​,1−x2​1​=1+n=1∑∞​(−1)n(2n)!!(2n−1)!!⋅x2n​,1+x2​1​=1+n=1∑∞​(2n)!!(2n−1)!!⋅x2n​,​R=1x∈(−1,1)x∈[−1,1]​​ 判断展开后级数的收敛域：通过求RRR和收敛区间，以及端点处分别判断，以及原函数的定义域来得出；这里的RRR是对xxx而言的，应注意处理换元后的RRR 傅里叶级数 求傅里叶级数的收敛 根据狄利克雷收敛定理，一个周期函数可以展开成傅里叶级数形式，但该级数只有连续时才收敛于原函数 该级数={f(x0),x=x0处连续f(x0+)+f(x0−)2,x=x0处为间断点=\\begin{cases}f(x_0),\u0026amp;x=x_0处连续\\\\\\Large\\frac{f(x_0^+)+f(x_0^-)}2\\normalsize,\u0026amp;x=x_0处为间断点\\end{cases}=⎩⎨⎧​f(x0​),2f(x0+​)+f(x0−​)​,​x=x0​处连续x=x0​处为间断点​ 应根据这一点来确定傅里叶级数的收敛域 求函数的傅里叶级数展开 周期完整的函数 f(x)∼a02+∑n=1∞(ancos⁡nπlx+bnsin⁡nπlx)\\begin{align}f(x)\\sim\\frac{a_0}2+\\sum_{n=1}^\\infty(a_n\\cos\\frac{n\\pi}lx+b_n\\sin\\frac{n\\pi}lx)\\end{align}f(x)∼2a0​​+n=1∑∞​(an​coslnπ​x+bn​sinlnπ​x)​​ {an=1l∫−llf(x)cos⁡nπlxdxbn=1l∫−llf(x)sin⁡nπlxdx\\begin{align}\\begin{cases}\\large a_n=\\frac1l\\int_{-l}^lf(x)\\cos\\frac{n\\pi}lxdx\\\\\\large b_n=\\frac1l\\int_{-l}^lf(x)\\sin\\frac{n\\pi}lxdx\\end{cases}\\end{align}⎩⎨⎧​an​=l1​∫−ll​f(x)coslnπ​xdxbn​=l1​∫−ll​f(x)sinlnπ​xdx​​​ 收敛域为原函数的所有连续点的集合 特别地，当函数为奇函数或偶函数时，只需要求bnb_nbn​或ana_nan​，且可将定积分的另一半提成222 周期不完整的函数 一般只要求展成余弦或正弦级数，只需偶延拓或奇延拓 注意展开后级数的收敛域 练习 1.证明∑n=2∞1np(ln⁡n)q的敛散性.1).当q\u0026lt;0时,un\u0026gt;1np,若p≤1,则原级数发散2).当p\u0026gt;1时,un≤{1np+q,q≤0,p\u0026gt;1−q时收敛1np,q\u0026gt;0,收敛故令p′+1\u0026lt;p,由lim⁡n→∞1np′(ln⁡n)q=0\u0026lt;1故un=1np−p′⋅1np′(ln⁡n)q\u0026lt;1np−p′,原级数收敛.3).当p=1,q≥0时,un非负连续递减,与对应反常积分敛散性相同.\\begin{align}\u0026amp;1.证明\\sum_{n=2}^\\infty\\frac1{n^p(\\ln n)^q}的敛散性.\\\\\u0026amp;1).当q\u0026lt;0时,u_n\u0026gt;\\frac1{n^p},若p\\le1,则原级数发散\\\\\u0026amp;2).当p\u0026gt;1时,u_n\\le\\begin{cases}\\frac1{n^{p+q}},\u0026amp;q\\le0,\u0026amp;p\u0026gt;1-q时收敛\\\\\\frac1{n^p},\u0026amp;q\u0026gt;0,\u0026amp;收敛\\end{cases}\\\\\u0026amp;故令p\u0026#x27;+1\u0026lt;p,由\\lim_{n\\rightarrow\\infty}\\frac1{n^{p\u0026#x27;}(\\ln n)^q}=0\u0026lt;1\\\\\u0026amp;故u_n=\\frac1{n^{p-p\u0026#x27;}}·\\frac1{n^{p\u0026#x27;}(\\ln n)^q}\u0026lt;\\frac1{n^{p-p\u0026#x27;}},原级数收敛.\\\\\u0026amp;3).当p=1,q\\ge0时,u_n非负连续递减,与对应反常积分敛散性相同.\\end{align}​1.证明n=2∑∞​np(lnn)q1​的敛散性.1).当q\u0026lt;0时,un​\u0026gt;np1​,若p≤1,则原级数发散2).当p\u0026gt;1时,un​≤{np+q1​,np1​,​q≤0,q\u0026gt;0,​p\u0026gt;1−q时收敛收敛​故令p′+1\u0026lt;p,由n→∞lim​np′(lnn)q1​=0\u0026lt;1故un​=np−p′1​⋅np′(lnn)q1​\u0026lt;np−p′1​,原级数收敛.3).当p=1,q≥0时,un​非负连续递减,与对应反常积分敛散性相同.​​ 2.∑n=2∞1(ln⁡n)ln⁡n[由已知得该级数收敛,则放大.](ln⁡n)ln⁡n\u0026gt;2ln⁡n,un\u0026lt;12ln⁡n,故收敛.\\begin{align}\u0026amp;2.\\sum_{n=2}^\\infty\\frac1{(\\ln n)^{\\ln n}}\\\\\u0026amp;[由已知得该级数收敛,则放大.]\\\\\u0026amp;(\\ln n)^{\\ln n}\u0026gt;2^{\\ln n},u_n\u0026lt;\\frac1{2^{\\ln n}},故收敛.\\end{align}​2.n=2∑∞​(lnn)lnn1​[由已知得该级数收敛,则放大.](lnn)lnn\u0026gt;2lnn,un​\u0026lt;2lnn1​,故收敛.​​ 3.∑n=1∞nn−1(2n2+ln⁡n+2)n+12[根值判别法.]lim⁡n→∞unn=lim⁡n→∞n2n2+ln⁡n+2\u0026lt;lim⁡n→∞nn=1,故收敛.\\begin{align}\u0026amp;3.\\sum_{n=1}^\\infty\\frac{n^{n-1}}{(2n^2+\\ln n+2)^{\\frac{n+1}2}}\\\\\u0026amp;[根值判别法.]\\\\\u0026amp;\\lim_{n\\rightarrow\\infty}\\sqrt[n]{u_n}=\\lim_{n\\rightarrow\\infty}\\frac n{\\sqrt{2n^2+\\ln n+2}}\u0026lt;\\lim_{n\\rightarrow\\infty}\\frac nn=1,故收敛.\\end{align}​3.n=1∑∞​(2n2+lnn+2)2n+1​nn−1​[根值判别法.]n→∞lim​nun​​=n→∞lim​2n2+lnn+2​n​\u0026lt;n→∞lim​nn​=1,故收敛.​​ 交错级数优先考虑莱布尼兹判别法，但无法使用时，考虑SnS_nSn​的敛散性\n由于有奇偶性的分别，此时应先考虑S2nS_{2n}S2n​，再由un→0u_n\\rightarrow0un​→0推广到SnS_nSn​的敛散性\n4.∑n=2∞(−1)n[n+(−1)n]p,(p\u0026gt;0)1).当p\u0026gt;1时,∣un∣∼1np,原级数绝对收敛.2).当p≤1时,∣un∣发散,而S2n=(13p−12p)+(15p−14p)+⋯+(1(2n+1)p−1(2n)p)每项都小于0,故{S2n}单调递减.又S2n=−12p+(13p−14p)+⋯+(1(2n−1)p−1(2n)p)+1(2n+1)p\u0026gt;−12p故S2n有下界,故lim⁡n→∞S2n+1=lim⁡n→∞S2n+1(2n+3)p=lim⁡n→∞S2n=S故此时原级数条件收敛.\\begin{align}\u0026amp;4.\\sum_{n=2}^\\infty\\frac{(-1)^n}{[n+(-1)^n]^p},(p\u0026gt;0)\\\\\u0026amp;1).当p\u0026gt;1时,|u_n|\\sim\\frac1{n^p},原级数绝对收敛.\\\\\u0026amp;2).当p\\le1时,|u_n|发散,而S_{2n}=(\\frac1{3^p}-\\frac1{2^p})+(\\frac1{5^p}-\\frac1{4^p})+\\dots+(\\frac1{(2n+1)^p}-\\frac1{(2n)^p})\\\\\u0026amp;每项都小于0,故\\{S_{2n}\\}单调递减.\\\\\u0026amp;又S_{2n}=-\\frac1{2^p}+(\\frac1{3^p}-\\frac1{4^p})+\\dots+(\\frac1{(2n-1)^p}-\\frac1{(2n)^p})+\\frac1{(2n+1)^p}\u0026gt;-\\frac1{2^p}\\\\\u0026amp;故S_{2n}有下界,故\\lim_{n\\rightarrow\\infty}S_{2n+1}=\\lim_{n\\rightarrow\\infty}S_{2n}+\\frac1{(2n+3)^p}=\\lim_{n\\rightarrow\\infty}S_{2n}=S\\\\\u0026amp;故此时原级数条件收敛.\\end{align}​4.n=2∑∞​[n+(−1)n]p(−1)n​,(p\u0026gt;0)1).当p\u0026gt;1时,∣un​∣∼np1​,原级数绝对收敛.2).当p≤1时,∣un​∣发散,而S2n​=(3p1​−2p1​)+(5p1​−4p1​)+⋯+((2n+1)p1​−(2n)p1​)每项都小于0,故{S2n​}单调递减.又S2n​=−2p1​+(3p1​−4p1​)+⋯+((2n−1)p1​−(2n)p1​)+(2n+1)p1​\u0026gt;−2p1​故S2n​有下界,故n→∞lim​S2n+1​=n→∞lim​S2n​+(2n+3)p1​=n→∞lim​S2n​=S故此时原级数条件收敛.​​ 5.下列命题正确的是()A.若∑n=1∞an收敛,则∑n=1∞(−1)nnan收敛an=(−1)nnB.若正项级数∑n=1∞an满足an+1an\u0026lt;1(n=1,2,...),则∑n=1∞an收敛C.若lim⁡n→∞anbn=1,则级数∑n=1∞an与∑n=1∞bn同敛散交错级数D.若an≤bn≤cn(n≥1),且∑n=1∞an和∑n=1∞cn都收敛,则∑n=1∞bn收敛\\begin{align}\u0026amp;5.下列命题正确的是()\\\\\u0026amp;A.若\\sum_{n=1}^\\infty a_n收敛,则\\sum_{n=1}^\\infty\\frac{(-1)^n}{\\sqrt n}a_n收敛\u0026amp;a_n=\\frac{(-1)^n}{\\sqrt n}\\\\\u0026amp;B.若正项级数\\sum_{n=1}^{\\infty}a_n满足\\frac{a_{n+1}}{a_n}\u0026lt;1(n=1,2,...),则\\sum_{n=1}^\\infty a_n收敛\\\\\u0026amp;C.若\\lim_{n\\rightarrow\\infty}\\frac{a_n}{b_n}=1,则级数\\sum_{n=1}^\\infty a_n与\\sum_{n=1}^\\infty b_n同敛散\u0026amp;交错级数\\\\\u0026amp;D.若a_n\\le b_n\\le c_n(n\\ge1),且\\sum_{n=1}^\\infty a_n和\\sum_{n=1}^\\infty c_n都收敛,则\\sum_{n=1}^\\infty b_n收敛\\end{align}​5.下列命题正确的是()A.若n=1∑∞​an​收敛,则n=1∑∞​n​(−1)n​an​收敛B.若正项级数n=1∑∞​an​满足an​an+1​​\u0026lt;1(n=1,2,...),则n=1∑∞​an​收敛C.若n→∞lim​bn​an​​=1,则级数n=1∑∞​an​与n=1∑∞​bn​同敛散D.若an​≤bn​≤cn​(n≥1),且n=1∑∞​an​和n=1∑∞​cn​都收敛,则n=1∑∞​bn​收敛​an​=n​(−1)n​交错级数​​ 交错级数不可使用比较判别法的极限形式判断 类似夹逼定理，在两收敛级数中间的级数也收敛 6.验证y(x)=∑n=0∞x3n(3n)!满足y′′+y′+y=ex,并求∑n=0∞x3n(3n)!的和函数对y(x)逐项求导即可验证.由λ2+λ+1=0,解得λ1,2=−1±3i2故齐次通解为y1(x)=e−12(C1cos⁡32x+C2sin⁡32x)设特解形式为y∗(x)=Aex,则A=13由于y(x)在x=0处展开,故求此处的特解,将x=0代入y(x)=y1(x)+y∗(x)得y(0)=1=C1+13,y′(0)=0=−C12+32C2+13∴y(x)=e−12(23cos⁡32x)+13ex\\begin{align}\u0026amp;6.验证y(x)=\\sum_{n=0}^\\infty\\frac{x^{3n}}{(3n)!}满足y\u0026#x27;\u0026#x27;+y\u0026#x27;+y=e^x,并求\\sum_{n=0}^\\infty\\frac{x^{3n}}{(3n)!}的和函数\\\\\u0026amp;对y(x)逐项求导即可验证.\\\\\u0026amp;由\\lambda^2+\\lambda+1=0,解得\\lambda_{1,2}=\\frac{-1\\pm\\sqrt3i}2\\\\\u0026amp;故齐次通解为y_1(x)=e^{-\\frac12}(C_1\\cos\\frac{\\sqrt3}2x+C_2\\sin\\frac{\\sqrt3}2x)\\\\\u0026amp;设特解形式为y^*(x)=Ae^x,则A=\\frac13\\\\\u0026amp;由于y(x)在x=0处展开,故求此处的特解,将x=0代入\\\\\u0026amp;y(x)=y_1(x)+y^*(x)得y(0)=1=C_1+\\frac13,y\u0026#x27;(0)=0=-\\frac{C_1}2+\\frac{\\sqrt3}2C_2+\\frac13\\\\\u0026amp;\\therefore y(x)=e^{-\\frac12}(\\frac23\\cos\\frac{\\sqrt3}2x)+\\frac13e^x\\end{align}​6.验证y(x)=n=0∑∞​(3n)!x3n​满足y′′+y′+y=ex,并求n=0∑∞​(3n)!x3n​的和函数对y(x)逐项求导即可验证.由λ2+λ+1=0,解得λ1,2​=2−1±3​i​故齐次通解为y1​(x)=e−21​(C1​cos23​​x+C2​sin23​​x)设特解形式为y∗(x)=Aex,则A=31​由于y(x)在x=0处展开,故求此处的特解,将x=0代入y(x)=y1​(x)+y∗(x)得y(0)=1=C1​+31​,y′(0)=0=−2C1​​+23​​C2​+31​∴y(x)=e−21​(32​cos23​​x)+31​ex​​ 7.若an条件收敛,求幂级数∑n=1∞an(x−1)n的收敛域.解:由an条件收敛,得幂级数R=1,收敛区间为(0,2)当x=0时,原级数发散;当x=2时,原级数收敛\\begin{align}\u0026amp;7.若a_n条件收敛,求幂级数\\sum_{n=1}^\\infty a_n(x-1)^n的收敛域.\\\\\u0026amp;解:\\\\\u0026amp;由a_n条件收敛,得幂级数R=1,收敛区间为(0,2)\\\\\u0026amp;当x=0时,原级数发散;当x=2时,原级数收敛\\end{align}​7.若an​条件收敛,求幂级数n=1∑∞​an​(x−1)n的收敛域.解:由an​条件收敛,得幂级数R=1,收敛区间为(0,2)当x=0时,原级数发散;当x=2时,原级数收敛​​ 8.幂级数∑n=0∞2n+1n!xn,求收敛域、和函数.解:易得收敛域为(−∞,+∞)设和函数为S(x),则S(x)=2s(x)−ex,s(x)=∑n=0∞(n+1)xnn!,∫0xs(x)dx=xex,s(x)=(x+1)ex故S(x)=(2x+1)ex,x∈(−∞,+∞)\\begin{align}\u0026amp;8.幂级数\\sum_{n=0}^\\infty\\frac{2n+1}{n!}x^n,求收敛域、和函数.\\\\\u0026amp;解:\\\\\u0026amp;易得收敛域为(-\\infty,+\\infty)\\\\\u0026amp;设和函数为S(x),则S(x)=2s(x)-e^x,\\\\\u0026amp;s(x)=\\sum_{n=0}^\\infty\\frac{(n+1)x^n}{n!},\\\\\u0026amp;\\int_0^xs(x)dx=xe^x,s(x)=(x+1)e^x\\\\\u0026amp;故S(x)=(2x+1)e^x,x\\in(-\\infty,+\\infty)\\end{align}​8.幂级数n=0∑∞​n!2n+1​xn,求收敛域、和函数.解:易得收敛域为(−∞,+∞)设和函数为S(x),则S(x)=2s(x)−ex,s(x)=n=0∑∞​n!(n+1)xn​,∫0x​s(x)dx=xex,s(x)=(x+1)ex故S(x)=(2x+1)ex,x∈(−∞,+∞)​​ 9.求∑n=0∞(n+1)2n!.解:设S(x)=∑n=0∞(n+1)2n!xn,则∫0xS(x)−1=∑n=0∞n+1n!xn+1=(x2+x)ex故S(x)=(x2+3x+1)ex,∴S(1)=5e\\begin{align}\u0026amp;9.求\\sum_{n=0}^\\infty\\frac{(n+1)^2}{n!}.\\\\\u0026amp;解:\\\\\u0026amp;设S(x)=\\sum_{n=0}^\\infty\\frac{(n+1)^2}{n!}x^n,则\\int_0^xS(x)-1=\\sum_{n=0}^\\infty\\frac{n+1}{n!}x^{n+1}=(x^2+x)e^x\\\\\u0026amp;故S(x)=(x^2+3x+1)e^x,\\therefore S(1)=5e\\end{align}​9.求n=0∑∞​n!(n+1)2​.解:设S(x)=n=0∑∞​n!(n+1)2​xn,则∫0x​S(x)−1=n=0∑∞​n!n+1​xn+1=(x2+x)ex故S(x)=(x2+3x+1)ex,∴S(1)=5e​​","permalink":"https://echo.ishian.me/2024/04/17/calculus-series-exercise/","summary":"整理常数项级数敛散性判别与幂级数求和的解题思路和常用结论","title":"微积分: 级数练习题"},{"content":"无穷级数 常数项级数 定义 无穷多个数相加，记为\n∑n=1∞un\\begin{align}\\sum_{n=1}^\\infty u_n\\end{align}n=1∑∞​un​​​，称为常数项无穷级数，又称常数项级数或级数\n部分和数列 令\nSn=∑k=1nuk(n=1,2,...)\\begin{align}S_n=\\sum_{k=1}^nu_k(n=1,2,...)\\end{align}Sn​=k=1∑n​uk​(n=1,2,...)​​，我们说，当n→∞n\\rightarrow\\inftyn→∞时，\nSn≈∑n=1∞un\\begin{align}S_n\\approx\\sum_{n=1}^\\infty u_n\\end{align}Sn​≈n=1∑∞​un​​​敛散性 当\nlim⁡n→∞Sn=S\\begin{align}\\lim_{n\\rightarrow\\infty}S_n=S\\end{align}n→∞lim​Sn​=S​​，且SSS为一个有限值时，称\n∑n=1∞un\\begin{align}\\sum_{n=1}^\\infty u_n\\end{align}n=1∑∞​un​​​收敛，且称SSS为它的和，否则称它发散\n对于敛散性问题，我们将它转换成求SnS_nSn​通项，再判断极限是否存在的问题\n常见级数 调和级数： ∑n=1∞1n\\begin{align}\\sum_{n=1}^\\infty \\frac1n\\end{align}n=1∑∞​n1​​​发散 证明:un=1n=∫nn+11ndx\u0026gt;∫nn+11xdx∴Sn\u0026gt;∫1n+11xdx=ln⁡x∣1+∞=+∞∴∑n=1∞1n发散\\begin{align}\u0026amp;证明:u_n=\\frac1n=\\int_n^{n+1}\\frac1ndx\u0026gt;\\int_n^{n+1}\\frac1xdx\\\\\u0026amp;\\therefore S_n\u0026gt;\\int_1^{n+1}\\frac1xdx=\\ln x\\Bigg|_1^{+\\infty}=+\\infty\\\\\u0026amp;\\therefore\\sum_{n=1}^\\infty \\frac1n发散 \\end{align}​证明:un​=n1​=∫nn+1​n1​dx\u0026gt;∫nn+1​x1​dx∴Sn​\u0026gt;∫1n+1​x1​dx=lnx​1+∞​=+∞∴n=1∑∞​n1​发散​​ p级数(超调和级数)： ∑n=1∞1np\\begin{align}\\sum_{n=1}^\\infty\\frac1{n^p}\\end{align}n=1∑∞​np1​​​当p≤1p\\le1p≤1时发散，当p\u0026gt;1p\u0026gt;1p\u0026gt;1时收敛 证明:un=1np=∫nn+11npdx\u0026gt;∫nn+11xpdxun=1np=∫n−1n1npdx\u0026lt;∫n−1n1xpdx∴Sn\u0026gt;∫1n+11xpdx，Sn\u0026lt;∫0n1xpdx1)当p=1时，为调和级数，已证明为发散2)当p\u0026lt;1时，Sn\u0026gt;11−px1−p∣1+∞，易得p\u0026lt;1时发散3)当p\u0026gt;1时，Sn\u0026lt;11−px1−p∣1+∞，易得p\u0026gt;1时收敛\\begin{align}\u0026amp;证明:u_n=\\frac1{n^p}=\\int_n^{n+1}\\frac1{n^p}dx\u0026gt;\\int_n^{n+1}\\frac1{x^p}dx\\\\\u0026amp;u_n=\\frac1{n^p}=\\int_{n-1}^{n}\\frac1{n^p}dx\u0026lt;\\int_{n-1}^{n}\\frac1{x^p}dx\\\\\u0026amp;\\therefore S_n\u0026gt;\\int_1^{n+1}\\frac1{x^p}dx，S_n\u0026lt;\\int_0^n\\frac1{x^p}dx\\\\\u0026amp;1)当p=1时，为调和级数，已证明为发散\\\\\u0026amp;2)当p\u0026lt;1时，S_n\u0026gt;\\frac1{1-p}x^{1-p}\\Bigg|_1^{+\\infty}，易得p\u0026lt;1时发散\\\\\u0026amp;3)当p\u0026gt;1时，S_n\u0026lt;\\frac1{1-p}x^{1-p}\\Bigg|_1^{+\\infty}，易得p\u0026gt;1时收敛\\end{align}​证明:un​=np1​=∫nn+1​np1​dx\u0026gt;∫nn+1​xp1​dxun​=np1​=∫n−1n​np1​dx\u0026lt;∫n−1n​xp1​dx∴Sn​\u0026gt;∫1n+1​xp1​dx，Sn​\u0026lt;∫0n​xp1​dx1)当p=1时，为调和级数，已证明为发散2)当p\u0026lt;1时，Sn​\u0026gt;1−p1​x1−p​1+∞​，易得p\u0026lt;1时发散3)当p\u0026gt;1时，Sn​\u0026lt;1−p1​x1−p​1+∞​，易得p\u0026gt;1时收敛​​ ∑n=2∞1np(ln⁡x)q\\begin{align}\\sum_{n=2}^\\infty\\frac1{n^p(\\ln x)^q}\\end{align}n=2∑∞​np(lnx)q1​​​当p\u0026lt;1p\u0026lt;1p\u0026lt;1时发散；当p\u0026gt;1p\u0026gt;1p\u0026gt;1时收敛；当p=1p=1p=1时，{发散,q≤1收敛,q\u0026gt;1\\begin{cases}发散,\u0026amp;q\\le1\\\\收敛,\u0026amp;q\u0026gt;1\\end{cases}{发散,收敛,​q≤1q\u0026gt;1​ 级数的性质 一个级数乘以一个实数不改变其敛散性 两收敛级数相加减得到的级数仍收敛，且S=S1±S2S=S_1\\pm S_2S=S1​±S2​ 改变级数的有限个项，级数的敛散性不变 若用括号将级数(S1)的部分项合并为一项，将得到的级数称为S2，若S1收敛则S2仍收敛，若S2发散则S1必发散 ∑n=1∞un收敛⇒lim⁡n→∞un=0，反之不成立(例如调和级数); lim⁡n→∞un≠0⇒∑n=1∞un发散\\begin{align}\\sum_{n=1}^\\infty u_n收敛\\Rightarrow\\lim_{n\\rightarrow\\infty}u_n=0，反之不成立(例如调和级数);\\ \\lim_{n\\rightarrow\\infty}u_n\\ne0\\Rightarrow\\sum_{n=1}^\\infty u_n发散\\end{align}n=1∑∞​un​收敛⇒n→∞lim​un​=0，反之不成立(例如调和级数); n→∞lim​un​=0⇒n=1∑∞​un​发散​​ 正项级数的比较判别法 一般形式 对于正项级数，{Sn}\\{S_n\\}{Sn​}本身是单调递增的，根据单调有界收敛原理，若\nlim⁡n→∞Sn\\begin{align}\\lim_{n\\rightarrow\\infty}S_n\\end{align}n→∞lim​Sn​​​存在，则该级数收敛\n正项级数的比较判别法与无穷区间上反常积分的比较判别法类似： 若存在N∈N+,当n≥N时0\u0026lt;un≤vn,若∑n=1∞un发散,则∑n=1∞vn发散;若∑n=1∞vn收敛,则∑n=1∞un收敛 \\begin{align}若存在N\\in N^+,当n\\ge N时0\u0026lt;u_n\\le v_n,若\\sum_{n=1}^\\infty u_n发散,则\\sum_{n=1}^\\infty v_n发散;若\\sum_{n=1}^\\infty v_n收敛,则\\sum_{n=1}^\\infty u_n收敛\\end{align} 若存在N∈N+,当n≥N时0\u0026lt;un​≤vn​,若n=1∑∞​un​发散,则n=1∑∞​vn​发散;若n=1∑∞​vn​收敛,则n=1∑∞​un​收敛​​ 在用比较判别法判断时，应强调一般项的正负和用于比较的标准级数\n标准级数一般为p级数、等比级数等\n极限形式 若lim⁡n→∞unvn=l(un、vn\u0026gt;0):1)若0\u0026lt;l\u0026lt;+∞，则∑n=1∞un和∑n=1∞vn的敛散性相同(此时k1vn\u0026lt;un\u0026lt;k2vn)2)若l=0，则说明un\u0026lt;vn，对应比较判别法3)若l=+∞，则说明vn\u0026lt;un \\begin{align}\u0026amp;\\large若\\lim_{n\\rightarrow\\infty}\\frac{u_n}{v_n}=l(u_n、v_n\u0026gt;0):\\\\\u0026amp;\\large1)若0\u0026lt;l\u0026lt;+\\infty，则\\sum_{n=1}^\\infty u_n和\\sum_{n=1}^\\infty v_n的敛散性相同(此时k_1v_n\u0026lt;u_n\u0026lt;k_2v_n)\\\\\u0026amp;\\large2)若l=0，则说明u_n\u0026lt;v_n，对应比较判别法\\\\\u0026amp;\\large3)若l=+\\infty，则说明v_n\u0026lt;u_n\\end{align} ​若n→∞lim​vn​un​​=l(un​、vn​\u0026gt;0):1)若0\u0026lt;l\u0026lt;+∞，则n=1∑∞​un​和n=1∑∞​vn​的敛散性相同(此时k1​vn​\u0026lt;un​\u0026lt;k2​vn​)2)若l=0，则说明un​\u0026lt;vn​，对应比较判别法3)若l=+∞，则说明vn​\u0026lt;un​​​正项级数的比值判别法 比较判别法需要借助一个别的标准级数，而比值判别法由级数本身来确定它的敛散性 若lim⁡n→∞un+1un=ρ(un\u0026gt;0):1)当ρ\u0026lt;1时，级数收敛2)当ρ\u0026gt;1或ρ=+∞时，级数发散 \\begin{align}\u0026amp;\\large若\\lim_{n\\rightarrow\\infty}\\frac{u_{n+1}}{u_n}=\\rho(u_n\u0026gt;0):\\\\\u0026amp;\\large1)当\\rho\u0026lt;1时，级数收敛\\\\\u0026amp;\\large2)当\\rho\u0026gt;1或\\rho=+\\infty时，级数发散\\end{align} ​若n→∞lim​un​un+1​​=ρ(un​\u0026gt;0):1)当ρ\u0026lt;1时，级数收敛2)当ρ\u0026gt;1或ρ=+∞时，级数发散​​正项级数的根植判别法 也称柯西判别法，当一般项的外部带n次方时可以使用 若lim⁡n→∞unn=ρ(un\u0026gt;0):1)当ρ\u0026lt;1时，级数收敛2)当ρ\u0026gt;1或ρ=+∞时，级数发散 \\begin{align}\u0026amp;\\large若\\lim_{n\\rightarrow\\infty}\\sqrt[n]{u_n}=\\rho(u_n\u0026gt;0):\\\\\u0026amp;\\large1)当\\rho\u0026lt;1时，级数收敛\\\\\u0026amp;\\large2)当\\rho\u0026gt;1或\\rho=+\\infty时，级数发散\\end{align} ​若n→∞lim​nun​​=ρ(un​\u0026gt;0):1)当ρ\u0026lt;1时，级数收敛2)当ρ\u0026gt;1或ρ=+∞时，级数发散​​正项级数的积分判别法 上述的调和级数和p级数敛散性问题借用反常积分转换为了反常函数判别法的问题，对于正项级数，如果它的一般项单调递减，则它与\n∫1+∞u(x)dx\\begin{align}\\int_1^{+\\infty}u(x)dx\\end{align}∫1+∞​u(x)dx​​的敛散性相同\n交错级数的莱布尼茨判别法 交错级数，即各项正负相间的级数，表现为\n∑n=1∞(−1)n−1un\\begin{align}\\sum_{n=1}^\\infty(-1)^{n-1}u_n\\end{align}n=1∑∞​(−1)n−1un​​​若unu_nun​单调递减且\nlim⁡n→∞un=0\\begin{align}\\lim_{n\\rightarrow\\infty}u_n=0\\end{align}n→∞lim​un​=0​​，则交错级数收敛\n绝对收敛与条件收敛 对任意项级数，由其每一项的绝对值组成的新级数称为绝对值级数，如果它收敛，则原级数也收敛，这称为原级数绝对收敛。因此对于一个交错级数，既可以用莱布尼茨判别法，也可以用正项级数的判别法\n但是，当绝对值级数发散时，不能断定原级数发散，而只能说明它不是绝对收敛。如果原级数收敛而绝对值级数发散，则称原级数条件收敛，例如\n∑n=1∞(−1)n−11n\\begin{align}\\sum_{n=1}^\\infty(-1)^{n-1}\\frac1n\\end{align}n=1∑∞​(−1)n−1n1​​​因为交错级数的部分和数列是小于它的绝对值级数的部分和数列的，这就很像比较判别法：大的收敛则小的也收敛；而大的发散，小的不一定发散\n但是，由比值判别法或根值判别法得出绝对值级数发散的原级数也发散，这很好理解：由比值判别法得到unu_nun​单调递增，不符合莱布尼茨判别法的第一条件；由根值判别法得到\nlim⁡n→∞un=+∞\\begin{align}\\lim_{n\\rightarrow\\infty}u_n=+\\infty\\end{align}n→∞lim​un​=+∞​​，不符合莱布尼茨判别法的第二条件\n-\u0026gt;插曲：一个很有意思的事，欧拉证明欧拉公式时用到了泰勒级数的知识，被称为形式推导，没有考虑到eiπe^{i\\pi}eiπ展开后条件收敛的情况，这时调节项的位置有可能改变它的敛散性，这样证明是不严谨的\n黎曼重排定理：一个条件收敛的级数存在不同的排序方法，使其收敛于任意数(包括+∞+\\infty+∞)\n证明：假设级数\n∑n=1∞un\\begin{align}\\sum_{n=1}^\\infty u_n\\end{align}n=1∑∞​un​​​条件收敛，由于\n∑n=1∞∣un∣\\begin{align}\\sum_{n=1}^\\infty |u_n|\\end{align}n=1∑∞​∣un​∣​​发散，故必有发散的两个子级数\n∑n=1∞an、∑n=1∞bn\\begin{align}\\sum_{n=1}^\\infty a_n、\\sum_{n=1}^\\infty b_n\\end{align}n=1∑∞​an​、n=1∑∞​bn​​​，其中an\u0026lt;0,bn\u0026gt;0a_n\u0026lt;0,b_n\u0026gt;0an​\u0026lt;0,bn​\u0026gt;0，由原级数条件收敛的性质，虽然它们发散，但\nlim⁡n→∞an=lim⁡n→∞bn=0\\begin{align}\\lim_{n\\rightarrow\\infty}a_n=\\lim_{n\\rightarrow\\infty}b_n=0\\end{align}n→∞lim​an​=n→∞lim​bn​=0​​设想一个值S，它可以是有限值或无穷，从bnb_nbn​中取有限个项直至重排数列的和大于S，再从ana_nan​中取有限个项直至重排数列的和小于S，重复此操作直到an与bna_n与b_nan​与bn​剩下无穷个趋于0的项，这样，重排后的数列和将无限接近S\n一个简单、不严谨的例子：级数(1−1)+(1−1)+...(1-1)+(1-1)+...(1−1)+(1−1)+...收敛于0，取一个1放在开头，变成：1+(1−1)+(1−1)+...1+(1-1)+(1-1)+...1+(1−1)+(1−1)+...，则它收敛于1。这只是便于理解，但它是错误的，不能随意去掉收敛级数的括号或给发散级数添加无限个括号\n函数项级数 定义 所谓函数项级数，就是每一项都由一个函数组成的级数\n如果在x=x0x=x_0x=x0​处形成的数项级数收敛，则称它为这个函数项级数的收敛点，否则称为发散点\n所有收敛点的集合称为这个幂级数的收敛域\n幂级数 幂级数是最广泛的一类函数项级数，\n∑n=0∞anxn\\begin{align}\\sum_{n=0}^\\infty a_nx^n\\end{align}n=0∑∞​an​xn​​称为xxx的幂级数\n阿贝尔定理 若一个幂级数在x=x0x=x_0x=x0​处收敛，则它在(−∣x0∣, ∣x0∣)(-|x_0|,\\ |x_0|)(−∣x0​∣, ∣x0​∣)上绝对收敛；如果在此处发散，则它在(−∞, −∣x0∣)∪(∣x0∣, +∞)(-\\infty,\\ -|x_0|)\\cup(|x_0|,\\ +\\infty)(−∞, −∣x0​∣)∪(∣x0​∣, +∞)上发散\n幂级数只有三种情况：存在一个RRR\n如果0\u0026lt;R\u0026lt;+∞0\u0026lt;R\u0026lt;+\\infty0\u0026lt;R\u0026lt;+∞，则幂级数在(−R,R)(-R,R)(−R,R)上绝对收敛，在(−∞,−R)∪(R,+∞)(-\\infty,-R)\\cup(R,+\\infty)(−∞,−R)∪(R,+∞)上发散 如果R=0R=0R=0，则幂级数处处发散 如果R=+∞R=+\\inftyR=+∞，则幂级数处处绝对收敛 所以，收敛域一般是形如(−R,R)(-R,R)(−R,R)的区间，因此在x=±Rx=\\pm Rx=±R处需要额外判断敛散性\n收敛半径计算法 RRR的求法类似比值判别法，对于幂级数\n∑n=0∞anxn\\begin{align}\\sum_{n=0}^\\infty a_nx^n\\end{align}n=0∑∞​an​xn​​的绝对值级数，假设有：\nlim⁡n→∞∣an+1xn+1anxn∣=ρ∣x∣,即lim⁡n→∞∣an+1an∣=ρ\\begin{align}\\lim_{n\\rightarrow\\infty}\\Bigg|\\frac{a_{n+1}x^{n+1}}{a_nx^n}\\Bigg|=\\rho|x|,即\\lim_{n\\rightarrow\\infty}\\Bigg|\\frac{a_{n+1}}{a_n}\\Bigg|=\\rho\\end{align}n→∞lim​​an​xnan+1​xn+1​​=ρ∣x∣,即n→∞lim​​an​an+1​​​=ρ​​则当ρ∣x∣\u0026lt;1\\rho|x|\u0026lt;1ρ∣x∣\u0026lt;1，即\n∣x∣\u0026lt;1ρ\\begin{align}|x|\u0026lt;\\frac1\\rho\\end{align}∣x∣\u0026lt;ρ1​​​时，级数收敛，反之发散\n因此：当ρ≠0\\rho\\ne0ρ=0时，\nR=1ρ\\begin{align}R=\\frac1\\rho\\end{align}R=ρ1​​​；当ρ=0\\rho=0ρ=0时，R=+∞R=+\\inftyR=+∞；当ρ=+∞\\rho=+\\inftyρ=+∞时，R=0R=0R=0\n所以，我们求\nlim⁡n→∞∣anan+1∣\\begin{align}\\lim_{n\\rightarrow\\infty}\\Bigg|\\frac{a_n}{a_{n+1}}\\Bigg|\\end{align}n→∞lim​​an+1​an​​​​​，对应的结果便是RRR\n这个方法只适用于缺有限项或不缺项的幂级数，而类似\n∑n=0∞anx2n\\begin{align}\\sum_{n=0}^\\infty a_nx^{2n}\\end{align}n=0∑∞​an​x2n​​这样缺无穷多项的级数应直接用比值判别法\n推论：形似\n∑n=0∞anxkn\\begin{align}\\sum_{n=0}^\\infty a_nx^{kn}\\end{align}n=0∑∞​an​xkn​​的收敛半径为Rk\\sqrt[k]RkR​\n性质 线性运算：两个幂级数相加减，收敛半径为R=min⁡(R1,R2)R=\\min(R_1,R_2)R=min(R1​,R2​) 乘法运算：两个幂级数相乘，收敛半径也为RRR 若 ∑n=0∞anxn\\begin{align}\\sum_{n=0}^\\infty a_nx^n\\end{align}n=0∑∞​an​xn​​收敛半径为RRR，则 ∑n=1∞nanxn−1、∑n=2∞n(n−1)anxn−2\\begin{align}\\sum_{n=1}^\\infty na_nx^{n-1}、\\sum_{n=2}^\\infty n(n-1)a_nx^{n-2}\\end{align}n=1∑∞​nan​xn−1、n=2∑∞​n(n−1)an​xn−2​​和 ∑n=0∞1n+1anxn+1\\begin{align}\\sum_{n=0}^\\infty \\frac{1}{n+1}a_nx^{n+1}\\end{align}n=0∑∞​n+11​an​xn+1​​的收敛半径也为RRR 假设幂级数的和函数为S(x)S(x)S(x)，收敛半径为RRR，则S(x)S(x)S(x)在(−R,R)(-R,R)(−R,R)上连续且可导且可积，且： S′(x)=∑n=1∞nanxn−1\\begin{align}S\u0026#x27;(x)=\\sum_{n=1}^\\infty na_nx^{n-1}\\end{align}S′(x)=n=1∑∞​nan​xn−1​​，根据第三点，S′(x)S\u0026#x27;(x)S′(x)对应的幂级数收敛半径也为RRR ∫0xS(x)dx=∑n=0∞1n+1anxn+1\\begin{align}\\int_0^xS(x)dx=\\sum_{n=0}^\\infty \\frac{1}{n+1}a_nx^{n+1}\\end{align}∫0x​S(x)dx=n=0∑∞​n+11​an​xn+1​​，根据第三点，它对应的幂级数收敛半径也为RRR 求导或积分后对应的幂级数在x=Rx=Rx=R上的敛散性可能变化 泰勒级数 幂级数形式简单，有许多性质，而且可以把无法用初等函数表示的函数写成幂级数(例如一些函数的原函数)\n把已知函数化为幂级数：\n由f(x)在x=x0f(x)在x=x_0f(x)在x=x0​处的泰勒展开\nf(x)=f(x0)+f′(x0)(x−x0)+f′′(x0)2!(x−x0)2+...\\begin{align}f(x)=f(x_0)+f\u0026#x27;(x_0)(x-x_0)+\\frac{f\u0026#x27;\u0026#x27;(x_0)}{2!}(x-x_0)^2+...\\end{align}f(x)=f(x0​)+f′(x0​)(x−x0​)+2!f′′(x0​)​(x−x0​)2+...​​，得f(x)f(x)f(x)在x=x0x=x_0x=x0​处的泰勒级数为\nf(x)=∑n=0∞f(n)(x0)n!(x−x0)n\\begin{align}f(x)=\\sum_{n=0}^\\infty \\frac{f^{(n)}(x_0)}{n!}{(x-x_0)}^n\\end{align}f(x)=n=0∑∞​n!f(n)(x0​)​(x−x0​)n​​，充要条件为f(x)f(x)f(x)在x=x0x=x_0x=x0​处有任意阶导数且\nlim⁡n→∞(拉格朗日型余项)\\begin{align}\\lim_{n\\rightarrow\\infty}(拉格朗日型余项)\\end{align}n→∞lim​(拉格朗日型余项)​​趋于0\n一般地，将f(x)f(x)f(x)展开为泰勒级数的步骤为：\n对f(x)f(x)f(x)高阶求导，得到幂级数 求幂级数的收敛域 证明 lim⁡n→∞f(n+1)(ξ)(n+1)!(x−x0)n+1=0\\begin{align}\\lim_{n\\rightarrow\\infty}\\frac{f^{(n+1)}(\\xi)}{(n+1)!}(x-x_0)^{n+1}=0\\end{align}n→∞lim​(n+1)!f(n+1)(ξ)​(x−x0​)n+1=0​​ 更好的方法为：记住常见的泰勒级数，对它们或求导，或积分，或换元来求所求函数的幂级数，例如要求g(x)=f′(x)=cos⁡xg(x)=f\u0026#x27;(x)=\\cos xg(x)=f′(x)=cosx的级数，只需对f(x)=sin⁡xf(x)=\\sin xf(x)=sinx的泰勒级数逐项求导即可\n常见的麦克劳林级数：\n傅里叶级数 f(x)=a02+∑n=1∞(ancos⁡nx+bnsin⁡nx)\\begin{align}f(x)=\\frac{a_0}2+\\sum_{n=1}^\\infty(a_n\\cos nx+b_n\\sin nx)\\end{align}f(x)=2a0​​+n=1∑∞​(an​cosnx+bn​sinnx)​​狄利克雷收敛定理 如果Tf(x)=2πT_{f(x)}=2\\piTf(x)​=2π且f(x)f(x)f(x)在(−π,π](-\\pi,\\pi](−π,π]上满足：\n连续或有有限个第一类间断点 有有限个单调区间 则f(x)f(x)f(x)可展开为处处收敛的傅里叶级数且\nS(x)=a02+∑n=1∞(ancos⁡nx+bnsin⁡nx)={f(x),在x处连续 lim⁡x→x−f(x)+lim⁡x→x+f(x)2,x为间断点a0=1π∫−ππf(x)dx, an=1π∫−ππf(x)cos⁡nxdx, bn=1π∫−ππf(x)sin⁡nxdx\\begin{align}\u0026amp;S(x)=\\frac{a_0}2+\\sum_{n=1}^\\infty(a_n\\cos nx+b_n\\sin nx)=\\begin{cases}f(x),\u0026amp;在x处连续\\\\\\ \\large\\frac{\\lim_{x\\rightarrow x^-}f(x)+\\lim_{x\\rightarrow x^+}f(x)}2,\u0026amp;x为间断点\\end{cases}\\\\\u0026amp;a_0=\\frac1\\pi\\int_{-\\pi}^\\pi f(x)dx,\\ a_n=\\frac1\\pi\\int_{-\\pi}^\\pi f(x)\\cos nxdx,\\ b_n=\\frac1\\pi\\int_{-\\pi}^\\pi f(x)\\sin nxdx\\end{align}​S(x)=2a0​​+n=1∑∞​(an​cosnx+bn​sinnx)={f(x), 2limx→x−​f(x)+limx→x+​f(x)​,​在x处连续x为间断点​a0​=π1​∫−ππ​f(x)dx, an​=π1​∫−ππ​f(x)cosnxdx, bn​=π1​∫−ππ​f(x)sinnxdx​​T=2l\\bf T=2lT=2l(一般周期)函数 f(x)∼a02+∑n=1∞(ancos⁡πlnx+bnsin⁡πlnx)a0=1l∫−llf(x)dx, an=1l∫−llf(x)cos⁡πlnxdx, bn=1l∫−llf(x)sin⁡πlnxdx\\begin{align}\u0026amp;f(x)\\sim\\frac{a_0}2+\\sum_{n=1}^\\infty(a_n\\cos \\frac\\pi lnx+b_n\\sin \\frac\\pi lnx)\\\\\u0026amp;a_0=\\frac1l\\int_{-l}^l f(x)dx,\\ a_n=\\frac1l\\int_{-l}^l f(x)\\cos \\frac \\pi lnxdx,\\ b_n=\\frac1l\\int_{-l}^l f(x)\\sin \\frac \\pi lnxdx\\end{align}​f(x)∼2a0​​+n=1∑∞​(an​coslπ​nx+bn​sinlπ​nx)a0​=l1​∫−ll​f(x)dx, an​=l1​∫−ll​f(x)coslπ​nxdx, bn​=l1​∫−ll​f(x)sinlπ​nxdx​​延拓：给一个只在有限区间上有定义的函数添加定义(如新定义它的周期)，从而拓广函数的定义域\n在[0,l][0,l][0,l]上有定义的函数的傅里叶展开：在[−l,0)[-l,0)[−l,0)上添加定义即可，有两种：\n奇延拓：使其展成正弦级数， f(x)∼∑n=1∞bnsin⁡πlnx\\begin{align}f(x)\\sim \\sum_{n=1}^\\infty b_n\\sin \\frac\\pi lnx\\end{align}f(x)∼n=1∑∞​bn​sinlπ​nx​​ 偶延拓：使其展成余弦级数， f(x)∼a02+∑n=1∞ancos⁡πlnx\\begin{align}f(x)\\sim\\frac{a_0}2+\\sum_{n=1}^\\infty a_n\\cos \\frac\\pi lnx\\end{align}f(x)∼2a0​​+n=1∑∞​an​coslπ​nx​​ ","permalink":"https://echo.ishian.me/2024/04/14/calculus-series/","summary":"讲解常数项级数的敛散性、常见级数、性质及幂级数的相关概念","title":"微积分: 级数"},{"content":"Regex 基本介绍 Regex全称Regular Expression，译为正则表达式或正规表达式 Regex最常用于文本内容的模糊搜索，旨在使用一系列的语法规则高效地匹配任意文本 抽象定义如下： 字母表：由所有可能出现的字符组成的集合 串：空串以及字符集中的字符的组合 语言：串的集合 正则表达式：经过形式化描述的规则，文本内容(整个语言)经过给定的正则表达式得到匹配结果(语言的子集，称为正规集) 正则表达式用一系列简洁的符号来表示语言(字符串集合)的运算： 语言的并(即两边集合的任意元素)：(r) | (s) 语言的连接(即在左边集合的任意串后拼接右边集合的任意串)：(r)(s) 语言的闭包(即集合中的串出现任意次地连接起来)：(r)* 语言的正闭包(即集合中的串出现至少一次地连接起来)：(r)+ 实际上是一个语法糖，等价于(r)(r)* 语言与空串的并(语法糖)，表示集合中的串出现至多一次：(r)? 运算的优先级：* \u0026gt; 连接 \u0026gt; | 在实现上，正则表达式拥有多套标准 POSIX在制定类Unix操作系统的接口时顺带制定了regex的标准，POSIX标准又分为基础BRE与扩展ERE两种 Perl语言经过多次迭代后由于其regex功能十分强大好用，出现了PCRE标准，可以说它是一套工业标准 PCRE PCRE全称Perl Compatible Regular Expressions，是用C编写的兼容Perl(一种不再流行的语言)正规式语法的正则表达式库 现在的大多数编程语言的正则表达式库属于PCRE派系，因此它也成为了一种标准 匹配、分组、环视 默认情况下，正则表达式会正向扫描，并将匹配成功的文本消耗掉，因此实现单遍扫描的效果 如果希望使用已经被扫描过的字符，可以由以下方法实现： 默认情况下，正则表达式会贪婪地消耗字符，例如面对a.*a这样的表达式，它不会遇到两个a就停止而是继续匹配直到遇到最后一个a 分组：由元字符()括起，一个分组可以表示一个完整的正则表达式，分组有以下类型： 匿名捕获分组：类似(exp)的分组，它会消耗匹配的字符，同时存储它们，随后可用\\正整数引用第正整数个分组的匹配结果 注意引用分组结果和完整的正则表达式不一样，它们等价于匹配的结果而不是正则表达式 非捕获分组：类似(?:exp)的分组，它会消耗匹配的字符，但不会存储它们 命名捕获分组：类似(?P\u0026lt;name\u0026gt; exp)的分组，在匿名捕获分组的基础上，它会将匹配结果赋给name，命名分组同时也会占用整数引用(相当于有两个名字) 定义命名捕获分组后，可以使用(?P=\u0026lt;name\u0026gt;)引用由name这个分组匹配到的字符串 如果希望不消耗字符，即多遍扫描，可以使用环视，或者称为预查 预查是一种零宽度断言，会不消耗字符地检查后续(正向)、之前(反向)的文本是否存在能被exp匹配上的匹配项 这种行为通常用于条件判断，防止匹配过程中把检查项中间的文本也消耗掉了，导致中间的文本没有经过检查 肯定正向预查：类似(?=exp)的表达式 否定正向预查：类似(?!exp)的表达式，返回肯定正向预查的相反值 肯定反向预查：类似(?\u0026lt;=exp)的表达式 否定反向预查：类似(?\u0026lt;!exp)的表达式 这里的反向指的是在右边表达式的匹配项的位置上，从右到左查找 零宽度断言十分有用，例如当我们需要依据边界来获取内容，但又不希望匹配到边界字符本身的时候，使用预查十分有效 其它常用元字符 在实现上，此前所说的*、+、?等由于拥有额外的含义，因此自然地需要区分转义与非转义，这些字符称为元字符 元字符不一定是转义/非转义字符，例如*、+、?等不需要用\\转义就能表达特殊含义，b、z等需要用\\转义才能表达特殊含义 .：匹配除\\n外的任意字符 锚点字符：确定匹配位置，它们是零宽度的断言，仅匹配但不消耗字符，可以理解为匹配一个间隙 实际上它们算是一些语法糖，都可以用预查来表示 ^：匹配一行的开始位置 $：匹配一行的结束位置 \\b：匹配字母与非字母之间的位置 \\B：匹配字母与字母之间的位置 \\G：匹配上一次匹配结束的位置，用于链式搜索 \\A、\\Z、\\z：匹配整个文件的开始、第一行结束、结束 量词字符：作用于前面的正则表达式，表示匹配的个数 *：匹配任意次 ?：匹配至多一次 +：匹配至少一次 {n}：匹配恰好n次 {n, }：匹配至少n次 {a, b}：匹配至少a次、至多b次 惰性匹配：在上述量词后添加?，这个匹配项会尽可能少地匹配字符 字符组：匹配集合内的单个字符 []：字符集合应由[]括住 -：在字符集合里，如果被两个字符夹住，则a-b表示从a到b的连续字符 例如[a-z]表示匹配一个a到z的字符 ^：若出现在[]内的开始位置，则表示反集，匹配不在集合内的单个字符 例如[^a-z]表示匹配一个不是a到z的字符 此外，还有一些元字符等价于常用的字符组(但可能部分语言不支持)： \\w等价于[a-zA-Z0-9]，\\W等价于[^a-zA-Z0-9] \\s等价于[ \\t\\n\\r]，\\S等价于[^ \\t\\n\\r] \\d等价于[0-9]，\\D等价于[^0-9] |：用于分割不同的表达式，通常用法是由()包裹并随之使用量词来实现分支结合多次选择的效果 |的优先级是小于()的 关于转义：需要看被什么环境包裹 如果最外层为()，说明内部会被视为正常的完整的表达式，所有元字符是否转义的规则同上 如果除去最外层的所有()后剩下的最外层为[]，说明内部是字符类，大部分字符(包括元字符)不需要用\\转义就能表示普通字符 Vim的Regex SQL的Regex ","permalink":"https://echo.ishian.me/2024/04/02/regex/","summary":"介绍正则表达式的形式化定义、PCRE语法及匹配、分组、环视等用法","title":"正则表达式: 使用regex进行模糊查询"},{"content":"string类 ctors c\u0026#43;\u0026#43;// 定义:uint指size_type,即size_t,或许是unsigned int或unsigned long long string(); // 默认ctor,创建长度为0的对象 string(uint n,char c); // 创建内容为n个字符c构成的字符串的对象 string(const char*); // 提供C风格字符串,创建内容为该字符串的对象 string(const char*,uint n);// 提供C风格字符串和一个长度,创建内容为字符串前n个字符的对象 string(const string\u0026amp;); // 复制构造函数,提供一个string对象,创建与该对象完全相同的新对象 // 创建内容为下标从idx开始数,一共n(默认为其长度)个字符的对象,即string(str,0)与string(str)等效 string(const string\u0026amp;,uint idx,uint n=npos); string(It begin,It end);// 提供begin,end两指针或两迭代器,创建内容为[begin,end)的对象 string(string\u0026amp;\u0026amp;); // 移动构造函数,可在临时对象将要析构而资源需要再利用时使用 string(initializer_list\u0026lt;char\u0026gt;);// 提供由单字符组成的列表,创建内容为它们整合而成的字符串的对象 // 示例 char CStr[6] = \u0026#34;12345\u0026#34;; // C风格字符串 string A_str; // 默认ctor string B_str(\u0026#34;12345\u0026#34;); // string字符串 string C_str(CStr); string C_str(B_str); // 拆子串的方法 string D_str(CStr,3) // D_str==\u0026#34;123\u0026#34; 新对象字符数为3,内容为前3个字符 string E_str(B_str,1); // E_str==\u0026#34;2345\u0026#34;新对象字符数为5(字符串长)-1,内容为下标1开始到结尾 string F_str(B_str,1,2);// F_str==\u0026#34;23\u0026#34; 新对象字符数为2,内容为下标1开始数,2个字符 auto it = B_str.begin();// type(it)==string::iterator string G_str(it,it+3); // G_str==\u0026#34;123\u0026#34; 新对象字符数等于it+3-it,内容为it开始数,3个字符 与C风格字符串不同，string不带'\\0'，在输入时也不需要考虑字符串的长度，string类会自动调整到刚刚好的空间，它所能容纳的长度为string::npos=-1(无符号整型的最大值)，在不同位数处理器下会有不同，可能是unsigned long long或unsigned int的最大值\n这些构造函数使得需要创建子串对象时只需要知道起始点和子串字符数，就能很快算出参数\n关于复制构造函数和移动构造函数的区别，以一个临时对象为例，假设还需要使用这个对象的内容，在被析构时，如果使用复制构造函数转移内容，需要：\n申请新的堆内存 复制粘贴临时对象的内容 释放临时对象的堆内存 而移动构造函数只需要将临时对象的堆内存转移给新对象，这个ctor通常由编译器自行选择使用，以优化性能，如果手动使用，需要注意它不保证const，可能造成问题\n方法 重载运算符 关于部分重载运算符如=,+=,==,[],\u0026lt;\u0026lt;,\u0026gt;\u0026gt;等，较容易理解，不赘述\n关于比较运算符\u0026gt;,\u0026lt;,\u0026gt;=,\u0026lt;=，它们调用的方法类似C中的strcmp()，是比较字符的ASCII码大小而非长度\nstring类没有定义-=,-(),-(other)运算符\n需要注意用[]访问时，编译时不会抛出异常，即越界时有可能发生运行时错误\n遍历访问与转换 使用[]时，完全靠自己谨慎来保证不出错，而string类提供at(int)方法，在越界时，会抛出out_of_range异常，这意味着可以try_catch处理它而非立刻终止程序(这里的越界指的是提供的参数大于等于size()，因为string尾部不含'\\0')\n关于length()和size()方法，它们功能一样，都返回总字符数，size()的出现是为了配合STL容器要求的统一名称\n所有STL都提供便于遍历的类似指针的迭代器，通常用以下方法获取string迭代器：\nbegin()：返回指向开头字符的正向迭代器 end()：返回指向结尾字符的下一个位置的正向迭代器 rbegin()：返回指向结尾字符的反向迭代器(reserve begin()) rend()：返回指向开头字符的上一个位置的反向迭代器 在这些方法前加上'c'(例如cbegin())，将返回只读迭代器，用于处理const修饰的对象，但事实上，这四个方法都有对应的重载版本，当该对象被const修饰时，自动使用这个版本返回只读迭代器，所以这个'c'可加可不加\n那么遍历变得很简单，而不需要用[]或at()访问字符：\nc\u0026#43;\u0026#43;for (auto it = str.begin();it != str.end();it++){ cout \u0026lt;\u0026lt; *it; }在std中，transform()方法能够快速遍历STL容器并应用所提供的函数或函数指针：\nc\u0026#43;\u0026#43;// 定义:InIt指InputIterator,OutIt指OutputIterator // 提供一元函数将[Ibegin,Iend)范围内元素都通过所提供的op,并存储在从Obegin开始的对象里 transform(InIt Ibegin,InIt Iend,OutIt Obegin,UnaryOperation); // 提供二元函数将[Ibegin_1,Iend)和[Ibegin_2,Iend)范围内元素通过op,存储在从Obegin开始的对象里 transform(InIt Ibegin_1,InIt Iend,InIt Ibegin_2,OutIt Obegin,BinaryOperation); // 示例 string a(\u0026#34;abcdefg\u0026#34;); transform(a.begin(),a.end(),a.begin(),::toupper);// 将a的所有小写字母都变成大写,存到a里 增删改查 insert()：用于前插入\nc\u0026#43;\u0026#43;// 定义 insert(uint idx,string\u0026amp;); // 将提供的string插到str[idx]前 insert(uint idx,const char*);// 提供C风格字符串,会删除\\0 // 提供string引用,将它从下标idx_2开始数n个字符的子串插到str[idx_1]前 // (经测试,虽然没有char*的函数重载,但传入char*是可行的) insert(uint idx_1,string\u0026amp;,uint idx_2,uint n); // 相当于上一条中,idx_2=0 insert(uint idx,const char*,uint n);// 提供string\u0026amp;是不行的 append()：用于后插入，完全可以被operator+()和insert()替代\nerase()：删除某字符，自动调整空间\n","permalink":"https://echo.ishian.me/2024/03/14/cpp-string/","summary":"讲解std::string的构造函数、容量与访问、修改及查找等常用接口","title":"Cpp STL: std::string"},{"content":"Bash Shell命令 Bash Shell是大多数Linux发行版的默认shell，对于绝大多数命令都可以通过man命令查看详细用法和选项\nroot用户 区别于Windows系统的Administrator用户和更复杂的权限机制，在Linux中，root(超级管理员用户)具有最高的权限，可以调度系统的所有东西，结构上十分简单，但使用时更需慎重\n默认情况下，root用户是锁定的，如果需要暂时使用root的权限：\nsh$ sudo cat /etc/sudoers # 查看被允许使用sudo命令的用户,只有root权限可查看 $ visudo # 修改/etc/sudoers文件,只有root权限可修改 $ visudo -c # 检查/etc/sudoers文件,每次修改后都应运行这条命令 $ sudo [command] # root权限运行命令,首次使用需输入当前用户密码 sudo options: -i # 暂时切换到root用户 -l # 查看当前权限 $ su user # 切换用户,除非是root用户,否则需输入对方用户的密码, $ exit # 退出当前用户需要反复强调的是，平常开发几乎用不上root权限，短暂需要使用也应使用后尽快exit；不像Windows系统，Linux系统的软件运行时很少要求root权限，这让用户增加戒心而非像使用Windows一样无脑授权\n开发中，应清楚知道自己在干什么，知道为什么一定要用sudo，并尽量少用它\n选择包管理工具 有两种选择：apt-get与apt，后者被视为前者的加强版，是apt-cache和apt-get的集合，最好不要尝试其它包管理工具，否则可能扰乱apt的数据库，使一些命令变成危险操作\napt获取软件资源的仓库位于/etc/apt/sources.list中，如果要修改，记得小心、备份\n它们都需要管理员权限，常用命令如下：\nsh$ sudo apt update # 列出可更新软件名单 sudo apt install \u0026lt;p1\u0026gt; \u0026lt;p2\u0026gt; # 安装p1,p2,...软件 若存在则尝试更新 sudo apt upgrade # 升级软件,应当定期执行该命令以保证系统稳定性 sudo apt remove \u0026lt;p1\u0026gt; \u0026lt;p2\u0026gt; # 删除p1,p2,...软件 sudo apt purge \u0026lt;p1\u0026gt; \u0026lt;p2\u0026gt; # 删除p1,p2,...软件,并删除相关依赖包和配置文件,有危险 sudo apt autoremove # 删除不再被需要的依赖包,如果一直用的是apt包管理工具,则不危险 sudo apt list [option] # 列出... install options: --no-upgrade # 若软件已安装,则不更新 --only-upgrade # 若软件未安装,则不安装;若已安装,则尝试更新文件系统 区别于Windows系统的盘符分区，Linux采取的是让根目录/作为所有文件或目录的祖宗路径，将所有文件纳入单个虚拟目录结构中，文件路径中没有指示其所在驱动器的信息\n但硬盘驱动器不会只有一个，一般装载着系统文件的驱动器为根驱动器，在其它驱动器的文件会通过挂载点与根驱动器联系起来\n路径分为绝对路径和相对路径，形如./path和../path路径前带点的路径即相对路径，分别表示相对当前目录下和相对于上级目录下，可以用多个../来跳转到更上级目录\n关于文件的常用命令如下：\nsh$ touch \u0026lt;file\u0026gt; # 若文件不存在,则创建它 $ cat \u0026lt;file\u0026gt; # 一次性展示文件内容 cat options: -A # 查看文件所有字符 -n # 查看时附上行号 $ less # 是more的升级版,用于分页查看文件 #[less is more] $ tail/head \u0026lt;file\u0026gt; # 查看文件的末尾/头部若干行,默认10行 $ file \u0026lt;file\u0026gt; # 查看文件类型,事实上也可查看目录,但现在发行版都有彩色显示关于目录的常用命令如下：\nsh$ cd \u0026lt;dir\u0026gt; # 跳到指定路径下 #[change directory] $ ls \u0026lt;file\u0026gt;/\u0026lt;dir\u0026gt; # 查看[文件是否存在]/[目录下非隐藏文件] #[list] ls options: -a # 查看目录下所有文件 -l # 查看目录下非隐藏文件的详细信息 -R # 递归查看,即会查看目录及其所有子目录 -al # 查看目录下所有文件的详细信息 $ la # 相当于ls -a $ ll # 相当于ls -al $ mkdir \u0026lt;dir\u0026gt; # 创建空目录 #[make directory] $ rmdir \u0026lt;dir\u0026gt; # 删除目录,不常用 #[remove directory]有些命令对文件与目录都有效：\nsh$ cp \u0026lt;file\u0026gt; \u0026lt;file\u0026gt;/\u0026lt;dir\u0026gt; #[copy] # 将前者复制到后者中,若为文件路径则以后者命名,若为目录路径则以前者命名 # 因为一个目录下文件不能重名,所以后者路径不能是同名文件和当前目录 $ rm \u0026lt;file\u0026gt; # 删除文件 #[remove] cp\u0026amp;rm options: -i # 覆盖/删除时发出询问,最好都加上这条选项,保证安全 -r # 递归复制/删除,可用于用于复制目录 -f # 强制执行,带有一定危险 $ sudo rm -rf \u0026lt;dir\u0026gt; # 十分危险的操作 # 尽管现在的发行版在要删除根目录时会发出警告,也不要轻易尝试 $ mv \u0026lt;file\u0026gt;/\u0026lt;dir\u0026gt; \u0026lt;dst\u0026gt; # 移动文件/目录至另一路径,常用于文件或目录改名、移动文件 #[move] mv options: -i # 覆盖文件时发出询问 -f # 强制执行文件权限 输入ls -al，可以看到文件和目录的一长串信息，其中头部内容就是该条文件的权限，它含有10个字符：\n第一个字符代表文件类型，常见的有l、d、-三种，分别对应软链接文件、目录、普通文件\n其余九个字符每三个为一组，分别对应属主权限(u,user)、属组权限(g,group)、其它用户权限(o,other)\n三个字符r、w、x，分别对应可读、可写、可执行\n关于权限的常用命令如下：\nsh$ chmod \u0026lt;paras\u0026gt; \u0026lt;file\u0026gt; # 改变文件模式 #[change mode] chmod paras: [u/g/o/none][+-=][rwx] # 增/减/等于指定权限,默认改变三者的权限 [mmm] # 一串三位、八进制数,每一位m表示对应用户的权限 # m=nnn # 每一位m等于一串三位、二进制数,每一位n表示是否拥有对应rwx权限Linux系统不同组会有不同权限，我们创建的第一个用户属于sudo组，有些发行版会为该用户创建一个独立的组，该用户创建的文件属于这个组，而通过sudo命令创建的文件又属于root组\u0026hellip;\n这种复杂灵活的机制让Linux在共享资源时保持有序、安全，关于组的常用命令如下：\nsh$ sudo groupadd # 创建组,和addgroup差不多 groupadd options: -g [id] # 指定GID $ groupmod # 修改组属性 groupmod options: -n # 改名 $ sudo groupdel # 删除组,和delgroup差不多 $ cat /etc/group # 查看所有组的信息,它们储存在/etc/group中 # 每行信息为:组名,密码,GID,[组员] $ chgrp \u0026lt;newGrp\u0026gt; \u0026lt;file/dir\u0026gt; # 改变文件/目录所属组 #[change group]用户分为系统用户和普通用户，系统用户是某些服务运行时所用的账户，系统一般会预留500或1000个UID给它们(即普通用户UID会从500或1000开始数)\n从未改变默认值的默认创建下，新用户将被分配到GID=100的公共组、不会为其设置过期日期\n一个用户只能有一个主要组，可以有多个附属组，登录时属于主要组，可通过命令切换到其它所属组\n关于用户的常用命令如下：\nsh$ cat /etc/passwd # 查看所有用户的信息,它们储存在/etc/passwd中 $ id # 查看用户信息 $ groups # 查看用户所在所有组 $ sudo useradd # 创建用户,和adduser是差不多 useradd options: -g \u0026lt;grp\u0026gt; # 指定主要组 -r # 创建系统用户 -u [id] # 指定UID -e # 指定过期时间 -G [grps] # 指定用户附属组 $ sudo userdel # 删除用户信息,不删除home目录下文件,和deluser差不多 userdel options: -r # 会删除home目录下文件,使用时需谨慎 $ usermod # 改变用户信息,一些选项与adduser的选项相同 usermod options: -l # 改名 -L/-U # 锁定/解锁用户 -p # 更改密码 -g # 更改用户主要组,重新登录后生效 -G [grps] # 更改用户附属组,是更改而非添加,具有一定危险 $ newgrp # 切换到其它所属组 $ chage # 更改用户过期时间无论是在/etc/group还是在/etc/passwd，出于安全因素，里面的密码字段都是'x'，真正的密码储存在/etc/shadow内，只有root用户有权限查看与修改，更改密码的命令如下：\nsh$ passwd \u0026lt;user\u0026gt; # 修改用户密码 passwd options: -d # 删除用户密码 $ gppasswd \u0026lt;grp\u0026gt; # 修改组密码,组外用户通过密码可暂时拥有其权限,不常用 gppasswd options: -a \u0026lt;user\u0026gt; # 使grp成为user的一个附属组,常用链接 如果多个项目需要使用相同文件，为了节省空间、或是方便管理与维护，Linux提供了链接功能\n链接分为软链接与硬链接：\n软链接(也称符号链接)：类似快捷方式，在软链接文件中存放的是源文件的路径，故占用空间小 硬链接：将创建一个虚拟文件，其本质上与源文件是同一个文件，共用同一个iNode，指向同一份数据块，即占用同一份空间 无论是软链接还是硬链接，通过链接文件都可以修改源文件的内容，区别是：\n删除掉软链接的源文件，则所有指向该源文件的软链接均失效；但如果创建了另外的、同路径的文件，则这些软链接又会指向这个新文件 只有删除掉全部的硬链接，它们的源文件才会从磁盘中释放掉 需要注意的是，硬链接不能对目录使用，也不能在不同文件系统间使用；而由于软链接存储的是路径，所以通常不应使用相对路径，否则容易失效\n关于链接的常用命令如下：\nsh$ ln \u0026lt;file1\u0026gt; \u0026lt;file2\u0026gt; # 给file1,file2创建硬链接;file1为源文件,必须存在 ln options: -s # 给file1,file2创建软链接;file1为源文件,必须存在 $ pwd # 显示所在目录的路径,可用于创建软链接时快捷找到路径 #[print work directory] pwd options: -P # 如果所在目录为软链接目录,则显示当前目录指向的源目录的路径 $ unlink \u0026lt;file\u0026gt; # 删除链接,事实上多使用rm -i而非unlink如要删除链接目录，需要十分小心，假设我们有源目录/tmp和链接目录/link-\u0026gt;/tmp：\nshrm ~/link/ # 删除/tmp,即源目录的所有文件,链接目录本身会保留 rm ~/link # 这是正确方式,删除链接目录而非其中的文件","permalink":"https://echo.ishian.me/2024/03/12/linux-file/","summary":"介绍 Linux 文件系统、文件权限、链接与常用 Bash 文件命令","title":"Linux: 文件系统"},{"content":"回溯与动态规划与贪心引入 概念 回溯法可用于求解绝大多数问题，基于深度优先，但并不是很优秀的方法，遵循自顶而下，通常通过递归实现 动态规划也可用于求解许多问题，从最小边界求出上层解，遵循自底而上，达到一定规模后则得到答案，但也需要更多思考 贪心思想总在追求局部最优解，由多个局部最优叠加得到全局最优 求解斐波那契数列 边界条件为a[0]=a[1]=1a[0]=a[1]=1a[0]=a[1]=1\n从回溯法的角度思考，将大问题拆解为子问题，只有深挖到边界条件时才回溯\nc\u0026#43;\u0026#43;int fibonacci(unsigned int _position) { if (!_position || _position == 1) { return 1; } return fibonacci(_position - 1) + fibonacci(_position - 2); }一般的回溯法必然涉及大量重复的运算，优化方法只有剪枝，即可以编写函数优化判断条件及时回溯，或用数组存储已经求出的记录：\nc\u0026#43;\u0026#43;int fibonacci(unsigned int _position) { static int hash[__INT_MAX__] {1, 1}; // 变长数组不能拥有静态存储期. if (hash[_position - 1]) { return hash[_position - 1]; } else { hash[_position - 1] = fibonacci(_position - 1) + fibonacci(_position - 2); } return hash[_position - 1]; }这已经有动态规划的影子了，只不过从概念上，动态规划是从a[0, 1]a[0,\\ 1]a[0, 1]开始找答案：\nc\u0026#43;\u0026#43;int fibonacci(unsigned int _position) { int dp[_position] {1, 1}; for (int i = 1; i != _position - 1; i++) { dp[i + 1] = dp[i] + dp[i - 1]; } return dp[_position - 1]; }显而易见修改为循环形式的求解能利用变长数组，节省了许多空间，这里称dp[0,\\ 1]为base case，即边界条件，dp[i + 1] = dp[i] + dp[i - 1]为状态转移方程，即从上一个已求解的子问题状态转移到当前问题状态的方程\n根据状态转移方程的不同可以实现对空间的优化，例如这个问题的方程，事实上只需要维护三个数，通过滚动数组，将过于小的问题抛弃：\nc\u0026#43;\u0026#43;int fibonacci(unsigned int _position) { int dp[3] {1, 1}; // 虽然用数组, 但是三个变量就可以完成记录. for (int i = 1; i != _position - 1; i++) { dp[(i + 1) % 3] = dp[i % 3] + dp[(i - 1) % 3]; } return dp[(_position - 1) % 3]; }拆解斐波那契数列 问题描述：任意一个正整数可以拆解为若干个斐波那契数列的数(可以重复)之和，求出子数个数最小的拆法\n这是一个完全背包问题，即一个容量固定的背包，需要用一些重量不同，数量无穷的物品刚好填满它，求出使用物品最少的方案\n如果使用贪心，即我需要更少的数，就选择更大的数，即时刻让接下来需要计算的数更小，这实际上有一个问题：如果拆解出的小问题恰好不大不小，反而增加了需要用到的数，结果就是错误的\n求最大连续子数组和 这种问题要求连续选择，反而用贪心思想和搜索更优：\n明确贪心：如果当前和小于零，则抛弃当前窗口，否则继续；如果新窗口和大于旧最大值，则更新答案窗口 c\u0026#43;\u0026#43;// 保证数组不为空. array max_son(vector\u0026lt;int\u0026gt;\u0026amp; arr) { int sum = arr[0], max=sum; auto itbegin=arr.begin(), itend=pbegin; // 存储答案的两个迭代器. auto itslow=itbegin, itfast=itslow; // 当前窗口的两个迭代器. while (itfast != arr.end()) { if (sum \u0026lt; 0) { itfast++; itslow=itfast; if (itfast != arr.end()) sum=*itfast; else break; } if (sum \u0026gt; max) { itbegin=itslow; itend=itfast; max=sum; } itfast++; } return array(itbegin, itend); }动态规划 基本思路 动态规划在贪心的基础上做出的优化在于，这种算法能看到更长远的子问题(因为本身就是从子问题上升求解的)，如果用f(x)表示对一个问题的最优解，那么dp在子问题中挑出最优解better[f(x1), f(x2), f(x3)]，最终访问到最基础的最优解，也就得到了全局的最优解\n例如用1, 5, 6分凑10分的情况，贪心会选择6+1+1+1+1，而动态规划选择better[f(10-6)+1, f(10-5)+1, f(10-1)+1]，即在全局上贪心\n所以对于能用dp解决的问题：\n拥有最优子结构：即大问题可拆分成子问题，而且子问题有唯一的最优解 能够确定初始状态，即base case 能够求出状态转移方程，即递推公式，这是拆分的基础 也就是，我在做决策时，只需要考虑当前状态了，因为子问题已经是最优了\n判断可以用动态规划后，可一步步思考解法：\n明确dp的维度及含义 列出所有状态 考虑不同状态，取最优解，列出状态转移方程 思考base case与如何初始化数组 dp维度的遍历顺序 测试时输出整个dp数组，方便寻找问题所在 基础背包问题 01背包 给定一个容量固定的背包，将一些物品放入其中，其中每种物品只有一件，求出最大价值方案/不同价值方案数/最少物品数\n每种物品只有选/不选两种状态，这称为01，以求出最大价值方案为例：\ndp[i][j]：表示用前i种物品填满容量为j的背包，所得到的最大价值 状态转移：dp[i][j]=max(dp[i-1][j], dp[i-1][j-weights[i]]+value[i])。max中前者为不选择第i种物品，则与使用前i-1种物品相同；后者为选择第i种物品，则当前价值为用前i-1种物品填满\u0026quot;当前容量减去当前物品的重量\u0026quot;背包的最大价值加上当前物品的价值。两者取最优，在这里表现为max()；为防止越界访问，定义当当前物品重量大于当前背包时，必定为不选择当前物品 base case：dp[...][0]=0，即容量为0，价值定为0；dp[0][weights[0]~k]=value[0]，即用第0种物品填满容量为weights[0]~k的背包，最大价值就是当前物品的价值。注意这里不要求恰好填满，如果要求恰好，则有所不同 这个问题不要求顺序，因为根据递推方程，当前元素由左上或正上方元素决定；根据base case，第0行与第0列都已经初始化；故顺序不同都可以在求当前位置前求出这两个位置的值 状态转移方程中，由于每种物品只有一件，所以每次迭代都需要i-1，避免重复选择：\nc\u0026#43;\u0026#43;#include \u0026lt;vector\u0026gt; // 这里进行简化, 用 value下标+1 表示该物品的重量. int max_value(const vector\u0026lt;int\u0026gt;\u0026amp; value, int k) { int dp[value.size()][k+1] {}; for (int i = 1; i \u0026lt; k+1; i++) { // i的初始化为weights[i]. dp[0][i]=value[0]; // 该问题不要求恰好填满, 所以初始化一个范围. } for (int i = 1; i \u0026lt; value.size(); i++) { for (int j = 1; j \u0026lt; k + 1; j++) { if (i+1 \u0026gt; j) { // 这里的i+1是简化后的重量, 具体问题中应为weights[i]. dp[i][j]=dp[i-1][j]; continue; } dp[i][j]=max(dp[i-1][j], dp[i-1][j-(i+1)] + value[i]); } } return dp[value.size()-1][k]; }观察递推方程可知，当前元素只与左上/正上方元素相关，则可以利用滚动数组优化空间：\nc\u0026#43;\u0026#43;#include \u0026lt;vector\u0026gt; int max_value(const vector\u0026lt;int\u0026gt;\u0026amp; value, int k) { int dp[k+1] {}; // 将物品维度压缩. for (int i = 1; i \u0026lt; k+1; i++) { dp[0][i]=value[0]; } for (int i = 1; i \u0026lt; value.size(); i++) { // 倒序遍历一定需要整一层的初始化. for (int j = k; j \u0026gt; 0; j--) { if (i+1 \u0026gt; j) { continue; // 直接用正上方的数据覆盖, 则不作改变. } dp[j]=max(dp[j], dp[j-(i+1)] + value[i]); } } return dp[k]; }注意这样优化必然需要让背包容量在内层循环，因为是行行覆盖；而且背包是倒序遍历，因为需要保证在每一层的计算中(i不变时)，每个物品只被选择一次；如果正序遍历，会因\u0026quot;由前一元素推出当前元素\u0026quot;的影响，错误的用新值覆盖本应用于计算的旧值\n完全背包 完全背包与01背包的不同在于，每个物品有无数个，这使它们有多种状态(选0,1,...,n次)，最多选择\u0026quot;背包容量除以重量\u0026quot;次；这样的问题可以转换为01背包(将每种物品按最多次数展开就是01背包)：\n如果不选择，则等于上层结果 如果选择若干次后不再选择，则等于该层结果 如果继续选择，则覆盖该层结果 则很容易想到新增一层循环，达成\u0026quot;选择多次\u0026quot;：\nc\u0026#43;\u0026#43;int max_value(const vector\u0026lt;int\u0026gt;\u0026amp; value, int k) { int dp[value.size()][k+1] {}; for (int i = 1; i * 1 \u0026lt; k+1, i += 1) { // 后两个表达式中的1是简化后的第0物品的重量. for (int j = i * 1; j \u0026lt; i * 2 \u0026amp;\u0026amp; j \u0026lt; k+1; j++) { dp[0][j]=i * value[0]; // 不要求填满. } } for (int i = 1; i \u0026lt; value.size(); i++) { for (int j = 1; j \u0026lt; k + 1; j++) { dp[i][j]=dp[i-1][j]; // 先进行初始化, 方便多次比较. for (int x = 1; x * (i+1) \u0026lt;= j; x++) { // 最多选择 容量/单个重量 次. dp[i][j]=max(dp[i][j], dp[i-1][j-x * (i+1)] + x * value[i]); } } } return dp[value.size()-1][k]; }注意到每次算出一个dp[i][j]，其实是对该元素正上方元素、正上方左边若干个元素(两两之间距离为weights[i])进行比较(只是因为max一次性只比较两个元素而遍历)，且每一层循环所要判断的物品是同一种(重量一样，选择不同个数物品后元素的距离一样)，那么只需要利用上一次比较的结果(即当前元素正上方左边元素的最优)与当前元素正上方元素相比较，就可以减少不必要的比较了，于是可以对状态转移方程优化：\nc\u0026#43;\u0026#43;int max_value(const vector\u0026lt;int\u0026gt;\u0026amp; value, int k) { int dp[value.size()][k+1] {}; for (int i = 1; i * 1 \u0026lt; k+1, i += 1) { for (int j = i * 1; j \u0026lt; i * 2 \u0026amp;\u0026amp; j \u0026lt; k+1; j++) { dp[0][j]=i * value[0]; } } for (int i = 1; i \u0026lt; value.size(); i++) { for (int j = 1; j \u0026lt; k + 1; j++) { if (j-(i+1) \u0026lt; 0) { dp[i][j]=dp[i-1][j]; continue; } // max前者为正上方元素, 后者为之前计算过的最优解+多选择一件的价值. dp[i][j]=max(dp[i-1][j], dp[i][j-(i+1)]+value[i]); } } return dp[value.size()-1][k]; }不难发现这个方程与01背包极为类似，只因后者变化而允许了重复选择\n效仿01背包，依然可以降维：\nc\u0026#43;\u0026#43;int max_value(const vector\u0026lt;int\u0026gt;\u0026amp; value, int k) { int dp[k+1] {}; for (int i = 1; i * 1 \u0026lt; k+1, i += 1) { for (int j = i * 1; j \u0026lt; i * 2 \u0026amp;\u0026amp; j \u0026lt; k+1; j++) { dp[j]=i * value[0]; } } for (int i = 1; i \u0026lt; value.size(); i++) { for (int j = 1; j \u0026lt; k + 1; j++) { // 这里是正序, 因为可以选择多次. if (i+1 \u0026gt; j) { continue; } dp[j]=max(dp[j], dp[j-(i+1)] + value[i]); } } return dp[k]; }完全背包可以考虑的一个优化点在于，对于求解价值问题，可以考虑将\u0026quot;重量相同但价值更小\u0026quot;与\u0026quot;价值相同但重量更大\u0026quot;的物品去除，因为可以选择无数次，而这样的优化首先需要O(n2)O(n^2)O(n2)的遍历，或者用一些高效的排序方法实现更低的复杂度优化\n多重背包与混合背包 多重背包与完全背包十分相似，它限定了物品的个数。最基础的解法为类似完全背包的三次循环，为了防止多次选择，一维数组解法的j层循环仍需逆序遍历\n因为这种解法实质上是通过完全展开所有的次数来表示所有的可能，也就是用**1进制**形式重构了物品，会造成多余无效的比较，所以：\n将这些1进制的多个同种物品压缩成n进制不同种的01物品，价值为n*value[i]，重量为n*weights[i]，个数为1 尝试找出一种压缩方法，能使压缩后的这些物品进行排列组合后能够表示所有的可能 因为01物品只有两种状态选或不选，所以只能用二进制压缩 完全背包问题可以完全优化掉个数的循环，而多重背包不行：\nc\u0026#43;\u0026#43;void init_input(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; input) { // 初始化输入. int n, m, v, w; // 物品种类数, 物品个数, 物品价值, 物品重量. cin \u0026gt;\u0026gt; n; for (int i = 0, j; i \u0026lt; n; i++) { cin \u0026gt;\u0026gt; m \u0026gt;\u0026gt; v \u0026gt;\u0026gt; w; j = 1; while (j \u0026lt;= m) { input.push_back(vector\u0026lt;int\u0026gt;(v*j, w*j)); m -= j; j \u0026lt;\u0026lt;= 1; } if (m) { // 如果剩余m个, 则把它压缩为1个, 加上这个余数就可以表示原来的m范围内所有数了. input.push_back(vector\u0026lt;int\u0026gt;(v*m, w*m)); } } }这样在遍历时套用01背包解法就行了，对于个数的时间复杂度由O(n)优化成了O(logN)，乘上价值与重量的时间复杂度的话，已经优化相当多了\n混合背包即将三种物品类型结合起来，很容易得出它的解法：\n如果是完全背包类型，则这一层以完全背包方程遍历，一维数组是顺序遍历 如果为多重背包类型，则转化为01背包，并加入到01背包的循环中，一维数组是倒序遍历 变种背包问题 以上是三种最基础、经典的背包问题，也有不同的变式，例如：\n外表上只含重量的背包问题：例如数组凑出固定和问题，在这个问题上，价值其实与重量相等\n输出具体方案：可以同时维护一个bool二维数组记录选择。例如01背包中，用0表示没有选择，并查询正上方的选择；用1表示选择了，并查询[i-1][j-weights[i]]的状态；最后通过递归逆序搜索，顺序输出即可\n作\u0026quot;恰好装满\u0026quot;的限制：初始化时必须注意有效状态(装满为有效，否则定义为-1或其它无效值)，在求最大价值问题上，应用INF或__INT_MAX__作为无效值(因为取最大)\n求解最少物品恰好装满问题：dp数组含义变为所需的最少物品，且初始化时无关值应用__INT_MAX__处理，在比较时使用min()，无效值为-1；之前提到的拆分斐波那契数列问题其实就是一个这样的完全背包问题\n求解恰好填满方案数问题：dp数组含义变为方案数，且递推方程由比较变为两种状态求和，无效值为-1\n求解价值最优方案数问题：同时维护一个二维数组记录最优方案数。如果选择/不选择价值相等，则当前方案数为两者相加，否则为更优方案的方案数，以01背包为例：\nc\u0026#43;\u0026#43;int strategy[value.size()][k+1] {}; for (...) { for (...) { // 二维数组求法. ...; // 递推公式. if (dp[i][j] == dp[i-1][j]) strategy[i][j] += strategy[i-1][j]; if (dp[i][j] == dp[i-1][j-w[i]]+value[i]) strategy[i][j] += strategy[i-1][j-w[i]]; } for (...) { // 一维数组求法. if (dp[j] \u0026gt; dp[j-w[i]]+value[i]) { strategy[j] += strategy[j]; } else if (dp[j] \u0026lt; dp[j-w[i]]+value[i]) { strategy[j] += strategy[j-w[i]]; dp[j] = strategy[j-w[i]]; } else { strategy[j] += strategy[j-w[i]]+strategy[j]; } } } 求解排列顺序问题：和求解组合数不同，对于完全/多重背包时，因为一种物品可以多次选择，于是有顺序问题，所以求解所有排列时，如果只使用两层循环，需要让外层循环为背包容量，内层循环为物品\n原因在于，求组合时，是先往背包中放同一种物品，然后遍历后续的物品，始终只有一种排列\n而求排列时，允许背包先放入不同的物品，然后再扩大容量\n可以这么认为，前者一次遍历只允许出现一种物品，后者一次遍历则是求出了小背包的所有组合\n连续选择问题：即要求选择的物品必须是连续的，这种方法难以通过dp求解，更好的办法是通过贪心与搜索\n对于背包问题，不用纠结它有没有背包，背包只是一系列问题的一种映射，重要的是\u0026quot;只需要作出当前决策\u0026quot;的特性\n进阶背包问题 二维背包问题 这类问题在基础背包问题上添加了一个限制，例如：\n两个背包问题：放入一个物品会使这两个背包都添加重量 数量限制问题：可以放入背包的物品最大数量有限(假设为k)，这个\u0026quot;最大数量\u0026quot;其实就是第二个容量为k的背包，放入物品同时会使它的重量加1 按照之前的思路，只需要添加一层循环与一个维度即可，利用滚动数组可以只使用二维数组\n分组背包问题 这类问题将n种物品改为n组物品，且在每组中只能选出1件，那就是很简单的嵌套最优01背包问题，只需要添加一层循环，但不需要添加数组维度：\nc\u0026#43;\u0026#43;for (int i = 1; i \u0026lt; group.size(); i++) { for (int j = k; j \u0026gt; 0; j--) { for (int x = 0; x \u0026lt; group[i].size(); x++) { if (i+1 \u0026gt; j) { // group[i].weights[x] \u0026gt; j. continue; } dp[j]=max(dp[j], dp[j-(i+1)] + group[i].value[x]); } } }这是依赖问题的基础，此问题的变形还有：\n每组不限种类，总共能选择n件，这其实是二维背包问题，淡化了\u0026quot;组\u0026quot;的分别 每组只能选出1种，且能够选择多/无数件，这也很好解决，套用多重/完全背包模板再添加循环即可 每组能够选出n种，每种能够选择1/多/无数件，这样的问题已经变得相当复杂，涉及不同排列组合问题 依赖背包问题与泛化物品 这是进阶背包问题，每种物品可能与其它物品有依赖关系，即选择附属物品必须选择对应的主物品，这使得附属物品有有效状态和无效状态之分，而不单纯是有效状态上选择或不选择的区别\n遍历思路一定是如果选择主物品，再考虑其附属物品，所以首先考虑缩小附属物品的范围，即先在每一个主物品的附属集合中使用背包策略求出子dp，再与不选主物品的情况捆绑成一个组，由于每个元素代表一种策略，而我们只能选择一种策略，故可以通过分组背包问题求解\n如果一组附属物品只依赖于一个主物品，那么产生的组就是子dp(每个元素需加上主物品的重量与价值，因为有依赖关系)，不选择主物品的情况就是不在这个组里选择任何物品\n泛化物品概念的来源是，假设这些附属物品也有附属物品，那么问题就变成了背包数，是一个图了\n根据依赖问题的思路，将附属物品的集合看作一种新的物品，这种物品可以是一个组，表现为一个dp数组，即提供不同的容量限制，它会返回不同的价值，这就是泛化物品\n泛化物品当然可以作为三个基础背包问题的对象，而一旦容量与类型确定，它的价值也都固定了：\n01泛化物品：提供容量k，选择则weight=k, value=dp[k] 多重或完全泛化物品：提供容量k，选择则weight=w[i], value=k/w[i] * dp[w[i]] 取泛化物品中的最优是有点麻烦的，因为它们的容量不确定，暴力解决是枚举所有分配容量的方案，再取最优\n例题 目标和 给你一个没有0的数组，每个数可以取正或负，得到一个表达式，再给你一个正的目标和，求出能够得到这个目标和的表达式数\n这也是很经典的01背包问题，求恰好填满，只是状态从是否选择变成了是否取正负且每个数必须选择，事实上这样会不能进行一维数组的空间优化：\nc\u0026#43;\u0026#43;int solution(vector\u0026lt;int\u0026gt;\u0026amp; arr, int target) { for (...) {...}; // 求出fabs(arr)的和sum. if (target \u0026gt; sum) { return 0; } int dp[sum+1] {}; dp[0] = 0; // 数组没有0, 故可以确定用第一个数凑出0的方案数为0. 如果允许不选, 应为dp[0]=1. dp[fabs(arr[0])]=1; for (int i = 1; i != arr.size(); i++) { for (int j = sum; j \u0026gt; 0; j--) { if (arr[i]+j \u0026gt; sum \u0026amp;\u0026amp; arr[i] \u0026gt; j) { // 既不能取正也不能取负. dp[j] = 0; // 如果允许不选, 这里应该不变. } else if (arr[i] \u0026gt; j) { // 如果只能取正. dp[j] = dp[arr[i] + j]; // 允许不选, 则+=. } else if (arr[i]+j \u0026gt; sum) { // 如果只能取负. dp[j] = dp[j-arr[i]]; } else { // 都可取. dp[j] = dp[arr[i] + j] + dp[j-arr[i]]; } } } return dp[target]; }很明显，dp同时受到左右两边元素的影响，是不能用一维数组的\n求解最后一块石头的重量 给你一些石头，石头之间可以互相抵消，求出抵消剩一块石头时，这块石头的最小重量\n如果要求两块石头相邻，很容易用贪心思想得到，每轮抵消我都要让最大的石头和旁边的石头抵消(无关左右)，每轮抵消后再次寻找最大的石头，因为虽然最大值可能互相隔离，但最终都要相碰，最后三个石头的抵消实际上是在做：\n新大石头-(旧大石头-隔壁石头)=新大石头-旧大石头+隔壁石头 它一定是两个小石头的和减去那个大石头\n那如果不要求相邻，根据上面的思路就是抽出一些石头取正数，一些石头取负数，然后求最小和，那么在求出dp后，从小遍历最后一行就能得到答案了\n分割字符串 给你一个字符串和字典，你可以重复使用字典中的字符串，求解是否能够通过字典来凑出它\n示例：str=\u0026quot;applepenapple\u0026quot;, dict=[\u0026quot;apple\u0026quot;, \u0026quot;pen\u0026quot;]，返回true\n这就是一个完全背包问题，字符串的容量为size()，字典中每个物品的重量也为size()，只需要求出所有能够恰好凑出容量的排列即可，注意需要保存具体的组合内容\n然而求出所有排列是深搜的做法，dp可以在此之前就进行判断，剔除无关的字符串，判断条件可以是集合的count()方法，并充分利用前面的状态，这样就可以将排列数转换为布尔值了\nc\u0026#43;\u0026#43;bool isdict(const string\u0026amp; str, const unordered_set\u0026lt;string\u0026gt;\u0026amp; dict) { bool dp[str.size()+1]; dp[0] = 1; // 表示可以凑出空字符串, 方便记录第一个出现的字典串的状态. for (int i = 1; i \u0026lt; str.size() + 1; i++) { // 先遍历背包容量, 即子串. for (int j = 0; j \u0026lt; i; j++) { // 遍历子串的子串. // 将0~i子串拆分为0~j和j~i两个子串, dp[j]表示可以凑成0~j, 后者表示字典含有j~i串. if (dp[j] \u0026amp;\u0026amp; dict.count(str.substr(j, i-j))) { dp[i] = 1; // 则可以凑成0~i. break; // 增加背包容量. } } } return dp[str.size()]; }打家劫舍系列问题 打家劫舍一：给你一行屋子，你可以抢劫非相邻房屋，求出最大收益\n这个问题，不同物品间有排斥关系，而且看似没有背包，很简单：\nc\u0026#43;\u0026#43;int max_p(const vector\u0026lt;int\u0026gt;\u0026amp; arr) { if (arr.size() == 1) { return arr[0]; } int dp[arr.size()] {}; // 这里用dp[i]表示抢劫前i间屋所得的最大收益. // 初始化: 0比1大或相等, 则抢0不抢1, 表现为[0]==[1]; 0比1小, 则抢1不抢0, 表现为[0]!=[1]. dp[0]=arr[0]; dp[1]=max(dp[0], arr[1]); for (int i = 2; i \u0026lt; arr.size(); i++) { if (dp[i-1] == dp[i-2]) { // 证明没有抢前者, 则假设抢当前屋, 不抢的情况在下一轮处理. dp[i]=dp[i-1]+arr[i]; } else { // 证明当前屋不可抢. if (arr[i] + dp[i-2] \u0026gt; dp[i-1]) { // 但如果抢i比抢i-1划算. dp[i-1]=dp[i-2]; // 则修改前一个元素的状态. dp[i]=dp[i-2] + arr[i]; } else { // 不抢. dp[i]=dp[i-1]; } } } return dp[arr.size()-1]; }打家劫舍二：仍然给你一些屋子，但这个屋子头尾相邻，你不能抢劫相邻的屋子\n这其实分成三种情况：\n头尾都不选：答案等于对中间元素进行dp 只选头：答案等于对前n-1个元素进行dp 只选尾：答案等于对后n-1个元素进行dp 第一种情况是被包含在后两种情况中的，也就是求的过程中就已经取了最优，而后两种情况只需要分别计算再取最优即可\n打家劫舍三：给你一些树状结构的房子，依然是不能抢劫相邻的屋子\n一节二叉树可以化为普通的线性数组，但多节多叉树就要考虑多种因素，那么其实这涉及到树形dp\n处理树形结构一个可行的方法就是递归搜索，并同时运用dp\n贪心之多背包问题 给你k个背包，n个珠子(n\u0026gt;=k)，你必须把所有珠子填入其中且保证每个背包含有至少1个珠子，且每个背包内珠子一定是连续放入的，每个背包的价值为最先放入珠子和最后放入珠子的和，求出所有背包价值和最大值与最小值的差值\n这个问题中各个背包之间的关系其实不大，很难用动态规划来求解，而是用贪心思想\n任意方案都要求将整个数组分割成k个连续子数组，以代数形式列出价值和：\n∵arr[0,n]=arr[0,n1],arr[n1+1,n2],…,arr[nk−1+1,n]∴sum=value(0+n1+(n1+1)+n2+⋯+(nk−1+1)+n) =value(0+(n1+(n1+1))+n2+⋯+(nk−1+1)+n)\\begin{align}\u0026amp;\\rm\\because arr[0,n]=arr[0,n_1],arr[n_1+1,n_2],\\dots,arr[n_{k-1}+1,n]\\\\\u0026amp;\\rm\\therefore sum=value\\Big(0+n_1+(n_1+1)+n_2+\\dots+(n_{k-1}+1)+n\\Big)\\\\\u0026amp;\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ =\\rm value\\Big(0+\\big(n_1+(n_1+1)\\big)+n_2+\\dots+(n_{k-1}+1)+n\\Big)\\end{align}​∵arr[0,n]=arr[0,n1​],arr[n1​+1,n2​],…,arr[nk−1​+1,n]∴sum=value(0+n1​+(n1​+1)+n2​+⋯+(nk−1​+1)+n) =value(0+(n1​+(n1​+1))+n2​+⋯+(nk−1​+1)+n)​​由观察可知，一个方案的价值总和等于头尾元素价值之和+所有分割处相邻元素价值之和，且共有k-1个分割处\n我们知道n的数组有n-1个分割处，那么只要把所有这些分割处两边元素和求出，再由贪心取最大/最小的k-1个分割处，就可以算出最大/最小值了：\nc\u0026#43;\u0026#43;int sub(vector\u0026lt;int\u0026gt;\u0026amp; arr, int k) { int sum = 0; for (auto it = arr.begin(); it != arr.end()-1; it++) { *it += *(it+1); // 数组内计算, 节省空间. } // 排序, 方便贪心, 这里因为数组内计算, 所以最后一个元素是无效的. sort(arr.begin(), arr.end()-1); for (int i = 0; i \u0026lt; k-1; i++) { // 遍历k-1次. sum += arr[arr.size()-2-i] - arr[i]; // 求的是差值. } return sum; }","permalink":"https://echo.ishian.me/2024/03/11/algo-dp-beginner/","summary":"由回溯与贪心引出动态规划思想，以斐波那契数列为例讲解记忆化与递推优化","title":"算法竞赛: 动态规划引入"},{"content":"不可变/可变类型 回顾一下不可变、可变类型的区别\n展现给开发者的：\n内置不可变类型不提供任何set类型的API，最多仅提供get类型的方法(例如点操作符、[]切片语法糖等)(且在用户试图修改这些方法的返回值时抛出TypeError或AttributeError)，用户只能通过赋值修改实例的引用来实现修改对象的功能 用户既不能修改内容，也不能在类外绑定新的实例属性 需要再次强调，不可变类型和C++ const\u0026amp;引用类型不是一种东西，更像C++ const*：用户完全可以修改其指向，只是不能修改其指向的内容\n很自然的是，因为所有对象均为引用(包括形参)，因此传递参数时： 若为不可变类型参数，则修改该参数不会影响实际参数(因为用户只能修改形参的指向) 若为可变类型参数，则修改其内容将影响实际参数(形参、实参指向相同的数据)、通过赋值修改形参的引用不会影响实际参数(修改形参的指向，未修改实参的引用及其指向的数据)\n用户定义的类均为可变类型，可以模仿不可变类型的行为，有多种灵活的方法： 首先是类属性这种所有实例共用的数据，在初始化时就应设置成不可变类型 定义不可变实例属性：\n用@property将一个实例方法转换为实例属性，当用户访问该实例属性时，相当于调用该实例方法；使该实例方法始终返回一个不可变类型实例，就能实现使该实例属性无法修改 最常用的保底机制：直接**重载__setattr__()**进行检查，拦截被修改/绑定的属性，可由用户决定是否抛出异常 也可以防止用户在类外绑定实例属性 不允许用户在类外绑定实例属性：除了上述方法外，还可定义类属性__slots__(一个由字符串组成的元组)，表示允许用户绑定的实例属性名\n实例原本通过字典__dict__维护其实例属性，但定义这个类属性后，实例不再拥有__dict__，而是一个固定长度的数组\n这个类属性不允许包含其它类属性名，否则会抛出ValueError 配合上在用户通过实例修改类属性时，解释器优先解释为用户想要创建和类属性同名的实例属性这个特性，这种操作必然会报错，虽然原因不同： (1)试图创建不在__slots__中的新的实例属性(2)试图通过实例修改类属性 还是一句话，尽量不要通过实例访问类属性，如果手贱，尽量只进行只读操作\n这个类属性不对子类生效\n内部实现细节：\n解释器会为内置不可变类型的对象维护缓存，使内容相同的对象只指向一份实际内存空间 当用户调用赋值符修改属性时，实际是调用内置的__setattr__()，这也是上述保底机制的原理 当用户调用赋值符修改对象引用时，维护原/新数据的引用计数，通过GC维护内存 序列公共API 序列的赋值：\n内置序列类型，即有序的数据集合，包括元组(不可变)、列表(可变)、字符串(不可变)、range()(不可变)\n运算符语法糖：\nseq1 + seq2：拼接序列，返回新实例 seq * n：拼接n个相同序列，返回新实例 val in/not in seq：判断值是否在序列中，返回布尔值 len()：返回长度\nmax()/min()：返回序列中最大/最小值，要求所有元素的类型相同(除非是python允许的隐式类型转换)\nrange(start, end, step=1)：创建从start到end(包含)的步长为step的range类(整数序列)，step默认为1，step允许为负数，不允许为零 和切片的功能不同，不能用于截取序列，其用法是快速创建整数数组方便产生for循环索引/数据分析\nslice(start, end, step=1)：参数用法与range()类似(end不包含)，但产生的序列不能访问，其正确用法是通过[]作用于其它序列上，截取子序列\n[]语法糖：\n[x]：访问下标为x的元素 [s:e]：等同于[slice(s, e)]，s、e允许为空，此时代表取到头/尾 [s:e:i]：等同于[slice(s, e, i)]，同样i允许为负数 enumerate(seq, start=1)：返回由元组构成的序列，其中元组的第一项为整数序列，从start(默认为1)开始，第二项为序列中的内容(从下标0代表的元素开始) 实际上，所有可遍历\n集合公共API ","permalink":"https://echo.ishian.me/2024/02/02/python-data-structure/","summary":"梳理Python可变与不可变类型、内置容器及自定义不可变类的实现技巧","title":"Python: 常用的数据结构"},{"content":"C++风格的I/O I/O是一门语言很重要的部分，\u0026lt;iostream\u0026gt;之所以含有相当一部分好用的函数，是因为在I/O实现时各个头文件互相包含，使得只需要包含\u0026lt;iostream\u0026gt;就能够使用很多的东西\n但如果要用STL、实现文件交互等，\u0026lt;iostream\u0026gt;并没有包含它们，推荐使用万能头文件\u0026lt;bits/stdc++.h\u0026gt;，包含几乎所有会用到的东西\nstdInput cin 在C++中，istream类的对象cin(在\u0026lt;iostream\u0026gt;中声明)替代了scanf，它自动创建标准输入缓冲区，从标准输入流提取输入\n这些对象被称为流对象，它将流和对象联系起来，一个非iostream,fstream类流对象只和一个缓冲区、两端(程序端、输出输入端)相关联\n最简单的获取输入的代码如下，它是格式化流插入符(即会将读取的字符串转换成后接对象的相应类型，除了它，其它方法都是非格式化读取)，接受一个引用：\nc\u0026#43;\u0026#43;cin \u0026gt;\u0026gt; identifier; // 有回显、有缓冲、忽略空格与换行,遇空格或回车停止 自然想到，如何读取空格，实现面向行的输入？cin有两种方法可以实现：\nc\u0026#43;\u0026#43;cin.getline(char*,std::streamsize);// 读取size-1个字符后或遇到换行符停止,且丢弃换行符 cin.get(char*,std::streamsize); // 读取size-1个字符后或遇到换行符停止,将换行符保留在缓冲区 std::streamsize是long int的重命名，即有符号整型；它们的共同特性是都不会将换行符读入到字符串中，且可以添加第三个char型参数表示自定义终止字符\n如果需要只读取缓冲区内的任意一字符，cin有重载的get()方法，其一的用法类似getchar()：\nc\u0026#43;\u0026#43;ch = cin.get(); // 读取一字符,返回它的字符编码;如遇到EOF,则返回eof(一个负数) cin.get(char\u0026amp; ch); // 将读取的字符赋值给ch;如遇到EOF,则返回0 它们常用于抛弃掉缓冲区的换行符，但一次调用只能丢弃一个字符，cin提供ignore()方法来一次性抛弃多个字符：\nc\u0026#43;\u0026#43;cin.ignore(std::streamsize n,delim=\u0026#39;\\n\u0026#39;); // 抛弃n个字符后或遇到delim并抛弃它后停止 operator\u0026gt;\u0026gt;(),cin.getline(),cin.get(...),cin.get(char),cin.ignore()都返回cin的引用，这意味着它们可以链式调用：\nc\u0026#43;\u0026#43;cin.getline(a,b).getline(a,b); // 读取两行(或是读取一串较长行) cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b; cin.get(ch).get(); // 连续读取两字符 (cin.get(ch)\u0026gt;\u0026gt;a).get(); cin.get().get(ch); // 错误,get(void)返回的不是istream引用 如果非要给cin改名，可以选择创建引用istream\u0026amp; in=cin(一般不会直接使用istream或ostream的构造函数，只有在创建文件流对象的时候会间接调用i(o)stream(sb\u0026amp;)、或是想实现多缓冲区输入输出时会直接使用它，在这里不作讨论)\nstring的输入 上述方法都不支持string类对象，然而显然string类更常用，于是\u0026lt;string\u0026gt;中有一个全局函数：\nc\u0026#43;\u0026#43;getline(istream\u0026amp;,string\u0026amp;,char=\u0026#39;\\n\u0026#39;);三个参数，第一个为自定义的输入流，通常是标准输入流cin，第三个参数为指定的结束符(默认为换行符)\n有些编译器在使用string类后默认添加\u0026lt;string\u0026gt;，所以就算没有包含它也能运行，但getline()是\u0026lt;string\u0026gt;的函数，应该养成习惯事先包含它，以免出错\nstdOutput cout 与cin的大部分内容类似，cout是ostream类的对象，它与标准输出流相关联，格式化输出如下，operator\u0026lt;\u0026lt;接受引用或常量：\nc\u0026#43;\u0026#43;cout \u0026lt;\u0026lt; identifier \u0026lt;\u0026lt; constant;由于string类定义的operator\u0026lt;\u0026lt;()能完整输出整个字符串，所以输出部分不像输入一样有专门的函数\n对应putchar()，cout也有类似的方法：\nc\u0026#43;\u0026#43;cout.put(char) // 输出单个字符 operator\u0026lt;\u0026lt;(),cout.put(char)都返回指向cout的引用，所以cout也可以链式输出：\nc\u0026#43;\u0026#43;(cout \u0026lt;\u0026lt; str \u0026lt;\u0026lt; endl).put(48)标准输出缓冲区采用行缓冲，它遇到换行符或eof时将自动刷新缓冲区，此外还提供了两个控制符：\nc\u0026#43;\u0026#43;cout \u0026lt;\u0026lt; flush \u0026lt;\u0026lt; endl; // flush直接刷新缓冲区,endl在缓冲区中插入换行符后再刷新它 精细控制输出 C++提供一种不逊于printf()那样精细控制输出位数、输出形式的，更简洁的输出方式，那就是许多控制符\n除了刷新缓冲区的flush和endl(在\u0026lt;ostream\u0026gt;中)，其它常用的控制符如下：\nhex,oct,dec：将整型数据以十六、八、十进制输出，在\u0026lt;bits/basic_ios.h\u0026gt;中 setprecision(int)：永久设置数据的精度(显示数据的总位数) setfill(char)：永久设置字段中填充的字符(默认为空格) setw(int)：暂时设置字段的宽度，在输出下一数据后恢复默认字段值(即0)，用于对齐画面等 除了第一条，其它三条控制符都需要包含头文件**\u0026lt;iomanip\u0026gt;**\n这些控制符实际上是调用iostream类的祖宗类ios_base的成员函数setf()和部分成员，来实现控制输出的功能，其它控制符详见fmtflags\n注：fmtflags不是控制符，而只是部分控制符调用了setf(fmtflags)而已，ios_base::fmtflags和后续将提到的ios_base::iostate,ios_base::openmode都是一串二进制掩码(bitmask)，这意味着它们可以通过'|'运算符叠加，在ios_base中提供了它们的一些枚举常量\nios(即basic_ios\u0026lt;char\u0026gt;)类是ios_base类的派生类，能使用它的所有公开和保护性成员，所以直接用ios::badbit等也是正确的，所以在除定义外的说明和实例中都使用ios::\n流状态 状态位 所有流对象都有称为流状态的属性，流的打开或关闭由三个状态位决定：\nbadbit：系统性错误时(如文件不存在、文件不允许访问等)被设置，这种错误一般无法恢复 failbit：格式错误时(如输入字符赋给整型对象、get(str)读取到空行等)被设置，这种错误可以恢复 eofbit：遇到eof时被设置 ios_base类中有ios_base::badbit,ios_base::failbit,ios_base::eofbit,ios_base::goodbit四种标准状态常量，它们以三位二进制数表示，分别为001,100,010,000\n当这三个状态位都被清除(即0)时，goodbit将被清除，表示这个流一切正常，是打开的\n检查状态的方法 可通过对应的方法(bad(),fail(),eof(),rdstate())查看这四个位的状态(具体应查看头文件定义，实际上有略微不同)\noperator!()和rdstate()效果一样，而good()是与operator!()相反的方法，当goodbit=0时返回true\n这些方法都在ios中定义\n需要清楚的是，rdstate()的返回值是ios_base::iostate对象，而其它方法都返回布尔类型，这意味着rdstate()方法还可以通过与标准状态常量对比进行错误检测\n这些方法都可用于检测错误，例如rdstate()可用于粗略地检查所有错误类型，出错则返回真\n这些流并不会在出错时报错，这涉及到ios::exceptions()方法，它默认返回goodbit，只有当**f.exceptions() \u0026amp; f.rdstate()不为零**时，程序才会抛出ios_base::failure错误\n如果需要让流报错，应改变exceptions()的返回值(使用它的重载函数exceptions(iostate))：\nc\u0026#43;\u0026#43;// 示例 f.exceptions(ios::failbit); // 设置成100,则一旦流的failbit被设置,将报错 try { ... } catch (ios::failure\u0026amp; message){ cerr \u0026lt;\u0026lt; message.what(); }清除状态的方法 ios::clear()和ios::setstate()方法可将流设置成正常，原理有所不同：\nclear()将该流的状态强制改成提供的状态，默认提供0，即ios::goodbit setstate()将该流的状态和提供的状态相叠加，相当于两个二进制数进行了按位或操作，实际上，进行f.setstate(state)相当于进行f.clear(f.rdstate() | state) c\u0026#43;\u0026#43;// 定义 file.clear(ios_base::iostate=0); // 默认参数为000,不设置任何位,即清除所有三位 file.setstate(ios_base::iostate); // 必须提供参数,只会设置它而不影响其它两位 // 示例 file.clear(ios::eofbit); // 参数为010,设置eofbit,清除其它两位 file.clear(ios::eofbit | ios::badbit) // 参数为011,清除failbit,设置其它两位 当然，可以用其他流的状态作为参数，只是这没有意义\n常用不带参数的clear()方法来强制打开流，而setstate()常被流对象自动调用，来叠加到自身的流状态里\n文件I/O 实现文件IO需要包含头文件\u0026lt;fstream\u0026gt;，创建输入流则创建ifstream对象，创建输出流则创建ofstream对象，只不过流的另一端由键盘和显示器变成了文件\n因为ifstream,ofstream分别为istream,ostream的派生类，所以可以像cin,cout一样使用方法，而除此之外，文件IO有额外的成员和方法\n创建文件流和关闭文件 ifstream和ofstream类中额外定义了默认构造函数和带字符串(有char*类型和string\u0026amp;类型的两个重载)参数的构造函数\n以ifstream为例，初始化一个文件流对象如下：\nc\u0026#43;\u0026#43;// 定义 ifstream fin; // 默认构造函数,这会创建一个新的缓冲区(fb)并将fb的引用传递给基类初始化器 // 将字符串传递给构造函数,它将创建缓冲区并以默认为ios::in的模式调用open(\u0026#34;fileName\u0026#34;) ifstream fin(char*,ios_base::openmode=ios_base::in); ifstream fin(string\u0026amp;,ios_base::openmode=ios_base::in);一个文件流对象除了继承istream/ostream的用于格式化输入输出的流装置外，还额外包含一个私有的filebuf对象，用于与文件进行底层交互；例如文件流对象含有的成员函数open()和close()，它们实质上是调用filebuf类的open()和close()，只是在类里面重写了一遍方便使用罢了\nfstream类的对象较为特殊，它继承了两个缓冲区，目的在于用一个对象实现对文件的输入和输出\n它们的析构函数是空的，这意味着文件是通过方法close()关闭的，即关闭文件不影响流对象的重复使用，而流的打开与关闭和流状态有关；这也更说明，流不是一个容器，而数据也只会暂时存储在缓冲区中\n文件模式 对于一个文件流对象fio，如果要检查它的流是否异常(而不是文件是否打开)，一般不用像cin,cout一样的operator!(),rdstate()方法，而是成员函数**is_open()**(还是调用filebuf类的成员，但这次是间接调用了file类的成员)，它能检查出“文件打开方式错误”一类的异常\nC++的文件模式设计与C有许多共通之处，但更好理解和记忆：\nios_base::openmode(以下省略ios::) C风格 意义 文件存在 文件不存在 in(ifstream默认值) \"r\" 读打开 正常 异常(failbit+badbit) out(ofstream默认值;默认加上trunc) \"w\" 写打开 清空文件 创建空文件 ate fseek(file*,0,SEEK_END) 跳到eof 文件必须已经打开 app(默认加上out) \"a\" 追加写打开 不会被清空,并在文件尾开始添加内容 创建空文件 trunc - 清空文件 文件必须已经写打开 binary \"b\" 二进制模式打开 文件必须已经打开 in | out(fstream默认值) \"r+\" 读写打开 不清空文件,并在文件开头开始添加内容 异常(failbit+badbit) in | out | trunc \"w+\" 读写打开 清空文件 创建空文件 other | ate \"other\"后,fseek(file*,0,SEEK_END) 打开文件后跳到eof 视other而定 other | binary \"other+b\" 二进制other模式打开 视other而定 以下为常用模式：\nc\u0026#43;\u0026#43;// 示例 ofstream(\u0026#34;tmp/old\u0026#34;); // 创建空文件或覆盖旧文件,并写打开; 和显式声明out或out|trunc等价 ofstream(\u0026#34;file\u0026#34;,ios::app); // 追加写打开; 和显式声明out|app等价 ifstream(\u0026#34;file\u0026#34;); // 只读打开; 和显式声明in等价 fstream(\u0026#34;file\u0026#34;); // 读写打开文件,不清空文件; 和显式声明in|out等价 fstream(\u0026#34;tmp/old\u0026#34;,ios::in|ios::out|ios::trunc);// 创建空文件或覆盖旧文件,并读写打开; 不常用 // 用于图像或其它二进制资源的读写: ofstream(\u0026#34;Icon/other\u0026#34;,ios::out|ios::binary); ifstream(\u0026#34;Icon/other\u0026#34;,ios::in|ios::binary);读写与异常处理 一般来说，只要打开或关闭文件正常，那么文件流就会保持正常，对于文本内容，像使用cin,cout一样即可；而对于二进制内容，可以用read()(读方法，在istream类中)和write()(写方法，在ostream类中)，它们和其它输入输出方法的区别是在读取/输出时不会添加\\0，且read()函数不提供默认或指定的终止符(getline()默认遇回车停止)：\nc\u0026#43;\u0026#43;fin.read(char*,int); // 读取指定个字符到char*中,不支持string类 fout.write(char*,int); // 向文件写入指定个字符,不支持string类 当然在关闭文件后，为了防止读到EOF或其他问题影响这个流对象的再使用，应选择改变exceptions()，或是不管不顾直接clear()强行打开流\n附 以阅读头文件的方式回顾所学(Doxygen风格注释)，以下是部分内容在头文件内的定义(小标题是头文件名)：\n各类关联 重命名：\n继承关系：\nios_base.h 以下都属于ios_base类：\n流状态常量：\n控制符相关：\n文件模式：\nbasic_ios.h 以上是ios类中的成员，下面这个控制符以及oct,dec等是本头文件内的内联函数：\nistream\u0026amp;ostream 以istream为例，operator\u0026lt;\u0026lt;与其类似：\nget(...)与其类似：\n基类初始化器：\n控制符flush和endl都在\u0026lt;ostream\u0026gt;里，它们是内联函数：\nifstream\u0026amp;ofstream 以ifstream类为例：\nfstream 这些是filebuf类里的：\n","permalink":"https://echo.ishian.me/2024/01/19/cpp-io/","summary":"讲解C++的iostream流对象、cin的各种读取方法及格式化输出操作","title":"Cpp: I/O"},{"content":"matplotlib数据可视化 基本用法 matplotlib用于将数据呈现为图，它并非python自带的库，需要pip install\n生成一条直线的步骤如下，它的原理是将提供的数组中各点连接，所以提供的数组越大，线越光滑\npythonimport matplotlib.pyplot as plt # 常用的是pyplot模块 import numpy as np # x,y可以是数字或字符串，为字符串时默认等距。 x = np.linspace(1,10,500) # linspace:在1-10区间内生成500个等距点,返回数组 y = 2*x + 1 # 直线方程 plt.figure() # 创建新图,方便开多个figure # plt.figure(num=4) 可以传数字参数num=?,空则默认按顺序命名 # plt.figure(figsize=(5,6)) 自定义宽,高(但实际上可以用鼠标拉长) plt.title(\u0026#39;a line\u0026#39;) # 图的名称 plt.plot(x,y) # 生成图,x轴数组为x,y轴数组为y plt.show() # 将该图展现出来 如图，底栏所示功能编号1-7\n返回首页(即一开始看到的页面) ​ 2-3. 后退/前进\n移动 放大窗口 调整图的边界参数 保存当前窗口 自定义线 如果想在一个figure内画多条线，plt.plot还可以接受其他参数用于区分两条线\npythony = exp(x) plt.figure() plt.plot(x,y) plt.plot(y,x,color=\u0026#39;red\u0026#39;,linestyle=\u0026#39;--\u0026#39;)# 默认蓝色,这里改为红色,虚线 # linestyle可简写为ls,color可简写为c plt.plot(x,y,linestyle=(0,(1,2,3))) # 元组定义线型 关于元组定义线型，(num,(n1,n2,n3,...))表示以后面元组的值作为单位画线 例如(0,(1,2,3))指的是(1磅实线,2磅空白,3磅实线)，因为是奇数个，所以下一组变为(1磅空白,2磅实线,3磅空白)，所以一般来说，常用的是偶数个元素的元组 关于num，它用于在显示标签时偏移的长度，例如：\ncolor参数可用简写如'r'(红色),'g'(绿色)，多条曲线不指定颜色时会自动选择不同颜色，其它颜色如下：\n'm'：洋红色 'w'：白色 'k'：黑色 'y'：黄色 'g'：绿色 '#000000'：自定义RGB颜色字符串-\u0026gt;RGB颜色查询对照表 其它常用linestyle如下：\n'-'/'solid'：实线，默认 '--'/'doshed'：虚线 ':'/'dotted'：点线 '-.'/dashdot：点划线 其它参数：\nlabel='name'：设置这条线的标签，可用plt.legend()显示 linewidth=num：线的宽度，默认为1，可简写为lw alpha：线的透明度，范围0~1 'bo'：改为用蓝色实心圈绘制而非连线，常用于绘制单独的点，通过改变首字母来改变颜色，以下标记也可以在前面添加表示颜色的字母，不再赘述 '.'/','：点/像素点(很小的点)绘制 '\u0026lt;'/'\u0026gt;'/'^'/'v'：各种三角 '1'/'2'/'3'/'4'：各种三叉 '*'：星号绘制 '+'：+号绘制，上面标记都只能跟在数组后且不能重复 如果只提供一个数组，则x会默认为[0,1,...,N-1]，N为提供的数组的元素个数\n绘图标记 除了改变线型，pyplot还可以用marker更改点的样式\npythonplt.plot(x,y,marker=\u0026#39;o\u0026#39;) # 每个点用实心圆代替(但是线型还是直线) plt.plot(x,y,marker=r\u0026#39;$\\alpha$\u0026#39;,markersize=10,label=\u0026#39;name\u0026#39;) # markersize可简写为ms plt.plot(x,y,marker=\u0026#39;*\u0026#39;,mec=\u0026#39;r\u0026#39;,mfc=\u0026#39;k\u0026#39;) # mec设置点边框的颜色,mfc设置点内部颜色 # mec:markeredgecolor, mfc:markerfacecolor除了线型所支持的标记外，点的标记还支持Latex语法\n可以用fmt='[mk][line][color]'参数快速设置点、线、色\npythonplt.plot(x,y,fmt=\u0026#39;o:k\u0026#39;) # 实心圆(点),虚线(线),黑色(色)自定义坐标轴 设置坐标轴的值 pythonplt.xlim((0,1)) # 限制x轴的范围 plt.ylim((1,2)) # 限制y轴的范围(也可以用[]括起) plt.xlabel(\u0026#39;label\u0026#39;) # 设定x轴的标签,即说明x轴是什么,同理可用plt.ylabel()设置y轴的标签 # loc属性:设置标签的位置,有right/left/center 默认center ---\u0026gt; title(),legend()都有该属性 # ylabel的loc属性有top/bottom/center plt.xticks(np.linspace(0,1,5)) # 更改x轴的单位,默认每0.5为一个节点,这里设置为将0-1分割为5点 # 后面数组对应前面数组的值,可以改成自己需要的名称 plt.xticks([0,1,5],[\u0026#39;none\u0026#39;,\u0026#39;middle\u0026#39;,\u0026#39;pretty good\u0026#39;]) 此外，后面数组还支持Latex语法，因为可能要用到转义字符等，所以这种情况需要在字符串前加上'r'/'R'，将它视为原始字符串(raw string)来解析，例如plt.xticks([0,1,5],[r'$\\theta$','$x$',r'$\\Theta$'])的效果为：\n移动坐标轴 需要用到gca()方法，全称get current axis，一般将返回的对象称为ax，以下是隐藏右轴及上轴的代码\npythonax = plt.gca() # 获取前面这整张图的坐标轴,有上下左右四条轴 for i in [\u0026#39;right\u0026#39;,\u0026#39;top\u0026#39;]: ax.spines[i].set_color(\u0026#39;none\u0026#39;) # 可以通过spines[]更改对应轴的属性四条轴分别为：top,bottom,left,right\n需要移动坐标轴，则要用到set_position()方法，它接受元组参数，效果如下\npythonax.spines[\u0026#39;left\u0026#39;].set_position((\u0026#39;data\u0026#39;,0)) # 将y轴移动到x=0的位置 一般来说，将坐标轴交点设置在(0,0)处的方法如下\npythonfor i in [\u0026#39;left\u0026#39;,\u0026#39;bottom\u0026#39;]: ax.spines[i].set_position((\u0026#39;data\u0026#39;,0))网格线 可用grid方法设置网格线：\npythonplt.grid(None,which=\u0026#39;major\u0026#39;,axis=\u0026#39;both\u0026#39;,...) # 其它参数和设置线型时类似 None：是否显示网格线，如果后面接了其它参数，则默认为1，可设置为0 which：显示主要/次要网格线，默认只显示主要，可改为minor或both axis：显示某方向的网格线，默认为both，可改为x或y 绘制子图 pyplot提供subplot()和subplots()方法，它能在一张画布中绘制多张图\nsubplot subplot有三个必选参数，从左到右分别是行、列、标号，可以加可选参数例如共享轴、标签、投影类型等，返回一个对象，在subplots里将详细讲述对象作图 或者，把逗号删去，使用一个三位数的参数，例如plt.subplot(111)\n子图里可以改变图的投影类型，如：\npythonplt.figure() # 创建画布,或: fig = plt.figure() # 将画布赋给fig ax = plt.subplot(111,projection=\u0026#39;polar\u0026#39;) # 极坐标投影,或: ax = plt.subplot(111,polar=1) # 等效其它投影类型还有：\naitoff hammer lambert mollweide 这些投影类型需借助其它库，超出了pyplot的范围，且都用作绘制世界地图，故不细讲\n注意到这个fig对象，它让切分画布变得便利：\npythonax1 = fig.add_subplot(111) # 将左上角的区域切分出来subplots subplots用于一次性创建多个子图，并返回数组对象方便操作，现在介绍不同于上述用函数作图的对象作图方法，这种方法更常用，便于进行精细化操作。当然，如果只需要简单的图形，用函数作图的方式也无妨\n它利用subplots返回画布和图对象，通过改变对象的属性，进行对特定画布中特定部分的修改：\npythonfig, ax = plt.subplots() # 返回画布,图数组(这里没给参数,默认生成一张图) ax.plot(x,y)这里的ax指的是axes，即画布中的一部分。这里因为只生成一张图，所以只有一个axes\n这种方法就不需要plt.figure()了，因为subplots()返回时就已创建一张画布了\n此外，还可以设置共享轴与投影形式：\npythonfig,(ax1,ax2) = plt.subplots(1,2,sharex=1,sharey=1) # 1即所有子图共享,0即所有子图独立 # 改变图形与subplot不同,需要将参数转换为字典传进去 fig,ax3 = plt.subplots(1,1,subplot_kw=dict(projection=\u0026#39;polar\u0026#39;)) fig,ax3 = plt.subplots(1,1,subplot_kw=dict(polar=1))还可设置为col(每列子图共享，列与列间独立)，row(每行子图共享，行与行间独立)\n其它设置与函数作图类似但部分有所不同：\npythonfig,axs = plt.subplots(2,2) # 两行两列,返回数组,用坐标访问 axs[0,0].set_title(\u0026#39;titleName\u0026#39;) # 标题 axs[0,0].plot(x,y,lw=1,c=\u0026#39;r\u0026#39;,marker=\u0026#39;*\u0026#39;,...) # plot axs[0,0].set_xticks(array) # 轴的单位 axs[0,0].set_xlim(-1,1) # 轴的范围 axs[0,0].spines[\u0026#39;right\u0026#39;].set_color(\u0026#39;none\u0026#39;) # 隐藏轴 axs[0,0].axis(\u0026#39;off\u0026#39;) # 隐藏所有轴,可以用xaxis/yaxis axs[0,0]1.set_xlabel(\u0026#39;labelName\u0026#39;) # 给x轴加标注其它图形 接下来简略介绍其它图形的绘制\n散点图(scatter) 除了x、y外，其它参数可以有：marker(改变点的样式),s(点的大小),c(点的颜色),lw等已经讲过的，和：\ncmap(colormap)：调色盘，只有当c是一个浮点数数组时启用，会将这些数值对应到调色盘内的某个颜色。默认为viridis，可以通过以下命令查看颜色条\npython# c=y,则y的数值大小可以清晰地映射到颜色条上 plt.scatter(x,y,c=y,cmap=\u0026#39;viridis\u0026#39;) plt.colorbar() # 函数作图 ax.figure.colorbar(ax.scatter(x,y,c=y,cmap=\u0026#39;viridis\u0026#39;)) # 对象作图其它可选调色盘：\nvmin,vmax：调节颜色条的范围，大于vmax的数值将被强制映射到vmax上，vmin同理\nnorm：也是用于调节范围的，优先级高于上一条，参数没搞懂，有如下：\nlinear log symlog asinh logit function functionlog 柱形图(bar/barh) bar即纵向柱形图，barh即横向柱形图。除x,y外的其它参数有：\nwidth：浮点数数组，表示柱子宽度 height：浮点数数组，表示横向柱子的宽度，它是barh()的参数 bottom：底线值，即y=?，默认0 color：颜色，不能缩写为c，可接受颜色数组 align：对齐方式，默认center，即值的中间，可改为edge(左对齐)，如要实现右对齐，可以传递负数宽度值 饼图(pie) 饼图的参数有点不同，它只需要一个x的参数，且它必须是非负数、非字符串的值。其它参数如下：\nexplode：一个数组，表示各个扇形的间隔。一般来说，没有显式说明的第二个参数默认为它 labels：一个列表，表示各个扇形的标签 colors：自定义颜色 autopct：数值显示形式，与C语言类似，%d表示整数，%0.1f表示一位小数，在后面加上%%表示百分比形式显示 labeldistance：标签的位置，默认1.1，即饼图外侧 pctdistance：与上一条类似，表示数值的位置 shadow：饼图是否有阴影，默认为0 radius：饼图半径 其它参数实用性不强，这里不再说明\n直方图(hist) 直方图用于统计数的个数，也只需要提供x。其它参数如下：\nbins：箱数，默认为10。hist将直方图分为bins个等距区间(即箱)，每个箱子的柱高表示这个区间内数的个数 density：是否以频率显示。默认为0，即默认为统计个数而非频率 weights：数组，表示每个箱子的权重，箱子中每有一个数就会乘以这个权重值 bottom：与柱形图功能相同 histtype：直方图样式，可选参数有(一些没有区别的已省去)： bar：默认值 step：空心柱 其它装饰 复杂子图 在讲述subplots时，只是提到了一个简便的画图方法，如果需要更加精细地切分画布，这种规规整整的方式就不行了。于是我们需要用到网格：\npythonplt.figure() ax1 = plt.subplot2grid((2,2),(0,0)) # 将画布切分为2行2列,并把(0,0)网格返回给ax对象 ax2 = plt.subplot2grid((2,2),(1,0),colspan=2) # ax2占满第二行 ax3 = plt.subplot2grid((2,2),(1,1),rowspan=2) # ax3占满第二列也可以用matplotlib.gridspec模块实现：\npythonimport matplotlib.gridspec as gridspec plt.figure() gs = gridspec.GridSpec(3,3) # 将画布切分成3行3列 ax1 = plt.subplot(gs[0,0]) ax2 = plt.subplot(gs[1:,1]) ax3 = plt.subplot(gs[-1,-1])网格坐标写法有如下：\nstart:end：从start开始占到end，例如[1:,1]为占了第一列的第二行到最后一行 -num：倒数第num行 这还是有点方正了，如果想随心所欲改变图的长宽，可以用update方法或plt.subplots_adjust，它们的参数是类似的：\npythongs1 = gridspec.GridSpec(3,3) gs1.update(left=0,right=0.5) # 限定gs1网格只占画布的左边其它参数：\nwspace：横向子图的间距，默认0.2 hspace：纵向子图的间距，默认0.2 top,bottom：上边界和下边界 中文显示 需要在开头加上：\npythonplt.rcParams[\u0026#34;font.family\u0026#34;]=\u0026#34;SimHei\u0026#34; # 将字体名称设置为\u0026#34;SimHei\u0026#34;(黑体)关于其它字体的设置：链接\n但是在正则表达式中似乎无效\ntick_params() 通过这个方法可以让刻度线消失：\npythonax.tick_params(length=0) # 直接设置刻度线的长度,或 ax.tick_params(bottom=0) # 不显示底轴的刻度线实例 code：\npythonimport numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec def main(): x = np.linspace(-5,5,30) xSin = np.linspace(0, 2*np.pi,30) theta = np.linspace(0, 2*np.pi,30) yExp = np.exp(x) ySin = np.sin(xSin) rHeart = np.sin(theta) plt.figure() # 左网格 gsL = gridspec.GridSpec(2,2) gsL.update(left=0.05,right=0.4,wspace=0.4) axSin = plt.subplot(gsL[0,0]) axPol = plt.subplot(gsL[0,1], polar=1) # 心形线极坐标映射 axExp = plt.subplot(gsL[1,:]) # 占满第二行 axSin.plot(xSin,ySin) axSin.set_xticks([0,np.pi,2*np.pi],[0,r\u0026#39;$\\pi$\u0026#39;,r\u0026#39;$2\\pi$\u0026#39;]) axSin.set_title(\u0026#39;y=sinx\u0026#39;,backgroundcolor=\u0026#39;skyblue\u0026#39;) axPol.plot(theta, rHeart) axPol.set_title(r\u0026#39;$\\rm r=sin\\theta$\u0026#39;,backgroundcolor=\u0026#39;skyblue\u0026#39;) axExp.plot(x,yExp,label=r\u0026#39;$y=e^x$\u0026#39;) axExp.plot(yExp,x,ls=(0,(0,1,2,1)),label=\u0026#39;y=lnx\u0026#39;) axExp.plot([0,1],[1,0],\u0026#39;ko\u0026#39;) axExp.set_xlim(-0.5,2) axExp.set_ylim(-5,10) axExp.set_xticks(np.linspace(0,2,3)) axExp.set_yticks(np.linspace(-5,10,4)) axExp.tick_params(length=0) axExp.set_title(r\u0026#39;$\\rm y=e^x\\ \u0026amp;\\ y=\\ln x$\u0026#39;,backgroundcolor=\u0026#39;pink\u0026#39;) plt.legend(loc=\u0026#39;upper right\u0026#39;) for i in [\u0026#39;right\u0026#39;,\u0026#39;top\u0026#39;]: axSin.spines[i].set_color(\u0026#39;none\u0026#39;) axExp.spines[i].set_color(\u0026#39;none\u0026#39;) for i in [\u0026#39;left\u0026#39;,\u0026#39;bottom\u0026#39;]: axSin.spines[i].set_position((\u0026#39;data\u0026#39;,0)) axExp.spines[i].set_position((\u0026#39;data\u0026#39;,0)) # 右网格 gsR = gridspec.GridSpec(2,2,left=0.5,right=0.9,hspace=0.3) axDens = plt.subplot(gsR[0,0]) axNums = plt.subplot(gsR[0,1]) axPie = plt.subplot(gsR[1,:]) rng = np.random xNums1 = rng.randn(1000) xNums2 = rng.randn(1000) axDens.hist(xNums1,alpha=0.7,color=\u0026#39;skyblue\u0026#39;, bins=100,weights=[np.array(2)for _ in range(1000)],density=1) axDens.hist(xNums2,alpha=0.7,color=\u0026#39;pink\u0026#39;,bins=100,density=1) axDens.tick_params(length=0) axDens.set_title(\u0026#39;two groups\\\u0026#39;s density\u0026#39;,backgroundcolor=\u0026#39;orange\u0026#39;) axNums.hist(xNums1,alpha=0.7,color=\u0026#39;skyblue\u0026#39;, bins=100,weights=[np.array(2)for _ in range(1000)]) axNums.hist(xNums2,alpha=0.7,color=\u0026#39;pink\u0026#39;,bins=100) axNums.tick_params(length=0) axNums.set_title(\u0026#39;two groups\u0026#39;,backgroundcolor=\u0026#39;red\u0026#39;) axPie.pie([40,30,20,10],colors=[\u0026#39;#a4f9e4\u0026#39;,\u0026#39;#f9c5a4\u0026#39;,\u0026#39;#c9a4f9\u0026#39;,\u0026#39;#d1f9a4\u0026#39;], autopct=\u0026#34;%d%%\u0026#34;,radius=1.5, labels=[\u0026#39;yorushika\u0026#39;,\u0026#39;atarayo\u0026#39;,\u0026#39;zutomayo\u0026#39;,\u0026#39;yoasobi\u0026#39;], labeldistance=0.7, pctdistance=0.3, shadow=1, explode=[0.1,0,0,0]) axPie.set_title(\u0026#34;The four \u0026#39;YoRu\u0026#39;s in my mind\u0026#34;) plt.show() if __name__ == \u0026#39;__main__\u0026#39;: main()run：\n","permalink":"https://echo.ishian.me/2024/01/11/python-pyplot/","summary":"介绍matplotlib.pyplot的基本用法：figure、plot及图的设置与交互操作","title":"Python: 使用pyplot可视化"},{"content":"极限 函数极限 1.lim⁡x→0tan⁡x−sin⁡xxln⁡(1+sin⁡2x)解:原式=lim⁡x→0tan⁡x−sin⁡xx3=12\\begin{align}\u0026amp;1.\\lim_{x\\rightarrow 0}\\frac{\\tan x-\\sin x}{x\\ln(1+\\sin^2x)}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{\\tan x-\\sin x}{x^3}=\\frac12\\end{align}​1.x→0lim​xln(1+sin2x)tanx−sinx​解:原式=x→0lim​x3tanx−sinx​=21​​​ 2.lim⁡x→0(x−sin⁡x)e−x21−x3−1解:原式=lim⁡x→016x3−12x2=−13\\begin{align}\u0026amp;2.\\lim_{x\\rightarrow0}\\frac{(x-\\sin x)e^{-x^2}}{\\sqrt{1-x^3}-1}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{\\frac16x^3}{-\\frac12x^2}=-\\frac13\\end{align}​2.x→0lim​1−x3​−1(x−sinx)e−x2​解:原式=x→0lim​−21​x261​x3​=−31​​​ 3.lim⁡x→0ln⁡(esin⁡x+1−cos⁡x3)−sin⁡xarctan⁡(41−cos⁡x3)解:原式=lim⁡x→0esin⁡x+1−cos⁡x3−1−sin⁡x41−cos⁡x3=lim⁡x→01+sin⁡x+sin⁡2x2−1−sin⁡x+o(x2)41−cos⁡x3+14=14\\begin{align}\u0026amp;3.\\lim_{x\\rightarrow0}\\frac{\\ln(e^{\\sin x}+\\sqrt[3]{1-\\cos x})-\\sin x}{\\arctan(4\\sqrt[3]{1-\\cos x})}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{e^{\\sin x}+\\sqrt[3]{1-\\cos x}-1-\\sin x}{4\\sqrt[3]{1-\\cos x}}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{1+\\sin x+\\frac{\\sin^2x}2-1-\\sin x+o(x^2)}{4\\sqrt[3]{1-\\cos x}}+\\frac14\\\\\u0026amp;=\\frac14\\end{align}​3.x→0lim​arctan(431−cosx​)ln(esinx+31−cosx​)−sinx​解:原式=x→0lim​431−cosx​esinx+31−cosx​−1−sinx​=x→0lim​431−cosx​1+sinx+2sin2x​−1−sinx+o(x2)​+41​=41​​​ 4.lim⁡x→01−cos⁡xcos⁡2xcos⁡3x3x2解:原式=lim⁡x→01−cos⁡x+cos⁡x(1−cos⁡2x+cos⁡2x(1−cos⁡3x3))x2=lim⁡x→012x2+12(1−cos⁡2x)+13(1−cos⁡3x)x2=lim⁡x→012x2+x2+32x2x2=3\\begin{align}\u0026amp;4.\\lim_{x\\rightarrow0}\\frac{1-\\cos x\\sqrt{\\cos 2x}\\sqrt[3]{\\cos 3x}}{x^2}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{1-\\cos x+\\cos x(1-\\sqrt{\\cos 2x}+\\sqrt{\\cos 2x}(1-\\sqrt[3]{\\cos 3x}))}{x^2}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{\\frac12x^2+\\frac12(1-\\cos 2x)+\\frac13(1-\\cos 3x)}{x^2}=\\lim_{x\\rightarrow0}\\frac{\\frac12x^2+x^2+\\frac32x^2}{x^2}=3\\end{align}​4.x→0lim​x21−cosxcos2x​3cos3x​​解:原式=x→0lim​x21−cosx+cosx(1−cos2x​+cos2x​(1−3cos3x​))​=x→0lim​x221​x2+21​(1−cos2x)+31​(1−cos3x)​=x→0lim​x221​x2+x2+23​x2​=3​​ 5.lim⁡x→π2(1−sin⁡x)(1−sin⁡x3)⋯(1−sin⁡xn)(1−sin⁡x)n−1=lim⁡x→π21n!(1−sin⁡x)n−1(1−sin⁡x)n−1=1n!\\begin{align}\u0026amp;5.\\lim_{x\\rightarrow\\frac\\pi2}\\frac{(1-\\sqrt{\\sin x})(1-\\sqrt[3]{\\sin x})\\cdots(1-\\sqrt[n]{\\sin x})}{(1-\\sin x)^{n-1}}\\\\\u0026amp;=\\lim_{x\\rightarrow\\frac\\pi2}\\frac{\\frac1{n!}(1-\\sin x)^{n-1}}{(1-\\sin x)^{n-1}}=\\frac1{n!}\\end{align}​5.x→2π​lim​(1−sinx)n−1(1−sinx​)(1−3sinx​)⋯(1−nsinx​)​=x→2π​lim​(1−sinx)n−1n!1​(1−sinx)n−1​=n!1​​​ 6.lim⁡x→01+x1−x1+2x1−2x41+3x1−3x6⋯1+nx1−nx2n−13πarctan⁡x−(x2+1)arctan⁡3x,n为正整数解:原式=ln⁡极限逆用lim⁡x→0ln⁡(1+x1−x1+2x1−2x41+3x1−3x6⋯1+nx1−nx2n)3πarctan⁡x−(x2+1)arctan⁡3x=lim⁡x→012(21−x−2)+14(21−2x−2)+⋯3πx−x5−x3=lim⁡x→0x1−x+x1−2x+⋯3πx−x5−x3=lim⁡x→nx3πx=n3π\\begin{align}\u0026amp;6.\\lim_{x\\rightarrow0}\\frac{\\sqrt{\\frac{1+x}{1-x}}\\sqrt[4]{\\frac{1+2x}{1-2x}}\\sqrt[6]{\\frac{1+3x}{1-3x}}\\cdots\\sqrt[2n]{\\frac{1+nx}{1-nx}}-1}{3\\pi\\arctan x-(x^2+1)\\arctan^3x},n为正整数\\\\\u0026amp;解:原式\\xlongequal{\\ln极限逆用}\\lim_{x\\rightarrow0}\\frac{\\ln(\\sqrt{\\frac{1+x}{1-x}}\\sqrt[4]{\\frac{1+2x}{1-2x}}\\sqrt[6]{\\frac{1+3x}{1-3x}}\\cdots\\sqrt[2n]{\\frac{1+nx}{1-nx}})}{3\\pi\\arctan x-(x^2+1)\\arctan^3x}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{\\frac12(\\frac2{1-x}-2)+\\frac14(\\frac{2}{1-2x}-2)+\\cdots}{3\\pi x-x^5-x^3}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{\\frac{x}{1-x}+\\frac x{1-2x}+\\cdots}{3\\pi x-x^5-x^3}=\\lim_{x\\rightarrow}\\frac{nx}{3\\pi x}=\\frac n{3\\pi}\\end{align}​6.x→0lim​3πarctanx−(x2+1)arctan3x1−x1+x​​41−2x1+2x​​61−3x1+3x​​⋯2n1−nx1+nx​​−1​,n为正整数解:原式ln极限逆用x→0lim​3πarctanx−(x2+1)arctan3xln(1−x1+x​​41−2x1+2x​​61−3x1+3x​​⋯2n1−nx1+nx​​)​=x→0lim​3πx−x5−x321​(1−x2​−2)+41​(1−2x2​−2)+⋯​=x→0lim​3πx−x5−x31−xx​+1−2xx​+⋯​=x→lim​3πxnx​=3πn​​​ 7.lim⁡x→∞(∫0xeu2du)2∫0xe2u2du解:原式=lim⁡x→∞2∫0xeu2duex2=2lim⁡x→∞ex22xex2=0\\begin{align}\u0026amp;7.\\lim_{x\\rightarrow\\infty}\\frac{(\\int_0^xe^{u^2}\\mathrm du)^2}{\\int_0^xe^{2u^2}\\mathrm du}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow\\infty}\\frac{2\\int_0^xe^{u^2}\\mathrm du}{e^{x^2}}=2\\lim_{x\\rightarrow\\infty}\\frac{e^{x^2}}{2xe^{x^2}}=0\\end{align}​7.x→∞lim​∫0x​e2u2du(∫0x​eu2du)2​解:原式=x→∞lim​ex22∫0x​eu2du​=2x→∞lim​2xex2ex2​=0​​ 8.lim⁡x→0(ex+e2x+⋯+enxn)ex解:原式=elim⁡x→0exln⁡(1+ex+e2x+⋯+enxn−1)幂上极限为lim⁡x→0exex+e2x+⋯+enx−nn=lim⁡x→0exx+2x+⋯+nxn=(n+1)e2故原极限=e(n+1)e2\\begin{align}\u0026amp;8.\\lim_{x\\rightarrow0}\\left(\\frac{e^x+e^{2x}+\\cdots+e^{nx}}n\\right)^{\\Large\\frac ex}\\\\\u0026amp;解:原式=e^{\\lim_{x\\rightarrow0}{\\Large\\frac ex}\\ln(1+\\frac{e^x+e^{2x}+\\cdots+e^{nx}}n-1)}\\\\\u0026amp;幂上极限为\\lim_{x\\rightarrow0}\\frac ex\\frac{e^x+e^{2x}+\\cdots+e^{nx}-n}n\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac ex\\frac{x+2x+\\cdots+nx}n=\\frac{(n+1)e}{2}\\\\\u0026amp;故原极限=e^{\\Large\\frac{(n+1)e}2}\\end{align}​8.x→0lim​(nex+e2x+⋯+enx​)xe​解:原式=elimx→0​xe​ln(1+nex+e2x+⋯+enx​−1)幂上极限为x→0lim​xe​nex+e2x+⋯+enx−n​=x→0lim​xe​nx+2x+⋯+nx​=2(n+1)e​故原极限=e2(n+1)e​​​ 9.若f(x)在点x=a处可导,且f(a)≠0,则lim⁡n→∞(f(a+1n)f(a))n原式=elim⁡n→∞nln⁡(f(a+1n)f(a))幂上极限=lim⁡n→∞nf(a+1n)−f(a)f(a)=f′(a)f(a)故原极限=ef′(a)f(a)\\begin{align}\u0026amp;9.若f(x)在点x=a处可导,且f(a)\\ne0,则\\lim_{n\\rightarrow\\infty}\\left(\\frac{f\\left(a+\\frac1n\\right)}{f(a)}\\right)^n\\\\\u0026amp;原式=e^{\\lim\\limits_{n\\rightarrow\\infty}n\\ln(\\frac{f\\left(a+\\frac1n\\right)}{f(a)})}\\\\\u0026amp;幂上极限=\\lim_{n\\rightarrow\\infty} n\\frac{f\\left(a+\\frac1n\\right)-f(a)}{f(a)}=\\frac{f\u0026#x27;(a)}{f(a)}\\\\\u0026amp;故原极限=e^{\\Large\\frac{f\u0026#x27;(a)}{f(a)}}\\end{align}​9.若f(x)在点x=a处可导,且f(a)=0,则n→∞lim​(f(a)f(a+n1​)​)n原式=en→∞lim​nln(f(a)f(a+n1​)​)幂上极限=n→∞lim​nf(a)f(a+n1​)−f(a)​=f(a)f′(a)​故原极限=ef(a)f′(a)​​​ 10.lim⁡x→∞e−x(1+1x)x2解:原式=elim⁡x→∞−x+x2ln⁡(1+1x)=elim⁡x→∞(−x+x2(1x−12x2+o(x2)))=e−12\\begin{align}\u0026amp;10.\\lim_{x\\rightarrow\\infty}e^{-x}\\left(1+\\frac1x\\right)^{x^2}\\\\\u0026amp;解:原式=e^{\\lim\\limits_{x\\rightarrow\\infty}-x+x^2\\ln(1+\\frac1x)}=e^{\\lim\\limits_{x\\rightarrow\\infty}(-x+x^2(\\frac1x-\\frac1{2x^2}+o(x^2)))}=e^{-\\frac12}\\end{align}​10.x→∞lim​e−x(1+x1​)x2解:原式=ex→∞lim​−x+x2ln(1+x1​)=ex→∞lim​(−x+x2(x1​−2x21​+o(x2)))=e−21​​​ 11.lim⁡x→0sin⁡2x−x2cos⁡2xx2sin⁡2x解:原式=lim⁡x→0(sin⁡x−x+x(1−cos⁡x))(sin⁡x−x+x(1+cos⁡x))x4=lim⁡x→0(−16x3+12x3)(−16x3+2x)x4=−13+1=23\\begin{align}\u0026amp;11.\\lim_{x\\rightarrow0}\\frac{\\sin^2x-x^2\\cos^2x}{x^2\\sin^2x}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{(\\sin x-x+x(1-\\cos x))(\\sin x-x+x(1+\\cos x))}{x^4}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{(-\\frac16x^3+\\frac12x^3)(-\\frac16x^3+2x)}{x^4}=-\\frac13+1=\\frac23\\end{align}​11.x→0lim​x2sin2xsin2x−x2cos2x​解:原式=x→0lim​x4(sinx−x+x(1−cosx))(sinx−x+x(1+cosx))​=x→0lim​x4(−61​x3+21​x3)(−61​x3+2x)​=−31​+1=32​​​ 12.若f(1)=0,f′(1)存在,则极限lim⁡x→0f(sin⁡2x+cos⁡x)tan⁡3x(ex2−1)sin⁡x解:原式=lim⁡x→0f(1+sin⁡2x+cos⁡x−1)−f(1)sin⁡2x+cos⁡x−1⋅tan⁡3x(sin⁡2x+cos⁡x−1)(ex2−1)sin⁡x=f′(1)⋅lim⁡x→03x(x2−12x2)x3=32f′(1)\\begin{align}\u0026amp;12.若f(1)=0,f\u0026#x27;(1)存在,则极限\\lim_{x\\rightarrow0}\\frac{f(\\sin ^2x+\\cos x)\\tan 3x}{(e^{x^2}-1)\\sin x}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{f(1+\\sin^2x+\\cos x-1)-f(1)}{\\sin^2x+\\cos x-1}·\\frac{\\tan 3x(\\sin^2x+\\cos x-1)}{(e^{x^2}-1)\\sin x}\\\\\u0026amp;=f\u0026#x27;(1)·\\lim_{x\\rightarrow0}\\frac{3x(x^2-\\frac12x^2)}{x^3}=\\frac32f\u0026#x27;(1)\\end{align}​12.若f(1)=0,f′(1)存在,则极限x→0lim​(ex2−1)sinxf(sin2x+cosx)tan3x​解:原式=x→0lim​sin2x+cosx−1f(1+sin2x+cosx−1)−f(1)​⋅(ex2−1)sinxtan3x(sin2x+cosx−1)​=f′(1)⋅x→0lim​x33x(x2−21​x2)​=23​f′(1)​​ 13.设f(x)有二阶连续导数,且f(0)=f′(0)=0,f′′(0)=6,则lim⁡x→0f(sin⁡2x)x4解:原式=lim⁡x→0sin⁡2x⋅f′(sin⁡2x)4x3=lim⁡x→0f′(sin⁡2x)−f′(0)2sin⁡2x=3\\begin{align}\u0026amp;13.设f(x)有二阶连续导数,且f(0)=f\u0026#x27;(0)=0,f\u0026#x27;\u0026#x27;(0)=6,则\\lim_{x\\rightarrow0}\\frac{f(\\sin ^2x)}{x^4}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{\\sin2x·f\u0026#x27;(\\sin^2x)}{4x^3}=\\lim_{x\\rightarrow0}\\frac{f\u0026#x27;(\\sin^2x)-f\u0026#x27;(0)}{2\\sin^2x}=3\\end{align}​13.设f(x)有二阶连续导数,且f(0)=f′(0)=0,f′′(0)=6,则x→0lim​x4f(sin2x)​解:原式=x→0lim​4x3sin2x⋅f′(sin2x)​=x→0lim​2sin2xf′(sin2x)−f′(0)​=3​​ 14.设f(x)在点x=1附近有定义,且在点x=1处可导,f(1)=0,f′(1)=2,求lim⁡x→0f(sin⁡x+cos⁡x)x2+xtan⁡x解:原式=lim⁡x→0\\begin{align}\u0026amp;14.设f(x)在点x=1附近有定义,且在点x=1处可导,f(1)=0,f\u0026#x27;(1)=2,求\\lim_{x\\rightarrow0}\\frac{f(\\sin x+\\cos x)}{x^2+x\\tan x}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\end{align}​14.设f(x)在点x=1附近有定义,且在点x=1处可导,f(1)=0,f′(1)=2,求x→0lim​x2+xtanxf(sinx+cosx)​解:原式=x→0lim​​​ 12.\\begin{align}\u0026amp;12.\\end{align}​12.​​ 12.\\begin{align}\u0026amp;12.\\end{align}​12.​​ 12.\\begin{align}\u0026amp;12.\\end{align}​12.​​ 12.\\begin{align}\u0026amp;12.\\end{align}​12.​​ 12.\\begin{align}\u0026amp;12.\\end{align}​12.​​数列极限 一元积分学 不定积分 常见微分等式 d(ln⁡(x+±k2+x2))=1±k2+x2\\begin{align}\\mathrm d\\left(\\ln(x+\\sqrt{\\pm k^2+x^2})\\right)=\\frac1{\\sqrt{\\pm k^2+x^2}}\\end{align}d(ln(x+±k2+x2​))=±k2+x2​1​​​ d(x±kx)=1∓kx2\\begin{align}\\mathrm d\\left(x\\pm\\frac kx\\right)=1\\mp\\frac k{x^2}\\end{align}d(x±xk​)=1∓x2k​​​，由于 (x±kx)2=x2+k2x2±2k\\begin{align}\\left(x\\pm\\frac kx\\right)^2=x^2+\\frac{k^2}{x^2}\\pm2k\\end{align}(x±xk​)2=x2+x2k2​±2k​​，遇到分子分母分别为二次、四次多项式，且不包含奇数幂次时，可考虑用分子凑出该微分，而分母通过配方化作该式的平方，实现降幂 d(sin⁡x±cos⁡x)=cos⁡x∓sin⁡x\\begin{align}\\mathrm d(\\sin x\\pm\\cos x)=\\cos x\\mp\\sin x\\end{align}d(sinx±cosx)=cosx∓sinx​​，在处理sin⁡xcos⁡x\\sin x\\cos xsinxcosx时很有效 常见计算技巧 更换积分对象：\n分部积分： ∫udv=uv−∫vdu\\begin{align}\\int u\\mathrm dv=uv-\\int v\\mathrm du\\end{align}∫udv=uv−∫vdu​​，若分部后的积分容易算，采用分部积分 例一：分母带平方或其它幂次方，可尝试用分母凑微分，实现分母降幂 三角代换或三角公式：\n包含 k2+x2dx\\begin{align}\\sqrt{k^2+x^2}\\mathrm dx\\end{align}k2+x2​dx​​的表达式，可令x=ktan⁡tx=k\\tan tx=ktant，则dx=ksec⁡2t dt、k2+x2=ksec⁡t\\mathrm dx=k\\sec^2t\\,\\mathrm dt、\\sqrt{k^2+x^2}=k\\sec tdx=ksec2tdt、k2+x2​=ksect 此外，通过直角三角形可知， sin⁡t=x1+x2、cos⁡t=11+x2\\begin{align}\\sin t=\\frac x{\\sqrt{1+x^2}}、\\cos t=\\frac1{\\sqrt{1+x^2}}\\end{align}sint=1+x2​x​、cost=1+x2​1​​​ 例如 ∫dx1+x2=x=tan⁡t∫sec⁡t dt=∫d(sin⁡t)1−sin⁡2t=ln⁡(1+sin⁡t1−sin⁡t)+C=ln⁡(x+1+x2)+C\\begin{align}\u0026amp;\\int\\frac{\\mathrm dx}{\\sqrt{1+x^2}}\\xlongequal{x=\\tan t}\\int\\sec t\\,\\mathrm dt=\\int\\frac{\\mathrm d(\\sin t)}{1-\\sin^2t}\\\\\u0026amp;=\\ln(\\sqrt{\\frac{1+\\sin t}{1-\\sin t}})+C=\\ln(x+\\sqrt{1+x^2})+C\\end{align}​∫1+x2​dx​x=tant∫sectdt=∫1−sin2td(sint)​=ln(1−sint1+sint​​)+C=ln(x+1+x2​)+C​​ 包含k2−x2dx\\sqrt{k^2-x^2}\\mathrm dxk2−x2​dx的表达式，可令x=ksin⁡tx=k\\sin tx=ksint或x=kcos⁡tx=k\\cos tx=kcost 包含x2−k2dx\\sqrt{x^2-k^2}\\mathrm dxx2−k2​dx的表达式，可令x=ksec⁡tx=k\\sec tx=ksect，则 dx=ksin⁡xcos⁡2x、x2−k2=ktan⁡x\\begin{align}\\mathrm dx=k\\frac{\\sin x}{\\cos^2x}、\\sqrt{x^2-k^2}=k\\tan x\\end{align}dx=kcos2xsinx​、x2−k2​=ktanx​​ 通过倍角公式，当分母是关于三角函数的多项式时，将其变为单项式 通过辅助角公式，将所有sin⁡x±cos⁡x、sin⁡xcos⁡x\\sin x\\pm\\cos x、\\sin x\\cos xsinx±cosx、sinxcosx统一为一个含有特定角度的三角函数；或者取sin⁡x±cos⁡x\\sin x\\pm\\cos xsinx±cosx化为微分d(±sin⁡x−cos⁡x)\\mathrm d(\\pm\\sin x-\\cos x)d(±sinx−cosx)，将剩余的sin⁡xcos⁡x\\sin x\\cos xsinxcosx化为(±sin⁡x−cos⁡x)2+k(\\pm\\sin x-\\cos x)^2+k(±sinx−cosx)2+k的形式后，令t=±sin⁡x−cos⁡xt=\\pm\\sin x-\\cos xt=±sinx−cosx 微分代换：分母根式借助配方和微分等式代换，但一般没有三角代换常用\nd(arcsin⁡x)=11−x2\\begin{align}\\mathrm d(\\arcsin x)=\\frac1{\\sqrt{1-x^2}}\\end{align}d(arcsinx)=1−x2​1​​​ d(ln⁡(x+1+x2))=11+x2\\begin{align}\\mathrm d\\left(\\ln(x+\\sqrt{1+x^2})\\right)=\\frac1{\\sqrt{1+x^2}}\\end{align}d(ln(x+1+x2​))=1+x2​1​​​ 区间再现：实际上是定积分的技巧，包括正弦、余弦函数的华里士公式；体现在不定积分上，实际上就是通过分部积分导出的含有重复待求积分的式子。当幂数不常见时，可通过该方法求出递推式z\n欧拉变换：设被积函数为关于xxx的，包含ax2+bx+c\\sqrt{ax^2+bx+c}ax2+bx+c​根式的函数\n欧拉第一代换：若a\u0026gt;0a\u0026gt;0a\u0026gt;0，则可令整个根式=t±ax=t\\pm\\sqrt ax=t±a​x 欧拉第二代换：若c\u0026gt;0c\u0026gt;0c\u0026gt;0，则可令整个根式=xt±c=xt\\pm\\sqrt c=xt±c​ 欧拉第三代换：若可十字相乘(有两不同实根)，可令整个根式=t(x−x1)=t(x-x_1)=t(x−x1​)，其中x1x_1x1​为其中一个实根 万能代换将三角函数换为有理式：令\nt=tan⁡x2\\begin{align}t=\\tan\\frac x2\\end{align}t=tan2x​​​，则\nsin⁡x=2t1+t2、cos⁡t=1−t21+t2、dx=21+t2dt\\begin{align}\\sin x=\\frac{2t}{1+t^2}、\\cos t=\\frac{1-t^2}{1+t^2}、\\mathrm dx=\\frac{2}{1+t^2}\\mathrm dt\\end{align}sinx=1+t22t​、cost=1+t21−t2​、dx=1+t22​dt​​ 积分抵消：仍是通过分部积分\nuv=∫udv+∫vdu\\begin{align}uv=\\int u\\mathrm dv+\\int v\\mathrm du\\end{align}uv=∫udv+∫vdu​​，若两个不定积分都不好求，但刚好能凑出两个该形式的不定积分，则可合成一个uvuvuv；有时有两个不相关的不定积分，也可尝试分别分部积分，分出的部分可能是有关的\n题集 典型分母降幂\n1.∫e−sin⁡xsin⁡2x(1−sin⁡x)2dx解:原式=2∫e−sin⁡xsin⁡x d(11−sin⁡x)=2(e−sin⁡xsin⁡x1−sin⁡x−∫d(e−sin⁡xsin⁡x)1−sin⁡x)=2(e−sin⁡xsin⁡x1−sin⁡x−∫e−sin⁡xd(sin⁡x))=2e−sin⁡x1−sin⁡x+C\\begin{align}\u0026amp;1.\\int\\frac{e^{-\\sin x}\\sin 2x}{(1-\\sin x)^2}\\mathrm dx\\\\\u0026amp;解:原式=2\\int e^{-\\sin x}\\sin x\\ \\mathrm d\\left(\\frac1{1-\\sin x}\\right)=2\\left(\\frac{e^{-\\sin x}\\sin x}{1-\\sin x}-\\int\\frac{\\mathrm d(e^{-\\sin x}\\sin x)}{1-\\sin x}\\right)\\\\\u0026amp;=2\\left(\\frac{e^{-\\sin x}\\sin x}{1-\\sin x}-\\int e^{-\\sin x}\\mathrm d(\\sin x)\\right)=\\frac{2e^{-\\sin x}}{1-\\sin x}+C\\end{align}​1.∫(1−sinx)2e−sinxsin2x​dx解:原式=2∫e−sinxsinx d(1−sinx1​)=2(1−sinxe−sinxsinx​−∫1−sinxd(e−sinxsinx)​)=2(1−sinxe−sinxsinx​−∫e−sinxd(sinx))=1−sinx2e−sinx​+C​​ 2.∫x2ex(x+2)2dx解:原式=−∫x2exd(1x+2)=−(x2exx+2−∫d(x2ex)x+2)=x−2x+2ex+C\\begin{align}\u0026amp;2.\\int\\frac{x^2e^x}{(x+2)^2}\\mathrm dx\\\\\u0026amp;解:原式=-\\int x^2e^x\\mathrm d\\left(\\frac1{x+2}\\right)=-\\left(\\frac{x^2e^x}{x+2}-\\int\\frac{\\mathrm d(x^2e^x)}{x+2}\\right)=\\frac{x-2}{x+2}e^x+C\\end{align}​2.∫(x+2)2x2ex​dx解:原式=−∫x2exd(x+21​)=−(x+2x2ex​−∫x+2d(x2ex)​)=x+2x−2​ex+C​​ 3.∫(2x+1)e−2xx2dx解:原式=−∫(2x+1)e−2xd(1x)=−((2x+1)e−2xx−∫d((2x+1)e−2x)x)=−e−2xx+C\\begin{align}\u0026amp;3.\\int\\frac{(2x+1)e^{-2x}}{x^2}\\mathrm dx\\\\\u0026amp;解:原式=-\\int(2x+1)e^{-2x}\\mathrm d\\left(\\frac1{x}\\right)=-\\left(\\frac{(2x+1)e^{-2x}}x-\\int\\frac{\\mathrm d((2x+1)e^{-2x})}x\\right)=-\\frac{e^{-2x}}x+C\\end{align}​3.∫x2(2x+1)e−2x​dx解:原式=−∫(2x+1)e−2xd(x1​)=−(x(2x+1)e−2x​−∫xd((2x+1)e−2x)​)=−xe−2x​+C​​ 4.∫xln⁡(x+1+x2)(1+x2)2dx原式=−12∫ln⁡(x+1+x2)d(11+x2)=−12(ln⁡(x+1+x2)1+x2−∫d(ln⁡(x+1+x2))1+x2)=−12(ln⁡(x+1+x2)1+x2−∫dx(1+x2)32)=三角代换x1+x2−ln⁡(x+1+x2)2(1+x2)+C\\begin{align}\u0026amp;4.\\int\\frac{x\\ln(x+\\sqrt{1+x^2})}{(1+x^2)^2}\\mathrm dx\\\\\u0026amp;原式=-\\frac12\\int\\ln(x+\\sqrt{1+x^2})\\mathrm d\\left(\\frac1{1+x^2}\\right)\\\\\u0026amp;=-\\frac12\\left(\\frac{\\ln(x+\\sqrt{1+x^2})}{1+x^2}-\\int\\frac{\\mathrm d\\left(\\ln(x+\\sqrt{1+x^2})\\right)}{1+x^2}\\right)\\\\\u0026amp;=-\\frac12\\left(\\frac{\\ln(x+\\sqrt{1+x^2})}{1+x^2}-\\int\\frac{\\mathrm dx}{(1+x^2)^{\\frac32}}\\right)\\xlongequal{三角代换}\\frac{x\\sqrt{1+x^2}-\\ln(x+\\sqrt{1+x^2})}{2(1+x^2)}+C\\end{align}​4.∫(1+x2)2xln(x+1+x2​)​dx原式=−21​∫ln(x+1+x2​)d(1+x21​)=−21​(1+x2ln(x+1+x2​)​−∫1+x2d(ln(x+1+x2​))​)=−21​(1+x2ln(x+1+x2​)​−∫(1+x2)23​dx​)三角代换2(1+x2)x1+x2​−ln(x+1+x2​)​+C​​ 5.∫ln⁡(x+1+x2)(1+x2)32dx解:原式=∫ln⁡(x+1+x2)d(x(1+x2)12)=xln⁡(x+1+x2)(1+x2)12−∫xdx1+x2=xln⁡(x+1+x2)1+x2−12ln⁡(1+x2)+C\\begin{align}\u0026amp;5.\\int\\frac{\\ln(x+\\sqrt{1+x^2})}{(1+x^2)^\\frac32}\\mathrm dx\\\\\u0026amp;解:原式=\\int\\ln(x+\\sqrt{1+x^2})\\mathrm d\\left(\\frac x{(1+x^2)^\\frac12}\\right)=\\frac{x\\ln(x+\\sqrt{1+x^2})}{(1+x^2)^\\frac12}-\\int\\frac{x\\mathrm dx}{1+x^2}\\\\\u0026amp;=\\frac{x\\ln(x+\\sqrt{1+x^2})}{\\sqrt{1+x^2}}-\\frac12\\ln(1+x^2)+C\\end{align}​5.∫(1+x2)23​ln(x+1+x2​)​dx解:原式=∫ln(x+1+x2​)d((1+x2)21​x​)=(1+x2)21​xln(x+1+x2​)​−∫1+x2xdx​=1+x2​xln(x+1+x2​)​−21​ln(1+x2)+C​​ 积分抵消，最后一步可不写\n6.∫(1+x−1x)ex+1xdx解:原式=∫[1+x(1−1x2)]ex+1xdx=∫ex+1xdx+∫xd(ex+1x)=I+xex+1x−I=xex+1x+C\\begin{align}\u0026amp;6.\\int\\left(1+x-\\frac1x\\right)e^{x+\\frac1x}\\mathrm dx\\\\\u0026amp;解:原式=\\int\\left[1+x\\left(1-\\frac1{x^2}\\right)\\right]e^{x+\\frac1x}\\mathrm dx=\\int e^{x+\\frac1x}\\mathrm dx+\\int x\\mathrm d(e^{x+\\frac1x})\\\\\u0026amp;=I+xe^{x+\\frac1x}-I=xe^{x+\\frac1x}+C\\end{align}​6.∫(1+x−x1​)ex+x1​dx解:原式=∫[1+x(1−x21​)]ex+x1​dx=∫ex+x1​dx+∫xd(ex+x1​)=I+xex+x1​−I=xex+x1​+C​​ 7.∫x+sin⁡x1+cos⁡xdx解:原式=∫x+sin⁡x2cos⁡2x2dx=∫x dx2cos⁡2x2+∫tan⁡x2dx=∫x d(tan⁡x2)+∫tan⁡x2dx=分部积分xtan⁡x2+C\\begin{align}\u0026amp;7.\\int\\frac{x+\\sin x}{1+\\cos x}\\mathrm dx\\\\\u0026amp;解:原式=\\int\\frac{x+\\sin x}{2\\cos^2{\\large\\frac x2}}\\mathrm dx=\\int\\frac{x\\,\\mathrm dx}{2\\cos^2{\\large\\frac x2}}+\\int\\tan\\frac x2\\mathrm dx\\\\\u0026amp;=\\int x\\,\\mathrm d\\left({\\tan\\frac x2}\\right)+\\int\\tan\\frac x2\\mathrm dx\\xlongequal{分部积分}x\\tan\\frac x2+C\\end{align}​7.∫1+cosxx+sinx​dx解:原式=∫2cos22x​x+sinx​dx=∫2cos22x​xdx​+∫tan2x​dx=∫xd(tan2x​)+∫tan2x​dx分部积分xtan2x​+C​​ 8.∫e2x(1+tan⁡x)2dx解一:原式=x=arctan⁡t∫e2arctan⁡t(1+t)21+t2dt=∫e2arctan⁡tdt+∫e2arctan⁡t2tt2+1dt=∫e2arctan⁡tdt+∫td(e2arctan⁡t)=te2arctan⁡t+C=tan⁡x⋅e2x+C解二:原式=∫e2x1+2sin⁡xcos⁡xcos⁡2xdx=∫e2xd(tan⁡x)+2∫e2xtan⁡xdx=∫e2xd(tan⁡x)+∫tan⁡x d(e2x)=tan⁡x⋅e2x+C\\begin{align}\u0026amp;8.\\int e^{2x}(1+\\tan x)^2\\mathrm dx\\\\\u0026amp;解一:原式\\xlongequal{x=\\arctan t}\\int e^{2\\arctan t}\\frac{(1+t)^2}{1+t^2}\\mathrm dt=\\int e^{2\\arctan t}\\mathrm dt+\\int e^{2\\arctan t}\\frac{2t}{t^2+1}\\mathrm dt\\\\\u0026amp;=\\int e^{2\\arctan t}\\mathrm dt+\\int t\\mathrm d\\left(e^{2\\arctan t}\\right)=te^{2\\arctan t}+C=\\tan x·e^{2x}+C\\\\\u0026amp;解二:原式=\\int e^{2x}\\frac{1+2\\sin x\\cos x}{\\cos^2x}\\mathrm dx=\\int e^{2x}\\mathrm d(\\tan x)+2\\int e^{2x}\\tan x\\mathrm dx\\\\\u0026amp;=\\int e^{2x}\\mathrm d(\\tan x)+\\int\\tan x\\,\\mathrm d\\left(e^{2x}\\right)=\\tan x·e^{2x}+C\\end{align}​8.∫e2x(1+tanx)2dx解一:原式x=arctant∫e2arctant1+t2(1+t)2​dt=∫e2arctantdt+∫e2arctantt2+12t​dt=∫e2arctantdt+∫td(e2arctant)=te2arctant+C=tanx⋅e2x+C解二:原式=∫e2xcos2x1+2sinxcosx​dx=∫e2xd(tanx)+2∫e2xtanxdx=∫e2xd(tanx)+∫tanxd(e2x)=tanx⋅e2x+C​​ 9.∫esin⁡xxcos⁡3x−sin⁡xcos⁡2xdx=∫xd(esin⁡x)−∫esin⁡xsin⁡xcos⁡2xdx=分母降幂∫xd(esin⁡x)−∫esin⁡xd(1cos⁡x)=∫xd(esin⁡x)−esin⁡xsec⁡x+∫sec⁡xd(esin⁡x)=∫xd(esin⁡x)−esin⁡xsec⁡x+∫esin⁡xdx=esin⁡x(x−sec⁡x)+C\\begin{align}\u0026amp;9.\\int e^{\\sin x}\\frac{x\\cos^3x-\\sin x}{\\cos^2x}\\mathrm dx=\\int x\\mathrm d\\left(e^{\\sin x}\\right)-\\int e^{\\sin x}\\frac{\\sin x}{\\cos^2x}\\mathrm dx\\\\\u0026amp;\\xlongequal{分母降幂}\\int x\\mathrm d\\left(e^{\\sin x}\\right)-\\int e^{\\sin x}\\mathrm d\\left(\\frac1{\\cos x}\\right)\\\\\u0026amp;=\\int x\\mathrm d\\left(e^{\\sin x}\\right)-e^{\\sin x}\\sec x+\\int\\sec x\\mathrm d\\left(e^{\\sin x}\\right)\\\\\u0026amp;=\\int x\\mathrm d\\left(e^{\\sin x}\\right)-e^{\\sin x}\\sec x+\\int e^{\\sin x}\\mathrm dx=e^{\\sin x}(x-\\sec x)+C\\end{align}​9.∫esinxcos2xxcos3x−sinx​dx=∫xd(esinx)−∫esinxcos2xsinx​dx分母降幂∫xd(esinx)−∫esinxd(cosx1​)=∫xd(esinx)−esinxsecx+∫secxd(esinx)=∫xd(esinx)−esinxsecx+∫esinxdx=esinx(x−secx)+C​​ 10.∫xarctan⁡xln⁡(1+x2)dx解:原式=12∫arctan⁡xln⁡(1+x2)d(1+x2)=12(arctan⁡xln⁡(1+x2)(1+x2)−∫(1+x2)d(arctan⁡xln⁡(1+x2)))=12(arctan⁡xln⁡(1+x2)(1+x2)−∫ln⁡(1+x2)dx−∫2xarctan⁡xdx)=12(arctan⁡xln⁡(1+x2)(1+x2)−(xln⁡(1+x2)−∫2x21+x2dx)−2∫xarctan⁡xdx)∫xarctan⁡xdx=12∫arctan⁡xd(x2+1)=arctan⁡x⋅(1+x2)−x2+C∫2x21+x2dx=2(x−arctan⁡x)+C综上,原式=arctan⁡x[(x2+1)ln⁡(1+x2)−(x2+3)]−xln⁡(1+x2)+3x2+C\\begin{align}\u0026amp;10.\\int x\\arctan x\\ln(1+x^2)\\mathrm dx\\\\\u0026amp;解:原式=\\frac12\\int\\arctan x\\ln(1+x^2)\\mathrm d\\left(1+x^2\\right)\\\\\u0026amp;=\\frac12\\left(\\arctan x\\ln(1+x^2)(1+x^2)-\\int(1+x^2)\\mathrm d\\left(\\arctan x\\ln(1+x^2)\\right)\\right)\\\\\u0026amp;=\\frac12\\left(\\arctan x\\ln(1+x^2)(1+x^2)-\\int\\ln(1+x^2)\\mathrm dx-\\int2x\\arctan x\\mathrm dx\\right)\\\\\u0026amp;=\\frac12\\left(\\arctan x\\ln(1+x^2)(1+x^2)-\\left(x\\ln(1+x^2)-\\int\\frac{2x^2}{1+x^2}\\mathrm dx\\right)-2\\int x\\arctan x\\mathrm dx\\right)\\\\\u0026amp;\\int x\\arctan x\\mathrm dx=\\frac12\\int\\arctan x\\mathrm d\\left(x^2+1\\right)=\\frac{\\arctan x·(1+x^2)-x}2+C\\\\\u0026amp;\\int\\frac{2x^2}{1+x^2}\\mathrm dx=2(x-\\arctan x)+C\\\\\u0026amp;综上,原式=\\frac{\\arctan{x[(x^2+1)\\ln(1+x^2)-(x^2+3)]-x\\ln(1+x^2)+3x}}2+C\\end{align}​10.∫xarctanxln(1+x2)dx解:原式=21​∫arctanxln(1+x2)d(1+x2)=21​(arctanxln(1+x2)(1+x2)−∫(1+x2)d(arctanxln(1+x2)))=21​(arctanxln(1+x2)(1+x2)−∫ln(1+x2)dx−∫2xarctanxdx)=21​(arctanxln(1+x2)(1+x2)−(xln(1+x2)−∫1+x22x2​dx)−2∫xarctanxdx)∫xarctanxdx=21​∫arctanxd(x2+1)=2arctanx⋅(1+x2)−x​+C∫1+x22x2​dx=2(x−arctanx)+C综上,原式=2arctanx[(x2+1)ln(1+x2)−(x2+3)]−xln(1+x2)+3x​+C​​ 11.∫x2+1x4+1dx解:原式=∫1+1x2x2+1x2dx=∫d(x−1x)(x−1x)2+2=12arctan⁡x−1x2+C\\begin{align}\u0026amp;11.\\int\\frac{x^2+1}{x^4+1}\\mathrm dx\\\\\u0026amp;解:原式=\\int\\frac{1+\\frac1{x^2}}{x^2+\\frac1{x^2}}\\mathrm dx=\\int\\frac{\\mathrm d\\left(x-\\frac1x\\right)}{(x-\\frac1x)^2+2}=\\frac1{\\sqrt2}{\\arctan\\frac{x-\\frac1x}{\\sqrt2}}+C\\end{align}​11.∫x4+1x2+1​dx解:原式=∫x2+x21​1+x21​​dx=∫(x−x1​)2+2d(x−x1​)​=2​1​arctan2​x−x1​​+C​​ 12.∫x2±11+kx2+x4dx解:原式=∫1±1x21x2+k+x2dx=∫d(x∓1x)(x∓1x)2+k±2=t=(x∓1x)1k±21k±2∫dtt2+1=1k±2arctan⁡x∓1xk±2+C\\begin{align}\u0026amp;12.\\int\\frac{x^2\\pm1}{1+kx^2+x^4}\\mathrm dx\\\\\u0026amp;解:原式=\\int\\frac{1\\pm\\frac1{x^2}}{\\frac1{x^2}+k+x^2}\\mathrm dx=\\int\\frac{\\mathrm d(x\\mp\\frac1x)}{(x\\mp\\frac1x)^2+k\\pm2}\\\\\u0026amp;\\xlongequal{t=(x\\mp\\frac1x)\\large\\frac1{\\sqrt{k\\pm2}}}\\frac1{\\sqrt{k\\pm2}}\\int \\frac{\\mathrm dt}{t^2+1}=\\frac1{\\sqrt{k\\pm2}}\\arctan\\frac{x\\mp\\frac1x}{\\sqrt{k\\pm2}}+C\\end{align}​12.∫1+kx2+x4x2±1​dx解:原式=∫x21​+k+x21±x21​​dx=∫(x∓x1​)2+k±2d(x∓x1​)​t=(x∓x1​)k±2​1​k±2​1​∫t2+1dt​=k±2​1​arctank±2​x∓x1​​+C​​ 13.∫1sin⁡3x+cos⁡3xdx,14\u0026lt;x\u0026lt;12解:原式=立方和公式∫1(sin⁡x+cos⁡x)(1−sin⁡xcos⁡x)dx=∫sin⁡x+cos⁡x(1+2sin⁡xcos⁡x)(1−sin⁡xcos⁡x)dx=∫d(sin⁡x−cos⁡x)(2−(sin⁡x−cos⁡x)2)(1−1−(sin⁡x−cos⁡x)22)=t=sin⁡x−cos⁡x2∫1(2−t2)(1+t2)dt=23arctan⁡t+132ln⁡(2+t2−t)+C=23arctan⁡(sin⁡x−cos⁡x)+132ln⁡(2+sin⁡x−cos⁡x2+cos⁡x−sin⁡x)+C\\begin{align}\u0026amp;13.\\int\\frac1{\\sin^3x+\\cos^3x}\\mathrm dx,\\frac14\u0026lt;x\u0026lt;\\frac12\\\\\u0026amp;解:原式\\xlongequal{立方和公式}\\int\\frac1{(\\sin x+\\cos x)(1-\\sin x\\cos x)}\\mathrm dx\\\\\u0026amp;=\\int\\frac{\\sin x+\\cos x}{(1+2\\sin x\\cos x)(1-\\sin x\\cos x)}\\mathrm dx\\\\\u0026amp;=\\int\\frac{\\mathrm d(\\sin x-\\cos x)}{(2-(\\sin x-\\cos x)^2)(1-\\frac{1-(\\sin x-\\cos x)^2}2)}\\\\\u0026amp;\\xlongequal{t=\\sin x-\\cos x}2\\int\\frac1{(2-t^2)(1+t^2)}\\mathrm dt=\\frac23\\arctan t+\\frac1{3\\sqrt2}\\ln(\\frac{\\sqrt2+t}{\\sqrt2-t})+C\\\\\u0026amp;=\\frac23\\arctan(\\sin x-\\cos x)+\\frac1{3\\sqrt2}\\ln(\\frac{\\sqrt2+\\sin x-\\cos x}{\\sqrt2+\\cos x-\\sin x})+C\\end{align}​13.∫sin3x+cos3x1​dx,41​\u0026lt;x\u0026lt;21​解:原式立方和公式∫(sinx+cosx)(1−sinxcosx)1​dx=∫(1+2sinxcosx)(1−sinxcosx)sinx+cosx​dx=∫(2−(sinx−cosx)2)(1−21−(sinx−cosx)2​)d(sinx−cosx)​t=sinx−cosx2∫(2−t2)(1+t2)1​dt=32​arctant+32​1​ln(2​−t2​+t​)+C=32​arctan(sinx−cosx)+32​1​ln(2​+cosx−sinx2​+sinx−cosx​)+C​​ 14.∫dxxx2−2x−3解:原式=∫dxx21−2x−3x2=−13∫d(3x+13)−(3x+13)2+43=−13∫d(32(3x+13))1−34(3x+13)2=−arcsin⁡(32(3x+13))3+C\\begin{align}\u0026amp;14.\\int\\frac{\\mathrm dx}{x\\sqrt{x^2-2x-3}}\\\\\u0026amp;解:原式=\\int\\frac{\\mathrm dx}{x^2\\sqrt{1-\\frac2x-\\frac3{x^2}}}=-\\frac1{\\sqrt3}\\int\\frac{\\mathrm d\\left(\\frac{\\sqrt3}x+\\frac1{\\sqrt3}\\right)}{\\sqrt{-(\\frac{\\sqrt3}x+\\frac1{\\sqrt3})^2+\\frac43}}\\\\\u0026amp;=-\\frac1{\\sqrt3}\\int\\frac{\\mathrm d(\\frac{\\sqrt3}2(\\frac{\\sqrt3}x+\\frac1{\\sqrt3}))}{\\sqrt{1-\\frac34(\\frac{\\sqrt3}x+\\frac1{\\sqrt3})^2}}=-\\frac{\\arcsin(\\frac{\\sqrt3}2(\\frac{\\sqrt3}x+\\frac1{\\sqrt3}))}{\\sqrt3}+C\\end{align}​14.∫xx2−2x−3​dx​解:原式=∫x21−x2​−x23​​dx​=−3​1​∫−(x3​​+3​1​)2+34​​d(x3​​+3​1​)​=−3​1​∫1−43​(x3​​+3​1​)2​d(23​​(x3​​+3​1​))​=−3​arcsin(23​​(x3​​+3​1​))​+C​​ 15.∫1−x1+xdx解:原式=t=1−x1+x−4∫t2(1+t2)2dt=2∫td(11+t2)=2(t1+t2−∫dt1+t2)=2(t1+t2−arctan⁡t)+C=(1+x)(1−x)−2arctan⁡1−x1+x+C\\begin{align}\u0026amp;15.\\int\\sqrt\\frac{1-x}{1+x}\\mathrm dx\\\\\u0026amp;解:原式\\xlongequal{t=\\sqrt{\\frac{1-x}{1+x}}}-4\\int\\frac{t^2}{(1+t^2)^2}\\mathrm dt=2\\int t\\mathrm d\\left(\\frac1{1+t^2}\\right)\\\\\u0026amp;=2\\left(\\frac t{1+t^2}-\\int\\frac{\\mathrm dt}{1+t^2}\\right)=2\\left(\\frac t{1+t^2}-\\arctan t\\right)+C\\\\\u0026amp;=\\sqrt{(1+x)(1-x)}-2\\arctan \\sqrt{\\frac{1-x}{1+x}}+C\\end{align}​15.∫1+x1−x​​dx解:原式t=1+x1−x​​−4∫(1+t2)2t2​dt=2∫td(1+t21​)=2(1+t2t​−∫1+t2dt​)=2(1+t2t​−arctant)+C=(1+x)(1−x)​−2arctan1+x1−x​​+C​​ 16.I(n)=∫1(a2+x2)ndx, n为正整数解:原式=x(a2+x2)n+2n∫x2(a2+x2)n+1dx=x(a2+x2)n+2n∫1(a2+x2)ndx−2na2∫1(a2+x2)n+1dx故有递推式:2na2⋅I(n+1)=(2n−1)⋅I(n)+x(a2+x2)n, 当n=1时,I(1)=arctan⁡x+C偏移后:I(n)=(2n−3)⋅I(n−1)+x(a2+x2)n−12(n−1)a2特别地, 当n=2时, I(2)=arctan⁡x+xa2+x22a2+C\\begin{align}\u0026amp;16.I(n)=\\int\\frac{1}{(a^2+x^2)^n}\\mathrm dx,\\ n为正整数\\\\\u0026amp;解:原式=\\frac x{(a^2+x^2)^n}+2n\\int\\frac{x^2}{(a^2+x^2)^{n+1}}\\mathrm dx\\\\\u0026amp;=\\frac x{(a^2+x^2)^n}+2n\\int\\frac{1}{(a^2+x^2)^n}\\mathrm dx-2na^2\\int\\frac{1}{(a^2+x^2)^{n+1}}\\mathrm dx\\\\\u0026amp;故有递推式:2na^2·I(n+1)=(2n-1)·I(n)+\\frac x{(a^2+x^2)^n},\\ 当n=1时,I(1)=\\arctan x+C\\\\\u0026amp;偏移后:I(n)=\\frac{(2n-3)·I(n-1)+\\Large \\frac x{(a^2+x^2)^{n-1}}}{2(n-1)a^2}\\\\\u0026amp;特别地,\\ 当n=2时,\\ I(2)=\\frac{\\arctan x+\\Large \\frac x{a^2+x^2}}{2a^2}+C\\end{align}​16.I(n)=∫(a2+x2)n1​dx, n为正整数解:原式=(a2+x2)nx​+2n∫(a2+x2)n+1x2​dx=(a2+x2)nx​+2n∫(a2+x2)n1​dx−2na2∫(a2+x2)n+11​dx故有递推式:2na2⋅I(n+1)=(2n−1)⋅I(n)+(a2+x2)nx​, 当n=1时,I(1)=arctanx+C偏移后:I(n)=2(n−1)a2(2n−3)⋅I(n−1)+(a2+x2)n−1x​​特别地, 当n=2时, I(2)=2a2arctanx+a2+x2x​​+C​​由题15、16,形如ax2+bx+c(k2+x2)2的被积函数可拆分成三部分计算\\begin{align}\u0026amp;由题15、16,形如\\frac{ax^2+bx+c}{(k^2+x^2)^2}的被积函数可拆分成三部分计算\\end{align}​由题15、16,形如(k2+x2)2ax2+bx+c​的被积函数可拆分成三部分计算​​ 17.I=∫k±x2dx, k±x2≥0,k\u0026gt;0解:I=xk±x2−∫x±xk±x2dx=xk±x2−I+k∫dxk±x2故I=12(xk+x2+kln⁡(x+k+x2))+Ck+x2时或I=12(xk−x2+karcsin⁡(xk))+Ck−x2时\\begin{align}\u0026amp;17.I=\\int\\sqrt{k\\pm x^2}\\mathrm dx,\\ k\\pm x^2\\ge0,k\u0026gt;0\\\\\u0026amp;解:I=x\\sqrt{k\\pm x^2}-\\int x\\frac{\\pm x}{\\sqrt{k\\pm x^2}}\\mathrm dx=x\\sqrt{k\\pm x^2}-I+k\\int\\frac{\\mathrm dx}{\\sqrt{k\\pm x^2}}\\\\\u0026amp;故I=\\frac12\\left(x\\sqrt{k+ x^2}+k\\ln(x+\\sqrt{k+ x^2})\\right)+C\u0026amp;k+x^2时\\\\\u0026amp;或I=\\frac12\\left(x\\sqrt{k- x^2}+k\\arcsin(\\frac x{\\sqrt k})\\right)+C\u0026amp;k-x^2时\\end{align}​17.I=∫k±x2​dx, k±x2≥0,k\u0026gt;0解:I=xk±x2​−∫xk±x2​±x​dx=xk±x2​−I+k∫k±x2​dx​故I=21​(xk+x2​+kln(x+k+x2​))+C或I=21​(xk−x2​+karcsin(k​x​))+C​k+x2时k−x2时​​ 中值定理 复习 常用定理定义 零点定理：若f(x)f(x)f(x)在[a,b][a,b][a,b]内连续，且f(a)f(b)\u0026lt;0f(a)f(b)\u0026lt;0f(a)f(b)\u0026lt;0，则至少存在一点x0∈(a,b)x_0\\in(a,b)x0​∈(a,b)，使f(x0)=0f(x_0)=0f(x0​)=0；零点定理可视为介值定理的特例 唯一零点定理：在零点定理成立的条件下，若f(x)f(x)f(x)可导且f′(x)≠0f\u0026#x27;(x)\\ne0f′(x)=0，则有且仅有一点使f(x0)=0f(x_0)=0f(x0​)=0 介值定理：设f(x)f(x)f(x)在(a,b)(a,b)(a,b)内连续，且最大值为MMM，最小值为mmm，若有m≤A≤Mm\\le A\\le Mm≤A≤M，则至少存在一点x0∈(a,b)x_0\\in(a,b)x0​∈(a,b)，使f(x0)=Af(x_0)=Af(x0​)=A 罗尔中值定理：若f(x)f(x)f(x)在闭区间[a,b][a,b][a,b]内函数连续、开区间(a,b)(a,b)(a,b)内函数可导，且f(a)=f(b)f(a)=f(b)f(a)=f(b)，则在开区间(a,b)(a,b)(a,b)内存在一点ξ\\xiξ，使f′(ξ)=0f\u0026#x27;(\\xi)=0f′(ξ)=0；Rolle\\rm RolleRolle中值定理是Largrange\\rm LargrangeLargrange中值定理的特例 拉格朗日中值定理：若f(x)f(x)f(x)在闭区间[a,b][a,b][a,b]内函数连续、开区间(a,b)(a,b)(a,b)内函数可导，则在开区间(a,b)(a,b)(a,b)内存在一点ξ\\xiξ，使 f′(ξ)=f(a)−f(b)a−b\\begin{align}f\u0026#x27;(\\xi)=\\frac{f(a)-f(b)}{a-b}\\end{align}f′(ξ)=a−bf(a)−f(b)​​​；Largrange\\rm LargrangeLargrange中值定理是Taylor\\rm TaylorTaylor中值定理的特例 泰勒中值定理：若f(x)f(x)f(x)在x0x_0x0​的(a,b)(a,b)(a,b)邻域内有n+1n+1n+1阶导，则存在介于xxx和x0x_0x0​之间的中值ξ\\xiξ(x∈(a,b)x\\in (a,b)x∈(a,b))，使其泰勒展开后拉格朗日型余项为 Rn=f(n+1)(ξ)(n+1)!(x−x0)n+1\\begin{align}R_n=\\frac{f^{(n+1)}(\\xi)}{(n+1)!}(x-x_0)^{n+1}\\end{align}Rn​=(n+1)!f(n+1)(ξ)​(x−x0​)n+1​​ 积分中值定理：若有 ∫abf(x)dx=0\\begin{align}\\int_a^bf(x)dx=0\\end{align}∫ab​f(x)dx=0​​，则存在一点x0∈(a,b)x_0\\in(a,b)x0​∈(a,b)，使f(x0)=0f(x_0)=0f(x0​)=0 构造辅助函数(-\u0026gt;微分方程) 通过中值定理证明给定方程F(x)=0F(x)=0F(x)=0的题型中，寻找辅助函数的过程相当于解出微分方程后移项，常见构造如下(可不记，相当于初等微分方程)：\n构造的G(x)G(x)G(x)可任意加常数CCC，该常数根据所给条件，最终必须凑出G(a)=G(b)G(a)=G(b)G(a)=G(b)，一般是000 形如f′(x)+P(x)f(x)=0f\u0026#x27;(x)+P(x)f(x)=0f′(x)+P(x)f(x)=0的待证方程，通常构造 G(x)=f(x)e∫P(x)dx+C\\begin{align}G(x)=f(x)e^{\\int P(x)dx}+C\\end{align}G(x)=f(x)e∫P(x)dx+C​​ 形如f′(x)+P(x)f(x)=Q(x)f\u0026#x27;(x)+P(x)f(x)=Q(x)f′(x)+P(x)f(x)=Q(x)的待证方程，通常构造 G(x)=f(x)e∫P(x)dx−∫e∫P(x)dxQ(x)dx+C\\begin{align}G(x)=f(x)e^{\\int P(x)dx}-\\int e^{\\int P(x)dx}Q(x)dx+C\\end{align}G(x)=f(x)e∫P(x)dx−∫e∫P(x)dxQ(x)dx+C​​ 形如f′′(x)+pf′(x)+qf(x)=Q(x)f\u0026#x27;\u0026#x27;(x)+pf\u0026#x27;(x)+qf(x)=Q(x)f′′(x)+pf′(x)+qf(x)=Q(x)的待证方程，通常将F(x)=f′(x)−Af(x)F(x)=f\u0026#x27;(x)-Af(x)F(x)=f′(x)−Af(x)作为整体降阶，相当于解差分方程，即λ2+pλ+q=0\\lambda^2+p\\lambda+q=0λ2+pλ+q=0的解分别作为A、PA、PA、P，得到等价的降阶方程F′(x)+PF(x)=Q(x)F\u0026#x27;(x)+PF(x)=Q(x)F′(x)+PF(x)=Q(x)；类似的三对角行列式、递推数列也是差分方程 以上是记忆型的知识点，事实上只要假设方程F(x)=0F(x)=0F(x)=0恒成立，解出f(x)f(x)f(x)的通解，再通过已知条件设定一个特解，将所有项移到一边，就能得到辅助函数：\n与关于f(x)f(x)f(x)微分方程的联系：如此构造出的 G(x)=f(x)e∫P(x)dx−∫e∫P(x)dxQ(x)dx=C\\begin{align}G(x)=f(x)e^{\\int P(x)dx}-\\int e^{\\int P(x)dx}Q(x)dx=C\\end{align}G(x)=f(x)e∫P(x)dx−∫e∫P(x)dxQ(x)dx=C​​，移项后求出的f(x)f(x)f(x)就是熟悉的公式 f(x)=e−∫P(x)dx(∫e∫P(x)dxQ(x)dx+C)\\begin{align}f(x)=e^{-\\int P(x)dx}\\left(\\int e^{\\int P(x)dx}Q(x)dx+C\\right)\\end{align}f(x)=e−∫P(x)dx(∫e∫P(x)dxQ(x)dx+C)​​；所以解出f(x)f(x)f(x)，辅助函数也就得到了 和微分方程的区别：微分方程恒成立，而中值定理往往是只有单中值或双中值满足给定方程，相当于求特解；反过来说，如果能保证构造出的G(x)G(x)G(x)恒等于常数，那么其中的f(x)f(x)f(x)就是通解了 题集 简单构造：\n1.设函数f(x)在[0,1]上连续,在(0,1)内可微,且\\begin{align}\u0026amp;1.设函数f(x)在[0,1]上连续,在(0,1)内可微,且\\end{align}​1.设函数f(x)在[0,1]上连续,在(0,1)内可微,且​​ f(0)=f(1)=0,f(12)=1 f(0)=f(1)=0,f\\left(\\frac12\\right)=1 f(0)=f(1)=0,f(21​)=1 证明:(1)存在ξ∈(12,1)使得f(ξ)=ξ(2)存在η∈(0,ξ)使得f′(η)=f(η)−η+1证:(1)令F(x)=f(x)−x,则F(12)=12,F(1)=−1,由零点定理,必然存在一点ξ∈(12,1)使F(ξ)=0,证毕[2](草稿纸过程:容易解得f(x)=e−x(xe−x+C),即辅助函数G(x)=f(x)ex−xe−x)令G(x)=f(x)ex−xe−x,则G(0)=0,G(ξ)=0,由罗尔中值定理,存在η∈(0,ξ)使G′(η)=0,证毕\\begin{align}\u0026amp;证明:(1)存在\\xi\\in\\left(\\frac12,1\\right)使得f(\\xi)=\\xi\\\\\u0026amp;(2)存在\\eta\\in(0,\\xi)使得f\u0026#x27;(\\eta)=f(\\eta)-\\eta+1\\\\\u0026amp;证:(1)令F(x)=f(x)-x,则F\\left(\\frac12\\right)=\\frac12,F(1)=-1,\\\\\u0026amp;由零点定理,必然存在一点\\xi\\in\\left(\\frac12,1\\right)使F(\\xi)=0,证毕\\\\\u0026amp;[2](草稿纸过程:容易解得f(x)=e^{-x}(xe^{-x}+C),即辅助函数G(x)=f(x)e^x-xe^{-x})\\\\\u0026amp;令G(x)=f(x)e^x-xe^{-x},则G(0)=0,G(\\xi)=0,由罗尔中值定理,存在\\eta\\in(0,\\xi)使G\u0026#x27;(\\eta)=0,证毕\\end{align}​证明:(1)存在ξ∈(21​,1)使得f(ξ)=ξ(2)存在η∈(0,ξ)使得f′(η)=f(η)−η+1证:(1)令F(x)=f(x)−x,则F(21​)=21​,F(1)=−1,由零点定理,必然存在一点ξ∈(21​,1)使F(ξ)=0,证毕[2](草稿纸过程:容易解得f(x)=e−x(xe−x+C),即辅助函数G(x)=f(x)ex−xe−x)令G(x)=f(x)ex−xe−x,则G(0)=0,G(ξ)=0,由罗尔中值定理,存在η∈(0,ξ)使G′(η)=0,证毕​​ 双中值：\n2.设f(x)在[0,1]上连续,f(x)在(0,1)上可导,且f(0)=0,f(1)=1证明:(1)存在x0∈(0,1),使f(x0)=2−3x0(2)存在ξ,η∈(0,1),且ξ≠η,使[1+f′(ξ)][1+f′(η)]=4证:(1)令F(x)=f(x)+3x−2,则F(0)=−2,F(1)=2,根据零点定理,存在x0∈(0,1)使F(x0)=0,证毕[2](双中值问题自然想法是拉中定理分区间)在(0,x0)区间上,拉中有2−3x0−f(0)x0=f′(ξ),ξ∈(0,x0)在(x0,1)区间上,同理有1−(2−3x0)1−x0=f′(η),η∈(x0,1)代入得证,证毕\\begin{align}\u0026amp;2.设f(x)在[0,1]上连续,f(x)在(0,1)上可导,且f(0)=0,f(1)=1\\\\\u0026amp;证明:(1)存在x_0\\in(0,1),使f(x_0)=2-3x_0\\\\\u0026amp;(2)存在\\xi,\\eta \\in(0,1),且\\xi\\ne\\eta,使[1+f\u0026#x27;(\\xi)][1+f\u0026#x27;(\\eta)]=4\\\\\u0026amp;证:(1)令F(x)=f(x)+3x-2,则F(0)=-2,F(1)=2,\\\\\u0026amp;根据零点定理,存在x_0\\in(0,1)使F(x_0)=0,证毕\\\\\u0026amp;[2](双中值问题自然想法是拉中定理分区间)\\\\\u0026amp;在(0,x_0)区间上,拉中有\\frac{2-3x_0-f(0)}{x_0}=f\u0026#x27;(\\xi),\\xi\\in(0,x_0)\\\\\u0026amp;在(x_0,1)区间上,同理有\\frac{1-(2-3x_0)}{1-x_0}=f\u0026#x27;(\\eta),\\eta\\in(x_0,1)\\\\\u0026amp;代入得证,证毕\\end{align}​2.设f(x)在[0,1]上连续,f(x)在(0,1)上可导,且f(0)=0,f(1)=1证明:(1)存在x0​∈(0,1),使f(x0​)=2−3x0​(2)存在ξ,η∈(0,1),且ξ=η,使[1+f′(ξ)][1+f′(η)]=4证:(1)令F(x)=f(x)+3x−2,则F(0)=−2,F(1)=2,根据零点定理,存在x0​∈(0,1)使F(x0​)=0,证毕[2](双中值问题自然想法是拉中定理分区间)在(0,x0​)区间上,拉中有x0​2−3x0​−f(0)​=f′(ξ),ξ∈(0,x0​)在(x0​,1)区间上,同理有1−x0​1−(2−3x0​)​=f′(η),η∈(x0​,1)代入得证,证毕​​如果无视第一问给的条件，则要逆向推导：\n(2)设在(0,1)上有一点x1,使得拉中有:f(x1)−f(0)x1=f′(ξ),f(1)−f(x1)1−x1=f′(η),则满足ξ≠η要使结论成立,则解出f(x1)=2−3x1成立,由(1)得总存在x0使该式成立则令x1=x0,故总存在...证毕\\begin{align}\u0026amp;(2)设在(0,1)上有一点x_1,使得拉中有:\\\\\u0026amp;\\frac{f(x_1)-f(0)}{x_1}=f\u0026#x27;(\\xi),\\frac{f(1)-f(x_1)}{1-x_1}=f\u0026#x27;(\\eta),则满足\\xi\\ne\\eta\\\\\u0026amp;要使结论成立,则解出f(x_1)=2-3x_1成立,由(1)得总存在x_0使该式成立\\\\\u0026amp;则令x_1=x_0,故总存在...证毕\\end{align}​(2)设在(0,1)上有一点x1​,使得拉中有:x1​f(x1​)−f(0)​=f′(ξ),1−x1​f(1)−f(x1​)​=f′(η),则满足ξ=η要使结论成立,则解出f(x1​)=2−3x1​成立,由(1)得总存在x0​使该式成立则令x1​=x0​,故总存在...证毕​​ 遇定积分可先通过积分中值定理得出一个零点，后续几问都相当于求解微分方程\n3.设函数f(x)在[a,b]上连续,在(a,b)内二阶可导,且\\begin{align}\u0026amp;3.设函数f(x)在[a,b]上连续,在(a,b)内二阶可导,且\\end{align}​3.设函数f(x)在[a,b]上连续,在(a,b)内二阶可导,且​​ f(a)=f(b)=0,∫abf(x)dx=0 f(a)=f(b)=0,\\int_a^bf(x)dx=0 f(a)=f(b)=0,∫ab​f(x)dx=0 (1)证明存在互不相同的点x1,x2∈(a,b),使得f′(xi)=f(xi),i=1,2;(2)证明存在ξ∈(a,b),ξ≠xi,i=1,2,使得f′′(ξ)=f(ξ);(3)证明存在η∈(a,b),η≠xi,i=1,2,使得f′′(η)−3f′(η)+2f(η)=0证:[1](积分中值定理)令F(x)=∫axf(x)dx,则F(a)=0,F(b)=0,故存在一点x0使得F′(x0)=f(x0)=0令G(x)=f(x)e−x,则G(a)=G(x0)=G(b)=0,则存在两点x1≠x2,使f′(xi)=f(xi),i=1,2其中x1∈(a,x0),x2∈(x0,b),证毕[2](λ2−1=0⇒f′′(x)−f′(x)+f′(x)−f(x)=0)令H(x)=ex(f′(x)−f(x)),则H(x1)=H(x2)=0,故存在ξ∈(x1,x2)使H′(ξ)=0,证毕[3](λ2−3λ+2=0⇒f′′(x)−f′(x)+2(f′(x)−f(x))=0)令K(x)=e2x(f′(x)−f(x)),...,证毕\\begin{align}\u0026amp;(1)证明存在互不相同的点x_1,x_2\\in(a,b),使得f\u0026#x27;(x_i)=f(x_i),i=1,2;\\\\\u0026amp;(2)证明存在\\xi\\in(a,b),\\xi\\ne x_i,i=1,2,使得f\u0026#x27;\u0026#x27;(\\xi)=f(\\xi);\\\\\u0026amp;(3)证明存在\\eta\\in(a,b),\\eta\\ne x_i,i=1,2,使得f\u0026#x27;\u0026#x27;(\\eta)-3f\u0026#x27;(\\eta)+2f(\\eta)=0\\\\\u0026amp;证:[1](积分中值定理)令F(x)=\\int_a^x f(x)dx,则F(a)=0,F(b)=0,故存在一点x_0使得F\u0026#x27;(x_0)=f(x_0)=0\\\\\u0026amp;令G(x)=f(x)e^{-x},则G(a)=G(x_0)=G(b)=0,则存在两点x_1\\ne x_2,使f\u0026#x27;(x_i)=f(x_i),i=1,2\\\\\u0026amp;其中x_1\\in(a,x_0),x_2\\in(x_0,b),证毕\\\\\u0026amp;[2](\\lambda^2-1=0\\Rightarrow f\u0026#x27;\u0026#x27;(x)-f\u0026#x27;(x)+f\u0026#x27;(x)-f(x)=0)\\\\\u0026amp;令H(x)=e^{x}(f\u0026#x27;(x)-f(x)),则H(x_1)=H(x_2)=0,故存在\\xi\\in(x_1,x_2)使H\u0026#x27;(\\xi)=0,证毕\\\\\u0026amp;[3](\\lambda^2-3\\lambda+2=0\\Rightarrow f\u0026#x27;\u0026#x27;(x)-f\u0026#x27;(x)+2(f\u0026#x27;(x)-f(x))=0)\\\\\u0026amp;令K(x)=e^{2x}(f\u0026#x27;(x)-f(x)),...,证毕\\end{align}​(1)证明存在互不相同的点x1​,x2​∈(a,b),使得f′(xi​)=f(xi​),i=1,2;(2)证明存在ξ∈(a,b),ξ=xi​,i=1,2,使得f′′(ξ)=f(ξ);(3)证明存在η∈(a,b),η=xi​,i=1,2,使得f′′(η)−3f′(η)+2f(η)=0证:[1](积分中值定理)令F(x)=∫ax​f(x)dx,则F(a)=0,F(b)=0,故存在一点x0​使得F′(x0​)=f(x0​)=0令G(x)=f(x)e−x,则G(a)=G(x0​)=G(b)=0,则存在两点x1​=x2​,使f′(xi​)=f(xi​),i=1,2其中x1​∈(a,x0​),x2​∈(x0​,b),证毕[2](λ2−1=0⇒f′′(x)−f′(x)+f′(x)−f(x)=0)令H(x)=ex(f′(x)−f(x)),则H(x1​)=H(x2​)=0,故存在ξ∈(x1​,x2​)使H′(ξ)=0,证毕[3](λ2−3λ+2=0⇒f′′(x)−f′(x)+2(f′(x)−f(x))=0)令K(x)=e2x(f′(x)−f(x)),...,证毕​​ 给出若干低阶导，求高阶导；或给出若干定积分，求本身或其导，都有可能考察泰勒中值定理\n通过待定系数法求出辅助函数的常量值，运用多次罗尔定理也可得到结论(又是通过微分方程，但是最高有三阶导)\n4.设函数f(x)在闭区间[−1,1]上具有连续的三阶导数,且f(−1)=0,f(1)=1,f′(0)=0求证:在开区间(−1,1)内至少存在一点ξ,使f′′′(ξ)=3证法一:在x=0点处泰勒展开,f(x)=f(0)+f′(0)x+f′′(0)x22+f′′′(ξ)x36,ξ介于x和0之间则{f(−1)=0=f(0)−f′(0)+f′′(0)2−f′′′(ξ1)6f(1)=1=f(0)+f′(0)+f′′(0)2+f′′′(ξ2)6得f(1)−f(−1)=1=2(f′(0)+f′′′(ξ1)+f′′′(ξ2)6)即f′′′(ξ1)+f′′′(ξ2)2=3,由于ξ1,ξ2∈(−1,1),设在该区间内f′′′(x)最小值为m,最大值为M则m≤f′′′(ξ1),f′′′(ξ2)≤M,故m≤f′′′(ξ1)+f′′′(ξ2)2≤M由介值定理,必存在一点ξ∈(−1,1),使f′′′(ξ)=f′′′(ξ1)+f′′′(ξ2)2=3,证毕\\begin{align}\u0026amp;4.设函数f(x)在闭区间[-1,1]上具有连续的三阶导数,且f(-1)=0,f(1)=1,f\u0026#x27;(0)=0\\\\\u0026amp;求证:在开区间(-1,1)内至少存在一点\\xi,使f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi)=3\\\\\u0026amp;证法一:在x=0点处泰勒展开,f(x)=f(0)+f\u0026#x27;(0)x+\\frac{f\u0026#x27;\u0026#x27;(0)x^2}2+\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi)x^3}6,\\xi介于x和0之间\\\\\u0026amp;则\\begin{cases}f(-1)=0=f(0)-f\u0026#x27;(0)+\\frac{f\u0026#x27;\u0026#x27;(0)}2-\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_1)}6\\\\f(1)=1=f(0)+f\u0026#x27;(0)+\\frac{f\u0026#x27;\u0026#x27;(0)}2+\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_2)}6\\end{cases}\\\\\u0026amp;得f(1)-f(-1)=1=2\\left(f\u0026#x27;(0)+\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_1)+f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_2)}6\\right)\\\\\u0026amp;即\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_1)+f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_2)}2=3,由于\\xi_1,\\xi_2\\in(-1,1),设在该区间内f\u0026#x27;\u0026#x27;\u0026#x27;(x)最小值为m,最大值为M\\\\\u0026amp;则m\\le f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_1),f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_2)\\le M,故m\\le\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_1)+f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_2)}2\\le M\\\\\u0026amp;由介值定理,必存在一点\\xi\\in(-1,1),使f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi)=\\frac{f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_1)+f\u0026#x27;\u0026#x27;\u0026#x27;(\\xi_2)}2=3,证毕\\end{align}​4.设函数f(x)在闭区间[−1,1]上具有连续的三阶导数,且f(−1)=0,f(1)=1,f′(0)=0求证:在开区间(−1,1)内至少存在一点ξ,使f′′′(ξ)=3证法一:在x=0点处泰勒展开,f(x)=f(0)+f′(0)x+2f′′(0)x2​+6f′′′(ξ)x3​,ξ介于x和0之间则{f(−1)=0=f(0)−f′(0)+2f′′(0)​−6f′′′(ξ1​)​f(1)=1=f(0)+f′(0)+2f′′(0)​+6f′′′(ξ2​)​​得f(1)−f(−1)=1=2(f′(0)+6f′′′(ξ1​)+f′′′(ξ2​)​)即2f′′′(ξ1​)+f′′′(ξ2​)​=3,由于ξ1​,ξ2​∈(−1,1),设在该区间内f′′′(x)最小值为m,最大值为M则m≤f′′′(ξ1​),f′′′(ξ2​)≤M,故m≤2f′′′(ξ1​)+f′′′(ξ2​)​≤M由介值定理,必存在一点ξ∈(−1,1),使f′′′(ξ)=2f′′′(ξ1​)+f′′′(ξ2​)​=3,证毕​​证法二:[草稿纸过程]由结论出发,令F′′(x)=f′′(x)−3x+A再积分令F′(x)=f′(x)−32x2+Ax+B再积分F(x)=f(x)−x32+A2x2+Bx+C,为了使其满足罗尔定理,令F(−1)=F(1)=F(0)=0∃两点使F′(x1)=F′(x2)=0令F′(0)=F′(x1)=F′(x2)=0∃两点使F(x3)=F(x4)=0求出A、B、C(即使包含未知的函数值也可,因为连续)得到的F(x)就是辅助函数,通过多次罗尔定理得证\\begin{align}\u0026amp;证法二:[草稿纸过程]\\\\\u0026amp;由结论出发,令F\u0026#x27;\u0026#x27;(x)=f\u0026#x27;\u0026#x27;(x)-3x+A\\\\\u0026amp;再积分令F\u0026#x27;(x)=f\u0026#x27;(x)-\\frac32x^2+Ax+B\\\\\u0026amp;再积分F(x)=f(x)-\\frac{x^3}2+\\frac A2x^2+Bx+C,\\\\\u0026amp;为了使其满足罗尔定理,令F(-1)=F(1)=F(0)=0\u0026amp;\\exist两点使F\u0026#x27;(x_1)=F\u0026#x27;(x_2)=0\\\\\u0026amp;令F\u0026#x27;(0)=F\u0026#x27;(x_1)=F\u0026#x27;(x_2)=0\u0026amp;\\exist两点使F(x_3)=F(x_4)=0\\\\\u0026amp;求出A、B、C(即使包含未知的函数值也可,因为连续)\\\\\u0026amp;得到的F(x)就是辅助函数,通过多次罗尔定理得证\\end{align}​证法二:[草稿纸过程]由结论出发,令F′′(x)=f′′(x)−3x+A再积分令F′(x)=f′(x)−23​x2+Ax+B再积分F(x)=f(x)−2x3​+2A​x2+Bx+C,为了使其满足罗尔定理,令F(−1)=F(1)=F(0)=0令F′(0)=F′(x1​)=F′(x2​)=0求出A、B、C(即使包含未知的函数值也可,因为连续)得到的F(x)就是辅助函数,通过多次罗尔定理得证​∃两点使F′(x1​)=F′(x2​)=0∃两点使F(x3​)=F(x4​)=0​​ 含变限积分的函数不需要继续积分，可借助积分中值定理和分部积分，因为只需要额外的一点\n5.设函数f(x)在[0,1]上具有连续导数,且∫01f(x)dx=52,∫01xf(x)dx=32证明:存在ξ∈(0,1),使f′(ξ)=3证:[草稿纸过程]同样为了使用多次罗尔定理,令F′(x)=f(x)−3x+AF(x)=∫0xf(t)dt−32x2+Ax+B令F(0)=F(1)=0解出A,B后,此时还需要找到一点可以使用积分中值定理,只需证∫01F(x)dx=0,则存在一点使F(x0)=0,且x0∈(0,1)由分部积分,原定积分=xF(x)∣01−∫01xF′(x)dx=F(1)−∫01x(f(x)−3x+A)确实等于0,则可通过三次罗尔定理得证\\begin{align}\u0026amp;5.设函数f(x)在[0,1]上具有连续导数,且\\int_0^1f(x)dx=\\frac52,\\int_0^1xf(x)dx=\\frac32\\\\\u0026amp;证明:存在\\xi\\in(0,1),使f\u0026#x27;(\\xi)=3\\\\\u0026amp;证:[草稿纸过程]\\\\\u0026amp;同样为了使用多次罗尔定理,令F\u0026#x27;(x)=f(x)-3x+A\\\\\u0026amp;F(x)=\\int_0^xf(t)dt-\\frac32x^2+Ax+B\\\\\u0026amp;令F(0)=F(1)=0解出A,B后,此时还需要找到一点\\\\\u0026amp;可以使用积分中值定理,只需证\\int_0^1F(x)dx=0,则存在一点使F(x_0)=0,且x_0\\in(0,1)\\\\\u0026amp;由分部积分,原定积分=xF(x)\\bigg|_0^1-\\int_0^1xF\u0026#x27;(x)dx=F(1)-\\int_0^1x(f(x)-3x+A)\\\\\u0026amp;确实等于0,则可通过三次罗尔定理得证\\end{align}​5.设函数f(x)在[0,1]上具有连续导数,且∫01​f(x)dx=25​,∫01​xf(x)dx=23​证明:存在ξ∈(0,1),使f′(ξ)=3证:[草稿纸过程]同样为了使用多次罗尔定理,令F′(x)=f(x)−3x+AF(x)=∫0x​f(t)dt−23​x2+Ax+B令F(0)=F(1)=0解出A,B后,此时还需要找到一点可以使用积分中值定理,只需证∫01​F(x)dx=0,则存在一点使F(x0​)=0,且x0​∈(0,1)由分部积分,原定积分=xF(x)​01​−∫01​xF′(x)dx=F(1)−∫01​x(f(x)−3x+A)确实等于0,则可通过三次罗尔定理得证​​ 6.设函数f(x)在(−∞,+∞)上具有二阶导数,并且\\begin{align}\u0026amp;6.设函数f(x)在(-\\infty,+\\infty)上具有二阶导数,并且\\end{align}​6.设函数f(x)在(−∞,+∞)上具有二阶导数,并且​​ f′′(x)\u0026gt;0,lim⁡x→+∞f′(x)=α\u0026gt;0,lim⁡x→−∞f′(x)=β\u0026lt;0 f\u0026#x27;\u0026#x27;(x)\u0026gt;0,\\lim_{x\\rightarrow+\\infty}f\u0026#x27;(x)=\\alpha\u0026gt;0,\\lim_{x\\rightarrow-\\infty}f\u0026#x27;(x)=\\beta\u0026lt;0 f′′(x)\u0026gt;0,x→+∞lim​f′(x)=α\u0026gt;0,x→−∞lim​f′(x)=β\u0026lt;0 且存在一点x0,使得f(x0)\u0026lt;0,证明:方程f(x)=0在(−∞,+∞)恰有两个实根先证至少有两个实根:证法一⋅广义洛必达法则:若有g(x0)→∞,且lim⁡x→x0f′(x)g′(x)=A,则无论f(x0)趋于何值,lim⁡x→x0f(x)g(x)=lim⁡x→x0f′(x)g′(x)=A根据该定理,未知f(x0)是否趋于无穷时,也可运用洛必达则lim⁡x→+∞f(x)x=lim⁡x→+∞f′(x)=α\u0026gt;0,lim⁡x→−∞f(x)x=lim⁡x→x0f′(x)=β\u0026lt;0故lim⁡x→+∞f(x)=+∞,lim⁡x→−∞f(x)=+∞,即∀N1\u0026gt;0,∃x1\u0026gt;N1使f(x1)\u0026gt;0∀N2\u0026lt;0,∃x2\u0026lt;N2使f(x2)\u0026gt;0,由存在x0使f(x0)\u0026lt;0,由零点定理得证至少有两个实根证法二⋅泰勒展开在任意一点x1处展开,f(x)=f(x1)+f′(x1)(x−x1)+f′′(ξ)(x−x1)22,ξ介于x和x1间由f′′(x)\u0026gt;0,得f(x)\u0026gt;f(x1)+f′(x1)(x−x1)根据已给条件,{∃充分大N1\u0026gt;0,当x1\u0026gt;N1时,f′(x1)\u0026gt;0∃充分大N2\u0026lt;0,当x1\u0026lt;N2时,f′(x1)\u0026lt;0因此lim⁡x→+∞f(x)\u0026gt;lim⁡x→+∞(f(x1)+f′(x1)(x−x1))\u0026gt;0,[当x1\u0026gt;N1时,该极限为(常数+正数∗正无穷)]lim⁡x→−∞f(x)\u0026gt;lim⁡x→−∞(f(x1)+f′(x1)(x−x1))\u0026gt;0,[当x1\u0026lt;N2时,该极限为(常数+负数∗负无穷)]通过和证法一相同的极限语言,零点定理得证再证只有两个实根:反证法,若有三个及以上实根,三次罗尔定理后应存在一点η使f′′(η)=0,然而f′′(x)\u0026gt;0,矛盾故f(x)=0恰有两个实根,证毕\\begin{align}\u0026amp;且存在一点x_0,使得f(x_0)\u0026lt;0,证明:方程f(x)=0在(-\\infty,+\\infty)恰有两个实根\\\\\u0026amp;先证至少有两个实根:\\\\\u0026amp;证法一\\cdot广义洛必达法则:若有g(x_0)\\rightarrow\\infty,且\\lim_{x\\rightarrow x_0}\\frac{f\u0026#x27;(x)}{g\u0026#x27;(x)}=A,\\\\\u0026amp;则{\\bf\\color{red}无论f(x_0)趋于何值},\\lim_{x\\rightarrow x_0}\\frac{f(x)}{g(x)}=\\lim_{x\\rightarrow x_0}\\frac{f\u0026#x27;(x)}{g\u0026#x27;(x)}=A\\\\\u0026amp;根据该定理,未知f(x_0)是否趋于无穷时,也可运用洛必达\\\\\u0026amp;则\\lim_{x\\rightarrow+\\infty}\\frac{f(x)}{x}=\\lim_{x\\rightarrow+\\infty}f\u0026#x27;(x)=\\alpha\u0026gt;0,\\lim_{x\\rightarrow-\\infty}\\frac{f(x)}x=\\lim_{x\\rightarrow x_0}f\u0026#x27;(x)=\\beta\u0026lt;0\\\\\u0026amp;故\\lim_{x\\rightarrow+\\infty}f(x)=+\\infty,\\lim_{x\\rightarrow-\\infty}f(x)=+\\infty,\\\\\u0026amp;即\\forall N_1\u0026gt;0,\\exist x_1\u0026gt;N_1使f(x_1)\u0026gt;0\\\\\u0026amp;\\forall N_2\u0026lt;0,\\exist x_2\u0026lt;N_2使f(x_2)\u0026gt;0,由存在x_0使f(x_0)\u0026lt;0,由零点定理得证至少有两个实根\\\\\u0026amp;证法二·泰勒展开\\\\\u0026amp;在任意一点x_1处展开,f(x)=f(x_1)+f\u0026#x27;(x_1)(x-x_1)+\\frac{f\u0026#x27;\u0026#x27;(\\xi)(x-x_1)^2}2,\\xi介于x和x_1间\\\\\u0026amp;由f\u0026#x27;\u0026#x27;(x)\u0026gt;0,得f(x)\u0026gt;f(x_1)+f\u0026#x27;(x_1)(x-x_1)\\\\\u0026amp;根据已给条件,\\begin{cases}{\\exist充分大N_1\u0026gt;0,当x_1\u0026gt;N_1时,f\u0026#x27;(x_1)\u0026gt;0}\\\\\\exist充分大N_2\u0026lt;0,当x_1\u0026lt;N_2时,f\u0026#x27;(x_1)\u0026lt;0\\end{cases}\\\\\u0026amp;因此\\lim_{x\\rightarrow+\\infty}f(x)\u0026gt;\\lim_{x\\rightarrow+\\infty}(f(x_1)+f\u0026#x27;(x_1)(x-x_1))\u0026gt;0,[当x_1\u0026gt;N_1时,该极限为(常数+正数*正无穷)]\\\\\u0026amp;\\lim_{x\\rightarrow-\\infty}f(x)\u0026gt;\\lim_{x\\rightarrow-\\infty}(f(x_1)+f\u0026#x27;(x_1)(x-x_1))\u0026gt;0,[当x_1\u0026lt;N_2时,该极限为(常数+负数*负无穷)]\\\\\u0026amp;通过和证法一相同的极限语言,零点定理得证\\\\\u0026amp;再证只有两个实根:\\\\\u0026amp;反证法,若有三个及以上实根,三次罗尔定理后应存在一点\\eta使f\u0026#x27;\u0026#x27;(\\eta)=0,然而f\u0026#x27;\u0026#x27;(x)\u0026gt;0,矛盾\\\\\u0026amp;故f(x)=0恰有两个实根,证毕\\end{align}​且存在一点x0​,使得f(x0​)\u0026lt;0,证明:方程f(x)=0在(−∞,+∞)恰有两个实根先证至少有两个实根:证法一⋅广义洛必达法则:若有g(x0​)→∞,且x→x0​lim​g′(x)f′(x)​=A,则无论f(x0​)趋于何值,x→x0​lim​g(x)f(x)​=x→x0​lim​g′(x)f′(x)​=A根据该定理,未知f(x0​)是否趋于无穷时,也可运用洛必达则x→+∞lim​xf(x)​=x→+∞lim​f′(x)=α\u0026gt;0,x→−∞lim​xf(x)​=x→x0​lim​f′(x)=β\u0026lt;0故x→+∞lim​f(x)=+∞,x→−∞lim​f(x)=+∞,即∀N1​\u0026gt;0,∃x1​\u0026gt;N1​使f(x1​)\u0026gt;0∀N2​\u0026lt;0,∃x2​\u0026lt;N2​使f(x2​)\u0026gt;0,由存在x0​使f(x0​)\u0026lt;0,由零点定理得证至少有两个实根证法二⋅泰勒展开在任意一点x1​处展开,f(x)=f(x1​)+f′(x1​)(x−x1​)+2f′′(ξ)(x−x1​)2​,ξ介于x和x1​间由f′′(x)\u0026gt;0,得f(x)\u0026gt;f(x1​)+f′(x1​)(x−x1​)根据已给条件,{∃充分大N1​\u0026gt;0,当x1​\u0026gt;N1​时,f′(x1​)\u0026gt;0∃充分大N2​\u0026lt;0,当x1​\u0026lt;N2​时,f′(x1​)\u0026lt;0​因此x→+∞lim​f(x)\u0026gt;x→+∞lim​(f(x1​)+f′(x1​)(x−x1​))\u0026gt;0,[当x1​\u0026gt;N1​时,该极限为(常数+正数∗正无穷)]x→−∞lim​f(x)\u0026gt;x→−∞lim​(f(x1​)+f′(x1​)(x−x1​))\u0026gt;0,[当x1​\u0026lt;N2​时,该极限为(常数+负数∗负无穷)]通过和证法一相同的极限语言,零点定理得证再证只有两个实根:反证法,若有三个及以上实根,三次罗尔定理后应存在一点η使f′′(η)=0,然而f′′(x)\u0026gt;0,矛盾故f(x)=0恰有两个实根,证毕​​ 7.设函数f(x)在区间[0,1]上连续且∫01f(x)dx≠0,证明:在区间[0,1]上存在三个不同的点x1,x2,x3,使得\\begin{align}\u0026amp;7.设函数f(x)在区间[0,1]上连续且\\int_0^1f(x)dx\\ne0,证明:在区间[0,1]上存在三个不同的点x_1,x_2,x_3,使得\\end{align}​7.设函数f(x)在区间[0,1]上连续且∫01​f(x)dx=0,证明:在区间[0,1]上存在三个不同的点x1​,x2​,x3​,使得​​ π8∫01f(x)dx=[11+x12∫0x1f(t)dt+f(x1)arctan⁡x1]x3 =[11+x22∫0x2f(t)dt+f(x2)arctan⁡x2](1−x3) \\frac\\pi8\\int_0^1f(x)dx=\\left[\\frac1{1+x_1^2}\\int_0^{x_1}f(t)dt+f(x_1)\\arctan x_1\\right]x_3\\\\\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ =\\left[\\frac1{1+x_2^2}\\int_0^{x_2}f(t)dt+f(x_2)\\arctan x_2\\right](1-x_3) 8π​∫01​f(x)dx=[1+x12​1​∫0x1​​f(t)dt+f(x1​)arctanx1​]x3​ =[1+x22​1​∫0x2​​f(t)dt+f(x2​)arctanx2​](1−x3​) 证:[草稿纸过程]观察两式,联想到分部积分,式子即F(x)=arctan⁡x∫0xf(t)dt的导数观察x3、1−x3,x3∈(0,1),然后x1、x2是双中值,尝试拉中证:[作答]令F(x)=arctan⁡x∫0xf(t)dt,则F(0)=0,F(1)=π4∫01f(x)dx由∫01f(x)dx≠0,得0\u0026lt;∣π8∫01f(t)dt∣\u0026lt;∣π4∫01f(t)dt∣又F(x)在[0,1]上连续,由介值定理必存在一点x3∈(0,1)使F(x3)=π8∫01f(t)dt在[0,x3]上拉中,π8∫01f(t)dtx3=F′(x1)=11+x12∫0x1f(t)dt+f(x1)arctan⁡x1在[x3,1]上拉中,π8∫01f(t)dt1−x3=F′(x2)=11+x22∫0x2f(t)dt+f(x2)arctan⁡x2证毕.\\begin{align}\u0026amp;证:[草稿纸过程]\\\\\u0026amp;观察两式,联想到分部积分,式子即F(x)=\\arctan x\\int_0^xf(t)dt的导数\\\\\u0026amp;观察x_3、1-x_3,x_3\\in(0,1),然后x_1、x_2是双中值,尝试拉中\\\\\u0026amp;证:[作答]\\\\\u0026amp;令F(x)=\\arctan x\\int_0^xf(t)dt,则F(0)=0,F(1)=\\frac\\pi4\\int_0^1f(x)dx\\\\\u0026amp;由\\int_0^1f(x)dx\\ne0,得0\u0026lt;\\left|\\frac\\pi8\\int_0^1f(t)dt\\right|\u0026lt;\\left|\\frac\\pi4\\int_0^1f(t)dt\\right|\\\\\u0026amp;又F(x)在[0,1]上连续,由介值定理必存在一点x_3\\in(0,1)使F(x_3)=\\frac\\pi8\\int_0^1f(t)dt\\\\\u0026amp;在[0,x_3]上拉中,\\frac{\\frac\\pi8\\int_0^1f(t)dt}{x_3}=F\u0026#x27;(x_1)=\\frac1{1+x_1^2}\\int_0^{x_1}f(t)dt+f(x_1)\\arctan x_1\\\\\u0026amp;在[x_3,1]上拉中,\\frac{\\frac\\pi8\\int_0^1f(t)dt}{1-x_3}=F\u0026#x27;(x_2)=\\frac1{1+x_2^2}\\int_0^{x_2}f(t)dt+f(x_2)\\arctan x_2\\\\\u0026amp;证毕.\\end{align}​证:[草稿纸过程]观察两式,联想到分部积分,式子即F(x)=arctanx∫0x​f(t)dt的导数观察x3​、1−x3​,x3​∈(0,1),然后x1​、x2​是双中值,尝试拉中证:[作答]令F(x)=arctanx∫0x​f(t)dt,则F(0)=0,F(1)=4π​∫01​f(x)dx由∫01​f(x)dx=0,得0\u0026lt;​8π​∫01​f(t)dt​\u0026lt;​4π​∫01​f(t)dt​又F(x)在[0,1]上连续,由介值定理必存在一点x3​∈(0,1)使F(x3​)=8π​∫01​f(t)dt在[0,x3​]上拉中,x3​8π​∫01​f(t)dt​=F′(x1​)=1+x12​1​∫0x1​​f(t)dt+f(x1​)arctanx1​在[x3​,1]上拉中,1−x3​8π​∫01​f(t)dt​=F′(x2​)=1+x22​1​∫0x2​​f(t)dt+f(x2​)arctanx2​证毕.​​ 8.设函数f(x)在[0,1]上有二阶导数,且有正常数A,B,使得\\begin{align}\u0026amp;8.设函数f(x)在[0,1]上有二阶导数,且有正常数A,B,使得\\end{align}​8.设函数f(x)在[0,1]上有二阶导数,且有正常数A,B,使得​​ ∣f(x)∣≤A,∣f′′(x)∣≤B |f(x)|\\le A,|f\u0026#x27;\u0026#x27;(x)|\\le B ∣f(x)∣≤A,∣f′′(x)∣≤B 证明:对于任意x∈[0,1],有∣f′(x)∣≤2A+B2证:在[0,1]中任意点展开,f(x)=f(x0)+f′(x0)(x−x0)+f′′(ξ)(x−x0)22,ξ介于x和x0间则{f(0)=f(x0)−f′(x0)x0+f′′(ξ1)x022f(1)=f(x0)+f′(x0)(1−x0)+f′′(ξ2)(1−x0)22f(1)−f(0)=f′(x0)+f′′(ξ2)(1−x0)2−f′′(ξ1)x022则有f(x0)=f(1)−f(0)+f′′(ξ1)x02−f′′(ξ2)(1−x0)22∣f′(x0)∣≤∣f(1)∣+∣f(0)∣+∣f′′(ξ1)x022∣+∣f′′(ξ2)(1−x0)22∣≤2A+(x02+(1−x0)2)B2≤2A+B2,证毕\\begin{align}\u0026amp;证明:对于任意x\\in[0,1],有|f\u0026#x27;(x)|\\le 2A+\\frac B2\\\\\u0026amp;证:在[0,1]中任意点展开,f(x)=f(x_0)+f\u0026#x27;(x_0)(x-x_0)+\\frac{f\u0026#x27;\u0026#x27;(\\xi)(x-x_0)^2}2,\\xi介于x和x_0间\\\\\u0026amp;则\\begin{cases}f(0)=f(x_0)-f\u0026#x27;(x_0)x_0+\\frac{f\u0026#x27;\u0026#x27;(\\xi_1)x_0^2}2\\\\f(1)=f(x_0)+f\u0026#x27;(x_0)(1-x_0)+\\frac{f\u0026#x27;\u0026#x27;(\\xi_2)(1-x_0)^2}2\\end{cases}\\\\\u0026amp;f(1)-f(0)=f\u0026#x27;(x_0)+\\frac{f\u0026#x27;\u0026#x27;(\\xi_2)(1-x_0)^2-f\u0026#x27;\u0026#x27;(\\xi_1)x_0^2}2\\\\\u0026amp;则有f(x_0)=f(1)-f(0)+\\frac{f\u0026#x27;\u0026#x27;(\\xi_1)x_0^2-f\u0026#x27;\u0026#x27;(\\xi_2)(1-x_0)^2}2\\\\\u0026amp;|f\u0026#x27;(x_0)|\\le|f(1)|+|f(0)|+\\left|\\frac{f\u0026#x27;\u0026#x27;(\\xi_1)x_0^2}2\\right|+\\left|\\frac{f\u0026#x27;\u0026#x27;(\\xi_2)(1-x_0)^2}2\\right|\\\\\u0026amp;\\le2A+(x_0^2+(1-x_0)^2)\\frac B2\\le2A+\\frac B2,证毕\\end{align}​证明:对于任意x∈[0,1],有∣f′(x)∣≤2A+2B​证:在[0,1]中任意点展开,f(x)=f(x0​)+f′(x0​)(x−x0​)+2f′′(ξ)(x−x0​)2​,ξ介于x和x0​间则{f(0)=f(x0​)−f′(x0​)x0​+2f′′(ξ1​)x02​​f(1)=f(x0​)+f′(x0​)(1−x0​)+2f′′(ξ2​)(1−x0​)2​​f(1)−f(0)=f′(x0​)+2f′′(ξ2​)(1−x0​)2−f′′(ξ1​)x02​​则有f(x0​)=f(1)−f(0)+2f′′(ξ1​)x02​−f′′(ξ2​)(1−x0​)2​∣f′(x0​)∣≤∣f(1)∣+∣f(0)∣+​2f′′(ξ1​)x02​​​+​2f′′(ξ2​)(1−x0​)2​​≤2A+(x02​+(1−x0​)2)2B​≤2A+2B​,证毕​​ 9.设函数f(x)在[−2,2]上二阶可导,且∣f(x)∣\u0026lt;1,又f2(0)+[f′(0)]2=4,试证在(−2,2)内至少存在一点ξ,使f(ξ)+f′′(ξ)=0[给出的等式条件求导后恰好包含待证式]证:令F(x)=f2(x)+[f′(x)]2,则F(0)=4,一个点不足以使用罗尔定理,因此要分区间然而提供的是不等式,故推出新的不等式后需通过介值定理(或直接用费马引理)[−2,0]上拉中,存在ξ1∈(−2,0)使f′(ξ1)=f(0)−f(−2)2,∣f′(ξ1)∣≤∣f(0)∣2+∣f(−2)∣2\u0026lt;1[0,2]上拉中,存在ξ2∈(0,2)使f′(ξ2)=f(2)−f(0)2,同理∣f′(ξ2)∣\u0026lt;1因此F(ξ1)\u0026lt;2,F(ξ2)\u0026lt;2,而F(0)=4故由局部极大值定理和费马引理,存在ξ∈(ξ1,ξ2),也即ξ∈(−2,2)使F′(ξ)=2f(ξ)f′(ξ)+2f′(ξ)f′′(ξ)=0,且它是一个极大值又F(ξ)=f2(ξ)+[f′(ξ)]2\u0026gt;2,(极大值的性质)得f′(ξ)≠0结论得证\\begin{align}\u0026amp;9.设函数f(x)在[-2,2]上二阶可导,且|f(x)|\u0026lt;1,又f^2(0)+[f\u0026#x27;(0)]^2=4,\\\\\u0026amp;试证在(-2,2)内至少存在一点\\xi,使f(\\xi)+f\u0026#x27;\u0026#x27;(\\xi)=0\\\\\u0026amp;[给出的等式条件求导后恰好包含待证式]\\\\\u0026amp;证:令F(x)=f^2(x)+[f\u0026#x27;(x)]^2,则F(0)=4,一个点不足以使用罗尔定理,因此要分区间\\\\\u0026amp;然而提供的是不等式,故推出新的不等式后需通过介值定理(或直接用费马引理)\\\\\u0026amp;[-2,0]上拉中,存在\\xi_1\\in(-2,0)使f\u0026#x27;(\\xi_1)=\\frac{f(0)-f(-2)}2,|f\u0026#x27;(\\xi_1)|\\le\\frac{|f(0)|}2+\\frac{|f(-2)|}2\u0026lt;1\\\\\u0026amp;[0,2]上拉中,存在\\xi_2\\in(0,2)使f\u0026#x27;(\\xi_2)=\\frac{f(2)-f(0)}2,同理|f\u0026#x27;(\\xi_2)|\u0026lt;1\\\\\u0026amp;因此F(\\xi_1)\u0026lt;2,F(\\xi_2)\u0026lt;2,而F(0)=4\\\\\u0026amp;故由局部极大值定理和费马引理,\\\\\u0026amp;存在\\xi\\in(\\xi_1,\\xi_2),也即\\xi\\in(-2,2)使F\u0026#x27;(\\xi)=2f(\\xi)f\u0026#x27;(\\xi)+2f\u0026#x27;(\\xi)f\u0026#x27;\u0026#x27;(\\xi)=0,且它是一个极大值\\\\\u0026amp;又F(\\xi)=f^2(\\xi)+[f\u0026#x27;(\\xi)]^2\u0026gt;2,(极大值的性质)得f\u0026#x27;(\\xi)\\ne0\\\\\u0026amp;结论得证\\end{align}​9.设函数f(x)在[−2,2]上二阶可导,且∣f(x)∣\u0026lt;1,又f2(0)+[f′(0)]2=4,试证在(−2,2)内至少存在一点ξ,使f(ξ)+f′′(ξ)=0[给出的等式条件求导后恰好包含待证式]证:令F(x)=f2(x)+[f′(x)]2,则F(0)=4,一个点不足以使用罗尔定理,因此要分区间然而提供的是不等式,故推出新的不等式后需通过介值定理(或直接用费马引理)[−2,0]上拉中,存在ξ1​∈(−2,0)使f′(ξ1​)=2f(0)−f(−2)​,∣f′(ξ1​)∣≤2∣f(0)∣​+2∣f(−2)∣​\u0026lt;1[0,2]上拉中,存在ξ2​∈(0,2)使f′(ξ2​)=2f(2)−f(0)​,同理∣f′(ξ2​)∣\u0026lt;1因此F(ξ1​)\u0026lt;2,F(ξ2​)\u0026lt;2,而F(0)=4故由局部极大值定理和费马引理,存在ξ∈(ξ1​,ξ2​),也即ξ∈(−2,2)使F′(ξ)=2f(ξ)f′(ξ)+2f′(ξ)f′′(ξ)=0,且它是一个极大值又F(ξ)=f2(ξ)+[f′(ξ)]2\u0026gt;2,(极大值的性质)得f′(ξ)=0结论得证​​ 通常双中值问题中，用于划分区间的中值x0x_0x0​是给出的(一般是关于x0x_0x0​的等式，例如题2.(1)2.(1)2.(1))，而在一些未给出的问题中，需要通过待定系数法求解，即假设存在可将大区间划分成两个子区间的中值，使得出的双中值满足待证结论，再求出它所满足的式子\n10.设函数f(x)在区间[0,1]上连续,且I=∫01f(x)dx≠0.证明:在(0,1)内存在不同的两点x1,x2使得1f(x1)+1f(x2)=2I证:令F(x)=∫0xf(t)dt,则F(0)=0,F(1)=I,,记F(x0)=A,x0∈(0,1)在[0,x0]上拉中,存在x1∈(0,x0)使Ax0=f(x1)在[x0,1]上拉中,存在x2∈(x0,1)使I−A1−x0=f(x2)则1f(x1)+1f(x2)=x0I−x0A+A−x0A(I−A)A令其=2I,解得I2x0−(2x0+1)IA+2A2=(x0I−A)(I−2A)=0,转化为证明存在一点x0,使x0I−F(x0)=0或I−2F(x0)=0即找到一点x0,使F(x0)=I2,显然由介值定理可得该点是存在的找出x0的过程为草稿纸过程,实际作答应直接说明存在x0使F(x0)=I2再顺着逻辑证明\\begin{align}\u0026amp;10.设函数f(x)在区间[0,1]上连续,且I=\\int_0^1f(x)dx\\ne0.证明:在(0,1)内存在不同的两点x_1,x_2使得\\\\\u0026amp;\\frac1{f(x_1)}+\\frac1{f(x_2)}=\\frac2I\\\\\u0026amp;证:令F(x)=\\int_0^xf(t)dt,则F(0)=0,F(1)=I,,记F(x_0)=A,x_0\\in(0,1)\\\\\u0026amp;在[0,x_0]上拉中,存在x_1\\in(0,x_0)使\\frac{A}{x_0}=f(x_1)\\\\\u0026amp;在[x_0,1]上拉中,存在x_2\\in(x_0,1)使\\frac{I-A}{1-x_0}=f(x_2)\\\\\u0026amp;则\\frac1{f(x_1)}+\\frac1{f(x_2)}=\\frac{x_0I-x_0A+A-x_0A}{(I-A)A}\\\\\u0026amp;令其=\\frac 2I,解得I^2x_0-(2x_0+1)IA+2A^2=(x_0I-A)(I-2A)=0,\\\\\u0026amp;转化为证明存在一点x_0,使x_0I-F(x_0)=0或I-2F(x_0)=0\\\\\u0026amp;即找到一点x_0,使F(x_0)=\\frac I2,显然由介值定理可得该点是存在的\\\\\u0026amp;找出x_0的过程为草稿纸过程,实际作答应直接说明存在x_0使F(x_0)=\\frac I2再顺着逻辑证明\\end{align}​10.设函数f(x)在区间[0,1]上连续,且I=∫01​f(x)dx=0.证明:在(0,1)内存在不同的两点x1​,x2​使得f(x1​)1​+f(x2​)1​=I2​证:令F(x)=∫0x​f(t)dt,则F(0)=0,F(1)=I,,记F(x0​)=A,x0​∈(0,1)在[0,x0​]上拉中,存在x1​∈(0,x0​)使x0​A​=f(x1​)在[x0​,1]上拉中,存在x2​∈(x0​,1)使1−x0​I−A​=f(x2​)则f(x1​)1​+f(x2​)1​=(I−A)Ax0​I−x0​A+A−x0​A​令其=I2​,解得I2x0​−(2x0​+1)IA+2A2=(x0​I−A)(I−2A)=0,转化为证明存在一点x0​,使x0​I−F(x0​)=0或I−2F(x0​)=0即找到一点x0​,使F(x0​)=2I​,显然由介值定理可得该点是存在的找出x0​的过程为草稿纸过程,实际作答应直接说明存在x0​使F(x0​)=2I​再顺着逻辑证明​​ 11.设f(x)在[0,+∞)上可微,f(0)=0,且存在常数A\u0026gt;0,使得∣f′(x)∣≤A∣f(x)∣在[0,+∞)上成立,试证明:在(0,+∞)上有f(x)≡0分析:在[0,x]上拉中,则存在一点x0∈(0,x)使f(x)=xf′(x0)则∣f(x)∣=∣xf′(x0)∣≤A∣f(x0)∣∣x∣,由∣f(x)∣≥0,要证f(x)≡0,即证∣f(x)∣≤0证:取 x∈(0,12A],记∣f(x1)∣为max⁡(∣f(x)∣,x∈(0,12A]),在[0,x1]上拉中,存在一点ξ1∈(0,x),使 ∣f(x1)∣≤A∣f(ξ1)∣∣x1∣≤∣f(ξ1)∣2≤∣f(x1)∣2故在此区间内,∣f(x)∣的最大值为0,即0≤∣f(x)∣≤0取x∈(12A,22A],拉中后仍能得出在此区间内∣f(x)∣的取值只能为0递推区间至[0,+∞)仍成立,证毕\\begin{align}\u0026amp;11.设f(x)在[0,+\\infty)上可微,f(0)=0,且存在常数A\u0026gt;0,使得\\\\\u0026amp;|f\u0026#x27;(x)|\\le A|f(x)|在[0,+\\infty)上成立,试证明:在(0,+\\infty)上有f(x)\\equiv0\\\\\u0026amp;分析:在[0,x]上拉中,则存在一点x_0\\in(0,x)使f(x)=xf\u0026#x27;(x_0)\\\\\u0026amp;则|f(x)|=|xf\u0026#x27;(x_0)|\\le A|f(x_0)||x|,由|f(x)|\\ge0,要证f(x)\\equiv0,即证|f(x)|\\le0\\\\\u0026amp;证:取\\ x\\in\\left(0,\\frac1{2A}\\right],记|f(x_1)|为\\max\\left(|f(x)|,x\\in\\left(0,\\frac1{2A}\\right]\\right),\\\\\u0026amp;在[0,x_1]上拉中,存在一点\\xi_1\\in(0,x),使\\ |f(x_1)|\\le A|f(\\xi_1)||x_1|\\le\\frac{|f(\\xi_1)|}2\\le\\frac{|f(x_1)|}2\\\\\u0026amp;故在此区间内,|f(x)|的最大值为0,即0\\le|f(x)|\\le0\\\\\u0026amp;取x\\in\\left(\\frac1{2A},\\frac2{2A}\\right],拉中后仍能得出在此区间内|f(x)|的取值只能为0\\\\\u0026amp;递推区间至[0,+\\infty)仍成立,证毕\\end{align}​11.设f(x)在[0,+∞)上可微,f(0)=0,且存在常数A\u0026gt;0,使得∣f′(x)∣≤A∣f(x)∣在[0,+∞)上成立,试证明:在(0,+∞)上有f(x)≡0分析:在[0,x]上拉中,则存在一点x0​∈(0,x)使f(x)=xf′(x0​)则∣f(x)∣=∣xf′(x0​)∣≤A∣f(x0​)∣∣x∣,由∣f(x)∣≥0,要证f(x)≡0,即证∣f(x)∣≤0证:取 x∈(0,2A1​],记∣f(x1​)∣为max(∣f(x)∣,x∈(0,2A1​]),在[0,x1​]上拉中,存在一点ξ1​∈(0,x),使 ∣f(x1​)∣≤A∣f(ξ1​)∣∣x1​∣≤2∣f(ξ1​)∣​≤2∣f(x1​)∣​故在此区间内,∣f(x)∣的最大值为0,即0≤∣f(x)∣≤0取x∈(2A1​,2A2​],拉中后仍能得出在此区间内∣f(x)∣的取值只能为0递推区间至[0,+∞)仍成立,证毕​​ 12.设函数f(x)在[0,1]上连续,且∫01f(x)dx=0,∫01xf(x)dx=1,试证:(1) ∃x0∈[0,1]使得∣f(x0)∣\u0026gt;4;(2) ∃x1∈[0,1]使得∣f(x1)∣=4\\begin{align}\u0026amp;12.设函数f(x)在[0,1]上连续,且\\int_0^1f(x)dx=0,\\int_0^1xf(x)dx=1,试证:\\\\\u0026amp;(1)\\ \\exist x_0\\in[0,1]使得\\Big|f(x_0)\\Big|\u0026gt;4;(2)\\ \\exist x_1\\in[0,1]使得\\Big|f(x_1)\\Big|=4\\\\\u0026amp;\\end{align}​12.设函数f(x)在[0,1]上连续,且∫01​f(x)dx=0,∫01​xf(x)dx=1,试证:(1) ∃x0​∈[0,1]使得​f(x0​)​\u0026gt;4;(2) ∃x1​∈[0,1]使得​f(x1​)​=4​​ 微分方程 复习 一阶非齐次线性微分方程：含公式 关于y(x)y(x)y(x)的y′(x)+P(x)y(x)=Q(x)y\u0026#x27;(x)+P(x)y(x)=Q(x)y′(x)+P(x)y(x)=Q(x)： y=e−∫P(x)dx(∫e∫P(x)dxQ(x)dx+C)\\begin{align}y=e^{-\\int P(x)\\mathrm dx}\\left(\\int e^{\\int P(x)\\mathrm dx}Q(x)\\mathrm dx+C\\right)\\end{align}y=e−∫P(x)dx(∫e∫P(x)dxQ(x)dx+C)​​ 二/高阶线性微分方程：解差分方程，当作两个一阶非齐次线性微分方程解；含公式，公式原理即前者所述 以下公式以特征方程的解λ\\lambdaλ为基础，实质上就是差分方程 二阶的推导：对y′′+py′+qy=f(x)y\u0026#x27;\u0026#x27;+py\u0026#x27;+qy=f(x)y′′+py′+qy=f(x)，有(y′−λ1y)′−λ2(y′−λ1y)=f(x)(y\u0026#x27;-\\lambda_1 y)\u0026#x27;-\\lambda_2(y\u0026#x27;-\\lambda_1y)=f(x)(y′−λ1​y)′−λ2​(y′−λ1​y)=f(x)，故有 y′−λ1y=eλ2x(∫e−λ2xf(x)dx+C1)y=eλ1x(∫(e(λ2−λ1)x⋅(∫e−λ2xf(x)dx+C1))dx+C2)\\begin{align}\u0026amp;y\u0026#x27;-\\lambda_1y=e^{\\lambda_2x}\\left(\\int e^{-\\lambda_2x}f(x)\\mathrm dx+C_1\\right)\\\\\u0026amp;y=e^{\\lambda_1 x}\\left(\\int \\left(e^{(\\lambda_2-\\lambda_1)x}·\\left(\\int e^{-\\lambda_2 x}f(x)\\mathrm dx+C_1\\right)\\right)\\mathrm dx+C_2\\right)\\end{align}​y′−λ1​y=eλ2​x(∫e−λ2​xf(x)dx+C1​)y=eλ1​x(∫(e(λ2​−λ1​)x⋅(∫e−λ2​xf(x)dx+C1​))dx+C2​)​​ 由数学归纳法有以下结论： 当f(x)=0,λ1≠λ2时,y=C1′eλ1x+C2′eλ2xλ2−λ1=C1′′eλ1x+C2′′eλ2x,(单根)当f(x)=0,λ1=λ2时,y=eλ1x(C1x+C2),(二重根,多重根类似)当f(x)=0,λ为共轭复数单根α±β i时,根据欧拉公式,y=eαx(C1cos⁡βx+C2sin⁡βx)当f(x)=0,λ为共轭复数n重根时,y=eαx((C1+C2x+⋯+Cnxn)cos⁡βx+(C1′+C2′x+⋯+Cn′xn)sin⁡βx)当f(x)≠0,且为(Axn+Bxn−1+⋯+C)eαx类型时,y的一个特解为(A′xn+B′xn−1+⋯+C′)eαxxk, 其中α为k重特征根当f(x)≠0,且为((Axn+Bxn−1+⋯+C)cos⁡βx+(A′xn+B′xn−1+⋯+C′)sin⁡βx)eαx时,y的一个特解为((A′′xn+B′′xn−1+⋯+C′′)cos⁡βx+(A′′′xn+B′′′xn−1+⋯+C′′′)sin⁡βx)eαxxk,其中α+β i为k重特征根当f(x)≠0且为上述三种情况的线性组合时,y的一个特解是各项特解的线性组合\\begin{align}\u0026amp;当f(x)=0,\\lambda_1\\ne\\lambda_2时,y=\\frac{C_1\u0026#x27;e^{\\lambda_1x}+C_2\u0026#x27;e^{\\lambda_2 x}}{\\lambda_2-\\lambda_1}=C_1\u0026#x27;\u0026#x27;e^{\\lambda_1 x}+C_2\u0026#x27;\u0026#x27;e^{\\lambda_2x},(单根)\\\\\u0026amp;当f(x)=0,\\lambda_1=\\lambda_2时,y=e^{\\lambda_1x}\\left(C_1x+C_2\\right),(二重根,多重根类似)\\\\\u0026amp;\\\\\u0026amp;当f(x)=0,\\lambda为共轭复数单根\\alpha\\pm\\beta\\ i时,根据欧拉公式,y=e^{\\alpha x}(C_1\\cos\\beta x+C_2\\sin\\beta x)\\\\\u0026amp;当f(x)=0,\\lambda为共轭复数n重根时,\\\\\u0026amp;y=e^{\\alpha x}((C_1+C_2x+\\cdots+C_nx^n)\\cos\\beta x+(C_1\u0026#x27;+C_2\u0026#x27;x+\\cdots+C_n\u0026#x27;x^n)\\sin\\beta x)\\\\\u0026amp;\\\\\u0026amp;当f(x)\\ne 0,且为(Ax^n+Bx^{n-1}+\\cdots+C)e^{\\alpha x}类型时,\\\\\u0026amp;y的一个特解为(A\u0026#x27;x^n+B\u0026#x27;x^{n-1}+\\cdots+C\u0026#x27;)e^{\\alpha x}x^k,\\ 其中\\alpha为k重特征根\\\\\u0026amp;当f(x)\\ne0,且为((Ax^n+Bx^{n-1}+\\cdots+C)\\cos\\beta x+(A\u0026#x27;x^n+B\u0026#x27;x^{n-1}+\\cdots+C\u0026#x27;)\\sin\\beta x)e^{\\alpha x}时,\\\\\u0026amp;y的一个特解为\\\\\u0026amp;((A\u0026#x27;\u0026#x27;x^n+B\u0026#x27;\u0026#x27;x^{n-1}+\\cdots+C\u0026#x27;\u0026#x27;)\\cos\\beta x+(A\u0026#x27;\u0026#x27;\u0026#x27;x^n+B\u0026#x27;\u0026#x27;\u0026#x27;x^{n-1}+\\cdots+C\u0026#x27;\u0026#x27;\u0026#x27;)\\sin\\beta x)e^{\\alpha x}x^k,\\\\\u0026amp;其中\\alpha+\\beta\\ i为k重特征根\\\\\u0026amp;当f(x)\\ne0且为上述三种情况的线性组合时,y的一个特解是各项特解的线性组合\\end{align}​当f(x)=0,λ1​=λ2​时,y=λ2​−λ1​C1′​eλ1​x+C2′​eλ2​x​=C1′′​eλ1​x+C2′′​eλ2​x,(单根)当f(x)=0,λ1​=λ2​时,y=eλ1​x(C1​x+C2​),(二重根,多重根类似)当f(x)=0,λ为共轭复数单根α±β i时,根据欧拉公式,y=eαx(C1​cosβx+C2​sinβx)当f(x)=0,λ为共轭复数n重根时,y=eαx((C1​+C2​x+⋯+Cn​xn)cosβx+(C1′​+C2′​x+⋯+Cn′​xn)sinβx)当f(x)=0,且为(Axn+Bxn−1+⋯+C)eαx类型时,y的一个特解为(A′xn+B′xn−1+⋯+C′)eαxxk, 其中α为k重特征根当f(x)=0,且为((Axn+Bxn−1+⋯+C)cosβx+(A′xn+B′xn−1+⋯+C′)sinβx)eαx时,y的一个特解为((A′′xn+B′′xn−1+⋯+C′′)cosβx+(A′′′xn+B′′′xn−1+⋯+C′′′)sinβx)eαxxk,其中α+β i为k重特征根当f(x)=0且为上述三种情况的线性组合时,y的一个特解是各项特解的线性组合​​ 全微分方程：凑微分法或偏积分法 题集 1.求解微分方程:{dydx−xy=xex2y(0)=1解:y的通解为e∫xdx(∫e∫−xdxxex2dx+C)=ex2+Cex22代入y(0)=1,则C=0, y=ex2\\begin{align}\u0026amp;1.求解微分方程:\\begin{cases}{\\large\\frac{\\mathrm dy}{\\mathrm dx}}-xy=xe^{x^2}\\\\y(0)=1\\end{cases}\\\\\u0026amp;解:y的通解为e^{\\int x\\mathrm dx}\\left(\\int e^{\\int -x\\mathrm dx} xe^{x^2}\\mathrm dx+C\\right)=e^{x^2}+Ce^{\\frac{x^2}2}\\\\\u0026amp;代入y(0)=1,则C=0,\\ y=e^{x^2}\\end{align}​1.求解微分方程:{dxdy​−xy=xex2y(0)=1​解:y的通解为e∫xdx(∫e∫−xdxxex2dx+C)=ex2+Ce2x2​代入y(0)=1,则C=0, y=ex2​​ 2.求解微分方程:{(x+1)dydx−xy=xex2y(0)=0解:y的通解为e∫xx+1dx(∫e∫−xx+1dx⋅xex2x+1dx+C)=exx+1(e2x2−2x4+C), 代入y(0)=0,得C=−14故y=exx+1(e2x2−2x4−14)\\begin{align}\u0026amp;2.求解微分方程:\\begin{cases}{(x+1){\\large\\frac{\\mathrm dy}{\\mathrm dx}}}-xy=xe^{x^2}\\\\y(0)=0\\end{cases}\\\\\u0026amp;解:y的通解为e^{^{\\int\\frac{x}{x+1}\\mathrm dx}}\\left(\\int e^{\\int-\\frac{x}{x+1}\\mathrm dx}·\\frac{xe^{x^2}}{x+1}\\mathrm dx+C\\right)\\\\\u0026amp;=\\frac{e^x}{x+1}\\left(\\frac{e^{2x^2-2x}}4+C\\right),\\ 代入y(0)=0,得C=-\\frac14\\\\\u0026amp;故y=\\frac{e^x}{x+1}\\left(\\frac{e^{2x^2-2x}}4-\\frac14\\right)\\end{align}​2.求解微分方程:{(x+1)dxdy​−xy=xex2y(0)=0​解:y的通解为e∫x+1x​dx(∫e∫−x+1x​dx⋅x+1xex2​dx+C)=x+1ex​(4e2x2−2x​+C), 代入y(0)=0,得C=−41​故y=x+1ex​(4e2x2−2x​−41​)​​ 3.设实数a≠0,求解微分方程{y′′−ay′2=0y(0)=0,y′(0)=−1解:易解得y′=−1ax+C1则y=−ln⁡(ax+C1)+C2代入y(0)=0,y′(0)=−1,得C1=1,C2=0故y=−ln⁡(ax+1)\\begin{align}\u0026amp;3.设实数a\\ne0,求解微分方程\\begin{cases}y\u0026#x27;\u0026#x27;-ay\u0026#x27;^2=0\\\\y(0)=0,y\u0026#x27;(0)=-1\\end{cases}\\\\\u0026amp;解:易解得y\u0026#x27;=-\\frac1{ax+C_1}\\\\\u0026amp;则y=-\\ln(ax+C_1)+C_2\\\\\u0026amp;代入y(0)=0,y\u0026#x27;(0)=-1,得C_1=1,C_2=0\\\\\u0026amp;故y=-\\ln(ax+1)\\end{align}​3.设实数a=0,求解微分方程{y′′−ay′2=0y(0)=0,y′(0)=−1​解:易解得y′=−ax+C1​1​则y=−ln(ax+C1​)+C2​代入y(0)=0,y′(0)=−1,得C1​=1,C2​=0故y=−ln(ax+1)​​ 4.求解微分方程:y′′−(y′)3=0的通解解:易得−(y′)−22=x+C即y′=1C1−2x故y=∫y′dx=−C1−2x+C2\\begin{align}\u0026amp;4.求解微分方程:y\u0026#x27;\u0026#x27;-(y\u0026#x27;)^3=0的通解\\\\\u0026amp;解:易得-\\frac{(y\u0026#x27;)^{-2}}2=x+C\\\\\u0026amp;即y\u0026#x27;=\\sqrt{\\frac1{C_1-2x}}\\\\\u0026amp;故y=\\int y\u0026#x27;\\mathrm dx=-\\sqrt{C_1-2x}+C_2\\end{align}​4.求解微分方程:y′′−(y′)3=0的通解解:易得−2(y′)−2​=x+C即y′=C1​−2x1​​故y=∫y′dx=−C1​−2x​+C2​​​ 5.已知y1=ex和y2=xex是齐次二阶常系数线性微分方程的解,则该微分方程是:解:易猜测特征根为 1(双重根),则特征方程为(λ−1)2=0故微分方程为y′′−2y′+y=0\\begin{align}\u0026amp;5.已知y_1=e^x和y_2=xe^x是齐次二阶常系数线性微分方程的解,则该微分方程是:\\\\\u0026amp;解:易猜测特征根为\\ 1(双重根),则特征方程为(\\lambda-1)^2=0\\\\\u0026amp;故微分方程为y\u0026#x27;\u0026#x27;-2y\u0026#x27;+y=0\\end{align}​5.已知y1​=ex和y2​=xex是齐次二阶常系数线性微分方程的解,则该微分方程是:解:易猜测特征根为 1(双重根),则特征方程为(λ−1)2=0故微分方程为y′′−2y′+y=0​​ 6.已知y1=xex+e2x,y2=xex+e−x,y3=xex+e2x−e−x是某二阶常系数线性非齐次微分方程的三个解,求其微分方程解:两特解之差为对应齐次方程的特解:y1∗=e−x,y2∗=e2x易得两根为−1,2,即对应齐次方程为y′′−y′−2y=0又有y3+y2−y1=xex也是非齐次方程的一个特解,代入得该非齐次方程为y′′−y′−2y=ex−2xex\\begin{align}\u0026amp;6.已知y_1=xe^x+e^{2x},y_2=xe^x+e^{-x},y_3=xe^x+e^{2x}-e^{-x}\\\\\u0026amp;是某二阶常系数线性非齐次微分方程的三个解,求其微分方程\\\\\u0026amp;解:两特解之差为对应齐次方程的特解:y^*_1=e^{-x},y^*_2=e^{2x}\\\\\u0026amp;易得两根为-1,2,即对应齐次方程为y\u0026#x27;\u0026#x27;-y\u0026#x27;-2y=0\\\\\u0026amp;又有y_3+y_2-y_1=xe^x也是非齐次方程的一个特解,\\\\\u0026amp;代入得该非齐次方程为y\u0026#x27;\u0026#x27;-y\u0026#x27;-2y=e^x-2xe^x\\end{align}​6.已知y1​=xex+e2x,y2​=xex+e−x,y3​=xex+e2x−e−x是某二阶常系数线性非齐次微分方程的三个解,求其微分方程解:两特解之差为对应齐次方程的特解:y1∗​=e−x,y2∗​=e2x易得两根为−1,2,即对应齐次方程为y′′−y′−2y=0又有y3​+y2​−y1​=xex也是非齐次方程的一个特解,代入得该非齐次方程为y′′−y′−2y=ex−2xex​​ 7.已知可导函数f(x)满足f(x)cos⁡x+2∫0xf(t)sin⁡tdt=x+1,则f(x)=_____.解:两边求导:−f(x)sin⁡x+f′(x)cos⁡x+2f(x)sin⁡x=1即f′(x)+tan⁡xf(x)−sec⁡x=0解得f(x)=e−∫tan⁡xdx(∫e∫tan⁡xdxsec⁡xdx+C)=sin⁡x+Ccos⁡x, 代入f(0)=1,得C=1,故f(x)=sin⁡x+cos⁡x\\begin{align}\u0026amp;7.已知可导函数f(x)满足f(x)\\cos x+2\\int_0^xf(t)\\sin t\\mathrm dt=x+1,则f(x)=\\_\\_\\_\\_\\_.\\\\\u0026amp;解:两边求导:-f(x)\\sin x+f\u0026#x27;(x)\\cos x+2f(x)\\sin x=1\\\\\u0026amp;即f\u0026#x27;(x)+\\tan xf(x)-\\sec x=0\\\\\u0026amp;解得f(x)=e^{-\\int \\tan x\\mathrm dx}\\left(\\int e^{\\int \\tan x\\mathrm dx}\\sec x\\mathrm dx+C\\right)\\\\\u0026amp;=\\sin x+C\\cos x,\\ 代入f(0)=1,得C=1,故f(x)=\\sin x+\\cos x\\end{align}​7.已知可导函数f(x)满足f(x)cosx+2∫0x​f(t)sintdt=x+1,则f(x)=_____.解:两边求导:−f(x)sinx+f′(x)cosx+2f(x)sinx=1即f′(x)+tanxf(x)−secx=0解得f(x)=e−∫tanxdx(∫e∫tanxdxsecxdx+C)=sinx+Ccosx, 代入f(0)=1,得C=1,故f(x)=sinx+cosx​​ 8.满足du(t)dt=u(t)+∫01u(t)dt及u(0)=1的可微函数u(t)为____.解:设I=∫01u(t)dt,由u′(t)=u(t)+I,得ln⁡∣u(t)+I∣=t+C即u(t)=Cet−I两边从0到1积分,得I=C(e−1)−I即I=C(e−1)2故u(t)=Cet−C(e−1)2,代入u(0)=1,得C=23−e故u(t)=2et+1−e3−e\\begin{align}\u0026amp;8.满足\\frac{\\mathrm du(t)}{\\mathrm dt}=u(t)+\\int_0^1u(t)\\mathrm dt及u(0)=1的可微函数u(t)为\\_\\_\\_\\_.\\\\\u0026amp;解:设I=\\int_0^1u(t)\\mathrm dt,由u\u0026#x27;(t)=u(t)+I,得\\ln|u(t)+I|=t+C\\\\\u0026amp;即u(t)=Ce^t-I\\\\\u0026amp;两边从0到1积分,得I=C(e-1)-I\\\\\u0026amp;即I=\\frac{C(e-1)}2\\\\\u0026amp;故u(t)=Ce^t-\\frac{C(e-1)}2,代入u(0)=1,得C=\\frac2{3-e}\\\\\u0026amp;故u(t)=\\frac{2e^t+1-e}{3-e}\\end{align}​8.满足dtdu(t)​=u(t)+∫01​u(t)dt及u(0)=1的可微函数u(t)为____.解:设I=∫01​u(t)dt,由u′(t)=u(t)+I,得ln∣u(t)+I∣=t+C即u(t)=Cet−I两边从0到1积分,得I=C(e−1)−I即I=2C(e−1)​故u(t)=Cet−2C(e−1)​,代入u(0)=1,得C=3−e2​故u(t)=3−e2et+1−e​​​ 9.求方程(2x+y−4)dx+(x+y−1)dy=0的通解解:全微分方程:d(x2−4x)+d(y22−y)+d(xy)=0故其通解为x2−4x+y22−y+xy+C\\begin{align}\u0026amp;9.求方程(2x+y-4)\\mathrm dx+(x+y-1)\\mathrm dy=0的通解\\\\\u0026amp;解:全微分方程:\\mathrm d(x^2-4x)+\\mathrm d(\\frac{y^2}2-y)+\\mathrm d(xy)=0\\\\\u0026amp;故其通解为x^2-4x+\\frac{y^2}2-y+xy+C\\end{align}​9.求方程(2x+y−4)dx+(x+y−1)dy=0的通解解:全微分方程:d(x2−4x)+d(2y2​−y)+d(xy)=0故其通解为x2−4x+2y2​−y+xy+C​​ 10.设函数f(x,y)具有一阶连续偏导数,满足df(x,y)=yeydx+x(1+y)eydy及f(0,0)=0,则f(x,y)=解:f(x,y)=yeyx+Q(y)fy′(x,y)=(1+y)eyx+Q′(y)=x(1+y)ey⇒Q′(y)=0⇒Q(y)=C故f(x,y)=xyey+C,代入f(0,0)=0得C=0故f(x,y)=xyey\\begin{align}\u0026amp;10.设函数f(x,y)具有一阶连续偏导数,满足\\mathrm df(x,y)=ye^y\\mathrm dx+x(1+y)e^y\\mathrm dy及f(0,0)=0,则f(x,y)=\\\\\u0026amp;解:f(x,y)=ye^yx+Q(y)\\\\\u0026amp;f_y\u0026#x27;(x,y)=(1+y)e^yx+Q\u0026#x27;(y)=x(1+y)e^y\\Rightarrow Q\u0026#x27;(y)=0\\Rightarrow Q(y)=C\\\\\u0026amp;故f(x,y)=xye^y+C,代入f(0,0)=0得C=0\\\\\u0026amp;故f(x,y)=xye^y\\end{align}​10.设函数f(x,y)具有一阶连续偏导数,满足df(x,y)=yeydx+x(1+y)eydy及f(0,0)=0,则f(x,y)=解:f(x,y)=yeyx+Q(y)fy′​(x,y)=(1+y)eyx+Q′(y)=x(1+y)ey⇒Q′(y)=0⇒Q(y)=C故f(x,y)=xyey+C,代入f(0,0)=0得C=0故f(x,y)=xyey​​ 11.已知du(x,y)=ydx−xdy3x2−2xy+3y2,则u(x,y)=____.解:u(x,y)=∫y3x2−2xy+3y2dx=y∫dx(3x−y3)2+83y2=24arctan⁡(3x−y22y)+Q(y)uy′(x,y)=24⋅−22y−22(3x−y)8y21+9x2−6xy+y28y2+Q′(y)=−x3x2−2xy+3y2解得Q′(y)=0,即Q(y)=C故u(x,y)=24arctan⁡(3x−y22y)++C\\begin{align}\u0026amp;11.已知\\mathrm du(x,y)=\\frac{y\\mathrm dx-x\\mathrm dy}{3x^2-2xy+3y^2},则u(x,y)=\\_\\_\\_\\_.\\\\\u0026amp;解:u(x,y)=\\int\\frac y{3x^2-2xy+3y^2}\\mathrm dx=y\\int\\frac{\\mathrm dx}{(\\sqrt 3x-\\frac{y}{\\sqrt 3})^2+\\frac83y^2}\\\\\u0026amp;=\\frac{\\sqrt2}4\\arctan(\\frac{3x-y}{2\\sqrt2y})+Q(y)\\\\\u0026amp;u_y\u0026#x27;(x,y)=\\frac{\\sqrt2}4·\\frac{\\frac{-2\\sqrt2y-2\\sqrt2(3x-y)}{8y^2}}{1+\\frac{9x^2-6xy+y^2}{8y^2}}+Q\u0026#x27;(y)=\\frac{-x}{3x^2-2xy+3y^2}\\\\\u0026amp;解得Q\u0026#x27;(y)=0,即Q(y)=C\\\\\u0026amp;故u(x,y)=\\frac{\\sqrt2}4\\arctan(\\frac{3x-y}{2\\sqrt2y})++C\\end{align}​11.已知du(x,y)=3x2−2xy+3y2ydx−xdy​,则u(x,y)=____.解:u(x,y)=∫3x2−2xy+3y2y​dx=y∫(3​x−3​y​)2+38​y2dx​=42​​arctan(22​y3x−y​)+Q(y)uy′​(x,y)=42​​⋅1+8y29x2−6xy+y2​8y2−22​y−22​(3x−y)​​+Q′(y)=3x2−2xy+3y2−x​解得Q′(y)=0,即Q(y)=C故u(x,y)=42​​arctan(22​y3x−y​)++C​​ 12.设f(u,v)具有连续偏导数,满足fu(u,v)+fv(u,v)=uv,求y(x)=e−2xf(x,x)所满足的一阶微分方程,并求其通解解:两边对x求导,得y′(x)=e−2x(f1′(x,x)+f2′(x,x)−2f(x,x))=e−2xx2−2y(x)故y=e−∫2dx(∫e∫2dxe−2xx2dx+C)=e−2x(x33+C)\\begin{align}\u0026amp;12.设f(u,v)具有连续偏导数,满足f_u(u,v)+f_v(u,v)=uv,\\\\\u0026amp;求y(x)=e^{-2x}f(x,x)所满足的一阶微分方程,并求其通解\\\\\u0026amp;解:两边对x求导,得y\u0026#x27;(x)=e^{-2x}(f_1\u0026#x27;(x,x)+f\u0026#x27;_2(x,x)-2f(x,x))=e^{-2x}x^2-2y(x)\\\\\u0026amp;故y=e^{-\\int2\\mathrm dx}\\left(\\int e^{\\int 2\\mathrm dx}e^{-2x}x^2\\mathrm dx+C\\right)=e^{-2x}\\left(\\frac{x^3}3+C\\right)\\end{align}​12.设f(u,v)具有连续偏导数,满足fu​(u,v)+fv​(u,v)=uv,求y(x)=e−2xf(x,x)所满足的一阶微分方程,并求其通解解:两边对x求导,得y′(x)=e−2x(f1′​(x,x)+f2′​(x,x)−2f(x,x))=e−2xx2−2y(x)故y=e−∫2dx(∫e∫2dxe−2xx2dx+C)=e−2x(3x3​+C)​​ 13.设f(x)有连续导数,且f(1)=2.记z=f(exy2),若∂z∂x=z,f(x)在x\u0026gt;0的表达式为____.解:zx=f′(exy2)⋅y2ex=f(exy2),解得ln⁡∣f(exy2)∣=ln⁡∣exy2∣+C0即f(exy2)=Cexy2,也即f(x)=Cx代入f(1)=2,得C=2,即f(x)=2x当x\u0026gt;0时,exy2\u0026gt;y2≥0,则f(x)在x\u0026gt;0的表达式为f(x)=2x,x\u0026gt;0\\begin{align}\u0026amp;13.设f(x)有连续导数,且f(1)=2.记z=f(e^xy^2),若\\frac{\\part z}{\\part x}=z,f(x)在x\u0026gt;0的表达式为\\_\\_\\_\\_.\\\\\u0026amp;解:z_x=f\u0026#x27;(e^xy^2)·y^2e^x=f(e^xy^2),解得\\ln|f(e^xy^2)|=\\ln|e^xy^2|+C_0\\\\\u0026amp;即f(e^xy^2)=Ce^xy^2,也即f(x)=Cx\\\\\u0026amp;代入f(1)=2,得C=2,即f(x)=2x\\\\\u0026amp;当x\u0026gt;0时,e^xy^2\u0026gt;y^2\\ge0,则f(x)在x\u0026gt;0的表达式为f(x)=2x,x\u0026gt;0\\end{align}​13.设f(x)有连续导数,且f(1)=2.记z=f(exy2),若∂x∂z​=z,f(x)在x\u0026gt;0的表达式为____.解:zx​=f′(exy2)⋅y2ex=f(exy2),解得ln∣f(exy2)∣=ln∣exy2∣+C0​即f(exy2)=Cexy2,也即f(x)=Cx代入f(1)=2,得C=2,即f(x)=2x当x\u0026gt;0时,exy2\u0026gt;y2≥0,则f(x)在x\u0026gt;0的表达式为f(x)=2x,x\u0026gt;0​​ 14.设可微函数f(x,y)满足∂f∂x=−f(x,y),f(0,π2)=1,且\\begin{align}\u0026amp;14.设可微函数f(x,y)满足\\frac{\\part f}{\\part x}=-f(x,y),f\\left(0,\\frac\\pi2\\right)=1,且\\end{align}​14.设可微函数f(x,y)满足∂x∂f​=−f(x,y),f(0,2π​)=1,且​​ lim⁡n→∞(f(0,y+1n)f(0,y))n=ecot⁡y \\begin{align}\\lim_{n\\rightarrow \\infty}\\left(\\frac{f\\left(0,y+{\\Large\\frac1n}\\right)}{f(0,y)}\\right)^n=e^{\\cot y}\\end{align} n→∞lim​​f(0,y)f(0,y+n1​)​​n=ecoty​​ 则f(x,y)=____.解:由该极限可得lim⁡n→∞enln⁡f(0,y+1n)f(0,y)=ecot⁡y即lim⁡n→∞(f(0,y+1n)−f(0,y))nf(0,y)=cot⁡y即fy′(0,y)f(0,y)=cot⁡y,解得f(0,y)=Csin⁡y,代入f(0,π2)=1,得C=1即f(0,y)=sin⁡y设f(x,y)=P(x)sin⁡y+Q(x),则P(0)=1, Q(0)=0,fx′(x,y)=−f(x,y)⇒−P(x)sin⁡y−Q(x)=P′(x)sin⁡y+Q′(x)\\begin{align}\u0026amp;则f(x,y)=\\_\\_\\_\\_.\\\\\u0026amp;解:由该极限可得\\lim_{n\\rightarrow\\infty}e^{n\\ln\\large\\frac{f\\left(0,y+\\frac1n\\right)}{f(0,y)}}=e^{\\cot y}\\\\\u0026amp;即\\lim_{n\\rightarrow\\infty}\\frac{(f(0,y+\\frac1n)-f(0,y))n}{f(0,y)}=\\cot y\\\\\u0026amp;即\\frac{f_y\u0026#x27;(0,y)}{f(0,y)}=\\cot y,解得f(0,y)=C\\sin y,代入f(0,\\frac\\pi2)=1,得C=1\\\\\u0026amp;即f(0,y)=\\sin y\\\\\u0026amp;设f(x,y)=P(x)\\sin y+Q(x),则P(0)=1,\\ Q(0)=0,\\\\\u0026amp;f_x\u0026#x27;(x,y)=-f(x,y)\\Rightarrow-P(x)\\sin y-Q(x)=P\u0026#x27;(x)\\sin y+Q\u0026#x27;(x)\\end{align}​则f(x,y)=____.解:由该极限可得n→∞lim​enlnf(0,y)f(0,y+n1​)​=ecoty即n→∞lim​f(0,y)(f(0,y+n1​)−f(0,y))n​=coty即f(0,y)fy′​(0,y)​=coty,解得f(0,y)=Csiny,代入f(0,2π​)=1,得C=1即f(0,y)=siny设f(x,y)=P(x)siny+Q(x),则P(0)=1, Q(0)=0,fx′​(x,y)=−f(x,y)⇒−P(x)siny−Q(x)=P′(x)siny+Q′(x)​​解得P(x)=e−x,Q(x)=0即f(x,y)=e−xsin⁡y\\begin{align}\u0026amp;解得P(x)=e^{-x},Q(x)=0\\\\\u0026amp;即f(x,y)=e^{-x}\\sin y\\end{align}​解得P(x)=e−x,Q(x)=0即f(x,y)=e−xsiny​​ 15.设函数f(x)的导数f′(x)在[0,1]上连续,f(0)=f(1)=0,且满足\\begin{align}\u0026amp;15.设函数f(x)的导数f\u0026#x27;(x)在[0,1]上连续,f(0)=f(1)=0,且满足\\end{align}​15.设函数f(x)的导数f′(x)在[0,1]上连续,f(0)=f(1)=0,且满足​​ ∫01[f′(x)]2dx−8∫01f(x)dx+43=0 \\int_0^1\\left[f\u0026#x27;(x)\\right]^2\\mathrm dx-8\\int_0^1f(x)\\mathrm dx+\\frac43=0 ∫01​[f′(x)]2dx−8∫01​f(x)dx+34​=0 则f(x)=____.解:∫01f(x)dx=xf(x)∣01−∫xf′(x)dx故该等式可化为∫01[[f′(x)]2+8xf′(x)]dx+43=0即∫01[[f′(x)]2+8xf′(x)+43]dx=0若[f′(x)]2+8xf′(x)+43=[F(x)]2≥0,则由该积分可得F(x)=0待定系数法设F(x)=f′(x)+4x+A,其中A为常数则有A2+8A∫01xdx+2A∫01f′(x)dx=43−16∫01x2dx即A2+4A=−4,解得A=−2得F(x)=f′(x)+4x−2=0解得f(x)=−2x2+2x+C,由f(0)=f(1)=0,得C=0故f(x)=−2x2+2x\\begin{align}\u0026amp;则f(x)=\\_\\_\\_\\_.\\\\\u0026amp;解:\\int_0^1f(x)\\mathrm dx=xf(x)\\Bigg|_0^1-\\int xf\u0026#x27;(x)\\mathrm dx\\\\\u0026amp;故该等式可化为\\int_0^1[[f\u0026#x27;(x)]^2+8 xf\u0026#x27;(x)]\\mathrm dx+\\frac43=0\\\\\u0026amp;即\\int_0^1[[f\u0026#x27;(x)]^2+8xf\u0026#x27;(x)+\\frac43]\\mathrm dx=0\\\\\u0026amp;若[f\u0026#x27;(x)]^2+8xf\u0026#x27;(x)+\\frac43=[F(x)]^2\\ge0,则由该积分可得F(x)=0\\\\\u0026amp;待定系数法设F(x)=f\u0026#x27;(x)+4x+A,其中A为常数\\\\\u0026amp;则有A^2+8A\\int_0^1x\\mathrm dx+2A\\int_0^1f\u0026#x27;(x)\\mathrm dx=\\frac43-16\\int_0^1 x^2\\mathrm dx\\\\\u0026amp;即A^2+4A=-4,解得A=-2\\\\\u0026amp;得F(x)=f\u0026#x27;(x)+4x-2=0\\\\\u0026amp;解得f(x)=-2x^2+2x+C,由f(0)=f(1)=0,得C=0\\\\\u0026amp;故f(x)=-2x^2+2x\\end{align}​则f(x)=____.解:∫01​f(x)dx=xf(x)​01​−∫xf′(x)dx故该等式可化为∫01​[[f′(x)]2+8xf′(x)]dx+34​=0即∫01​[[f′(x)]2+8xf′(x)+34​]dx=0若[f′(x)]2+8xf′(x)+34​=[F(x)]2≥0,则由该积分可得F(x)=0待定系数法设F(x)=f′(x)+4x+A,其中A为常数则有A2+8A∫01​xdx+2A∫01​f′(x)dx=34​−16∫01​x2dx即A2+4A=−4,解得A=−2得F(x)=f′(x)+4x−2=0解得f(x)=−2x2+2x+C,由f(0)=f(1)=0,得C=0故f(x)=−2x2+2x​​ 16.设f(x)在[0,+∞)上是有界连续函数,证明:方程y′′+14y′+13y=f(x)的每一个解在[0,+∞)上都是有界函数解:由y′′+14y′+13y=f(x)⇒λ1=−13,λ2=−1y′+13y=e−x(∫exf(x)dx+C1),解得y=e−13x(∫e12x(∫exf(x)dx+C1)+C2)由f(x)在[0,+∞)上是有界连续函数,故exf(x)可积,设m≤f(x)≤M则mex+C1≤∫exf(x)dx+C1≤Mex+C1则m13e13x+C112e12x+C2≤∫e12x(∫exf(x)dx+C1)+C2≤M13e13x+C112e12x+C2即m13+C112e−x+C2e−13x≤y≤M13+C112e−x+C2e−13x其中0\u0026lt;e−13x\u0026lt;e−x≤1,(x∈[0,+∞))即对y的通解,它在[0,+∞)上都是有界函数\\begin{align}\u0026amp;16.设f(x)在[0,+\\infty)上是有界连续函数,证明:方程y\u0026#x27;\u0026#x27;+14y\u0026#x27;+13y=f(x)的\\\\\u0026amp;每一个解在[0,+\\infty)上都是有界函数\\\\\u0026amp;解:由y\u0026#x27;\u0026#x27;+14y\u0026#x27;+13y=f(x)\\Rightarrow\\lambda_1=-13,\\lambda_2=-1\\\\\u0026amp;y\u0026#x27;+13y=e^{-x}\\left(\\int e^xf(x)\\mathrm dx+C_1\\right),\\\\\u0026amp;解得y=e^{-13 x}\\left(\\int e^{12x}\\left(\\int e^xf(x)\\mathrm dx+C_1\\right)+C_2\\right)\\\\\u0026amp;由f(x)在[0,+\\infty)上是有界连续函数,故e^xf(x)可积,设m\\le f(x)\\le M\\\\\u0026amp;则me^x+C_1\\le \\int e^xf(x)\\mathrm dx+C_1\\le Me^x+C_1\\\\\u0026amp;则\\frac m{13}e^{13x}+\\frac{C_1}{12}e^{12x}+C_2\\le \\int e^{12x}\\left(\\int e^xf(x)\\mathrm dx+C_1\\right)+C_2\\le \\frac M{13}e^{13x}+\\frac{C_1}{12}e^{12x}+C_2\\\\\u0026amp;即\\frac m{13}+\\frac{C_1}{12}e^{-x}+C_2e^{-13x}\\le y\\le \\frac M{13}+\\frac{C_1}{12}e^{-x}+C_2e^{-13x}\\\\\u0026amp;其中0\u0026lt;e^{-13x}\u0026lt;e^{-x}\\le 1,(x\\in[0,+\\infty))\\\\\u0026amp;即对y的通解,它在[0,+\\infty)上都是有界函数\\end{align}​16.设f(x)在[0,+∞)上是有界连续函数,证明:方程y′′+14y′+13y=f(x)的每一个解在[0,+∞)上都是有界函数解:由y′′+14y′+13y=f(x)⇒λ1​=−13,λ2​=−1y′+13y=e−x(∫exf(x)dx+C1​),解得y=e−13x(∫e12x(∫exf(x)dx+C1​)+C2​)由f(x)在[0,+∞)上是有界连续函数,故exf(x)可积,设m≤f(x)≤M则mex+C1​≤∫exf(x)dx+C1​≤Mex+C1​则13m​e13x+12C1​​e12x+C2​≤∫e12x(∫exf(x)dx+C1​)+C2​≤13M​e13x+12C1​​e12x+C2​即13m​+12C1​​e−x+C2​e−13x≤y≤13M​+12C1​​e−x+C2​e−13x其中0\u0026lt;e−13x\u0026lt;e−x≤1,(x∈[0,+∞))即对y的通解,它在[0,+∞)上都是有界函数​​ 13.\\begin{align}\u0026amp;13.\\end{align}​13.​​13.\\begin{align}\u0026amp;13.\\end{align}​13.​​13.\\begin{align}\u0026amp;13.\\end{align}​13.​​f(x,y)=(y+1)2−(2−x)ln⁡x,求由曲线f=0绕y=−1旋转而成的几何体的体积\\begin{align}\u0026amp;f(x,y)=(y+1)^2-(2-x)\\ln x,求由曲线f=0绕y=-1旋转而成的几何体的体积\\end{align}​f(x,y)=(y+1)2−(2−x)lnx,求由曲线f=0绕y=−1旋转而成的几何体的体积​​","permalink":"https://echo.ishian.me/2023/12/11/calculus-exercise-1/","summary":"微积分极限与积分的计算练习题集，附详细求解过程","title":"微积分: 练习题"},{"content":"JavaScript语法 简介 JS实现了html文档与用户的更丰富的交互，实现在客户端处理部分信息而不是每次交互都向服务器报告。本身的一套标准为ECMAScript，除此以外还和DOM、BOM紧密相连，是这三个部分实现了JS的功能\nDOM和BOM都是API(可以理解为一个封装好的类)，分别定义了对**html文档**、对浏览器的接口和方法，在今后将会涉及，在这里着重讲述语法\nJS的设计有许多C++及类C++语言的影子，理解起来不难\n与HTML的接口 \u0026lt;script\u0026gt; 想在HTML文档中运行js脚本，只需要添加\u0026lt;script\u0026gt;\u0026lt;/script\u0026gt;元素，虽然可以直接在元素内添加行内脚本，但更好的做法是引用外部文件，好处有：\n方便管理、修改js代码 一份多用，方便浏览器缓存使用 也有坏处，如果引用了其它域的文件，则有可能被传入恶意脚本；所以引用外部文件应该确保安全可信\n\u0026lt;script\u0026gt;元素含有诸多属性：\nsrc：类似\u0026lt;img\u0026gt;的src，用于指示外部文件来源，可配合\u0026lt;base\u0026gt;使用相对地址；需要注意如果一条\u0026lt;script\u0026gt;元素既有src又有内部代码，那么浏览器会忽视内部，只执行外部文件 integrity：接收资源时先对比数字签名，确保其来源可信，是防范恶意脚本的武器 async：立即下载这条元素指示的外部文件，但不影响其它页面或脚本的加载，即异步脚本；异步脚本脱离了执行顺序，所以不应该在里面进行DOM操作，以免导致异常 defer：立即下载这条元素指示的外部文件，但可被推迟到所有其它页面或脚本加载完再执行；这样的脚本没有脱离顺序而只是延后执行了 js本身并不包含DOM等，所以单独运行访问window等对象的程序是有错误的，只有在html文档中才可以正常运行\n脚本位置 以前，脚本都放在\u0026lt;head\u0026gt;内，以集中js和css的外部文件；但这将使浏览器在加载完所有脚本后才开始渲染\u0026lt;body\u0026gt;的内容，所以现在都选择将\u0026lt;script\u0026gt;部分放在\u0026lt;body\u0026gt;最后\n数据类型 基本数据类型 js的数据类型管理是松散的，它的关键字不决定数据类型而是作用域等；有六种基本数据类型是原始类型，其中五种类型含有字面量(一个常量)：\nNumber：数值类型，字面量包括全体数字、NaN、Infinity与-Infinity String：字符串类型，字面量包括所有字符串 Boolean：布尔值类型，字面量有true与false Undefined：未定义类型，字面量只有undefined，没有相应函数 Null：空类型，字面量只有null，它是一个空指针，所以用typeof null返回的是object类型，没有相应函数 剩下的一个类型为Symbol(符号类型)，它没有字面量语法，要用特殊的函数来声明；设计如此特殊的Symbol是为了提供一个全局唯一的标识符，以避免属性键冲突问题\n原始值与引用值 以上六种数据类型都是原始值，其对象没有属性和方法，且互相赋值时进行的操作是完全复制；这些类型和Object类型可以作为Object类型对象的属性，对象类型的对象实质上是引用值，互相赋值时进行的操作是起别名，而不是创建副本\n要创建一个原始值，可以直接使用字面量语法，或是调用相应类型的函数：\njavascriptlet val_1 = val; // 使用字面量,自动分析val的类型. let val_2 = Number(val); // 使用字面量,自动将val转换成指定的类型. let val_3 = Symbol(); // Symbol类型没有字面量. 要创建一个对象类型的值，可以使用字面量，也可以使用new加上一个类型，这相当于一个构造函数：\njavascriptlet obj = {}; // 使用字面量,创建空对象. let obj_1 = Object(val); // 创建对应类型的对象,自动分析val的类型. let obj_2 = new Number(val); // 创建对应类型的对象,自动将val转换成指定类型. let obj_3 = new Object(5); // 与obj_3 = Object(5);等价. let obj_copy = Object(obj_3); // 与obj_copy = obj_3;等价. 如果提供了指定的类型(obj_2)，或是使用Object()时提供的是原始值(obj_1,obj_3)，那么这种方式得到的对象称为包装对象，可以简单理解为被允许含有属性和方法的原始值，在之后会提到\n创建好一个对象之后，可以随时为它添加属性，也可以在初始化时赋予：\njavascriptlet obj = { // 初始化时添加属性. key: val, // 键值对形式-[属性名: 属性值]. key: val, ... }; obj.key = val; // 之后添加属性. obj.array = [1,\u0026#39;2\u0026#39;,3]; // 添加数组属性. 一个数组也是Object类型的，它是一种特殊的对象，其中的成员都是属性，每个属性都有一个索引(即下标，从0开始)\nSymbol Symbol类型的值不能通过字面量声明，而且，虽然它可以作为属性，但不能是包装对象：\njavascriptlet sym_0 = Symbol(); // 声明一个唯一的标识符. let sym_1 = Symbol(\u0026#39;1\u0026#39;); // 声明一个唯一的标识符,\u0026#39;1\u0026#39;为它的解释. let sym_2 = Symbol(\u0026#39;1\u0026#39;); // 声明一个唯一的标识符,\u0026#39;1\u0026#39;为它的解释,且sym_1 != sym_2. let sym_error = new Symbol(); // 错误声明. let sym_copy = sym_0; // 则sym_copy == sym_0. 括号内的字符串(如果不是则调用String())只是一个解释，不影响这个值的唯一性；所以就算两个值使用同样的解释，它们也是不一样的\n如果想重复用同一个Symbol，除了直接赋值，还可以用注册表，能更好地管理符号：\njavascriptlet sym_1 = Symbol.for(\u0026#39;key\u0026#39;); // 注册一个为\u0026#39;key\u0026#39;的键,同时它也作为符号的解释. let sym_2 = Symbol.for(\u0026#39;key\u0026#39;); // 先寻找,发现\u0026#39;key\u0026#39;已存在,则变成sym_2 = sym_1;. 同样，虽然使用相同的解释，由注册表创建的符号和一般形式创建的符号是不同的：\njavascriptlet sym_1=Symbol.for(\u0026#39;k\u0026#39;), sym_2=Symbol.for(\u0026#39;k\u0026#39;), sym_3=Symbol(\u0026#39;k\u0026#39;); sym_1 === sym_2; // true. sym_1 === sym_3; // false. 数据间的转换 数据类型管理松散虽然方便，但也因此带来一些麻烦，为此需要熟悉数据间的转换规则，可以调用转类型函数：\nNumber()： 以下为合法数值：如果是小数，则必须含'.'，且小数点前后至少存在一边；如果是假值(例如Infinity)，则返回假值；如果以0开头，则为八进制数；如果以0x或0X开头，则为十六进制数 对字符串，如果它只是合法数值加上了引号(空字符串为0)，则原封不动变成数值，注意它检测不出八进制数，因为它会忽略开头的0；否则返回NaN 对布尔值，true返回1，false返回0 对undefined，返回NaN 对null，返回0 对对象，如果是包装对象、或是只含一个元素的数组，则以上述规则转换；如果是空对象，则返回NaN 不能将符号类型转换成数值 parseInt()/parseFloat()：如果只想将字符串转换成整数或小数，可以选择它们，它们会取字符串的第一个数值(直到遇到非整数/小数字符时停止，如果没有则为NaN)。它与Number()逻辑上有些不同，例如空字符串返回NaN而不是0，而\u0026quot;12abc\u0026quot;返回12而不是NaN。parseInt()还接受第二个参数，以确定数值的进制(默认为自动识别) String()： 所有字符串都是合法的，可以用''、\u0026quot;\u0026quot;、``(反引号)包围，用'\\'转义，但只有用反引号包围的字符串能够解析插值符 对所有假值和数值，一般是原封不动；如果数值是十六进制或八进制，则先转换成十进制 对符号类型，返回Symbol(description) 对对象，如果是包装对象或数组，一般原封不动；其它会返回\u0026quot;[object Object]\u0026quot; String.raw`` ：将反引号包围的字符串视作原始字符串，不会解析转义字符、插值等 Boolean()： 对数值，非0或Infinity等假值返回true，0或NaN返回false 对字符串，非空返回true，否则返回false 对对象和符号类型，都返回true 对未定义与null，返回false Symbol()：调用String() undefined与null：只能转换成其它类型，不能由其它类型转换而来 Object()：将其它类型转换成对象 除未定义与空类型都有toString()方法，相当于调用String()\n引用类型 介绍 由基本数据类型可组成对象类型，将这个设计好的对象类型称为一个引用类型；类似面向对象中的类(但js中的引用类型不具备类的许多特征，是不完整的类)\n创建一个引用类型与数据类型相似，不带new则是创建一个与基本数据类型对应的值(隐式调用这个引用类型的类型转换方法)，带new则是使用构造函数，创建一个对象类型的引用值，一个新实例\n基本引用类型 标准提供了一些自带的引用类型，例如之前所说的五种含有构造函数的基本数据类型：\nDate：时间类型，继承String，重写toString()(返回一个字符串，表示所在时区、时间)和valueOf()(返回一个数值，表示距离UTC:1970.1.1.00:00:00的毫秒) RegExp：正则表达式类型，字面量由两个\u0026rsquo;/\u0026lsquo;夹住的匹配文本、和跟在后面的标记字母组成。例如/abc/g匹配整个字符串的abc，标记可以一起使用，标记有： g：查找全部而非第一个字符串 i：不区分大小写 m：不因达到行末而停止 Boolean,Number,String：之前提到的包装对象就是用这些引用类型创建的，原始值没有属性和方法，但为了让原始值有着对象的行为，js保留了这些引用类型。当通过原始值调用方法时，系统会调用这些引用类型的构造函数和方法(这个临时对象生存周期在句内，这是不能给一个原始值添加属性或方法的原因)。不建议显式创建包装对象 Error,ReferenceError等：用于处理错误的类型，例如访问未声明对象会抛出ReferenceError 一些内置对象 不详细展开：\nGlobal：它无法被访问，所有在全局范围内定义的对象、函数都将成为它的成员，包括标准已经定义的parseInt()等。它还有其它的重要的成员函数： encodeURIComponent()：编码URI，方便传递给浏览器。与encodeURI()(编码所有非标准字符)相比，它会编码所有非字母字符，更常用 decodeURIComponent()：解码URI，替换所有特殊字符 eval()：解释提供的字符串，相当于运行所提供的字符串，方便处理用户输入。使用它应慎重，因为会遭到恶意攻击 window：它是Window引用类型的一个内置实例，是DOM展示出来的接口，被视为Global的代理 Math：存放数学常量和方法的对象，含有E,PI等常量，max(),min(),ceil(),floor(),random()等方法 集合引用类型 Array：数组，它的每个槽位可以放不同数据类型的值，构造一个Array有多种方法：\njavascriptlet arr = Array(Number); // 构造含Number个元素的数组. let arr2 = Array(...); // 构造内容为...的数组. let arr3 = new Array(); // 与let arr3 = Array();等价. Map：映射，专门用于存储多个键值对，虽然很多方法可以由Object实现，但也有不同：它可以用任何数据类型作为键，且会维护顺序方便迭代\nWeakMap：弱映射，与Map的区别在于它的键的数据类型只能是Object，它的弱在于存储的键是弱实例，不属于正式的引用，不会阻止垃圾回收\nSet：数据的集合，在功能上像是加强的Map，也支持顺序迭代\nWeakSet：弱集合，与Set的区别在于它的数据只能是Object类型的，同样不会阻止垃圾回收\n运算符 不同类型的数据可以相互转换，还存在一些假值，这使运算符较为琐碎，也因此要注意表达式的运算顺序：\nSymbol类型不能参与运算；双目运算中只有一边是对象类型时，将调用valueOf()方法 单目!：调用Boolean()，再取反；两次!相当于一次Boolean() 单目+/-：对数字和Infinity则是变为正/负值(0也分正负，但+0 === -0)，NaN则还是NaN；字符串类型会先调用Number()；其它类型会先调用String()，再调用Number() 双目+/-： 两边均为数值：存在一边为NaN，则为NaN；无穷减无穷(或负无穷加无穷)也为NaN；无穷加无穷为无穷；具体数和无穷相加减得到这个无穷 其它情况相加：都调用String()，然后拼接 其它情况相减：都调用Number()，然后运算 双目*： 两边均为数值：存在一边为NaN，则为NaN；无穷与其它值相乘还是无穷，同号正异号负 只有数值和字符串：先调用Number()，再相乘 其它情况：先调用String()，再调用Number() 双目/： 两边均为数值：一般数字都视作小数运算；如果存在一边为NaN、或0/0、无穷除无穷，则为NaN；非0除以0得无穷；非无穷除以无穷得0 只有数值和字符串：先调用Number()，再相除 其它情况：先调用String()，再调用Number() 双目**：类似python，求幂，与数学逻辑类似；1的无穷为NaN 比较操作符： 两边均为数值：任一为NaN时，小、大、等于均返回false(包括NaN==NaN)；Infinity等于Infinity；null等于undefined 两边均为字符串：逐个比较字符编码 其中一边为数值或布尔值：将两边转换成数值 两边都是对象：查看是否指向同一对象 全等(===)、不全等(!==)：上面的比较操作符比较反直觉，而这两个运算符类似强类型比较，它们不会转换两边的类型；对全等来说，只有在两边类型、值完全一致时才会返回true 三目运算符：exp ? T_val : F_val 关键字 定义变量 关键字并不指定变量的数据类型，而是指定其作用域：\njavascriptval_0; // 全局变量,第一次声明时必须初始化. var val_1; // 作用域为函数内. let val_2; // 作用域为块内. const val_3; // 声明不变量,声明时必须初始化,不能再改变;作用域为块内. 使用var时，声明语句会提升至函数头部，且只保留一次声明：\njavascriptvar val = 1; if (1) { var val = 3; } // 相当于: var val; val = 1; if (1) { val = 3; }所以同名的var变量在函数体内，即使被块分隔，却始终是一个变量；为了防止不经意的重名，不推荐使用var\n而let和const则更讨喜，它们的声明语句不会提升，且作用域为块内，所以块内外的量是不同的；但因为不会提升也不会合并，所以不能在一个块内用let和const声明同名变量；由于一切对象都以引用值传递，所以优先考虑使用const，其次为let，最后是var\njavascriptlet a; if (1) { let a; // 和块外的不冲突,是不同的变量.与C一样,块内优先. let a; // 和上一条冲突. }一个量可以通过插值符顺畅地在字符串中解析：\njavascriptlet a = 1, b = 2; `a + b = ${a + b}` // a + b = 3 \u0026#34;a + b = ${a + b}\u0026#34; // a + b = ${a + b} 对于全局变量，如果已确保不再使用，可以赋null，这有利于浏览器进行内存回收\n循环语句 除了C的if、while、do-while，还提供了几种语句：\nfor (variable in object)(for-in)：遍历对象的非符号键属性，在每一轮循环中赋值给variable，顺序未知 for (variable of iterable)(for-of)：遍历可迭代对象(例如字符串、数组)的元素，在每一轮循环中赋值给variable，以迭代顺序输出 for await (variable of iterable)(for-await-of)：for-of的扩展，用于遍历异步可迭代对象 labelName : 循环：给循环加标签，结合continue与break在嵌套循环中使用 with(object)：创建一个属于object的块，在块内使用变量时首先检查是否已声明，若无则检查是否为这个object的成员方法。因此块内调用这个object的方法时有时不用显式写出它，但它影响性能、难以调试维护，在严格模式下禁用也不推荐使用 实际上每轮循环中的variable都是临时创建并初始化的，在一轮结束后会丢弃。关键字let,const都可使用，只是在块内会有区别，多次强调的是应该多用const！\nJs的switch语句在C的基础上，可以对任何数据类型进行switch，case后也可以接表达式或变量；在比较时使用全等，不会进行类型转换\n定义函数 只需要关键字function，就可以定义一个函数，在不接参数的return后，跳出函数并返回undefined\n函数对返回类型管理不严格，但不建议将返回undefined和返回实际值混合使用\n迭代器 迭代器让使用者不需要事先得知其数据结构就可以顺序访问其值，迭代器关注的只有\u0026quot;如何访问下一个值\u0026quot;这个问题。可迭代对象(iterable)通常是集合引用类型，即包含元素有限、含有顺序，具体是看是否实现了[Symbol.iterator]()方法，它会返回一个可迭代对象，调用第一次next()后它将指向第一个元素\n标准原生的可迭代对象拥有如下方法：\nnext()：无论是否存在下一元素，都会顺序迭代一次，返回一个iteratorResult对象 iteratorResult对象必然含有两个属性：\ndone：判断现在的iterable是否可以继续调用next() value：如果可以调用，则输出现在iterable指向的值 ","permalink":"https://echo.ishian.me/2023/12/09/javascript-beginner/","summary":"介绍 JavaScript 基本语法、数据类型、运算符与 HTML 接口","title":"JavaScript: 基本使用"},{"content":"VS Code 基本功能介绍 VS Code是微软开发的开源的代码编辑器，支持Windows、MacOS、Linux等操作系统 其最显著的优点是轻量且快速，且得益于活跃的插件生态而支持多种编程语言 高度灵活的自定义配置也是其吸引我的一点 左侧一栏为快捷的插件打开方式，包括资源管理器、项目管理器、扩展市场等 资源管理器：与其他IDE类似，VS Code将一个项目当作一个工作区来管理，提供的快捷方式包括打开文件夹等 其它更多有关文件管理的功能在顶栏的“文件”中 项目管理器是图标为分叉的一项，它允许将项目绑定一个版本控制管理器，例如Git或SVN，同时也支持绑定Github或其它远程仓库托管网站的账号 扩展市场：这是体现VS Code丰富生态的地方 顶上一个搜索栏可以最重要的作用是通过键入一个\u0026gt;，以快速搜索并调用插件的功能，之前所说的功能均可由搜索栏搜索得到 以上UI的布局均可随意拖动，以及可以在右上方调整布局 配置：VS Code将配置分为三级，默认配置、用户配置、工作区配置，默认配置存储于defaultSettings.json中，另外两者存储在同名文件settings.json中，所有均在**首选项:打开XX配置(JSON)**中，VS Code提供了UI界面帮助修改，但建议直接编辑json文件 默认配置：VS Code本身及其插件会提供一系列的配置项，插件维护者需要为每一个配置项提供默认值，用户下载该插件后则会登记在默认配置中 该文件是只读文件，无需在意 用户配置：用户配置是用户自定义的全局的配置，存放在本机用户家目录下，是最常改的配置文件，在用户定义后，它会覆盖默认配置，否则使用默认配置 工作区配置：存放在工作文件夹下自动生成的./vscode/settings.json，它会覆盖所有上级配置，未定义的配置项使用用户配置 基本的配置 编辑器的基本配置(仅包括影响较大的可设置为非默认值的配置)：\n区分内联建议与快速建议：快速建议指的是由字典服务提供的键入值后在光标右侧显示的提示，内联建议指由内置AI智能生成的代码补全 不建议使用内联建议\n不在提交字符时接受建议(建议即快速提示)：\njson\u0026#34;editor.acceptSuggestionOnCommitCharacter\u0026#34;: false 不在按下Enter时接受建议：\njson\u0026#34;editor.acceptSuggestionOnEnter\u0026#34;: \u0026#34;off\u0026#34; 设置在保存代码时进行的动作：\njson\u0026#34;editor.codeActionsOnSave\u0026#34;: { // 在显示保存时自动import所需包 \u0026#34;source.organizeImports\u0026#34;: \u0026#34;explicit\u0026#34;, // 有其它source动作 } 设置打开文件时不检测tabSize与insertSpaces，即编辑器无法改变预设tab的空格数以及预设的是否用空格代替tab\njson\u0026#34;editor.detectIndentation\u0026#34;: false 设置保存文件时自动格式化(格式化程序需可用，通常分别设置格式化程序而在全局设置一次以下属性)：\njson\u0026#34;editor.formatOnSave\u0026#34;: true 设置键入Tab键时写入\\t而不是空格：\njson\u0026#34;editor.insertSpaces\u0026#34;: false此外，如果要设置一个\\t的空格数以及一个缩进的空格数，可以设置editor.tabSize以及editor.indentSize\n设置接受快速建议时，替换右侧的代码：\njson\u0026#34;editor.suggest.insertMode\u0026#34;: \u0026#34;replace\u0026#34; 设置在宽度不足时换行显示：\njson\u0026#34;editor.wordWrap\u0026#34;: \u0026#34;on\u0026#34; 通过设置\u0026quot;[xxx]\u0026quot;字典，可以设置专门适用于某种文件的配置，例如：\njson\u0026#34;[java]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;redhat.java\u0026#34; }很常用的是对不同的文件配置不同的默认格式化程序\n推荐插件 中文与颜色主题 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code：简体中文插件 设置颜色主题：在左下齿轮图标中设置主题\u0026gt;颜色主题，或通过搜索栏搜索\u0026gt;首选项: 颜色主题，第一项可以搜索查找本机没有的主体并自动下载对应的插件 推荐的主题(浅色党)，安装对应的浅色主题插件后会有对应的暗色主题 Default Light Modern：VS Code的内置浅色主题 Github Light：类Github风格 Monokai Pro Light：偏灰色主题 Falcon Light：比较护眼的主题，有青、绿、灰、粉、黄色 Trae Light、Pure White、Solarized Light也是不错的主题 其它主题可在https://vscodethemes.com/快速预览 Markdown Markdown PDF：支持导出为PDF、HTML、图片、json等\nmarkdownlint：语法高亮与风格检测，同时提供更好用的格式化程序，通过以下配置来设置格式化程序、忽略部分警告：\njson\u0026#34;[markdown]\u0026#34;: { \u0026#34;editor.defaultFormatter\u0026#34;: \u0026#34;DavidAnson.vscode-markdownlint\u0026#34;, }, \u0026#34;markdownlint.config\u0026#34;: { // 警告与错误提示 \u0026#34;default\u0026#34;: true, \u0026#34;MD033\u0026#34;: false, } Markdown Preview Enhanced：提供更好的预览，通过以下配置来支持行内LaTeX公式渲染：\njson\u0026#34;markdown-preview-enhanced.mathRenderingOption\u0026#34;: \u0026#34;MathJax\u0026#34; Markdown Preview Mermaid Support：提供mermaid代码块的渲染\nGitHub Markdown Preview：使用Github的预览风格\nC/C++ C/C++：\nC/C++ Runner：支持运行C/Cpp代码，包含以下配置：\njson// 额外包含的路径 \u0026#34;C_Cpp_Runner.includePaths\u0026#34;: [ ], // C/Cpp标准 \u0026#34;C_Cpp_Runner.cStandard\u0026#34;: \u0026#34;c17\u0026#34; \u0026#34;C_Cpp_Runner.cppStandard\u0026#34;: \u0026#34;c++17\u0026#34; clangd\nCMake与CMake Tools\nJava Extension Pack for Java ?Java Language Support Spring Boot Extension Pack 远程连接 Remote Development 数据库 Database Client Vim Vim settings.json代替了vimrc配置： 配置插入模式的快捷键，建议绑定esc\njson\u0026#34;vim.insertModeKeyBindings\u0026#34;: [ { \u0026#34;before\u0026#34;: [ \u0026#34;j\u0026#34;, \u0026#34;j\u0026#34; ], \u0026#34;after\u0026#34;: [ \u0026#34;\u0026lt;esc\u0026gt;\u0026#34; ] } ]在插入模式下键入两次j等价于键入esc\n配置在正常模式下的快捷键(例如将保存文件设置为\u0026lt;leader\u0026gt;+s)：\njson\u0026#34;vim.normalModeKeyBindingsNonRecursive\u0026#34;: [ { \u0026#34;before\u0026#34;: [ \u0026#34;\u0026lt;leader\u0026gt;\u0026#34;, \u0026#34;s\u0026#34; ], \u0026#34;commands\u0026#34;: [ \u0026#34;:w!\u0026#34; ] }, { \u0026#34;before\u0026#34;: [ \u0026#34;\u0026lt;leader\u0026gt;\u0026#34;, \u0026#34;q\u0026#34; ], \u0026#34;commands\u0026#34;: [ \u0026#34;:q!\u0026#34; ] } ] 配置leader键为空格(默认为\\)：\njson\u0026#34;vim.leader\u0026#34;: \u0026#34;\u0026lt;space\u0026gt;\u0026#34;, LaTeX LaTeX Vue Vue (Official) Vetur Python Python Extension Pack Black Formatter Flake8 Jupyter Rust 其它插件 GitHub Pull Requests：支持在VS Code中提PR与讨论Issue ","permalink":"https://echo.ishian.me/2023/12/08/vscode/","summary":"介绍VS Code的界面功能、三级配置体系与常用插件设置","title":"VS Code: 较为轻量的IDE"},{"content":"反常积分 即广义积分，分为无穷区间上与无界函数上两种\n计算 无穷区间上 ∫a+∞f(x)dx=lim⁡b→+∞∫abf(x)dx\\begin{align}\u0026amp;\\int_a^{+\\infty}f(x)dx=\\lim_{b\\rightarrow+\\infty}\\int_a^bf(x)dx\\end{align}​∫a+∞​f(x)dx=b→+∞lim​∫ab​f(x)dx​​拆为两项，其中需要关注\nlim⁡x→∞f(x)\\begin{align}\\lim_{x\\rightarrow\\infty}f(x)\\end{align}x→∞lim​f(x)​​一项，如果它存在，则积分收敛，否则发散\n需要熟悉不定积分的求法，利用牛顿莱布尼茨公式进行求解，不同之处在于它需要计算一项或两项的极限值\neg:\n计算∫0+∞xe−x(1+e−x)2dx解:原式=t=−x∫0+∞tet(1+et)2dt=(−t1+et)∣0+∞+∫0+∞1et(1+et)det=(ln⁡et(1+et)−t1+et)∣0+∞=ln⁡2\\begin{align}\u0026amp;计算\\int_0^{+\\infty}\\frac{xe^{-x}}{(1+e^{-x})^2}dx\\\\\u0026amp;解:原式\\xlongequal{t=-x}\\int_0^{+\\infty}\\frac{te^t}{(1+e^t)^2}dt=(-\\frac{t}{1+e^t})\\Bigg|_0^{+\\infty}+\\int_0^{+\\infty}\\frac{1}{e^t(1+e^t)}de^t\\\\\u0026amp;=(\\ln\\frac{e^t}{(1+e^t)}-\\frac{t}{1+e^t})\\Bigg|_0^{+\\infty}=\\ln2\\end{align}​计算∫0+∞​(1+e−x)2xe−x​dx解:原式t=−x∫0+∞​(1+et)2tet​dt=(−1+ett​)​0+∞​+∫0+∞​et(1+et)1​det=(ln(1+et)et​−1+ett​)​0+∞​=ln2​​证明广义积分等式 一般需要作代换，可以考虑倒代换、三角代换等\neg:\n证明∫0+∞dx1+x4=∫0+∞x21+x4dx，并求值证:左边=t=1x∫+∞0−dxx2(1+1x4)=∫0+∞x21+x4dx=右边∴原式=12∫0+∞1+x21+x4dx=12∫0+∞1+1x2x2+1x2dx=12∫0+∞d(x−1x)(x−1x)2+2=24arctan⁡[22(x−1x)]∣0+∞=2π4\\begin{align}\u0026amp;证明\\int_0^{+\\infty}\\frac{dx}{1+x^4}=\\int_0^{+\\infty}\\frac{x^2}{1+x^4}dx，并求值\\\\\u0026amp;证:左边\\xlongequal{t=\\Large \\frac{1}{x}}\\int_{+\\infty}^0-\\frac{dx}{x^2(1+\\frac{1}{x^4})}=\\int_0^{+\\infty}\\frac{x^2}{1+x^4}dx=右边\\\\\u0026amp;\\therefore原式=\\frac{1}{2}\\int_0^{+\\infty}\\frac{1+x^2}{1+x^4}dx=\\frac{1}{2}\\int_0^{+\\infty}\\frac{1+\\large\\frac{1}{x^2}}{x^2+\\large\\frac{1}{x^2}}dx=\\frac{1}{2}\\int_0^{+\\infty}\\frac{d({x-\\large\\frac{1}{x}})}{(x-{\\large\\frac{1}{x}})^2+2}=\\\\\u0026amp;\\frac{\\sqrt2}{4}\\arctan{\\bigg[\\frac{\\sqrt2}{2}(x-\\frac{1}{x})\\bigg]}\\Bigg|_0^{+\\infty}=\\frac{\\sqrt2\\pi}{4}\\end{align}​证明∫0+∞​1+x4dx​=∫0+∞​1+x4x2​dx，并求值证:左边t=x1​∫+∞0​−x2(1+x41​)dx​=∫0+∞​1+x4x2​dx=右边∴原式=21​∫0+∞​1+x41+x2​dx=21​∫0+∞​x2+x21​1+x21​​dx=21​∫0+∞​(x−x1​)2+2d(x−x1​)​=42​​arctan[22​​(x−x1​)]​0+∞​=42​π​​​","permalink":"https://echo.ishian.me/2023/12/03/calculus-improper-int/","summary":"讲解无穷区间与无界函数两类反常积分的计算方法及敛散性判断","title":"微积分: 反常积分"},{"content":"定积分 定义 ∫abf(x)dx=lim⁡λ→0∑i=1nf(ξi)Δxi\\begin{align}\\int_a^bf(x)dx=\\lim_{\\lambda\\rightarrow0}\\sum_{i=1}^nf(\\xi_i)\\Delta x_i\\end{align}∫ab​f(x)dx=λ→0lim​i=1∑n​f(ξi​)Δxi​​​可积的条件 f(x)在[a, b]上连续 f(x)在[a, b]上单调 f(x)在[a, b]上有界，且只有有限个间断点 一些特殊性质 单调性(反之不成立) 若f(x)与g(x)在[a,b]上均可积，且f(x)≤g(x)，则∫abf(x)dx≤∫abg(x)dx\\begin{align}若f(x)与g(x)在[a,b]上均可积，且f(x)\\le g(x)，则\\int_a^bf(x)dx\\le\\int_a^bg(x)dx\\end{align}若f(x)与g(x)在[a,b]上均可积，且f(x)≤g(x)，则∫ab​f(x)dx≤∫ab​g(x)dx​​最大最小值\\textcolor{red}{最大最小值}最大最小值 若M和m分别是可积函数f(x)的最大值和最小值，则m(b−a)≤∫abf(x)dx≤M(b−a)若M和m分别是可积函数f(x)的最大值和最小值，则\\\\m(b-a)\\le\\int_a^bf(x)dx\\le M(b-a)若M和m分别是可积函数f(x)的最大值和最小值，则m(b−a)≤∫ab​f(x)dx≤M(b−a)\n区间可加性\\textcolor{red}{区间可加性}区间可加性 可以分为若干个分割，即∫abf(x)dx=∫acf(x)dx+∫cbf(x)dx∫abf(x)dx=−∫baf(x)dx\\begin{align}\u0026amp;可以分为若干个分割，即\\\\\u0026amp;\\int_a^bf(x)dx=\\int_a^cf(x)dx+\\int_c^bf(x)dx\\\\\u0026amp;\\int_a^bf(x)dx=-\\int_b^af(x)dx\\end{align}​可以分为若干个分割，即∫ab​f(x)dx=∫ac​f(x)dx+∫cb​f(x)dx∫ab​f(x)dx=−∫ba​f(x)dx​​乘法可积 若f(x),g(x)在[a,b]上可积,则f(x)g(x)在[a,b]上也可积若f(x), g(x)在[a,b]上可积,则f(x)g(x)在[a,b]上也可积若f(x),g(x)在[a,b]上可积,则f(x)g(x)在[a,b]上也可积\n积分中值定理\\textcolor{red}{积分中值定理}积分中值定理 若f(x)在[a,b]上连续,则存在ξ∈[a,b],使∫abf(x)dx=f(ξ)(b−a)\\begin{align}\u0026amp;若f(x)在[a,b]上连续,则存在\\xi\\in[a,b],使\\\\\u0026amp;\\int_a^bf(x)dx=f(\\xi)(b-a)\\end{align}​若f(x)在[a,b]上连续,则存在ξ∈[a,b],使∫ab​f(x)dx=f(ξ)(b−a)​​牛顿莱布尼茨公式 求定积分时，可以直接求f(x)不带C的原函数F(x)∫abf(x)dx=F(x)(b−a)\\begin{align}\u0026amp;求定积分时，可以直接求f(x)不带C的原函数F(x)\\\\ \u0026amp;\\int_a^bf(x)dx=F(x)(b-a)\\end{align}​求定积分时，可以直接求f(x)不带C的原函数F(x)∫ab​f(x)dx=F(x)(b−a)​​变上限函数 定义(f不带C的一个特殊原函数) ϕ(x)=∫axf(t)dt\\begin{align}\\phi(x)=\\int_a^xf(t)dt\\end{align}ϕ(x)=∫ax​f(t)dt​​性质 连续且有界 如果f可积，则ϕ(x)连续且有界如果f可积，则\\phi(x)连续且有界如果f可积，则ϕ(x)连续且有界\n导函数 ϕ′(x)=f(x)\\phi\u0026#x27;(x)=f(x)ϕ′(x)=f(x)\n任何连续函数都具有原函数\n[题型]计算定积分 1.∫abf(x)dx=∫abf(a+b−x)dx\\textcolor{red}{1.\\int_a^bf(x)dx=\\int_a^bf(a+b-x)dx}1.∫ab​f(x)dx=∫ab​f(a+b−x)dx 即令t=a+b-x, 此方法可以证明它本身和f(sin x)与f(cos x)的关系\n利用此方法还可以计算：∫abf(x)dx=12∫ab(f(x)+f(a+b−x))dx\\begin{align}利用此方法还可以计算：\\int_a^bf(x)dx=\\frac{1}{2}\\int_a^b(f(x)+f(a+b-x))dx\\end{align}利用此方法还可以计算：∫ab​f(x)dx=21​∫ab​(f(x)+f(a+b−x))dx​​2.利用牛顿莱布尼茨公式 3.利用奇偶性\\textcolor{red}{3.利用奇偶性}3.利用奇偶性 ∫−aaf(x)dx=∫0a(f(x)+f(−x))dx\\begin{align}\\int_{-a}^af(x)dx=\\int_0^a{(f(x)+f(-x))dx}\\end{align}∫−aa​f(x)dx=∫0a​(f(x)+f(−x))dx​​当f(x)是奇函数时,\n∫−aaf(x)dx=0\\begin{align}\\int_{-a}^af(x)dx=0\\end{align}∫−aa​f(x)dx=0​​当f(x)是偶函数时,\n∫−aaf(x)=2∫0af(x)dx\\begin{align}\\int_{-a}^af(x)=2\\int_0^af(x)dx\\end{align}∫−aa​f(x)=2∫0a​f(x)dx​​当f(x)中有一部分是奇或偶函数时也可尝试这个公式\n4.利用周期性 若f(x)以T为周期，则∫aa+Tf(x)dx=∫0Tf(x)dx∫aa+nTf(x)dx=n∫0Tf(x)dx\\begin{align}\u0026amp;若f(x)以T为周期，则\\\\\u0026amp;\\int_a^{a+T}f(x)dx=\\int_0^Tf(x)dx\\\\\u0026amp;\\int_a^{a+nT}f(x)dx=n\\int_0^Tf(x)dx\\end{align}​若f(x)以T为周期，则∫aa+T​f(x)dx=∫0T​f(x)dx∫aa+nT​f(x)dx=n∫0T​f(x)dx​​5.关于三角函数的定积分 1).∫0π2(cos⁡x)dx=∫0π2(sin⁡x)dx\\begin{align}1).\\int_0^{\\frac{\\pi}{2}}(\\cos x)dx=\\int_0^{\\frac{\\pi}{2}}(\\sin x)dx\\end{align}1).∫02π​​(cosx)dx=∫02π​​(sinx)dx​​​ 1)可以使\nt=π2−x证明\\begin{align}t=\\frac{\\pi}{2}-x证明\\end{align}t=2π​−x证明​​2).∫0πxf(sin⁡x)dx=π2∫0πf(sin⁡x)dx=π∫0π2f(sin⁡x)dx\\begin{align}2).\\int_0^\\pi xf(\\sin x)dx=\\frac{\\pi}{2}\\int_0^\\pi f(\\sin x)dx=\\pi\\int_0^\\frac{\\pi}{2}f(\\sin x)dx\\end{align}2).∫0π​xf(sinx)dx=2π​∫0π​f(sinx)dx=π∫02π​​f(sinx)dx​​3).∫0π2f(sin⁡nx)dx当n为偶数时,∫0π2f(sinnx)dx=n−1nn−3n−2...12⋅π2当n为奇数时,∫0π2f(sinnx)dx=n−1nn−3n−2...13⋅1\\begin{align}\u0026amp;3).\\int_0^\\frac{\\pi}{2}f(\\sin^nx)dx\\\\ \u0026amp;当n为偶数时,\\int_0^\\frac{\\pi}{2}f(sin^nx)dx=\\frac{n-1}{n}\\frac{n-3}{n-2}...\\frac{1}{2}·\\frac{\\pi}{2}\\\\\u0026amp;当n为奇数时,\\int_0^\\frac{\\pi}{2}f(sin^nx)dx=\\frac{n-1}{n}\\frac{n-3}{n-2}...\\frac{1}{3}·1\\end{align}​3).∫02π​​f(sinnx)dx当n为偶数时,∫02π​​f(sinnx)dx=nn−1​n−2n−3​...21​⋅2π​当n为奇数时,∫02π​​f(sinnx)dx=nn−1​n−2n−3​...31​⋅1​​[题型]计算极限 求数列多项和的极限 先考虑夹逼定理\n若无法夹逼，可以提出\n1n\\begin{align}\\frac{1}{n}\\end{align}n1​​​后，化为一个0到1的定积分\n例：\n求lim⁡n→∞(nn2+12+nn2+22+nn2+32+...+nn2+n2)解：原式=1n∑i=1∞n⋅nn2+i2(n→∞)=∫0111+x2dx=arctan⁡1=π4\\begin{align}\u0026amp;求\\lim_{n\\rightarrow\\infty}(\\frac{n}{n^2+1^2}+\\frac{n}{n^2+2^2}+\\frac{n}{n^2+3^2}+...+\\frac{n}{n^2+n^2})\\\\\u0026amp;解：\\\\\u0026amp;原式=\\frac{1}{n}\\sum_{i=1}^\\infty n·\\frac{n}{n^2+i^2}(n\\rightarrow\\infty)=\\int_0^1\\frac{1}{1+x^2}dx=\\arctan 1=\\frac{\\pi}{4}\\end{align}​求n→∞lim​(n2+12n​+n2+22n​+n2+32n​+...+n2+n2n​)解：原式=n1​i=1∑∞​n⋅n2+i2n​(n→∞)=∫01​1+x21​dx=arctan1=4π​​​关于变限函数导数的计算 通过换元，可以得到\n∫v(x)u(x)f(t)dt=∫0u(x)f(t)dt−∫0v(x)f(t)dt=f(u(x))u′(x)−f(v(x))v′(x)\\begin{align}\\int_{v(x)}^{u(x)}f(t)dt=\\int_0^{u(x)}f(t)dt-\\int_0^{v(x)}f(t)dt=f\\Big(u(x)\\Big)u\u0026#x27;(x)-f\\Big(v(x)\\Big)v\u0026#x27;(x)\\end{align}∫v(x)u(x)​f(t)dt=∫0u(x)​f(t)dt−∫0v(x)​f(t)dt=f(u(x))u′(x)−f(v(x))v′(x)​​求面积 求面积时一定注意结果是正数\n单个函数 最基础的，求一个连续图形的面积和x轴包围的面积，即求\n∫aby dx\\begin{align}\\int_a^by\\ dx\\end{align}∫ab​y dx​​如果要求和y轴包围的面积，反函数连续，即求\n∫abx dy\\begin{align}\\int_a^bx\\ dy\\end{align}∫ab​x dy​​多条曲线 如果求多条曲线包围的面积，先观察它或反函数在这个区间内是否连续，取微分时，被积函数是连续的可以方便计算，同时也要判断反函数是否好求\neg1：\n在第一象限内，求曲线y=−x2+1上的一点，使该点处的切线与所给曲线及两坐标轴围成的图形面积为最小，并求此最小面积解:先求切线，设切点为(a,−a2+1)则切线为y=−2ax+a2+1得到与x、y轴的交点分别为(a2+12a, 0)、(0, a2+1)则S=∫0a2+12a(−2ax+a2+1)dx−∫01(−x2+1)dx=a34+a2+14a−23∴S′=3a24+12−14a2(a∈[0,1])，令S′=0，得a2=13，即a=13易得该点为最小值Smin=29(23−3)\\begin{align}\u0026amp;在第一象限内，求曲线\\rm y=-x^2+1上的一点，使该点处的切线与所给曲线及两坐标轴围成的图形面积为\\\\\u0026amp;最小，并求此最小面积\\\\\u0026amp;解:先求切线，设切点为(a,-a^2+1)\\\\\u0026amp;则切线为\\rm y=-2ax+a^2+1\\\\\u0026amp;得到与x、y轴的交点分别为(\\frac{a^2+1}{2a},\\ 0)、(0,\\ a^2+1)\\\\\u0026amp;则S=\\int_0^{\\Large\\frac{a^2+1}{2a}}(-2ax+a^2+1)dx-\\int_0^1(-x^2+1)dx=\\frac{a^3}{4}+\\frac{a}{2}+\\frac{1}{4a}-\\frac{2}{3}\\\\\u0026amp;\\therefore S\u0026#x27;=\\frac{3a^2}{4}+\\frac{1}{2}-\\frac{1}{4a^2}(a\\in[0,1])，令S\u0026#x27;=0，得a^2=\\frac{1}{3}，即a=\\frac{1}{\\sqrt3}\\\\\u0026amp;易得该点为最小值S_{min}=\\frac{2}{9}(2\\sqrt3-3)\\end{align}​在第一象限内，求曲线y=−x2+1上的一点，使该点处的切线与所给曲线及两坐标轴围成的图形面积为最小，并求此最小面积解:先求切线，设切点为(a,−a2+1)则切线为y=−2ax+a2+1得到与x、y轴的交点分别为(2aa2+1​, 0)、(0, a2+1)则S=∫02aa2+1​​(−2ax+a2+1)dx−∫01​(−x2+1)dx=4a3​+2a​+4a1​−32​∴S′=43a2​+21​−4a21​(a∈[0,1])，令S′=0，得a2=31​，即a=3​1​易得该点为最小值Smin​=92​(23​−3)​​ eg2：\n已知抛物线y=px2+qx(其中p\u0026lt;0,q\u0026gt;0)在第一象限内与直线x+y=5相切，且抛物线与x轴围成的平面图形的面积为S(1)问:p和q为何值时，S达到最大值?(2)求出此最大值令y=0得x=−qp，故S=∫0−qp(px2+qx)dx=q36p2又由相切，得{2px0+q=−1px02+qx0=−x0+5，切点为(x0,y0)∴S=200q33(q+1)4，令S′=0，得q=3，易得Smax=22532\\begin{align}\u0026amp;已知抛物线\\rm y=px^2+qx(其中p\u0026lt;0,q\u0026gt;0)在第一象限内与直线x+y=5相切，且抛物线与x轴围成\\\\\u0026amp;的平面图形的面积为S\\\\\u0026amp;(1)问:p和q为何值时，S达到最大值?\\\\\u0026amp;(2)求出此最大值\\\\\u0026amp;令y=0得x=-\\frac{q}{p}，故S=\\int_0^{\\large-\\frac{q}{p}}(px^2+qx)dx=\\frac{q^3}{6p^2}\\\\\u0026amp;又由相切，得\\begin{cases}2px_0+q=-1\\\\px_0^2+qx_0=-x_0+5\\end{cases}，切点为(x_0,y_0)\\\\\u0026amp;\\therefore S=\\frac{200q^3}{3(q+1)^4}，令S\u0026#x27;=0，得q=3，易得S_{max}=\\frac{225}{32}\\end{align}​已知抛物线y=px2+qx(其中p\u0026lt;0,q\u0026gt;0)在第一象限内与直线x+y=5相切，且抛物线与x轴围成的平面图形的面积为S(1)问:p和q为何值时，S达到最大值?(2)求出此最大值令y=0得x=−pq​，故S=∫0−pq​​(px2+qx)dx=6p2q3​又由相切，得{2px0​+q=−1px02​+qx0​=−x0​+5​，切点为(x0​,y0​)∴S=3(q+1)4200q3​，令S′=0，得q=3，易得Smax​=32225​​​ eg3：\n设F(x)={e2x,x≤0e−2x,x\u0026gt;0，S表示夹在x轴与曲线y=F(x)之间的面积，对任何t\u0026gt;0，S1(t)表示矩形−t≤x≤t,0≤y≤F(t)的面积，求(1)S(t)=S−S1(t)的表达式(2)S(t)的最小值解:S(t)=2∫0+∞e−2xdx−2t⋅e−2t=1−2t⋅e−2t∴S′(t)=(4t−2)e−2t令S′(t)=0，得t=12,易得S(t)min=1−1e\\begin{align}\u0026amp;设F(x)=\\begin{cases}e^{2x},\u0026amp;x\\le0\\\\e^{-2x},\u0026amp;x\u0026gt;0\\end{cases}，S表示夹在x轴与曲线y=F(x)之间的面积，对任何t\u0026gt;0，S_1(t)表示矩形\\\\\u0026amp;-t\\le x\\le t,0\\le y\\le F(t)的面积，求\\\\\u0026amp;(1)S(t)=S-S_1(t)的表达式\\\\\u0026amp;(2)S(t)的最小值\\\\\u0026amp;解:S(t)=2\\int_0^{+\\infty}e^{-2x}dx-2t·e^{-2t}=1-2t·e^{-2t}\\\\\u0026amp;\\therefore S\u0026#x27;(t)=(4t-2)e^{-2t}\\\\\u0026amp;令S\u0026#x27;(t)=0，得t=\\frac{1}{2},易得S(t)_{min}=1-\\frac{1}{e}\\end{align}​设F(x)={e2x,e−2x,​x≤0x\u0026gt;0​，S表示夹在x轴与曲线y=F(x)之间的面积，对任何t\u0026gt;0，S1​(t)表示矩形−t≤x≤t,0≤y≤F(t)的面积，求(1)S(t)=S−S1​(t)的表达式(2)S(t)的最小值解:S(t)=2∫0+∞​e−2xdx−2t⋅e−2t=1−2t⋅e−2t∴S′(t)=(4t−2)e−2t令S′(t)=0，得t=21​,易得S(t)min​=1−e1​​​极坐标下面积 在利用定积分前，需要知道怎么将一般方程转换成极坐标方程：令x=acos⁡θ, y=asin⁡θ\\rm x=a\\cos\\theta,\\ y=a\\sin\\thetax=acosθ, y=asinθ即可\neg1：\n求双纽线(x2+y2)2=x2−y2\\rm (x^2+y^2)^2=x^2-y^2(x2+y2)2=x2−y2的极坐标方程\n解:令x=rcos⁡θ,y=rsin⁡θ,得r4=r2(cos⁡2θ−sin⁡2θ),即r=cos⁡2θ=ρ(θ)\\begin{align}\u0026amp;解:令x=r\\cos\\theta,y=r\\sin\\theta,得r^4=r^2(\\cos^2\\theta-\\sin^2\\theta),即r=\\sqrt{\\cos2\\theta}=\\rho(\\theta)\\end{align}​解:令x=rcosθ,y=rsinθ,得r4=r2(cos2θ−sin2θ),即r=cos2θ​=ρ(θ)​​接下来求它围成的面积，对θ\\thetaθ取微分，将面积分割为许多个扇形，而\nS扇形=12r2θ\\begin{align}\\mathbf{S_{扇形}=\\frac{1}{2}r^2\\theta}\\end{align}S扇形​=21​r2θ​​那么，积分上下限怎么求呢? θ\\thetaθ是从原点出发而言的，双纽线是对称图形，这意味着只需要求第一象限部分的面积，那么积分下限就是0，而**r\\mathbf rr是大于0的**，因此令r=0\\mathbf{r=0}r=0，得到\nθ=π4\\begin{align}\\theta=\\frac{\\pi}{4}\\end{align}θ=4π​​​因此\nS=4∫0π412r2dθ\\begin{align}\\rm S=4\\int_0^{\\large\\frac{\\pi}{4}}\\frac{1}{2}r^2d\\theta\\end{align}S=4∫04π​​21​r2dθ​​ eg2：求两种极坐标下曲线围成的面积，注意一般这种曲线具有对称性，所以是两倍的\n[0,π]区间或[−π2,π2]\\begin{align}[0,\\pi]区间或[-\\frac{\\pi}{2},\\frac{\\pi}{2}]\\end{align}[0,π]区间或[−2π​,2π​]​​求心脏线\nρ=a(1+cos⁡φ)\\begin{align}\\rho=a(1+\\cos\\varphi)\\end{align}ρ=a(1+cosφ)​​与圆ρ=a\\rho=aρ=a所围成的各部分的面积(a\u0026gt;0)\n解:(1)求共有面积[简单来说,谁的r小就取谁的表达式,因为是′′共有面积′′]∴当cos⁡θ≤0时，取心脏线ρ1，否则取ρ2∴S=12⋅2(∫0π2a2dθ+∫π2π(a(1+cos⁡φ))2dθ)(2)求心脏线独有面积[当心脏线大于圆时计算面积]S=2⋅12∫0π2(a(1+cos⁡θ))2−a2dθ(3)求圆独有面积:与(2)同理，不多赘述\\begin{align}\u0026amp;解:\\\\\u0026amp;(1)求共有面积\\\\\u0026amp;[简单来说,谁的r小就取谁的表达式,因为是\u0026#x27;\u0026#x27;共有面积\u0026#x27;\u0026#x27;]\\\\\u0026amp;\\therefore当\\cos\\theta\\le0时，取心脏线\\rho_1，否则取\\rho_2\\\\\u0026amp;\\therefore S=\\frac{1}{2}·2\\Bigg(\\int_0^{\\large\\frac{\\pi}{2}}a^2d\\theta+\\int_{\\large\\frac{\\pi}{2}}^\\pi\\Big (a(1+\\cos\\varphi)\\Big)^2d\\theta\\Bigg)\\\\\u0026amp;(2)求心脏线独有面积\\\\\u0026amp;[当心脏线大于圆时计算面积]\\\\\u0026amp;S=2·\\frac{1}{2}\\int_0^{\\large\\frac{\\pi}{2}}\\Big(a(1+\\cos\\theta)\\Big)^2-a^2d\\theta\\\\\u0026amp;(3)求圆独有面积:与(2)同理，不多赘述\\end{align}​解:(1)求共有面积[简单来说,谁的r小就取谁的表达式,因为是′′共有面积′′]∴当cosθ≤0时，取心脏线ρ1​，否则取ρ2​∴S=21​⋅2(∫02π​​a2dθ+∫2π​π​(a(1+cosφ))2dθ)(2)求心脏线独有面积[当心脏线大于圆时计算面积]S=2⋅21​∫02π​​(a(1+cosθ))2−a2dθ(3)求圆独有面积:与(2)同理，不多赘述​​ eg3：简易的画图可以帮助更快地做题，我们将右式在[0,2π](或[−π,π])[0,2\\pi](或[-\\pi,\\pi])[0,2π](或[−π,π])上展开，大于0的范围合起来就是图\n求曲线r=2sin⁡θ及r2=cos⁡2θ所围成的公共部分的面积解:[分析:只有r大于0时才算进图里，对r1来说,θ∈[0,π],对r2来说,θ∈[−π4,π4]\u0026amp;[5π4,7π4]]令r1=r2，得θ=π6或5π6,当π6\u0026lt;θ\u0026lt;π4时，r1\u0026gt;r2∴S=12⋅2(∫0π62sin⁡2θ dθ+∫π6π4cos⁡2θ dθ)=π6+1−32\\begin{align}\u0026amp;\\rm求曲线r=\\sqrt2\\sin\\theta及r^2=\\cos2\\theta所围成的公共部分的面积\\\\\u0026amp;解:[分析:只有r大于0时才算进图里，对r_1来说,\\theta\\in{\\small[0,\\pi]},对r_2来说,\\theta\\in{\\small[-\\frac{\\pi}{4},\\frac{\\pi}{4}]\\And[\\frac{5\\pi}{4},\\frac{7\\pi}{4}]}]\\\\\u0026amp;令r_1=r_2，得\\theta=\\frac{\\pi}{6}或\\frac{5\\pi}{6},当\\frac{\\pi}{6}\u0026lt;\\theta\u0026lt;\\frac{\\pi}{4}时，r_1\u0026gt;r_2\\\\\u0026amp;\\therefore S=\\frac12·2(\\int_0^{\\large\\frac{\\pi}{6}}2\\sin^2\\theta\\ d\\theta+\\int_{\\large\\frac{\\pi}{6}}^{\\large\\frac{\\pi}{4}}\\cos2\\theta \\ d\\theta)=\\frac\\pi6+\\frac{1-\\sqrt3}2\\end{align}​求曲线r=2​sinθ及r2=cos2θ所围成的公共部分的面积解:[分析:只有r大于0时才算进图里，对r1​来说,θ∈[0,π],对r2​来说,θ∈[−4π​,4π​]\u0026amp;[45π​,47π​]]令r1​=r2​，得θ=6π​或65π​,当6π​\u0026lt;θ\u0026lt;4π​时，r1​\u0026gt;r2​∴S=21​⋅2(∫06π​​2sin2θ dθ+∫6π​4π​​cos2θ dθ)=6π​+21−3​​​​旋转体表面积 求体积 柱壳法 当所求旋转体是中空时，可以用柱壳法求体积，以每层薄薄的\u0026quot;柱壳\u0026quot;为dtdtdt，对\u0026rsquo;周长⋅高周长·高周长⋅高\u0026lsquo;积分\neg：\n求曲线y=x2−2x,y=0,x=1,x=3所围成的平面图形绕y轴旋转一周的旋转体的体积V解:先画图\\begin{align}\u0026amp;\\rm求曲线y=x^2-2x,y=0,x=1,x=3所围成的平面图形绕y轴旋转一周的旋转体的体积V\\\\\u0026amp;解:先画图\\end{align}​求曲线y=x2−2x,y=0,x=1,x=3所围成的平面图形绕y轴旋转一周的旋转体的体积V解:先画图​​ 因为[1,2]范围是负数,所以V=∫212πxy dx+∫232πxy dx=9π\\begin{align}\u0026amp;\\rm因为[1,2]范围是负数,所以V=\\int_2^12\\pi xy\\ dx+\\int_2^32\\pi xy\\ dx=9\\pi\\end{align}​因为[1,2]范围是负数,所以V=∫21​2πxy dx+∫23​2πxy dx=9π​​其中，2πx\\rm 2\\pi x2πx为每个柱壳的周长，y\\rm yy为每个柱壳的高。注意，当旋转轴不是单纯的x轴或y轴时，求柱壳体积需额外注意，例如本例，如果绕x=−1x=-1x=−1旋转，则V柱壳=2π(x+1)y\\rm V_{柱壳}=2\\pi(x+1)yV柱壳​=2π(x+1)y\n截面法 显然，不是所有的情况都适用柱壳法，大多数情况下使用的是截面法，特别是多条复杂曲线所围成的图形\neg：\n设直线y=22x和y=x2围成的面积为S1,它们与x=1围成的面积为S2,求这两个平面图形绕x轴旋转而成旋转体的总体积V解:画图\\begin{align}\u0026amp;\\rm设直线y=\\frac{\\sqrt2}2x和y=x^2围成的面积为S_1,它们与x=1围成的面积为S_2,求这两个平面图形绕x轴旋转\\\\\u0026amp;而成旋转体的总体积\\rm V\\\\\u0026amp;解:画图\\end{align}​设直线y=22​​x和y=x2围成的面积为S1​,它们与x=1围成的面积为S2​,求这两个平面图形绕x轴旋转而成旋转体的总体积V解:画图​​ V=∫022π(x22−x4)dx+∫221π(x4−x22)dx=(1+2)π30\\begin{align}\u0026amp;\\rm V=\\int_0^{\\large\\frac{\\sqrt{2}}2}\\pi(\\frac{x^2}2-x^4)dx+\\int_{\\large\\frac{\\sqrt{2}}2}^1\\pi(x^4-\\frac{x^2}2)dx=\\frac{(1+\\sqrt2)\\pi}{30}\\end{align}​V=∫022​​​π(2x2​−x4)dx+∫22​​1​π(x4−2x2​)dx=30(1+2​)π​​​","permalink":"https://echo.ishian.me/2023/11/28/calculus-definite-int/","summary":"整理定积分的定义、可积条件、性质与积分中值定理","title":"微积分: 定积分"},{"content":"不定积分 公式 基本公式 ∫kdx=kx+C, ∫kxαdx=kα+1xα+1+C(α≠−1)∫dxx=ln∣x∣+C,∫ekxdx=1kekx+C∫sin⁡xdx=−cos⁡x+C,∫cos⁡xdx=sin⁡x+C,∫sec⁡2xdx=tan⁡x+C∫csc⁡2xdx=∫dxsin⁡2x=−cot⁡x+C∫sec⁡xtan⁡xdx=sec⁡x+C,∫csc⁡xcot⁡xdx=∫cos⁡xsin⁡2xdx=−csc⁡x+C∫11+x2dx=arctan⁡x+C,∫dx1−x2=arcsin⁡x+C\\begin{align} \u0026amp;\\int kdx=kx+C,\\ \\int kx^\\alpha dx=\\frac{k}{\\alpha+1}x^{\\alpha+1}+C(\\alpha\\neq -1)\\\\\u0026amp; \\int \\frac{dx}{x}=ln|x|+C, \\int e^{kx}dx=\\frac{1}{k}e^{kx}+C\\\\\u0026amp; \\int \\sin xdx=-\\cos x+C, \\int \\cos xdx=\\sin x+C, \\int \\sec^2xdx=\\tan x+C\\\\\u0026amp; \\int\\csc^2xdx=\\int\\frac{dx}{\\sin^2x}=-\\cot x+C\\\\\u0026amp; \\int\\sec x\\tan xdx=\\sec x+C, \\int\\csc x\\cot xdx=\\int\\frac{\\cos x}{\\sin^2x}dx=-\\csc x+C\\\\\u0026amp; \\int \\frac{1}{1+x^2}dx=\\arctan x+C,\\int\\frac{dx}{\\sqrt{1-x^2}}=\\arcsin x+C \\end{align}​∫kdx=kx+C, ∫kxαdx=α+1k​xα+1+C(α=−1)∫xdx​=ln∣x∣+C,∫ekxdx=k1​ekx+C∫sinxdx=−cosx+C,∫cosxdx=sinx+C,∫sec2xdx=tanx+C∫csc2xdx=∫sin2xdx​=−cotx+C∫secxtanxdx=secx+C,∫cscxcotxdx=∫sin2xcosx​dx=−cscx+C∫1+x21​dx=arctanx+C,∫1−x2​dx​=arcsinx+C​​拓展公式(需要熟悉推导过程) ∫tan⁡xdx=∫sin⁡xdxcos⁡x=−ln⁡∣cos⁡x∣+C∫cot⁡xdx=∫cos⁡xdxsin⁡x=ln∣sin⁡x∣+C∫sec⁡xdx=∫dxcos⁡x=∫cos⁡xdxcos⁡2x=∫d(sin⁡x)1−sin⁡2x=12ln∣1+sin⁡x1−sin⁡x∣+C∫csc⁡xdx=∫sin⁡xdxsin⁡2x=−∫d(cos⁡x)1−cos⁡2x=−12ln∣1+cos⁡x1−cos⁡x∣+C对于sin⁡nx与cos⁡nx，n较小时若n为偶数:直接通过cos2x=1−2sin2x=2cos2x−1降次若n为奇数:将一个抽取出来凑微分，剩下的部分通过sin2x+cos2x=1进行替换若n较大:记公式(没必要)∫sin⁡nxdx=−1nsinn−1xcosx+∫n−1nsinn−2xdx\\begin{align}\u0026amp; \\int\\tan xdx=\\int\\frac{\\sin xdx}{\\cos x}=-\\ln|\\cos x|+C\\\\\u0026amp; \\int\\cot xdx=\\int\\frac{\\cos xdx}{\\sin x}=ln|\\sin x|+C\\\\\u0026amp; \\int\\sec xdx=\\int\\frac{dx}{\\cos x}=\\int\\frac{\\cos xdx}{\\cos^2x}=\\int\\frac{d(\\sin x)}{1-\\sin^2x}=\\frac{1}{2}ln|\\frac{1+\\sin x}{1-\\sin x}|+C\\\\\u0026amp; \\int\\csc xdx=\\int\\frac{\\sin xdx}{\\sin^2x}=-\\int\\frac{d(\\cos x)}{1-\\cos^2x}=-\\frac{1}{2}ln|\\frac{1+\\cos x}{1-\\cos x}|+C\\\\\\\\\\\\\u0026amp; 对于\\sin^nx与\\cos^nx，n较小时\\\\\u0026amp; 若n为偶数:\\\\\u0026amp; 直接通过cos2x=1-2sin^2x=2cos^2x-1降次\\\\\u0026amp; 若n为奇数:\\\\\u0026amp; 将一个抽取出来凑微分，剩下的部分通过sin^2x+cos^2x=1进行替换\\\\\\\\\u0026amp; 若n较大:记公式(没必要)\\int\\sin^nxdx=-\\frac{1}{n}sin^{n-1}xcosx+\\int\\frac{n-1}{n}sin^{n-2}xdx \\end{align}​∫tanxdx=∫cosxsinxdx​=−ln∣cosx∣+C∫cotxdx=∫sinxcosxdx​=ln∣sinx∣+C∫secxdx=∫cosxdx​=∫cos2xcosxdx​=∫1−sin2xd(sinx)​=21​ln∣1−sinx1+sinx​∣+C∫cscxdx=∫sin2xsinxdx​=−∫1−cos2xd(cosx)​=−21​ln∣1−cosx1+cosx​∣+C对于sinnx与cosnx，n较小时若n为偶数:直接通过cos2x=1−2sin2x=2cos2x−1降次若n为奇数:将一个抽取出来凑微分，剩下的部分通过sin2x+cos2x=1进行替换若n较大:记公式(没必要)∫sinnxdx=−n1​sinn−1xcosx+∫nn−1​sinn−2xdx​​常凑微分 (凑常数):∫f(ax+b)dx=1a∫f(ax+b)d(ax+b)=1a∫f(t)dt∣t=ax+b(凑xα):xαdx=1α+1dxα+1(凑1x):1xdx=d(ln⁡x)(凑sec⁡2x):sec⁡2xdx=d(tan⁡x)(凑csc⁡2x):csc⁡2xdx=−d(cot⁡x)(凑x):dxx=2d(x)(凑dx二次项式):将分母凑配方，得到1−Ax2的形式(凑1±1x2):(1±1x2)dx=d(x∓1x),而(x∓1x)2=(x2+1x2)±2 即通过倒代换凑出\\begin{align}\u0026amp; (凑常数):\\int f(ax+b)dx=\\frac{1}{a}\\int f(ax+b)d(ax+b)=\\frac{1}{a}\\int f(t)dt\\Big\\vert_{t=ax+b}\\\\\u0026amp; (凑x^\\alpha):x^{\\alpha}dx=\\frac{1}{\\alpha+1}dx^{\\alpha+1}\\\\\u0026amp; (凑\\frac{1}{x}):\\frac{1}{x}dx=d(\\ln x)\\\\\u0026amp; (凑\\sec^2 x):\\sec^2 xdx=d(\\tan x)\\\\\u0026amp; (凑\\csc^2 x):\\csc^2 xdx=-d(\\cot x)\\\\\u0026amp;(凑\\sqrt x):\\frac{dx}{\\sqrt x}=2d(\\sqrt x)\\\\\u0026amp;(凑\\frac{dx}{\\sqrt{二次项式}}):将分母凑配方，得到\\sqrt{1-Ax^2}的形式\\\\\u0026amp;(凑1\\pm\\frac{1}{x^2}):(1\\pm\\frac{1}{x^2})dx=d(x\\mp\\frac{1}{x}),而(x\\mp\\frac{1}{x})^2=(x^2+\\frac{1}{x^2})\\pm 2\\\\\u0026amp;\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ 即通过倒代换凑出\\end{align}​(凑常数):∫f(ax+b)dx=a1​∫f(ax+b)d(ax+b)=a1​∫f(t)dt​t=ax+b​(凑xα):xαdx=α+11​dxα+1(凑x1​):x1​dx=d(lnx)(凑sec2x):sec2xdx=d(tanx)(凑csc2x):csc2xdx=−d(cotx)(凑x​):x​dx​=2d(x​)(凑二次项式​dx​):将分母凑配方，得到1−Ax2​的形式(凑1±x21​):(1±x21​)dx=d(x∓x1​),而(x∓x1​)2=(x2+x21​)±2 即通过倒代换凑出​​分部积分 ∫f(x)g′(x)dx=(f(x)⋅g(x))′−∫f′(x)⋅g(x)\\begin{align}\\int f(x)g\u0026#x27;(x)dx=\\Big(f(x)·g(x)\\Big)\u0026#x27;-\\int f\u0026#x27;(x)·g(x)\\end{align}∫f(x)g′(x)dx=(f(x)⋅g(x))′−∫f′(x)⋅g(x)​​求不定积分的技巧 分式 假分式化为真分式(分子最高次幂大于等于分母最高次幂) exp1:\n求∫x3x+3dx解:原式=∫(x+3)(x2−3x+9)−27x+3dx=13x3−32x2+9x−27ln∣x+3∣+C\\begin{align}\u0026amp;求\\int\\frac{x^3}{x+3}dx\\\\\u0026amp;解:\\\\\u0026amp;原式=\\int\\frac{(x+3)(x^2-3x+9)-27}{x+3}dx=\\frac{1}{3}x^3-\\frac{3}{2}x^2+9x-27ln|x+3|+C\\end{align}​求∫x+3x3​dx解:原式=∫x+3(x+3)(x2−3x+9)−27​dx=31​x3−23​x2+9x−27ln∣x+3∣+C​​具体方法:\n真分式化为四种分式 1.∫Ax−adx Aln∣x−a∣2.∫A(x−a)ndx A11−n(x−a)1−n3.∫Mx+Nx2+px+qdx 将分母配方为(x−a)2+b的形式即可4.∫Mx+N(x2+px+q)ndx\\begin{align}\u0026amp;1.\\int\\frac{A}{x-a}dx\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ Aln|x-a|\\\\\u0026amp;2.\\int\\frac{A}{(x-a)^n}dx\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ A\\frac{1}{1-n}(x-a)^{1-n}\\\\\u0026amp;3.\\int\\frac{Mx+N}{x^2+px+q}dx\\ \\ \\ \\ \\ 将分母配方为(x-a)^2+b的形式即可\\\\\u0026amp;4.\\int\\frac{Mx+N}{(x^2+px+q)^n}dx\\end{align}​1.∫x−aA​dx Aln∣x−a∣2.∫(x−a)nA​dx A1−n1​(x−a)1−n3.∫x2+px+qMx+N​dx 将分母配方为(x−a)2+b的形式即可4.∫(x2+px+q)nMx+N​dx​​怎么拆开? 假设我们有:\n1.分子(x+a)p(x+b)q,则应拆成:A1(x+a)p+A2(x+a)p−1+A3(x+a)p−2+...+B1(x+a)q−1+B2(x+a)q−2+...2.分子(x2+px+q)(其它分母),则应拆成:Ax+Bx2+px+q+...\\begin{align}\u0026amp;1.\\frac{分子}{(x+a)^p(x+b)^q},则应拆成:\\\\\u0026amp;\\frac{A_1}{(x+a)^p}+\\frac{A_2}{(x+a)^{p-1}}+\\frac{A_3}{(x+a)^{p-2}}+...+\\frac{B_1}{(x+a)^{q-1}}+\\frac{B_2}{(x+a)^{q-2}}+...\\\\\\\\\u0026amp;2.\\frac{分子}{(x^2+px+q)(其它分母)},则应拆成:\\\\\u0026amp;\\frac{Ax+B}{x^2+px+q}+...\\end{align}​1.(x+a)p(x+b)q分子​,则应拆成:(x+a)pA1​​+(x+a)p−1A2​​+(x+a)p−2A3​​+...+(x+a)q−1B1​​+(x+a)q−2B2​​+...2.(x2+px+q)(其它分母)分子​,则应拆成:x2+px+qAx+B​+...​​然后得到:\n分子=分式1分子+分式2分子+...\\begin{align}分子=分式1分子+分式2分子+...\\end{align}分子=分式1分子+分式2分子+...​​","permalink":"https://echo.ishian.me/2023/11/12/calculus-indefinite-int/","summary":"整理不定积分的基本公式、拓展公式、凑微分与换元等积分方法","title":"微积分: 不定积分"},{"content":"极限 证明数列极限存在 应当利用单调有界收敛原理\neg:\n已知x1=1，xn+1=11+xn，n=1,2,3,...，证明lim⁡n→∞xn存在证:∵xn+1−xn=−xn2+xn−11+xn\u0026gt;0，∴xn单调递减又xn\u0026gt;0恒成立，∴xn有界根据有界收敛原理，lim⁡n→∞xn存在，令A=11+A，得极限=1+52\\begin{align}\u0026amp;已知x_1=1，x_{n+1}=\\frac{1}{1+x_n}，n=1,2,3,...，证明\\lim_{n\\rightarrow\\infty}x_n存在\\\\\u0026amp;证:\\\\\u0026amp;\\because x_{n+1}-x_n=-\\frac{x_n^2+x_n-1}{1+x_n}\u0026gt;0，\\therefore x_n单调递减\\\\\u0026amp;又x_n\u0026gt;0恒成立，\\therefore x_n有界\\\\\u0026amp;根据有界收敛原理，\\lim_{n\\rightarrow\\infty}x_n存在，令A=\\frac{1}{1+A}，得极限=\\frac{1+\\sqrt5}{2}\\end{align}​已知x1​=1，xn+1​=1+xn​1​，n=1,2,3,...，证明n→∞lim​xn​存在证:∵xn+1​−xn​=−1+xn​xn2​+xn​−1​\u0026gt;0，∴xn​单调递减又xn​\u0026gt;0恒成立，∴xn​有界根据有界收敛原理，n→∞lim​xn​存在，令A=1+A1​，得极限=21+5​​​​证明极限不存在 判断极限存在时，一定要注意趋近值的两边\neg1:\n1.当x→1时，函数x2−1x−1e1x−1的极限1. 当x\\rightarrow1时，函数\\Large\\frac{x^2-1}{x-1}e^{\\frac{1}{x-1}}\\normalsize的极限1.当x→1时，函数x−1x2−1​ex−11​的极限\nA. 等于2 B. 等于0 C. 为∞\\infty∞ D. 不存在但不为∞\\infty∞\neg2:\n当x→x0时，判断左右极限当x→∞时，取相近的两无穷大数判断证明lim⁡x→+∞xsin⁡x不存在证:取x1=nπ，x2=nπ+π2易得lim⁡x→+∞f(x1)=0，lim⁡x→+∞f(x1)=+∞故不存在\\begin{align}\u0026amp;当x\\rightarrow x_0时，判断左右极限\\\\\u0026amp;当x\\rightarrow\\infty时，取相近的两无穷大数判断\\\\\u0026amp;证明\\lim_{x\\rightarrow+\\infty}x\\sin x不存在\\\\\u0026amp;证:取x_1=n\\pi，x_2=n\\pi+\\frac{\\pi}{2}\\\\\u0026amp;易得\\lim_{x\\rightarrow+\\infty}f(x_1)=0，\\lim_{x\\rightarrow+\\infty}f(x_1)=+\\infty\\\\\u0026amp;故不存在\\end{align}​当x→x0​时，判断左右极限当x→∞时，取相近的两无穷大数判断证明x→+∞lim​xsinx不存在证:取x1​=nπ，x2​=nπ+2π​易得x→+∞lim​f(x1​)=0，x→+∞lim​f(x1​)=+∞故不存在​​ eg3: 换元法将复杂问题变简单\nlim⁡x→1(1−x)2e1x−1解:lim⁡x→1−f(x)=0lim⁡x→1+f(x)=t=1x−1lim⁡t→+∞ett2=+∞故原极限不存在\\begin{align}\u0026amp;\\lim_{x\\rightarrow1}(1-x)^2e^{\\Large\\frac{1}{x-1}}\\\\\u0026amp;解:\\lim_{x\\rightarrow1^-}f(x)=0\\\\\u0026amp;\\lim_{x\\rightarrow1^+}f(x)\\xlongequal{\\Large t=\\frac{1}{x-1}}\\lim_{t\\rightarrow+\\infty}\\frac{e^t}{t^2}=+\\infty\\\\\u0026amp;故原极限不存在\\end{align}​x→1lim​(1−x)2ex−11​解:x→1−lim​f(x)=0x→1+lim​f(x)t=x−11​t→+∞lim​t2et​=+∞故原极限不存在​​判断数列/函数的极限 数列是特殊的，需要注意它是一个个点组成的，在判断有界无界，收敛发散时与函数不同\neg1:\n​ 2. 设数列 xn与yn满足lim⁡n→∞xnyn=0\\begin{aligned}x_n与y_n满足\\lim_{n\\rightarrow\\infty}x_ny_n=0\\end{aligned}xn​与yn​满足n→∞lim​xn​yn​=0​，则正确的是\n​ A. 若xnx_nxn​发散，则yny_nyn​必发散 B. 若xnx_nxn​无界，则yny_nyn​必有界\n​ C. 若xnx_nxn​有界，则yny_nyn​必为无穷小 D. 若\n1xn\\begin{align}\\frac{1}{x_n}\\end{align}xn​1​​​为无穷小，则yny_nyn​必为无穷小\neg2:\n关于数列{xnx_nxn​}，下列说法不正确的是\nA. 若lim⁡x→∞xn=a,则lim⁡x→∞x2n=lim⁡x→∞x2n−1=a\\begin{align}若\\lim_{x\\rightarrow\\infty}x_n=a,则\\lim_{x\\rightarrow\\infty}x_{2n}=\\lim_{x\\rightarrow\\infty}x_{2n-1}=a\\end{align}若x→∞lim​xn​=a,则x→∞lim​x2n​=x→∞lim​x2n−1​=a​​B. 若lim⁡x→∞x2n=lim⁡x→∞x2n−1=a,则lim⁡x→∞xn=a\\begin{align}若\\lim_{x\\rightarrow\\infty}x_{2n}=\\lim_{x\\rightarrow\\infty}x_{2n-1}=a,则\\lim_{x\\rightarrow\\infty}x_n=a\\end{align}若x→∞lim​x2n​=x→∞lim​x2n−1​=a,则x→∞lim​xn​=a​​C. 若lim⁡x→∞xn=a,则lim⁡x→∞x3n=lim⁡x→∞x3n−1=a\\begin{align}若\\lim_{x\\rightarrow\\infty}x_n=a,则\\lim_{x\\rightarrow\\infty}x_{3n}=\\lim_{x\\rightarrow\\infty}x_{3n-1}=a\\end{align}若x→∞lim​xn​=a,则x→∞lim​x3n​=x→∞lim​x3n−1​=a​​D. 若lim⁡x→∞x3n=lim⁡x→∞x3n−1=a,则lim⁡x→∞xn=a\\begin{align}若\\lim_{x\\rightarrow\\infty}x_{3n}=\\lim_{x\\rightarrow\\infty}x_{3n-1}=a,则\\lim_{x\\rightarrow\\infty}x_n=a\\end{align}若x→∞lim​x3n​=x→∞lim​x3n−1​=a,则x→∞lim​xn​=a​​ 函数中，注意∞\\infty∞是有符号的，在判断极限是否存在时需注意两边极限，例如\nlim⁡x→∞arctan⁡x，lim⁡x→1arctan⁡1x−1都不存在\\begin{align}\\lim_{x\\rightarrow\\infty}\\arctan x，\\lim_{x\\rightarrow1}\\arctan\\frac{1}{x-1}都不存在\\end{align}x→∞lim​arctanx，x→1lim​arctanx−11​都不存在​​分子或分母有理化 应用于去根号\n平方差 eg:\nlim⁡x→−∞x(x2+100+x)解:原式=lim⁡x→−∞100xx2+100−x=lim⁡x→−∞100−1+100x2−1=−50\\begin{align}\u0026amp;\\lim_{x\\rightarrow-\\infty}x(\\sqrt{x^2+100}+x)\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow-\\infty}\\frac{100x}{\\sqrt{x^2+100}-x}=\\lim_{x\\rightarrow-\\infty}\\frac{100}{-\\sqrt{1+\\Large\\frac{100}{x^2}}\\normalsize-1}=-50\\end{align}​x→−∞lim​x(x2+100​+x)解:原式=x→−∞lim​x2+100​−x100x​=x→−∞lim​−1+x2100​​−1100​=−50​​需要注意的是，由于x趋于负无穷，从根号里提出一个x时需要变号!\n三角函数内去根号 根据三角函数的周期性，可以让根号通过平方差进行更换\neg:\n求lim⁡x→01−cos⁡xcos⁡2xcos⁡3x3x2解:原式=lim⁡x→01−cos⁡x+cos⁡x(1−cos⁡2xcos⁡3x3)x2=lim⁡x→01−cos⁡x+cos⁡x(1−cos⁡2x+cos⁡2x(1−cos⁡3x3))x2=12+1+23=3\\begin{align}\u0026amp;求\\lim_{x\\rightarrow0}\\frac{1-\\cos x\\sqrt{\\cos{2x}}\\sqrt[3]{\\cos{3x}}}{x^2}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{1-\\cos x+\\cos x(1-\\sqrt{\\cos{2x}}\\sqrt[3]{\\cos{3x}})}{x^2}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{1-\\cos x+\\cos x(1-\\sqrt{\\cos{2x}}+\\sqrt{\\cos{2x}}(1-\\sqrt[3]{\\cos{3x}}))}{x^2}\\\\\u0026amp;=\\frac{1}{2}+1+\\frac{2}{3}=3\\end{align}​求x→0lim​x21−cosxcos2x​3cos3x​​解:原式=x→0lim​x21−cosx+cosx(1−cos2x​3cos3x​)​=x→0lim​x21−cosx+cosx(1−cos2x​+cos2x​(1−3cos3x​))​=21​+1+32​=3​​ 更多例子(由于typora导出时会强制替换为绝对路径，又没有服务器供存放附件，所以link失效)\n倒代换 对于趋于无穷的x，建议先做一次倒代换，这能使问题更清晰，有时甚至可以直接解出题目\n当然，从根号提出时一定注意x的正负!\n还有一个好处是，对于sin⁡x\\sin xsinx这类x趋于无穷时是一个有界量，x趋于0时sin x~x的易混淆的极限值不容易被坑\n还有类似ex，cos⁡x，xαe^x，\\cos x，x^\\alphaex，cosx，xα等等\neg1:\n设\nf(x)=tan⁡x∣x∣arctan⁡1x，则\\begin{align}f(x)=\\frac{\\tan x}{|x|}\\arctan\\frac{1}{x}，则\\end{align}f(x)=∣x∣tanx​arctanx1​，则​​\nA. x=0是振荡间断点 B. x=0是无穷间断点 C. x=0是可去间断点 D. x=0是跳跃间断点\neg2:\nlim⁡x→+∞x3(x3+xx6+x3+13−sin⁡1x)解:原式=lim⁡x→01x3(x5+x3x6+x3+13−sin⁡x)=lim⁡x→0(x(x2+1x6+x3+13−1)+x−sin⁡x)x3=lim⁡x→0−x6−x3+x23x2(x6+x3+1)+16=12\\begin{align}\u0026amp;\\lim_{x\\rightarrow+\\infty}x^3\\Bigg(\\sqrt[3]{\\frac{x^3+x}{x^6+x^3+1}}-\\sin{\\frac{1}{x}}\\Bigg)\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{1}{x^3}\\Bigg(\\sqrt[3]{\\frac{x^5+x^3}{x^6+x^3+1}}-\\sin x\\Bigg)=\\lim_{x\\rightarrow0}\\frac{\\Bigg(x\\Big(\\Large\\sqrt[3]{\\frac{x^2+1}{x^6+x^3+1}}\\normalsize-1\\Big)+x-\\sin x\\Bigg)}{x^3}\\\\\u0026amp;=\\lim_{x\\rightarrow0}\\frac{-x^6-x^3+x^2}{3x^2(x^6+x^3+1)}+\\frac{1}{6}=\\frac{1}{2}\\end{align}​x→+∞lim​x3(3x6+x3+1x3+x​​−sinx1​)解:原式=x→0lim​x31​(3x6+x3+1x5+x3​​−sinx)=x→0lim​x3(x(3x6+x3+1x2+1​​−1)+x−sinx)​=x→0lim​3x2(x6+x3+1)−x6−x3+x2​+61​=21​​​多项式/多因式极限 夹逼定理 在求多项式子和的极限时，可以放缩求极限或者化为定积分。在这里只讨论前者\n例如\nlim⁡n→∞f(n)n=1\\begin{align}\\lim_{n\\rightarrow\\infty}\\sqrt[n]{f(n)}=1\\end{align}n→∞lim​nf(n)​=1​​ eg1:\nlim⁡n→+∞(nn2+π+nn2+2π+...+nn2+nπ)解:原式≤lim⁡n→+∞(n2n2+π)=1原式≥lim⁡n→+∞(n2n2+nπ)=1故原式=1\\begin{align}\u0026amp;\\lim_{n\\rightarrow+\\infty}(\\frac{n}{n^2+\\pi}+\\frac{n}{n^2+2\\pi}+...+\\frac{n}{n^2+n\\pi})\\\\\u0026amp;解:原式\\le\\lim_{n\\rightarrow+\\infty}(\\frac{n^2}{n^2+\\pi})=1\\\\\u0026amp;原式\\ge\\lim_{n\\rightarrow+\\infty}(\\frac{n^2}{n^2+n\\pi})=1\\\\\u0026amp;故原式=1\\end{align}​n→+∞lim​(n2+πn​+n2+2πn​+...+n2+nπn​)解:原式≤n→+∞lim​(n2+πn2​)=1原式≥n→+∞lim​(n2+nπn2​)=1故原式=1​​ eg2: 利用回归法证明不等式后夹逼\n证明不等式12n\u0026lt;12⋅34⋅...⋅2n−12n\u0026lt;12n+1，并求lim⁡n→∞12⋅34⋅...⋅2n−12n证:1). 中间式子=32⋅54⋅...⋅2n−12n−2⋅12n\u0026gt;12n2). 两边平方，得1⋅322⋅3⋅542⋅...⋅(2n−1)(2n+1)(2n)2⋅12n+1\u0026lt;12n+1∴lim⁡n→∞12⋅34⋅...⋅2n−12n=0\\begin{align}\u0026amp;证明不等式\\frac{1}{2n}\u0026lt;\\frac{1}{2}·\\frac{3}{4}·...·\\frac{2n-1}{2n}\u0026lt;\\frac{1}{\\sqrt{2n+1}}，并求\\lim_{n\\rightarrow\\infty}\\frac{1}{2}·\\frac{3}{4}·...·\\frac{2n-1}{2n}\\\\\u0026amp;证:1).\\ 中间式子=\\frac{3}{2}·\\frac{5}{4}·...·\\frac{2n-1}{2n-2}·\\frac{1}{2n}\u0026gt;\\frac{1}{2n}\\\\\u0026amp;2).\\ 两边平方，得\\frac{1·3}{2^2}·\\frac{3·5}{4^2}·...·\\frac{(2n-1)(2n+1)}{(2n)^2}·\\frac{1}{2n+1}\u0026lt;\\frac{1}{2n+1}\\\\\u0026amp;\\therefore\\lim_{n\\rightarrow\\infty}\\frac{1}{2}·\\frac{3}{4}·...·\\frac{2n-1}{2n}=0\\end{align}​证明不等式2n1​\u0026lt;21​⋅43​⋅...⋅2n2n−1​\u0026lt;2n+1​1​，并求n→∞lim​21​⋅43​⋅...⋅2n2n−1​证:1). 中间式子=23​⋅45​⋅...⋅2n−22n−1​⋅2n1​\u0026gt;2n1​2). 两边平方，得221⋅3​⋅423⋅5​⋅...⋅(2n)2(2n−1)(2n+1)​⋅2n+11​\u0026lt;2n+11​∴n→∞lim​21​⋅43​⋅...⋅2n2n−1​=0​​合并后求极限 观察式子，可以加减或乘除式子可以将原式合并(回归法)，或根据等比/等差数列求和\neg1:\nlim⁡n→∞(1n2+1+2n2+2+...+nn2+n)解:原式≤lim⁡n→∞1+2+...+nn2=lim⁡n→∞n(n+1)2n2=12\\begin{align}\u0026amp;\\lim_{n\\rightarrow\\infty}(\\frac{1}{n^2+1}+\\frac{2}{n^2+2}+...+\\frac{n}{n^2+n})\\\\\u0026amp;解:原式\\le\\lim_{n\\rightarrow\\infty}\\frac{1+2+...+n}{n^2}=\\lim_{n\\rightarrow\\infty}\\frac{n(n+1)}{2n^2}=\\frac{1}{2}\\end{align}​n→∞lim​(n2+11​+n2+22​+...+n2+nn​)解:原式≤n→∞lim​n21+2+...+n​=n→∞lim​2n2n(n+1)​=21​​​原式≥lim⁡n→∞1+2+...+nn2+n=lim⁡n→∞n(n+1)2(n2+n)=12\\begin{align}\u0026amp;原式\\ge\\lim_{n\\rightarrow\\infty}\\frac{1+2+...+n}{n^2+n}=\\lim_{n\\rightarrow\\infty}\\frac{n(n+1)}{2(n^2+n)}=\\frac{1}{2}\\end{align}​原式≥n→∞lim​n2+n1+2+...+n​=n→∞lim​2(n2+n)n(n+1)​=21​​​ eg2:\nlim⁡n→+∞(1+x)(1+x2)...(1+x2n)，(∣x∣≤1)解:原式=lim⁡n→+∞(1−x)(1+x)(1+x2)...(1+x2n)1−x=1−x2n+11−x=11−x\\begin{align}\u0026amp;\\lim_{n\\rightarrow+\\infty}(1+x)(1+x^2)...(1+x^{2^n})，(|x|\\le1)\\\\\u0026amp;解:原式=\\lim_{n\\rightarrow+\\infty}\\frac{(1-x)(1+x)(1+x^2)...(1+x^{2^n})}{1-x}=\\frac{1-x^{2^{n+1}}}{1-x}=\\frac{1}{1-x}\\end{align}​n→+∞lim​(1+x)(1+x2)...(1+x2n)，(∣x∣≤1)解:原式=n→+∞lim​1−x(1−x)(1+x)(1+x2)...(1+x2n)​=1−x1−x2n+1​=1−x1​​​ eg3:\nlim⁡n→∞(1+1n2)⋅(1+2n2)⋅...⋅(1+nn2)解:原式=elim⁡n→∞ln⁡(1+1n2)+ln⁡(1+2n2)+...+ln⁡(1+nn2)=elim⁡n→∞n(n+1)2n2=e12\\begin{align}\u0026amp;\\lim_{n\\rightarrow\\infty}(1+\\frac{1}{n^2})·(1+\\frac{2}{n^2})·...·(1+\\frac{n}{n^2})\\\\\u0026amp;解:原式=e^{\\Large\\lim_{n\\rightarrow\\infty}\\ln(1+\\frac{1}{n^2})+\\ln(1+\\frac{2}{n^2})+...+\\ln(1+\\frac{n}{n^2})}\\\\\u0026amp;=e^{\\Large\\lim_{n\\rightarrow\\infty}\\frac{n(n+1)}{2n^2}}=e^{\\frac{1}{2}}\\end{align}​n→∞lim​(1+n21​)⋅(1+n22​)⋅...⋅(1+n2n​)解:原式=elimn→∞​ln(1+n21​)+ln(1+n22​)+...+ln(1+n2n​)=elimn→∞​2n2n(n+1)​=e21​​​求同底数或同幂数项的极限 并非一定是把后面项提出，提出一项的目的是让括号内式子**变为\u0026quot;1+0\u0026quot;或\u0026quot;1+−11^+-11+−1\u0026quot;**的形式\neg:\n求f(x)=lim⁡n→∞ln⁡(en+xn)n解:1).当x\u0026lt;e时，原式=lim⁡n→∞ln⁡en+ln⁡(1+(xe)n)n=12).当x=e时，原式=13).当x\u0026gt;e时，原式=lim⁡n→∞ln⁡xn+ln⁡(1+(ex)n)n=ln⁡x\\begin{align}\u0026amp;求f(x)=\\lim_{n\\rightarrow\\infty}\\frac{\\ln{(e^n+x^n)}}{n}\\\\\u0026amp;解:1).当x\\lt e时，原式=\\lim_{n\\rightarrow\\infty}\\frac{\\ln{e^n}+\\ln{(1+\\Large(\\frac{x}{e})^n)}}{n}\\normalsize=1\\\\\u0026amp;2).当x=e时，原式=1\\\\\u0026amp;3).当x\\gt e时，原式=\\lim_{n\\rightarrow\\infty}\\frac{\\ln{x^n}+\\ln{(1+\\Large(\\frac{e}{x})^n)}}{n}\\normalsize=\\ln x\\end{align}​求f(x)=n→∞lim​nln(en+xn)​解:1).当x\u0026lt;e时，原式=n→∞lim​nlnen+ln(1+(ex​)n)​=12).当x=e时，原式=13).当x\u0026gt;e时，原式=n→∞lim​nlnxn+ln(1+(xe​)n)​=lnx​​极限中参数的确定 先确定带变量的值，再确定其他参数，有时需要通过洛必达法则确定参数\neg:\nlim⁡x→+∞(1−x63−ax2−b)=0，求a和b的值解:原式=lim⁡x→+∞[x2(1x6−13−a)−b]，得−1−a=0，即a=−1∴b=lim⁡x→+∞(1−x63−ax2)=lim⁡x→+∞x213x6=0\\begin{align}\u0026amp;\\lim_{x\\rightarrow+\\infty}(\\sqrt[3]{1-x^6}-ax^2-b)=0，求a和b的值\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow+\\infty}\\Big[x^2(\\sqrt[3]{\\frac{1}{x^6}-1}-a)-b\\Big]，得-1-a=0，即a=-1\\\\\u0026amp;\\therefore b=\\lim_{x\\rightarrow+\\infty}(\\sqrt[3]{1-x^6}-ax^2)=\\lim_{x\\rightarrow+\\infty}x^2\\frac{1}{3x^6}=0\\end{align}​x→+∞lim​(31−x6​−ax2−b)=0，求a和b的值解:原式=x→+∞lim​[x2(3x61​−1​−a)−b]，得−1−a=0，即a=−1∴b=x→+∞lim​(31−x6​−ax2)=x→+∞lim​x23x61​=0​​ 关于计算极限时需要注意的细节 有限个无穷小的和或积是无穷小，无限个项则不成立\n无穷小与有界量的乘积是无穷小\n计算极限时不能随意进行代入或等价无穷小替换\n3.3. 原因：极限是整体变化的，但是有时候这样替换是正确的，这是因为我们把四则运算的过程简化了\n这一问题的更详细的说明是：直接代入或等价无穷小替换是有精度的，分母越小，越会对替换带来的变化起反应，导致极限运算错误\n判断方法：进行四则运算拆开时，需要两项因式极限都存在才可以进行替换\n简单例子:\nlim⁡x→0x−sin⁡xx3=lim⁡x→01x2−lim⁡x→0sin⁡xx3=∞−∞，不能替换lim⁡x→0x+sin⁡xx=lim⁡x→01+lim⁡x→0sin⁡xx=1+1，可以替换\\begin{align}\u0026amp;\\lim_{x\\rightarrow0}\\frac{x-\\sin x}{x^3}=\\lim_{x\\rightarrow0}\\frac{1}{x^2}-\\lim_{x\\rightarrow0}\\frac{\\sin x}{x^3}=\\infty-\\infty，不能替换\\\\\u0026amp;\\lim_{x\\rightarrow0}\\frac{x+\\sin x}{x}=\\lim_{x\\rightarrow0}1+\\lim_{x\\rightarrow0}\\frac{\\sin x}{x}=1+1，可以替换\\end{align}​x→0lim​x3x−sinx​=x→0lim​x21​−x→0lim​x3sinx​=∞−∞，不能替换x→0lim​xx+sinx​=x→0lim​1+x→0lim​xsinx​=1+1，可以替换​​经典例子 eg1:\nlim⁡x→0xcos⁡x−sin⁡xx3解:原式=lim⁡x→0xcos⁡x−x+x−sin⁡xx3=−13\\begin{align}\u0026amp;\\lim_{x\\rightarrow0}\\frac{x\\cos x-\\sin x}{x^3}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow0}\\frac{x\\cos x-x+x-\\sin x}{x^3}=-\\frac{1}{3}\\end{align}​x→0lim​x3xcosx−sinx​解:原式=x→0lim​x3xcosx−x+x−sinx​=−31​​​ eg2: 这是一个隐含四则运算的极限，不能直接套用lim⁡x→∞(1+1x)x=ex\\lim_{x\\rightarrow\\infty}{(1+\\large\\frac{1}{x})^x}=e^xlimx→∞​(1+x1​)x=ex，最后用到洛必达法则\nlim⁡x→+∞(1+1x)xex解:原式=lim⁡x→+∞exln⁡(1+1x)ex=lim⁡x→+∞ex2(ln⁡(1+1x)−1x)=elim⁡x→0ln⁡(1+x)−xx2=e−12\\begin{align}\u0026amp;\\lim_{x\\rightarrow+\\infty}\\frac{(1+\\large\\frac{1}{x})^x}{e^x}\\\\\u0026amp;解:原式=\\lim_{x\\rightarrow+\\infty}\\frac{e^{x\\ln(1+\\Large\\frac{1}{x})}}{e^x}=\\lim_{x\\rightarrow+\\infty}e^{x^2\\Big(\\ln(1+\\Large\\frac{1}{x}\\normalsize)-\\Large\\frac{1}{x}\\normalsize\\Big)}=e^{\\lim_{x\\rightarrow0}\\large\\frac{\\Large\\ln(1+x)-x}{x^2}}=e^{-\\frac{1}{2}}\\end{align}​x→+∞lim​ex(1+x1​)x​解:原式=x→+∞lim​exexln(1+x1​)​=x→+∞lim​ex2(ln(1+x1​)−x1​)=elimx→0​x2ln(1+x)−x​=e−21​​​ eg3:\nlim⁡x→+∞ex(1+1x)x2解:原式=elim⁡x→+∞x−x2ln⁡(1+1x)=elim⁡x→0x−ln⁡(1+x)x2=e12\\begin{align}\u0026amp;\\lim_{x\\rightarrow+\\infty}\\frac{e^x}{(1+\\large\\frac{1}{x})^{x^2}}\\\\\u0026amp;解:原式=e^{\\Large\\lim_{x\\rightarrow+\\infty}x-x^2\\ln(1+\\frac{1}{x})}=e^{\\Large\\lim_{x\\rightarrow0}\\LARGE\\frac{x-\\ln(1+x)}{x^2}}=e^\\frac{1}{2}\\end{align}​x→+∞lim​(1+x1​)x2ex​解:原式=elimx→+∞​x−x2ln(1+x1​)=elimx→0​x2x−ln(1+x)​=e21​​​ 发现不能直接替换后，可以尝试泰勒展开或者**回归法(加一项减一项)**来解题\n定理证明题 介值定理：在闭区间[a,b][a,b][a,b]上的连续函数任取一点，该点必定满足m≤f(x)≤Mm\\le f(x)\\le Mm≤f(x)≤M\n引申出零点定理：如果存在两点异号，则必定存在一点∈(a,b)\\in(a,b)∈(a,b)满足f(x)=0f(x)=0f(x)=0\n保号性定理：如果有\nlim⁡x→x0f(x)\u0026gt;0\\begin{align}\\lim_{x\\rightarrow x_0}f(x)\u0026gt;0\\end{align}x→x0​lim​f(x)\u0026gt;0​​，则在x0x_0x0​某去心邻域内，f(x)\u0026gt;0f(x)\u0026gt;0f(x)\u0026gt;0成立\neg1:\n设f(x)在[0,1]上连续，f(1)=0，lim⁡x→12f(x)−1(x−12)2=1，证明:1).存在ξ∈(12,1)，使f(ξ)=ξ 2).f(x)在[0,1]上最大值大于1解:1)由洛必达法则，f(12)=1令F(x)=f(x)−x，易得{F(1)=−1F(12)=12∴连续函数F(x)存在一点ξ∈(12,1)，使得F(ξ)=02).∵lim⁡x→12f(x)−1(x−12)2=1\u0026gt;0由保号性可知，当x∈(12−δx,12)∪(12,12+δx)时，f(x)−1(x−12)2\u0026gt;0∴在这个去心邻域内f(x)\u0026gt;1\\begin{align}\u0026amp;设f(x)在[0,1]上连续，f(1)=0，\\lim_{x\\rightarrow\\frac{1}{2}}\\frac{f(x)-1}{\\large(x-\\frac{1}{2})^{\\normalsize2}}=1，证明:\\\\\u0026amp;1). 存在\\xi\\in(\\frac{1}{2},1)，使f(\\xi)=\\xi\\ \\ \\ \\ \\ 2). f(x)在[0,1]上最大值大于1\\\\\u0026amp;解:1)由洛必达法则，f(\\frac{1}{2})=1\\\\\u0026amp;令F(x)=f(x)-x，易得\\begin{cases}F(1)=-1\\\\F(\\large\\frac{1}{2})=\\frac{1}{2}\\end{cases}\\\\\u0026amp;\\therefore 连续函数F(x)存在一点\\xi\\in(\\frac{1}{2},1)，使得F(\\xi)=0\\\\\u0026amp;2). \\because\\lim_{x\\rightarrow\\frac{1}{2}}\\frac{f(x)-1}{\\large(x-\\frac{1}{2})^{\\normalsize2}}=1\u0026gt;0\\\\\u0026amp;由保号性可知，当x\\in(\\frac{1}{2}-\\delta x,\\frac{1}{2})\\cup(\\frac{1}{2},\\frac{1}{2}+\\delta x)时，\\frac{f(x)-1}{\\large(x-\\frac{1}{2})^{\\normalsize2}}\u0026gt;0\\\\\u0026amp;\\therefore在这个去心邻域内f(x)\u0026gt;1\\end{align}​设f(x)在[0,1]上连续，f(1)=0，x→21​lim​(x−21​)2f(x)−1​=1，证明:1).存在ξ∈(21​,1)，使f(ξ)=ξ 2).f(x)在[0,1]上最大值大于1解:1)由洛必达法则，f(21​)=1令F(x)=f(x)−x，易得{F(1)=−1F(21​)=21​​∴连续函数F(x)存在一点ξ∈(21​,1)，使得F(ξ)=02).∵x→21​lim​(x−21​)2f(x)−1​=1\u0026gt;0由保号性可知，当x∈(21​−δx,21​)∪(21​,21​+δx)时，(x−21​)2f(x)−1​\u0026gt;0∴在这个去心邻域内f(x)\u0026gt;1​​ eg2:\n设a2m\u0026lt;0a_{2m}\u0026lt;0a2m​\u0026lt;0，证明：x2m+x2m−1a1+...+xa2m−1+a2m=0x^{2m}+x^{2m-1}a_1+...+xa_{2m-1}+a_{2m}=0x2m+x2m−1a1​+...+xa2m−1​+a2m​=0至少有两个零点\n证:左边=x2m(1+a1x+...+a2m−1x2m−1+a2mx2m)当x→∞时，原式=+∞又当x=0时，原式=a2m\u0026lt;0∴对(−∞,0)和(0,+∞)分别使用零点定理，得证\\begin{align}\u0026amp;证:左边=x^{2m}\\Big(1+\\frac{a_1}{x}+...+\\frac{a_{2m-1}}{x^{2m-1}}+\\frac{a_{2m}}{x^{2m}}\\Big)\\\\\u0026amp;当x\\rightarrow\\infty时，原式=+\\infty\\\\\u0026amp;又当x=0时，原式=a_{2m}\u0026lt;0\\\\\u0026amp;\\therefore对(-\\infty,0)和(0,+\\infty)分别使用零点定理，得证\\end{align}​证:左边=x2m(1+xa1​​+...+x2m−1a2m−1​​+x2ma2m​​)当x→∞时，原式=+∞又当x=0时，原式=a2m​\u0026lt;0∴对(−∞,0)和(0,+∞)分别使用零点定理，得证​​ eg3:\n设f(x)在[0,n](n∈N∗且n≥2)上连续，f(0)=f(n)，证明:存在ξ,ξ+1∈[0,n],使f(ξ)=f(ξ+1)证:令F(x)=f(x)−f(x+1)则F(0)=f(0)−f(1),F(1)=f(1)−f(2),...,F(n−1)=f(n−1)−f(n)合并得∑i=0n−1F(i)=f(0)−f(n)=0设Fmin=m,Fmax=M,可知nm≤∑i=0n−1F(i)≤nM即m≤1n∑i=0n−1F(i)≤M由介值定理可知，存在ξ∈[0,n−1],使f(ξ)=1n∑i=0n−1F(i)=0\\begin{align}\u0026amp;设f(x)在[0,n](n\\in N^*且n\\ge2)上连续，f(0)=f(n)，证明:存在\\xi,\\xi+1\\in[0,n],使f(\\xi)=f(\\xi+1)\\\\\u0026amp;证:令F(x)=f(x)-f(x+1)\\\\\u0026amp;则F(0)=f(0)-f(1),F(1)=f(1)-f(2),...,F(n-1)=f(n-1)-f(n)\\\\\u0026amp;合并得\\sum_{i=0}^{n-1}F(i)=f(0)-f(n)=0\\\\\u0026amp;设F_{min}=m,F_{max}=M,可知nm\\le\\sum_{i=0}^{n-1}F(i)\\le nM\\\\\u0026amp;即m\\le\\frac{1}{n}\\sum_{i=0}^{n-1}F(i)\\le M\\\\\u0026amp;由介值定理可知，存在\\xi\\in[0,n-1],使f(\\xi)=\\frac{1}{n}\\sum_{i=0}^{n-1}F(i)=0\\end{align}​设f(x)在[0,n](n∈N∗且n≥2)上连续，f(0)=f(n)，证明:存在ξ,ξ+1∈[0,n],使f(ξ)=f(ξ+1)证:令F(x)=f(x)−f(x+1)则F(0)=f(0)−f(1),F(1)=f(1)−f(2),...,F(n−1)=f(n−1)−f(n)合并得i=0∑n−1​F(i)=f(0)−f(n)=0设Fmin​=m,Fmax​=M,可知nm≤i=0∑n−1​F(i)≤nM即m≤n1​i=0∑n−1​F(i)≤M由介值定理可知，存在ξ∈[0,n−1],使f(ξ)=n1​i=0∑n−1​F(i)=0​​总结 求单项极限时，应想到回归法凑无穷小、换元法或平方差解决式子有理化问题、提出同幂或同底项、倒代换、化为e的对数、上下同除等技巧\n求多项和的极限时，应想到合并、夹逼、定积分，求多项积的极限时，应想到化为e的对数，拆开各因式\n求三角函数的极限时，运用和差化积或回归法(利用周期性)\n从根号中提出x时，一定注意是否需要变号，这在判断两边极限(或极限是否存在)问题时很关键\n对待数列时，应注意数列的特殊性，即非连续而是一点一点的，且n∈N∗n\\in N^*n∈N∗\n证明极限时，想到单调有界收敛原理\n证明式子时，应想到作辅助函数\n部分答案及解析 1. D\n​ 此题是经典的xα，α趋向正负无穷时是不一样的x^\\alpha，\\alpha趋向正负无穷时是不一样的xα，α趋向正负无穷时是不一样的\n2. D\n​ 此处只探讨B和D。\n​ B. 由于两者是数列，那么让前者为{0,1,0,1,\u0026hellip;}，让后者为{1,0,1,0,\u0026hellip;}，它们都无界，而乘积的极限为0\n​ D. 原式\n=lim⁡n→+∞yn1xn=0\\begin{align}=\\lim_{n\\rightarrow+\\infty}\\frac{y_n}{\\large\\frac{1}{x_n}}=0\\end{align}=n→+∞lim​xn​1​yn​​=0​​ 3. D\n4. C\n","permalink":"https://echo.ishian.me/2023/11/05/calculus-limit-exercise/","summary":"极限练习题集，讲解数列极限存在性证明与极限不存在的判定方法","title":"微积分: 极限练习题"},{"content":"极限 [注意]关于极限 x→0时,cos⁡1x,sin⁡1x都为有界量,和x相乘等于无穷小\\begin{align}x\\rightarrow 0时,\\cos \\frac{1}{x},\\sin \\frac{1}{x}都为有界量,和x相乘等于无穷小\\end{align}x→0时,cosx1​,sinx1​都为有界量,和x相乘等于无穷小​​ 合理利用换元,如求lim⁡x→0(xx+1)x可令t=x+1\\begin{align}合理利用换元,如求\\lim_{x\\rightarrow 0}(\\frac{x}{x+1})^x可令t=x+1\\end{align}合理利用换元,如求x→0lim​(x+1x​)x可令t=x+1​​ 合理利用夹逼定理，如求多项式极限时，有时也会用到定积分 求式子中参数a、b时，若一边为0，可猜测一边也为0(因为这样才有不为0的极限存在)，用洛必达法则求出参数 求反函数 虽然并不属于极限的范畴，但是是有可能考到的题型\n总的来说就是让newY=oldX，newX=f(x)\nnewX的范围就是f(x)的值域\neg:\nf(x)={1−2x2,x\u0026lt;−1x3,−1≤x≤212x−16,x\u0026gt;2求f−1(x)解:1).当x\u0026lt;−1时，f−1(x)=1−x2，x\u0026lt;−12).当−1≤x≤2时，f−1(x)=x3，−1≤x\u0026lt;83).当x\u0026gt;2时，f−1(x)=16+x12，x\u0026gt;8\\begin{align}\u0026amp;f(x)=\\begin{cases}1-2x^2,\u0026amp;x\\lt-1\\\\x^3,\u0026amp;-1\\le x\\le2\\\\12x-16,\u0026amp;x\\gt2\\end{cases}\\\\\u0026amp;求f^{-1}(x)\\\\\u0026amp;解:1).当x\\lt-1时，f^{-1}(x)=\\sqrt\\frac{1-x}{2}，x\\lt-1\\\\\u0026amp;2).当-1\\le x\\le2时，f^{-1}(x)=\\sqrt[3]{x}，-1\\le x\\lt8\\\\\u0026amp;3).当x\\gt2时，f^{-1}(x)=\\frac{16+x}{12}，x\u0026gt;8\\end{align}​f(x)=⎩⎨⎧​1−2x2,x3,12x−16,​x\u0026lt;−1−1≤x≤2x\u0026gt;2​求f−1(x)解:1).当x\u0026lt;−1时，f−1(x)=21−x​​，x\u0026lt;−12).当−1≤x≤2时，f−1(x)=3x​，−1≤x\u0026lt;83).当x\u0026gt;2时，f−1(x)=1216+x​，x\u0026gt;8​​ [题型]利用单调有界收敛定理证明函数极限存在 def：单调（增减）且有（上下）界的数列一定收敛\n放缩法证明其有（上/下）界\n观察法证明其单调性\n合理利用假设法\neg:设c\u0026gt;0,a1=c,且an+1=c+an.证明数列an收敛,并求其极限值证明:∵a2=c+a1\u0026gt;c=a1.假设an\u0026gt;an−1,则有an+1=c+an\u0026gt;c+an−1=an∴an单调递增又∵a2\u0026lt;c+1.假设an\u0026lt;c+1则有an+1=c+an\u0026lt;c+c+1\u0026lt;c+1∴an有上确界∴lim⁡n→∞an=c+1\\begin{align}\u0026amp;eg:\\\\ \u0026amp;设c\u0026gt;0,a_1=\\sqrt c,且a_{n+1}=\\sqrt{c+a_n}.证明数列{a_n}收敛,并求其极限值\\\\\u0026amp;证明:\\\\\u0026amp;\\because a_2=\\sqrt{c+a_1}\u0026gt;\\sqrt c=a_1.\\\\\u0026amp;假设a_n\u0026gt;a_n-1,则有a_{n+1}=\\sqrt{c+a_n}\u0026gt;\\sqrt{c+a_{n-1}}=a_n\\\\\u0026amp;\\therefore a_n单调递增\\\\\u0026amp;又\\because a_2\u0026lt;\\sqrt c+1.假设a_n\u0026lt;\\sqrt c+1\\\\\u0026amp;则有a_{n+1}=\\sqrt{c+a_n}\u0026lt;\\sqrt{c+\\sqrt c+1}\u0026lt;\\sqrt c+1\\\\\u0026amp;\\therefore a_n有上确界\\\\\u0026amp;\\therefore \\lim_{n\\rightarrow\\infty}a_n=\\sqrt c+1\\end{align}​eg:设c\u0026gt;0,a1​=c​,且an+1​=c+an​​.证明数列an​收敛,并求其极限值证明:∵a2​=c+a1​​\u0026gt;c​=a1​.假设an​\u0026gt;an​−1,则有an+1​=c+an​​\u0026gt;c+an−1​​=an​∴an​单调递增又∵a2​\u0026lt;c​+1.假设an​\u0026lt;c​+1则有an+1​=c+an​​\u0026lt;c+c​+1​\u0026lt;c​+1∴an​有上确界∴n→∞lim​an​=c​+1​​ 求极限 [题型]同底数异指数或同指数异底数(常考确定某个常量的值) 提出后一项求极限\neg:\n1):同指数异底数已知极限lim⁡n→∞nα(n+1)β−nβ=2017,求α,β解：∵原式=lim⁡n→∞nαnβ((n+1n)β−1)=lim⁡n→∞nαnβ(eβln⁡(n+1n)−1)=lim⁡n→∞nαnβ(βln⁡(n+1n))=lim⁡n→∞nαnβ1nβ=lim⁡n→∞nαnβ−1β=2017∴易得α=β−1,β=12017∴α=−20162017\\begin{align} \u0026amp;1):同指数异底数\\\\\u0026amp; 已知极限\\lim_{n\\rightarrow\\infty}\\frac{n^\\alpha}{(n+1)^\\beta-n^\\beta}=2017,求\\alpha,\\beta\\\\\u0026amp; 解：\\\\\u0026amp; \\because 原式=\\lim_{n\\rightarrow\\infty}\\frac{n^\\alpha}{n^\\beta((\\frac{n+1}{n})^\\beta-1)}\\\\\u0026amp; =\\lim_{n\\rightarrow\\infty}\\frac{n^\\alpha}{n^\\beta(e^{\\beta \\ln(\\frac{n+1}{n})}-1)}\\\\\u0026amp; =\\lim_{n\\rightarrow\\infty}\\frac{n^\\alpha}{n^\\beta(\\beta\\ln(\\frac{n+1}{n}))}\\\\\u0026amp; =\\lim_{n\\rightarrow\\infty}\\frac{n^\\alpha}{n^\\beta\\frac{1}{n}\\beta}=\\lim_{n\\rightarrow\\infty}\\frac{n^\\alpha}{n^{\\beta-1}\\beta}=2017\\\\\u0026amp; \\therefore 易得\\alpha=\\beta-1,\\beta=\\frac{1}{2017}\\\\\u0026amp; \\therefore \\alpha=-\\frac{2016}{2017}\\end{align}​1):同指数异底数已知极限n→∞lim​(n+1)β−nβnα​=2017,求α,β解：∵原式=n→∞lim​nβ((nn+1​)β−1)nα​=n→∞lim​nβ(eβln(nn+1​)−1)nα​=n→∞lim​nβ(βln(nn+1​))nα​=n→∞lim​nβn1​βnα​=n→∞lim​nβ−1βnα​=2017∴易得α=β−1,β=20171​∴α=−20172016​​​ 2):同底数异指数lim⁡x→0ex−e3xxk=c,c为非零常数,k为常数,求c,k解：∵原式=lim⁡x→0e3x(ex−3x−1)xk=lim⁡x→0e0(x−3x)xk=c∴k=13,c=−1\\begin{align}\u0026amp;2):同底数异指数\\\\\u0026amp; \\lim_{x\\rightarrow 0}\\frac{e^x-e^{\\sqrt{3}{x}}}{x^k}=c,c为非零常数,k为常数,求c,k\\\\\u0026amp; 解：\\\\\u0026amp; \\because 原式=\\lim_{x\\rightarrow 0}\\frac{e^{\\sqrt{3}{x}}(e^{x-\\sqrt{3}{x}}-1)}{x^k}\\\\\u0026amp; =\\lim_{x\\rightarrow 0}\\frac{e^0(x-\\sqrt{3}{x})}{x^k}=c\\\\\u0026amp; \\therefore k=\\frac{1}{3},c=-1\\end{align}​2):同底数异指数x→0lim​xkex−e3​x​=c,c为非零常数,k为常数,求c,k解：∵原式=x→0lim​xke3​x(ex−3​x−1)​=x→0lim​xke0(x−3​x)​=c∴k=31​,c=−1​​ [题型]上下同除 利用除以x3x^3x3和四则运算求极限\neg:计算lim⁡x→0tan⁡x−x(1+x−1)ln⁡(x2+1)+x4⋅cos⁡1x解：原式=lim⁡x→0tan⁡x−xx3(1+x−1)ln⁡(x2+1)+x4⋅cos⁡1xx3=23\\begin{align}\u0026amp;eg:\\\\\u0026amp; 计算\\lim_{x\\rightarrow 0}\\frac{\\tan x-x}{(\\sqrt{1+x}-1)\\ln(x^2+1)+x^4·\\cos \\frac{1}{x}}\\\\\u0026amp; 解：原式=\\lim_{x\\rightarrow 0}\\frac{\\frac{\\tan x-x}{x^3}}{\\frac{(\\sqrt{1+x}-1)\\ln(x^2+1)+x^4·\\cos \\frac{1}{x}}{x^3}}=\\frac{2}{3}\\end{align}​eg:计算x→0lim​(1+x​−1)ln(x2+1)+x4⋅cosx1​tanx−x​解：原式=x→0lim​x3(1+x​−1)ln(x2+1)+x4⋅cosx1​​x3tanx−x​​=32​​​ $\n[题型]间断点判断 找出间断点\n1.1. 寻找间断点时，这个点可能是未定义的，也可能是两边极限不等的\n1.2. 具体例子为：前者{1x,x=0ln⁡x,x=0\\begin{cases}\\large\\frac{1}{x}\\normalsize,\u0026amp;x=0\\\\\\ln x,\u0026amp;x=0\\end{cases}{x1​,lnx,​x=0x=0​，后者{x∞,x=1e1x−1,x=1\\begin{cases}x^\\infty,\u0026amp;x=1\\\\ e^{\\Large\\frac{1}{x-1}},\u0026amp;x=1\\end{cases}⎩⎨⎧​x∞,ex−11​,​x=1x=1​\n求极限\neg:求函数f(x)=(1+x)xtan⁡(x−π4)在区间(0,2π)内的间断点,并判断其类型解：令tan⁡(x−π4)=0,得x=π4或x=5π4令x−π4=π2+kπ,得x=3π4或x=7π4x→π4+时,lim⁡x→π4+f(x)不存在(趋于正无穷大)x→π4−时,lim⁡x→π4−f(x)=0,故x=π4是第二类间断点,x→5π4与其类似x→3π4时,lim⁡x→3π4f(x)=lim⁡x→7π4f(x)=1,故均为第一类间断点,由于两边极限相等,故为可去间断点\\begin{align}\u0026amp;eg:\\\\\u0026amp; 求函数f(x)=(1+x)^{\\frac{x}{\\tan(x-\\frac{\\pi}{4})}}在区间(0,2\\pi)内的间断点,并判断其类型\\\\\u0026amp; 解：\\\\\u0026amp; 令\\tan(x-\\frac{\\pi}{4})=0,得x=\\frac{\\pi}{4}或x=\\frac{5\\pi}{4}\\\\\u0026amp; 令x-\\frac{\\pi}{4}=\\frac{\\pi}{2}+k\\pi,得x=\\frac{3\\pi}{4}或x=\\frac{7\\pi}{4}\\\\\u0026amp; x\\rightarrow\\frac{\\pi}{4}^+时,\\lim_{x\\rightarrow\\frac{\\pi}{4}^+}f(x)不存在(趋于正无穷大)\\\\\u0026amp; x\\rightarrow\\frac{\\pi}{4}^-时,\\lim_{x\\rightarrow\\frac{\\pi}{4}^-}f(x)=0,故x=\\frac{\\pi}{4}是第二类间断点,x\\rightarrow\\frac{5\\pi}{4}与其类似\\\\\u0026amp; x\\rightarrow\\frac{3\\pi}{4}时,\\lim_{x\\rightarrow\\frac{3\\pi}{4}}f(x)=\\lim_{x\\rightarrow\\frac{7\\pi}{4}}f(x)=1,故均为第一类间断点,由于两边极限相等,故为可去间断点\\end{align}​eg:求函数f(x)=(1+x)tan(x−4π​)x​在区间(0,2π)内的间断点,并判断其类型解：令tan(x−4π​)=0,得x=4π​或x=45π​令x−4π​=2π​+kπ,得x=43π​或x=47π​x→4π​+时,x→4π​+lim​f(x)不存在(趋于正无穷大)x→4π​−时,x→4π​−lim​f(x)=0,故x=4π​是第二类间断点,x→45π​与其类似x→43π​时,x→43π​lim​f(x)=x→47π​lim​f(x)=1,故均为第一类间断点,由于两边极限相等,故为可去间断点​​ [题型]复合函数的极限 拆出f(x),用α(x)代替\neg:已知lim⁡x→01+f(x)sin⁡2x−1tan⁡3x=2,求lim⁡x→0f(x)解：令α(x)=1+f(x)sin⁡2x−1tan⁡3x,则lim⁡x→0α(x)=2∴f(x)=(α(x)tan⁡3x+1)2−1sin⁡2x∴lim⁡x→0f(x)=lim⁡x→02α(x)tan⁡3xsin⁡2x=6\\begin{align}\u0026amp;eg:\\\\\u0026amp; 已知\\lim_{x\\rightarrow 0}\\frac{\\sqrt{1+f(x)\\sin 2x}-1}{\\tan 3x}=2,求\\lim_{x\\rightarrow 0}f(x)\\\\\u0026amp; 解：\\\\\u0026amp; 令\\alpha(x)=\\frac{\\sqrt{1+f(x)\\sin 2x}-1}{\\tan 3x},则\\lim_{x\\rightarrow 0}\\alpha(x)=2\\\\\u0026amp; \\therefore f(x)=\\frac{(\\alpha(x)\\tan 3x+1)^2-1}{\\sin 2x}\\\\\u0026amp; \\therefore \\lim_{x\\rightarrow 0}f(x)=\\lim_{x\\rightarrow 0}\\frac{2\\alpha(x)\\tan 3x}{\\sin 2x}=6\\end{align}​eg:已知x→0lim​tan3x1+f(x)sin2x​−1​=2,求x→0lim​f(x)解：令α(x)=tan3x1+f(x)sin2x​−1​,则x→0lim​α(x)=2∴f(x)=sin2x(α(x)tan3x+1)2−1​∴x→0lim​f(x)=x→0lim​sin2x2α(x)tan3x​=6​​ 导数与微分 基本公式 (tan⁡x)′=1cos⁡2x=sec2x(sec⁡x)′=sec⁡xtan⁡x, (csc⁡x)′=−csc⁡xcot⁡x(arctanx)′=11+x2, (arccotx)′=−11+x2(arcsin⁡x)′=11−x2, (arccos⁡x)′=−11−x2\\begin{align}\u0026amp;(\\tan x)\u0026#x27;=\\frac{1}{\\cos^2x}=sec^2x\\\\\u0026amp; (\\sec x)\u0026#x27;=\\sec x\\tan x,\\ (\\csc x)\u0026#x27;=-\\csc x\\cot x\\\\\u0026amp; (arctan x)\u0026#x27;=\\frac{1}{1+x^2},\\ (arccot x)\u0026#x27;=-\\frac{1}{1+x^2}\\\\\u0026amp; (\\arcsin x)\u0026#x27;=\\frac{1}{\\sqrt{1-x^2}},\\ (\\arccos x)\u0026#x27;=-\\frac{1}{\\sqrt{1-x^2}}\\end{align}​(tanx)′=cos2x1​=sec2x(secx)′=secxtanx, (cscx)′=−cscxcotx(arctanx)′=1+x21​, (arccotx)′=−1+x21​(arcsinx)′=1−x2​1​, (arccosx)′=−1−x2​1​​​[题型]是否可导问题和求导数问题 先判断在该点是否连续，不连续则必不可导\n后用定义求左右导数，若都存在且相等才可导\n在其他部分正常求导即可 是否连续与是否可导不一样连续:lim⁡x→x0f(x)=f(x0)可导:f′(x0)=lim⁡x→x0+f(x)−f(x0)x−x0=lim⁡x→x0−f(x)−f(x0)x−x0 \\begin{align}\\Large\u0026amp;是否连续与是否可导不一样\\\\\\\\\u0026amp;连续:\\lim_{x\\rightarrow x_0}f(x)=f(x_0)\\\\\u0026amp;可导:f\u0026#x27;(x_0)=\\lim_{x\\rightarrow x_0^+}\\frac{f(x)-f(x_0)}{x-x_0}=\\lim_{x\\rightarrow x_0^-}\\frac{f(x)-f(x_0)}{x-x_0}\\end{align} ​是否连续与是否可导不一样连续:x→x0​lim​f(x)=f(x0​)可导:f′(x0​)=x→x0+​lim​x−x0​f(x)−f(x0​)​=x→x0−​lim​x−x0​f(x)−f(x0​)​​​ [题型]反函数的求导 f′(x)=1φ′(y)即dydx=1dxdy\\begin{align}\u0026amp;f\u0026#x27;(x) = \\frac{1}{\\varphi\u0026#x27;(y)}\\\\\u0026amp; 即\\frac{dy}{dx} = \\frac{1}{\\frac{dx}{dy}}\\end{align}​f′(x)=φ′(y)1​即dxdy​=dydx​1​​​[注意]函数的微分 函数的微分自变量的微分=函数的导数=f′(x)所以dy=f′(x)⋅dx函数的增量Δy=f(x+Δx)−f(x)当Δx→0时,Δy=f′(x)⋅Δx+o(Δx)=dy+o(Δx)\\begin{align}\u0026amp;\\frac{函数的微分}{自变量的微分}=函数的导数=f\u0026#x27;(x)\\\\\\\\\u0026amp; 所以\\textcolor{red}{dy = f\u0026#x27;(x)·dx}\\\\\u0026amp; 函数的增量\\Delta y = f(x + \\Delta x) - f(x)\\\\\u0026amp;当\\Delta x\\rightarrow 0时,\\textcolor{red}{\\Delta y = f\u0026#x27;(x)·\\Delta x + o(\\Delta x) = dy + o(\\Delta x)}\\end{align}​自变量的微分函数的微分​=函数的导数=f′(x)所以dy=f′(x)⋅dx函数的增量Δy=f(x+Δx)−f(x)当Δx→0时,Δy=f′(x)⋅Δx+o(Δx)=dy+o(Δx)​​[题型]高阶导数和微分 技巧 归纳为递推公式\nsin或cos进行降次(合并为幂次方或二倍角公式或积化和差公式)\n2.1 积化和差公式\n令α=a+b2,β=a−b2 例如sin⁡αcos⁡β可以化为12(sin⁡(α+β)+sin⁡(α−β))\\begin{align}\u0026amp;\\ \\ \\ \\ \\ \\ \\ 令\\alpha=\\frac{a+b}{2},\\beta=\\frac{a-b}{2}\\\\\u0026amp; \\ \\ \\ \\ \\ \\ \\ 例如\\sin\\alpha\\cos\\beta可以化为\\frac{1}{2}(\\sin(\\alpha+\\beta)+\\sin(\\alpha-\\beta))\\end{align}​ 令α=2a+b​,β=2a−b​ 例如sinαcosβ可以化为21​(sin(α+β)+sin(α−β))​​ 取对数法：多项乘法由于展开后更好求，一般可以取对数 莱布尼茨公式(前面系数与二次项系数类似) (f(x)⋅g(x))(n)=Cn0f⋅g(n)+Cn1f′⋅g(n−1)+…+Cn0f(n)⋅g可以分解假分式后利用莱布尼茨公式展开真分式\\begin{align}\u0026amp;(f\\small(x)·g(x)\\big)^{(n)} = C^0_nf·g^{(n)} + C^1_nf\u0026#x27;·g^{(n-1)} + … + C^0_nf{(n)}·g\\\\\\\\\u0026amp;可以分解假分式后利用莱布尼茨公式展开真分式\\end{align}​(f(x)⋅g(x))(n)=Cn0​f⋅g(n)+Cn1​f′⋅g(n−1)+…+Cn0​f(n)⋅g可以分解假分式后利用莱布尼茨公式展开真分式​​[题型]隐函数的求导 两边同时对x求导再整理即可\n[题型]参数方程求导 y′=dydx=dydtdxdty′′=ddx(y′)=ddt(y′)⋅1dxdt 即先对t求导,再乘上x对t求导的倒数\\begin{align}\u0026amp;y\u0026#x27; = \\frac{dy}{dx} = \\frac{\\frac{dy}{dt}}{\\frac{dx}{dt}}\\\\\u0026amp; y\u0026#x27;\u0026#x27; = \\frac{d}{dx}(y\u0026#x27;) = \\frac{d}{dt}(y\u0026#x27;)·\\frac{1}{\\frac{dx}{dt}}\\ 即先对t求导,再乘上x对t求导的倒数\\end{align}​y′=dxdy​=dtdx​dtdy​​y′′=dxd​(y′)=dtd​(y′)⋅dtdx​1​ 即先对t求导,再乘上x对t求导的倒数​​[题型]相关变化率 先找所给量间的关系 两边函数的导数如果都是和某个变量相关的不同函数，则两边同时求导 [题型]求曲率 K=ΔΘΔs=y′′(1+(y′)2)32Δs=1+(y′)2dx=dy2+dx2R=1K \\begin{align}\u0026amp;K=\\frac{\\Delta\\Theta}{\\Delta s}=\\frac{y\u0026#x27;\u0026#x27;}{(1+(y\u0026#x27;)^2)^\\frac{3}{2}}\\\\\u0026amp; \\Delta s=\\sqrt{1+(y\u0026#x27;)^2}dx=\\sqrt{dy^2+dx^2}\\\\\u0026amp; R=\\frac{1}{K}\\end{align} ​K=ΔsΔΘ​=(1+(y′)2)23​y′′​Δs=1+(y′)2​dx=dy2+dx2​R=K1​​​[注意]泰勒公式 常用拉格朗日公式：sin⁡x=x−x33!+x55!−……cos⁡x=1−x22+x44!−……ln⁡x=x−x22+x33−……ex=1+x+x22+x33!+……(1+x)α=1+αx+α(α−1)x22!+…… 常用拉格朗日公式：\\\\\\sin x=x-\\frac{x^3}{3!}+\\frac{x^5}{5!}-……\\\\ \\cos x=1-\\frac{x^2}{2}+\\frac{x^4}{4!}-……\\\\ \\ln x=x-\\frac{x^2}{2}+\\frac{x^3}{3}-……\\\\ e^x=1+x+\\frac{x^2}{2}+\\frac{x^3}{3!}+……\\\\ (1+x)^\\alpha=1+\\alpha x+\\frac{\\alpha(\\alpha-1)x^2}{2!}+……\\\\ 常用拉格朗日公式：sinx=x−3!x3​+5!x5​−……cosx=1−2x2​+4!x4​−……lnx=x−2x2​+3x3​−……ex=1+x+2x2​+3!x3​+……(1+x)α=1+αx+2!α(α−1)x2​+……","permalink":"https://echo.ishian.me/2023/10/30/calculus-limitation/","summary":"整理极限求解的注意事项与常见题型，包括单调有界收敛定理与反函数求法","title":"微积分: 关于极限"},{"content":"C 预处理 介绍 在编译一个.c文件时，最开始的操作便是预处理，它会将包含的文件展开，把使用过宏的地方替换为宏定义的语句，删除注释等等\u0026hellip;\n可以用gcc生成一个预处理后的文件(后缀为.i)\nsh# -E选项表示在预处理后停止，-C表示保留库函数里的注释，-o表示自定义输出文件的名称 gcc -E -C file.c -o file.i宏 宏，如其名，它将一串命令序列转换为简短的语句，用短短几个单词即可以实现强大复杂的功能\n预处理器对宏实际的操作是把参数作为字符串来替换并把这条替换后的语句插入程序控制流里\n运算符 \\(宏延续) 它用于隔开两个语句，当宏太长时，可用'\\'分隔为多行\n#(字符串常量化) 它可以将宏参数转换为字符串，字符串的内容是宏参数的名称，如要在字符串中转换需要再加上一层双引号\n##(标记粘贴) 它可以将两边的标记合并成一个标记\ndefined(已定义) 它接受一个标识符，若这个标识符已定义，返回true，否则返回false\n处理目标 #include 各种语言都有官方的函数库，这些库包含了许多优化度极高或十分方便的宏，想要使用它，我们需要声明\nc#include \u0026lt;stdio.h\u0026gt;上述语句包含了一个名为stdio.h的文件，如果使用gcc生成一个预处理后的文件(后缀为.i)，我们会发现这一文件条目很多，正是展开这一文件的效果\n我们也可以用双引号包含，这时预处理器会先从当前目录而不是从库函数目录里寻找\nc#include \u0026#34;myStd.h\u0026#34;在我们可以编写自己的头文件时，需要用这种包含方式包含\n#define 这是定义一个常量，或是一个简易函数等的方法\nc#define COL 50 #define PRINT(n) printf(\u0026#34;\u0026#34;#n\u0026#34;是%d\u0026#34;, n) #define PRINTMORE(...) printf(__VA_ARGS__) #define GIVE_NEW(n) aNum##n上述语句的第一条定义了一个名为COL，大小为50的常量\n第二条语句定义了一个PRINT宏函数，传入(int)n，打印\u0026quot;(n的名称)是(n的数值)\u0026quot;\n双引号括住#+宏参数名可以解析它为它的名称\n第三条语句的宏参数为\u0026quot;...\u0026quot;，它可以接受多个参数，并把这些参数传给__VA_ARGS__\n第四条语句被用于批量创建变量，##即黏合剂，将两边记号黏着\ncint GIVE_NEW(3)= 5; // 创建一个名为\u0026#34;aNum3\u0026#34;,值为5的int型变量 需要注意的是，当函数含参时，需要加上()括住，原因是传参时它将表达式参数当作字符串处理，加上括号才可以达到想要的结果\nc#define POWT1(n) ((n)*(n)) #define POWT2(n) (n*n) POWT1(5+4)==(5+4)*(5+4)==81; POWT2(5+4)==5+4*5+4==29;使用场景 宏是内联函数，即在预处理阶段时会把相应语句直接插入到程序中\n与一般函数不同，它不需要跳转，跳出，而只需要牺牲一定的空间；而一般函数会被保存为副本，每次调用都必须跳到入口进，从出口返回，这将增大时间成本\n因此，在一个函数十分简单时，可以将它写成一个宏，牺牲空间，换取时间\ntodo:编写简单的头文件 接下来，将通过编写一个简单的头文件来帮助熟悉#ifdef,#undef,inline等语句和关键字\n编译 介绍 C语言的编译过程即将高级语言转换为汇编语言的过程，它会将所有语句转换为汇编指令，并且会优化源代码\n可以用gcc生成一个编译后的文件(后缀为.s)\nshgcc -S file.i -o file.s可以发现生成后的文件已经是汇编代码了\nC语言语法 在这个阶段，开始涉及C语言的各种语法\n数据类型 C语言有许多关键字，其中不少是用于声明变量的\nc // 占用字节 16位 32位 64位 char ch = \u0026#39;a\u0026#39;; // 字符型 1 1 1 short sn = 1; // 短整型 2 2 2 int dn = 1; // 整型 2 4 4 long ln = 1; // 长整型 4 4 8 (尽量不使用,因为可能导致不兼容) float fn = 1.1; // 单精度浮点型 4 4 4 double lfn=1.1; // 双精度浮点型 8 8 8 long long lln=1; // 长长整型 8 8 long double ldn=1; // 12 16 // 所有指针类型 2 4 8 // unsigned:表示无符号，并不会改变位数 4位字节如int范围为-2147483648~2147483647，unsigned int范围为0~4294967295(20~43亿)(2322^{32}232)\n8位字节如long long范围为-9223372036854775808~-9223372036854775807\n​ unsigned long long范围为0~18446744073709551615(2642^{64}264)\n除了基本的数据类型，还可以声明结构体来储存多个类型的值\ncstruct STRUCT { int a; char b; char* p; char arr[10]; };给结构体变量的分配空间遵循对齐原则\n从第一个成员开始往下分配空间，默认从地址0开始存放 每个成员的偏移量必须是该成员基本数据类型的整数倍 总大小是最大成员基本数据类型的整数倍 例如，char占1个字节，那么它可以放在0,1,2,3,\u0026hellip;地址上；int占4个字节，那么它可以放在0,4,8,12,\u0026hellip;地址上\n指针类型占8个字节，那么它可以放在0,8,16,\u0026hellip;地址上；数组也同理，将它看作许多个基本类型成员的集合计算\n数据间的转换 C语言的char型比较特殊，它被转换为int数据存储，并用ASCII编码，可以和int类型数据相加减\nASCII码表\n它可以用\u0026quot;%d\u0026quot;转换符输出，但并非是一个整型数据!\n一般数据类型的转换如图所示，在赋值运算中可以进行自动类型转换将右值转换为左值的类型，在表达式运算中可以从低到高进行自动类型转换，例如整数相除的结果会被去掉小数部分 char→int→unsigned→long→double←float char\\rightarrow int\\rightarrow unsigned\\rightarrow long\\rightarrow double\\leftarrow float char→int→unsigned→long→double←float 指针类型十分严格，如果类型不同，则不能进行互相转换\nvoid*无类型指针是特例，可以强制转换为任意类型的一级指针，它是抽象的，并不具有一般指针类型变量的加减或给其他变量赋值的操作，它只能是左值\n数据的前缀 c011 // 表示八进制 0x11 // 表示十六进制 0b11 // 表示二进制 \u0026#39;\\072\u0026#39; // 表示转义为一个072对应的字符,实际占一个字符的空间 数据的后缀 c// 浮点数的指数表达法,后面的数字必须是整数[1.0或表达式(5+4)也是错误的],e或E均可 // e的前面数字只能是(0,10)间的数据 1.1e2 // 表示1.1*pow(10,2) -2.3e-4 // 表示2.3*pow(10,-4) // 其它后缀 1u(或U) // 表示是一个无符号数据,可以后加l或L表示无符号长整型 1.1f(或F) // 表示是一个float类型数据,若无后缀则表示double类型 2.2L(或l) // 表示是一个long double类型数据 运算符 运算符 优先级 运算顺序 [] 1 从左到右 () 对象成员. 指针成员-\u003e 单目- 2 从右到左 取反~ 自增++/自减--(包括前后缀) 解引用* 取地址\u0026 ! 强制类型转换(type) sizeof / 3 从左到右 乘法* % + 4 - \u003c\u003c 5 \u003e\u003e \u003e 6 \u003e= \u003c \u003c= == 7 != \u003c= 按位\u0026 8 按位^ 9 按位| 10 \u0026\u0026 11 || 12 ?: 13 从右到左 赋值符 14 基于优先级以及结合顺序，在这里讨论一些容易混淆的语句\nc// ++与*优先级相等,而它们是从右到左结合的 int* p; (*p)++; // 括号优先级最高,语句意义为先取p指向的值,然后让p指向的值自增 *p++; // 先取p指向的值,然后让p自增 ++*p; // 先让p指向的值自增,然后取p指向的值 ++(*p); // 与第三条语句等价 赋值运算符优先级极低，且赋值表达式的结果就是右值的结果\n数组 数组将多个相同类型的数据连在一起，方便进行访问和管理，许多数据类型的结构都可以由数组实现\n二维数组 初始化二维数组时，它的列不能为空\nc// 一层循环遍历二维数组 for (int i = 0; i \u0026lt; M * N; i++) printf(\u0026#34;%d \u0026#34;, a[i / N][i % N]);VLA(变长数组) VLA是一个概念，有许多种方法可以实现它，其中肯定需要用到的是\u0026lt;stdlib.h\u0026gt;里的malloc()或calloc()和free()函数(在C++中可用new和delete关键字)\n引入指针后，VLA的实现便很容易理解了\nc#include\u0026lt;stdlib.h\u0026gt; int n = 10; int* pa = (int*)malloc(n * sizeof(int)); // 接受一个参数:元素个数*单个元素空间 int* pa0 = (int*)malloc(n, sizeof(int)); // 会进行初始化,效率比malloc低 free(pa); // 释放内存 函数 C语言是一门面向过程的编程语言，函数对于它十分重要，它能把程序划分为多块，分别实现不同的功能\n接口与返回值 ctype function(paras){statements}上述例子里，paras表示参数，它是进入这个函数的入口时由上一进程传入的数据，type规定了function()的返回值的类型，如果不显式声明则默认为int\n形参 在函数中改变形参值无法改变上一进程中传进来的值，如要改变，可以传指针进行修改\n递归 递归很适合用于处理需要逆向思考的问题，例如一包数据的倒序存储等；但递归也容易造成栈溢出，递归的层级越大，风险便越大\n涉及递归函数时，首先需要找到递归出口，即递归基础，每进行一次递归，数据应越来越靠近这一出口\n其次是递归步骤，需要找到每次递归的数据间的联系与规律\n指针 C语言的指针强大，灵活\n指针基本类型 数据类型 名称 意义 type* 一级指针 指向一般数据类型的指针 type** 二级指针 指向指针的指针 type[*](n) 数组指针(行指针) 指向数组的指针 数组名的类型以及易混淆的声明 数组名的类型很好记，只要知道该数组装载着的最高维数据的类型，就知道数组名的类型是指向这一类型的指针\n声明方式 数据类型 意义 type a[n] type* 本身是一个一维数组，a是指向一般数据的常量指针 type a[m][n] type[*](n) 本身是一个二维数组，a是指向含n个元素的数组的常量指针 type *a[n] type** 本身是一个指针数组，a是指向指针的常量指针 type[*a](n)t type[*](n) 指向一维数组的指针，a[i]是未定义的 函数中的指针参数 向函数传递指针 地址或数组名向函数传递时，都会退化为指针，而且建立一个形参指针变量\n函数内的[形参]指针拥有一般指针变量的性质：例如可以进行加减(就算传入数组名，也会新建一个形参指针)\n由函数返回地址 返回形参或静态量的地址有意义\n返回局部变量的地址无意义：局部变量离开函数时被销毁，故访问该地址没有意义\n关于赋值 指针类型限定很严格，只能用相同类型的值赋值给指针变量\ncvoid func(int, int); //几个赋值的例子： int* p; int** pp; int arr[n]; int arr2[m][n]; int *parr[n]; int(*pa)[n]; p = arr; // √ pp = parr; // √ pa = arr2; // √ pa[?] = ??? // × void (*pfunc)(int, int); // 声明函数指针 (*pfunc)(a, b); // 调用函数指针 位运算 汇编 介绍 汇编即把汇编代码转换为机器语言(二进制机器码)的过程\n可以用gcc生成一个汇编后的文件(后缀为.o)\nshgcc -c file.s -o file.o汇编语言 介绍 汇编语言(assemble language,即asm)面向机器，将许多长串的二进制码编写为一套短小精炼的命令语句，它帮助我们在寄存器、IO出口和存贮器间传递数据；将命令变为二进制码让机器听懂等。如果读者常使用IDA，则对汇编语言会有所了解\n每一种处理器都有它们的一套汇编指令，它是软件开发者和机器直接进行交互的桥梁，对理解硬件之间的联系很有帮助\n高级语言中提供的十六进制与八进制，正是因为汇编语言的数据会以2的幂来存储\n数据间的转换 二进制数据每进四个位对应的十六进制数将进一位，每进三个位对应的八进制数将进一位\n2n2^n2n进制数拥有的这种特性让计算变得规律易懂\nassembly1111-1011-1110-0101 ---\u0026gt; 0xF-B-E-5 0xBA93 ---\u0026gt; 1011-1010-1001-0011负数的表示:二进制补码 assembly-num == ~num + 1 # 即先取反,再加1数据相减即让减数取反加1后进行加法运算，因为最后一位会因进位而丢失，所以可以正确表示两数相减\n如果规定为无符号数据，则最后一位参加，最大值变为两倍(即282^828)，否则为−27∼27−1-2^7\\sim2^7-1−27∼27−1\n数据寻址 处理器一次可以访问多个字节的内存，它先从内存获取指令，然后解码，最后执行；存储数据时，从低位开始压入栈，即会将低位字节存储于低地址上；当它取出数据时，会从高位开始取，这就像先入后出的栈一样\n通过这种方式，处理器搭建了一座内存和寄存器间的桥梁\nNASM NASM全称The Netwide Assembler，可以在Windows和Linux下使用\n我们在Linux环境下配置NASM，输入sudo apt install nasm即可\n汇编语法 汇编分为三部分:data, bss, text\nassemblysection.data section.bss section.text global _start ;entre _start: ;explaindata: 初始化数据或常量\nbss: 声明变量\ntext: 保存实际代码，必须以**global _start**开头，它规定了执行的开始位置\n;: 注释，以英文';'开头\n内存段分为数据段、代码段和堆栈段，数据段由.data和.bss表示、代码段由.text表示\n汇编语句 汇编有三种类型的语句，且汇编语言不区分大小写\n可执行指令 汇编器指令(伪指令) 宏指令 第一种语句告诉处理器要做什么，每一条语句汇编后都是一组二进制数，可被CPU执行；第二种语句不会变成机器语言，它们不可执行，但为汇编程序提供汇编信息(如数据和指令的区分，或数据的字长，或数据地址等)，辅助源程序的汇编；宏即一种文本替换机制，用于提高编程效率\n每行只有一条语句，每条语句遵循下面的格式\ntext[label] mnemonic [operands] [;conment] [名字] 指令助记符 [操作数] [注释] 0~9,a~z,A~Z,_,?,@,$ 实际可执行指令 数据或数据所在地址 方括号内是可选项数据项 常量：直接编码于指令中，不额外分配主存空间，也不存储在存贮器里\n进制 后缀 十进制 d/D(可省略) 十六进制 h/H,以A-F开头时需在最高位加0 二进制 b/B 变量：保存在可读可写的主存空间，声明一个变量的指令如下\nassemblyname DB n dup(2,dup(4)) ;dup为复制操作符,它会将括号内数据复制n遍,例如本例n=2: 2,4,4,2,4,4 name DW 20 dup(?) ;?表示随机数,常用于预留存储空间 name DW 22h,11h ;从左到右压入堆栈,即地址22h低于11h ;每个数据遵循小端存储,即高位数据存入高地址定义变量的伪指令如下图\n助记符 变量类型 所占位 byte(DB) 字节 8 word(DW) 字 16 dword(DD) 双字 32 qword(DQ) 四字 64 tword(DT) 十字 160 表达式：\n运算符类型 运算符 算术运算符 + - * / mod 逻辑运算符(只能对数值进行的按位运算) and or not xor 关系运算符(只能对数值的操作)成立返回0FFFFh(-1),否则返回0000h eq(=) le(\u003c=) ge(\u003e=) lt(\u003c) gt(\u003e) ne(!=) 取值运算符 seg(获取段地址) offset(获取段内偏移地址) type(获取类型属性,返回该类型的值) length(返回变量中元素个数) size(返回所占数据区的字节总数) 链接 介绍 之前提到预处理将展开库函数，事实上，这个过程导入的只有原型，真正的实现在链接库里\n而链接即把机器码和其它文件，库文件，启动文件链接起来的过程，将会生成一个可执行文件\n可以用gcc生成一个链接后的文件，已经到最后步骤了，需要做的事情很少\nshgcc file.o -o file.out","permalink":"https://echo.ishian.me/2023/10/09/c-beginner/","summary":"介绍C语言预处理、宏定义及常用运算符等基本语法知识","title":"C: 基本语法"},{"content":"\nMarkdown MD软件 Typora：Typora有“所见即所得”的特点，即你所看见的就是文件导出后的样子，付费 Obsidian：Obsidian的插件市场丰富，且支持笔记之间的链接功能，个人版免费 VS Code+Markdown All in One插件：左边编辑、右边预览，支持所见即所得，该插件提供了最基本的渲染以及快捷键配置，其它插件可查看Markdown插件 常用记号 '\\'：转义记号，类似C语言里的'\\n'等，它可以将内嵌的命令转义\n'#'：标题记号，它可以将一行文字快捷地变为一级标题 格式为：# title，井号越多，标题等级越低 具有该功能的快捷键是ctrl+$d($d表示标题等级)\n'*'或'_'：斜体记号，两个*组成一队，可以将中间的文本变为斜体 例如*abcd*的效果为：abcd 具有该功能的快捷键是ctrl+I\n'**'或'__'：加粗记号，两组**组成一对，可以将中间的文本加粗，且可以和斜体记号一起使用 例如***abcd***的效果为：abcd 具有该功能的快捷键是ctrl+B\n\u0026lsquo;`\u0026rsquo;(英文反引号)：行内代码记号，两个`组成一对，可以将中间的文本变为代码字体，注意代码块内的记号会失效，例如使用\u0026rsquo;\\\u0026lsquo;将其转义时有时会产生混乱，行内代码可以加粗 例如`#include`的效果是：#include 具有该功能的快捷键是shift+ctrl+`\n'```'：代码块记号，三个反引号两组组成一对，可以在其中输入代码语言与相应代码等，例如：\ncint main() { return 0; } '$$'：块级数学公式记号，两组$$组成一对，可以在其中用LaTeX语法写入数学公式。使用时只需键入\u0026rsquo;$$\u0026lsquo;后按下回车即可。例如：\naφ(x)% n=1 (a与n互素) a^{\\varphi(x)}\\%\\ n=1\\ (a与n互素) aφ(x)% n=1 (a与n互素)具体该如何写数学公式将在这里讲述\n'$'：行内数学公式记号符，两个$组成一对，可以在其中用LaTeX语法写入数学公式。 例如：\n∫0π2f(sin⁡x)dx=∫0π2f(cos⁡x)dx\\begin{align}\\int_0^{\\Large\\frac{\\pi}{2}}f(\\sin x)dx=\\int_0^{\\Large\\frac{\\pi}{2}}f(\\cos x)dx\\end{align}∫02π​​f(sinx)dx=∫02π​​f(cosx)dx​​ '[]()'：链接记号，在\u0026rsquo;[]\u0026lsquo;内输入文本，在\u0026rsquo;()\u0026lsquo;内输入链接，这个链接可以是网址、本地文件或文章内锚点，文件支持使用相对路径或绝对路径，建议使用相对路径 例如：Markdown，aFile，aFlag 'crtl+左键'点击可跳转至该链接 若链接中存在空格，建议使用%20代替 ()最后允许包含用\u0026quot;\u0026quot;或()或''包围的提示，鼠标悬停时会指出预设的文字\n'[][]'：引用表示的链接，由第二个[]作为中介传递链接，例如：\nmarkdown[文字][1] [1]: https://markdown.com.cn/basic-syntax/ (悬停提示) '![]()'：图像记号，与链接记号类似，但只能输入图片文件的路径，且会解析图片 例如： 图像与链接一样，支持引用表示与悬停提示\n'[toc]'：目录记号，可以用它生成自带锚点的目录，例如开头所示的索引\n'---'：分割线记号，可以用它生成一条直线，例如：\n内嵌HTML 表格 Typora支持内嵌HTML，常见用法为写表格，例如：\ntitle body exp1 111 exp2 11 exp3 如图，使用html语法，写出一个四行两列的、边界值为2的、内容'11'被设置为占据四行且居中的表格\n图片 通过\u0026lt;img /\u0026gt;标签可以定义并放缩图片： 图片可以缩放，即在前方的html标签内加上style=\u0026quot;zoom:num%\u0026quot;，num为缩放比例\n链接 \u0026lt;a id=\u0026quot;111\u0026quot;\u0026gt;\u0026lt;/a\u0026gt;：这条元素定义了一个id为'111'的锚点，在文章内部可以通过[点击](#111)链接跳转到这个锚点，例如本文在目录处设定了一个'HOME'锚点，您可以点击我来跳转 \u0026lt;a href=\u0026quot;https://www.baidu.com\u0026quot;\u0026gt;Baidu\u0026lt;/a\u0026gt;：这条元素定义了一个链接为百度域名，显示内容为'Baidu'的链接，Baidu\n强制分页 \u0026lt;div style=\u0026quot;page-break-after: always;\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; 在导出时，这条元素将强制新建一页\n透明字体及右对齐 \u0026lt;p style=\u0026quot;color: rgba(400, 0, 400, 0.2)\u0026quot; align=\u0026quot;right\u0026quot;\u0026gt;ishian\u0026lt;/p\u0026gt; style属性中，前三个数字为rgb值，第四个数字为透明度 这条元素定义了一个透明度为0.2，内容为'ishian'，右对齐的段落：\nishian\n","permalink":"https://echo.ishian.me/2023/10/03/markdown-beginner/","summary":"介绍 Markdown 常用记号与内嵌 HTML 实现表格、图片、分页等技巧","title":"Markdown: 简单易学的笔记语言"},{"content":" Site Link Notes Docker https://docs.docker.com/ Docker Engine / Compose official docs Kubernetes https://kubernetes.io/docs/ K8s concepts, tasks and API reference Redis https://redis.io/docs/latest/ Redis commands and operations docs Hugo https://gohugo.io/documentation/ Hugo static site generator docs Git https://git-scm.com/doc/ Official Git docs and the Pro Git book GitHub Actions https://docs.github.com/actions Workflow syntax and CI/CD guides ","permalink":"https://echo.ishian.me/wiki/infra-tools/","summary":"Official docs for Docker, Kubernetes, Redis, Hugo, Git and GitHub Actions","title":"Infra \u0026 Tools"},{"content":" Site Link Notes Go https://go.dev/doc/ Official Go language, standard library and tooling docs Node.js https://nodejs.org/docs/latest/api/ Node.js API reference for the latest release Spring https://docs.spring.io/spring-framework/reference/ Spring Framework reference; other projects at spring.io/projects Java https://dev.java/ Oracle\u0026rsquo;s official Java learning and docs hub Rust https://doc.rust-lang.org/book/ The Rust Programming Language and the standard library docs ","permalink":"https://echo.ishian.me/wiki/langs-frameworks/","summary":"Official docs for Go, Node.js, Spring, Java and Rust","title":"Languages \u0026 Frameworks"},{"content":" Site Link Notes Arch Wiki https://wiki.archlinux.org/ First stop for Arch Linux and general Linux desktop setup Linux Kernel Docs https://www.kernel.org/doc/html/latest/ Kernel subsystem and driver development docs GNOME Developer https://developer.gnome.org/ GTK / GLib / GNOME app development docs KDE Developer https://develop.kde.org/ Qt / KDE Plasma and app development docs chezmoi https://www.chezmoi.io/ Dotfile management across multiple machines Neovim https://neovim.io/doc/ Neovim user manual and Lua API docs ","permalink":"https://echo.ishian.me/wiki/linux-desktop/","summary":"Official docs for Arch Wiki, Linux Kernel, GNOME, KDE, chezmoi and Neovim","title":"Linux \u0026 Desktop"},{"content":"My Projects Project Description maa-pipeline.nvim Neovim integration for Maa pipeline protocol json(c) files mini.codex A minimal integration for codex in neovim tree-sitter-mybatis Tree-sitter grammer for MyBatis and iBATIS mapper files golem An agent lib based on eino and eino-ext codex-prompt-lsp Edit codex prompt in neovim with a lsp that supports completion and hover for skills, files, plugins and commands Projects I Contribute To Project Description MaaEnd MaaEnd 终末地小助手：基于视觉 AI 的「明日方舟：终末地」自动化工具 ","permalink":"https://echo.ishian.me/projects/","summary":"\u003ch2 id=\"my-projects\"\u003eMy Projects\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eProject\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eDescription\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"/maa-pipeline.nvim/\"\u003emaa-pipeline.nvim\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eNeovim integration for Maa pipeline protocol json(c) files\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"/mini.codex/\"\u003emini.codex\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eA minimal integration for codex in neovim\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"/tree-sitter-mybatis/\"\u003etree-sitter-mybatis\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eTree-sitter grammer for MyBatis and iBATIS mapper files\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"/golem/\"\u003egolem\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eAn agent lib based on eino and eino-ext\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"/codex-prompt-lsp/\"\u003ecodex-prompt-lsp\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eEdit codex prompt in neovim with a lsp that supports completion and hover for skills, files, plugins and commands\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"projects-i-contribute-to\"\u003eProjects I Contribute To\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eProject\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eDescription\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ca href=\"https://github.com/MaaEnd/MaaEnd\"\u003eMaaEnd\u003c/a\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMaaEnd 终末地小助手：基于视觉 AI 的「明日方舟：终末地」自动化工具\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e","title":"Projects"}]