<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom">
 
 <title>Cyeam Go学习优化沉淀 架构设计分享</title>
 <link href="https://blog.cyeam.com/" rel="self"/>
 <link href="https://blog.cyeam.com"/>
 <updated>2026-04-10T12:33:12+00:00</updated>
 <id>https://blog.cyeam.com</id>
 <author>
   <name>Bryce</name>
   <email>lichao@cyeam.com</email>
 </author>

 
 <entry>
   <title>LLM不能做什么？解析大模型的能力边界与适用场景</title>
   <link href="https://blog.cyeam.com/ai/2026/04/09/what-llm-cannot-do"/>
   <updated>2026-04-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/04/09/what-llm-cannot-do</id>
   <content type="html">
&lt;hr /&gt;

&lt;p&gt;大模型虽然能力强大，但并非万能，LLM访问次数就是其关键瓶颈之一。本质上，&lt;strong&gt;AI并不适合处理高频率、大批量的重复性任务&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;批量数据处理就是大模型的典型盲区。当你需要处理几千条数据记录时，不管采用MCP（模型上下文协议）还是让AI生成代码执行，都很难完整、可靠地完成任务。大模型的设计初衷是进行推理和生成，而非充当高速数据处理引擎。面对海量条目，API调用频率会迅速触及上限，响应延迟累积，最终导致任务超时或中断。&lt;/p&gt;

&lt;p&gt;以我实际经验来看，通过LLM和MCP批量上传图片场景，这种场景下LLM可以尝试主动提醒或者往控制调用次数的方向来执行：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;trae MCP上传了几张后就开始一本正经胡说八道，返回地址都是错误的。&lt;/li&gt;
  &lt;li&gt;claude+黄大善人模型上传没问题但一晚上只完成30%，几百张图片，因为要拼参数，模型调用次数是非常大的。&lt;/li&gt;
  &lt;li&gt;后面我还考虑过批量生成上传命令，但都不行，因为根本问题没解决——模型调用次数不会减少。后来索性让AI给我实现一个批量上传脚本解决掉了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;总结而言，单次调用、逻辑复杂、需要推理的任务适合大模型；而高频重复、数据量大、纯机械操作的任务则应交给传统脚本或专用工具。理解这一边界，才能避免把 Ferrari 当拖拉机用。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Harness Engineering深度解析：AI时代的系统控制新范式</title>
   <link href="https://blog.cyeam.com/ai/2026/04/06/harness-engineering"/>
   <updated>2026-04-06T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/04/06/harness-engineering</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#ai工程化三阶段&quot; id=&quot;markdown-toc-ai工程化三阶段&quot;&gt;AI工程化三阶段&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#code-harness-engineering架构&quot; id=&quot;markdown-toc-code-harness-engineering架构&quot;&gt;Code Harness Engineering架构&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#第一阶段系统启动与加载构建底座&quot; id=&quot;markdown-toc-第一阶段系统启动与加载构建底座&quot;&gt;第一阶段：系统启动与加载（构建底座）&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#1-实时代码仓库上下文-live-repo-context&quot; id=&quot;markdown-toc-1-实时代码仓库上下文-live-repo-context&quot;&gt;1. 实时代码仓库上下文 (Live Repo Context)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#2-提示词形态与缓存复用-prompt-shape-and-cache-reuse&quot; id=&quot;markdown-toc-2-提示词形态与缓存复用-prompt-shape-and-cache-reuse&quot;&gt;2. 提示词形态与缓存复用 (Prompt Shape And Cache Reuse)&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#stable-prompt-prefix稳定提示词前缀claudemd&quot; id=&quot;markdown-toc-stable-prompt-prefix稳定提示词前缀claudemd&quot;&gt;Stable Prompt Prefix（稳定提示词前缀）——CLAUDE.md&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#rules&quot; id=&quot;markdown-toc-rules&quot;&gt;Rules&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#第二阶段对话运行与组装核心循环&quot; id=&quot;markdown-toc-第二阶段对话运行与组装核心循环&quot;&gt;第二阶段：对话运行与组装（核心循环）&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#5-结构化的会话记忆-structured-session-memory&quot; id=&quot;markdown-toc-5-结构化的会话记忆-structured-session-memory&quot;&gt;5. 结构化的会话记忆 (Structured Session Memory)&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#记忆系统memory&quot; id=&quot;markdown-toc-记忆系统memory&quot;&gt;记忆系统Memory:&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#会话transcript&quot; id=&quot;markdown-toc-会话transcript&quot;&gt;会话Transcript：&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#第三阶段工具执行与安全管控驾驭核心&quot; id=&quot;markdown-toc-第三阶段工具执行与安全管控驾驭核心&quot;&gt;第三阶段：工具执行与安全管控（驾驭核心）&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#3-工具的接入与调用-tool-access-and-use&quot; id=&quot;markdown-toc-3-工具的接入与调用-tool-access-and-use&quot;&gt;3. 工具的接入与调用 (Tool Access and Use)&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#低幻觉&quot; id=&quot;markdown-toc-低幻觉&quot;&gt;低幻觉&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#强力提示词&quot; id=&quot;markdown-toc-强力提示词&quot;&gt;强力提示词&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#第四阶段上下文瘦身防爆炸与优化&quot; id=&quot;markdown-toc-第四阶段上下文瘦身防爆炸与优化&quot;&gt;第四阶段：上下文瘦身（防爆炸与优化）&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#4-给上下文瘦身防止撑爆-minimizing-context-bloat&quot; id=&quot;markdown-toc-4-给上下文瘦身防止撑爆-minimizing-context-bloat&quot;&gt;4. 给上下文瘦身，防止撑爆 (Minimizing Context Bloat)&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#压缩时机&quot; id=&quot;markdown-toc-压缩时机&quot;&gt;压缩时机&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#压缩方式&quot; id=&quot;markdown-toc-压缩方式&quot;&gt;压缩方式&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#第五阶段任务委派与复杂场景&quot; id=&quot;markdown-toc-第五阶段任务委派与复杂场景&quot;&gt;第五阶段：任务委派与复杂场景&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#6-任务委派与受限子智能体-delegation-with-bounded-subagents&quot; id=&quot;markdown-toc-6-任务委派与受限子智能体-delegation-with-bounded-subagents&quot;&gt;6. 任务委派与受限子智能体 (Delegation With (Bounded) Subagents)&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#配置方式&quot; id=&quot;markdown-toc-配置方式&quot;&gt;配置方式&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#调用方式&quot; id=&quot;markdown-toc-调用方式&quot;&gt;调用方式&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#完整架构图&quot; id=&quot;markdown-toc-完整架构图&quot;&gt;完整架构图&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#更多阅读&quot; id=&quot;markdown-toc-更多阅读&quot;&gt;更多阅读&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;ai工程化三阶段&quot;&gt;AI工程化三阶段&lt;/h1&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;维度&lt;/th&gt;
      &lt;th&gt;Prompt Engineering（提示词工程）&lt;/th&gt;
      &lt;th&gt;Context Engineering（上下文工程）&lt;/th&gt;
      &lt;th&gt;Harness Engineering（驾驭 / 控束工程）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;核心定位&lt;/td&gt;
      &lt;td&gt;指令优化&lt;/td&gt;
      &lt;td&gt;信息供给&lt;/td&gt;
      &lt;td&gt;系统控制&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;通俗比喻&lt;/td&gt;
      &lt;td&gt;写台词&lt;/td&gt;
      &lt;td&gt;搭布景 / 查资料&lt;/td&gt;
      &lt;td&gt;造赛车 / 拉缰绳&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;解决问题&lt;/td&gt;
      &lt;td&gt;模型听不懂、答非所问&lt;/td&gt;
      &lt;td&gt;模型记不住、知识匮乏&lt;/td&gt;
      &lt;td&gt;模型不靠谱、不可控、不安全&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;操作对象&lt;/td&gt;
      &lt;td&gt;纯文本字符串（Prompt）&lt;/td&gt;
      &lt;td&gt;会话历史 + 外部知识库（&lt;strong&gt;RAG&lt;/strong&gt;）&lt;/td&gt;
      &lt;td&gt;整个 Agent 生命周期 + 外部工具&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;技术手段&lt;/td&gt;
      &lt;td&gt;角色设定、Few-shot、CoT、模板&lt;/td&gt;
      &lt;td&gt;会话管理、向量检索、窗口截断&lt;/td&gt;
      &lt;td&gt;状态机、函数调用、护栏、自愈闭环&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;代码复杂度&lt;/td&gt;
      &lt;td&gt;低（字符串拼接）&lt;/td&gt;
      &lt;td&gt;中（数据库 / 向量库交互）&lt;/td&gt;
      &lt;td&gt;高（循环逻辑、异常处理）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;交互层级&lt;/td&gt;
      &lt;td&gt;单次交互（点）&lt;/td&gt;
      &lt;td&gt;多轮记忆（面）&lt;/td&gt;
      &lt;td&gt;自主执行（体）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;代表产物&lt;/td&gt;
      &lt;td&gt;各种提示词模板、咒语&lt;/td&gt;
      &lt;td&gt;RAG 系统、聊天记录管理器&lt;/td&gt;
      &lt;td&gt;OpenClaw、AutoGPT、Claude Code&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;code-harness-engineering架构&quot;&gt;Code Harness Engineering架构&lt;/h1&gt;

&lt;p&gt;Code Harness = 模型层 + 智能体循环 + 运行时支撑&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;模型层：LLM / Reasoning LLM（引擎）&lt;/li&gt;
  &lt;li&gt;智能体循环：Observe → Inspect → Choose → Act（决策闭环）&lt;/li&gt;
  &lt;li&gt;运行时支撑：上下文、工具、权限、缓存、记忆、子代理（脚手架）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1775461401/fddabad8-788e-43aa-af06-f99a7e861c60.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;第一阶段系统启动与加载构建底座&quot;&gt;第一阶段：系统启动与加载（构建底座）&lt;/h2&gt;

&lt;h3 id=&quot;1-实时代码仓库上下文-live-repo-context&quot;&gt;1. 实时代码仓库上下文 (Live Repo Context)&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    %% 样式定义：红色高亮核心节点，其他节点保持默认清晰样式
    classDef highlight fill:#ff4d4f,stroke:#cf1322,stroke-width:2.5px,font-weight:bold,color:#fff

    %% 流程节点
    A[&quot;Inspect current repo&quot;]:::normal --&amp;gt;|1| B[&quot;Git context&amp;lt;br/&amp;gt;Branch, status, commits&quot;]:::normal
    A --&amp;gt;|1| C[&quot;Project docs&amp;lt;br/&amp;gt;AGENTS.md, README.md, etc.&quot;]:::normal
    B --&amp;gt;|2| D[&quot;Workspace&amp;lt;br/&amp;gt;Summary&quot;]:::highlight
    C --&amp;gt; D
    D --&amp;gt;|3| E[&quot;Stable prompt prefix&amp;lt;br/&amp;gt;Rules, tools, workspace summary&quot;]:::normal
    E --&amp;gt;|4| F[&quot;Combined prompt&amp;lt;br/&amp;gt;Prefix + request&quot;]:::normal
    G[&quot;User request&amp;lt;br/&amp;gt;&apos;Write unit test for function xyz&apos;&quot;]:::normal --&amp;gt;|5| F
    F --&amp;gt;|6| H[LLM]:::normal
    H --&amp;gt;|7| I[&quot;Model response&amp;lt;br/&amp;gt;Tool call or final answer&quot;]:::normal
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;2-提示词形态与缓存复用-prompt-shape-and-cache-reuse&quot;&gt;2. 提示词形态与缓存复用 (Prompt Shape And Cache Reuse)&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    classDef highlight fill:#ff4d4f,stroke:#cf1322,stroke-width:2.5px,font-weight:bold,color:#fff

    A[&quot;Workspace&amp;lt;br/&amp;gt;Summary&quot;] --&amp;gt; B[&quot;Stable prompt prefix&amp;lt;br/&amp;gt;Rules, tools, workspace summary&quot;]:::highlight
    B --&amp;gt; C[&quot;Prompt assembly&amp;lt;br/&amp;gt;Prefix + memory + transcript + request&quot;]
    D[&quot;Compact Transcript&amp;lt;br/&amp;gt;Recent history&quot;]:::highlight --&amp;gt;|1| C
    E[&quot;Latest user request&amp;lt;br/&amp;gt;&apos;Improve xyz&apos;&quot;] --&amp;gt;|3| C
    F[&quot;Working memory&amp;lt;br/&amp;gt;Task, files, notes&quot;] --&amp;gt;|4| C
    C --&amp;gt;|5| G[LLM]
    G --&amp;gt;|6| H[&quot;Model response&amp;lt;br/&amp;gt;Tool call or final answer&quot;]
    H --&amp;gt;|7| F
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;纯聊天式的 LLM 使用中，每一轮对话都会把所有内容（规则、工具、仓库信息、历史对话）重建成一个完整的大 Prompt。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;结构化拆分 Prompt，分离「稳定前缀」与「动态内容」&lt;/li&gt;
  &lt;li&gt;标准化 Prompt Shape（形态），保证推理稳定性&lt;/li&gt;
  &lt;li&gt;对稳定前缀做缓存复用，降本提效&lt;/li&gt;
  &lt;li&gt;联动全链路模块，为 Agent 循环打基础&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;stable-prompt-prefix稳定提示词前缀claudemd&quot;&gt;Stable Prompt Prefix（稳定提示词前缀）——CLAUDE.md&lt;/h4&gt;

&lt;p&gt;CLAUDE.md 文件：你编写的指令，为 Claude 提供持久上下文。CLAUDE.md不会被压缩，每个文件目标在 200 行以下。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;your-project/
├── .claude/
│   ├── CLAUDE.md           # 主项目指令
│   └── rules/
│       ├── code-style.md   # 代码样式指南
│       ├── testing.md      # 测试约定
│       └── security.md     # 安全要求
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;rules&quot;&gt;Rules&lt;/h4&gt;

&lt;p&gt;CLAUDE.md 是「主项目说明」，rules 是「模块化补充规则」，二者共同构成项目级稳定提示词前缀，且 rules 优先级更高。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;位置&lt;/th&gt;
      &lt;th&gt;核心定位&lt;/th&gt;
      &lt;th&gt;内容特点&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;CLAUDE.md&lt;/td&gt;
      &lt;td&gt;./CLAUDE.md 或 ./.claude/CLAUDE.md&lt;/td&gt;
      &lt;td&gt;主项目说明&lt;/td&gt;
      &lt;td&gt;项目整体架构、全局约定、常用命令、团队共享的核心规则&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rules&lt;/td&gt;
      &lt;td&gt;./.claude/rules/*.md&lt;/td&gt;
      &lt;td&gt;模块化专项规则&lt;/td&gt;
      &lt;td&gt;特定语言指南、测试约定、API 标准、安全要求等细分主题&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;第二阶段对话运行与组装核心循环&quot;&gt;第二阶段：对话运行与组装（核心循环）&lt;/h2&gt;

&lt;h3 id=&quot;5-结构化的会话记忆-structured-session-memory&quot;&gt;5. 结构化的会话记忆 (Structured Session Memory)&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    classDef highlight fill:#ff4d4f,stroke:#cf1322,stroke-width:2.5px,font-weight:bold,color:#fff

    subgraph UsedInNextPrompt_Main
        A[&quot;New events&amp;lt;br/&amp;gt;User turns, tool results, LLM responses&quot;] --&amp;gt; B[&quot;Session file on disk&quot;]
        B --&amp;gt; C[&quot;Full transcript&amp;lt;br/&amp;gt;User requests, tools results, LLM responses&quot;]:::highlight
        B --&amp;gt; D[&quot;Working memory&amp;lt;br/&amp;gt;Task, files, notes&quot;]:::highlight
    end
    subgraph UsedInNextPrompt
        C --&amp;gt; E[&quot;Compact Transcript&amp;lt;br/&amp;gt;Recent, compacted history&quot;]:::highlight
        F[&quot;Workspace&amp;lt;br/&amp;gt;Summary&quot;] --&amp;gt; G[&quot;Stable prompt prefix&amp;lt;br/&amp;gt;Rules, tools, workspace summary&quot;]
        G --&amp;gt; H[&quot;Prompt assembly&amp;lt;br/&amp;gt;Prefix + memory + transcript + request&quot;]
        E --&amp;gt; H
        D --&amp;gt; H
        I[&quot;Latest user request&amp;lt;br/&amp;gt;&apos;Improve xyz&apos;&quot;] --&amp;gt; H
        H --&amp;gt; J[LLM]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;记忆系统memory&quot;&gt;记忆系统Memory:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;自动记忆：Claude 根据你的更正和偏好自己编写的笔记&lt;/li&gt;
  &lt;li&gt;使用Sonnet模型，扫描 memory 目录下所有 .md，排除 MEMORY.md，只读每个文件前 30 行 frontmatter，按 mtime 倒序排序，最多保留 200 个候选&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;CLAUDE.md 文件&lt;/th&gt;
      &lt;th&gt;自动记忆&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;你&lt;/td&gt;
      &lt;td&gt;Claude&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;指令和规则&lt;/td&gt;
      &lt;td&gt;学习和模式&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;项目、用户或组织&lt;/td&gt;
      &lt;td&gt;每个工作树&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;每个会话&lt;/td&gt;
      &lt;td&gt;每个会话（前 200 行或 25KB）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;编码标准、工作流、项目架构&lt;/td&gt;
      &lt;td&gt;构建命令、调试见解、Claude 发现的偏好&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;打开方式：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;autoMemoryEnabled&quot;: false
  &quot;autoMemoryDirectory&quot;: &quot;~/my-custom-memory-dir&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;存储方式：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/.claude/projects/&amp;lt;project&amp;gt;/memory/
├── MEMORY.md          # 简洁索引，加载到每个会话
├── debugging.md       # 关于调试模式的详细笔记
├── api-conventions.md # API 设计决策
└── ...                # Claude 创建的任何其他主题文件
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;会话transcript&quot;&gt;会话Transcript：&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;层级&lt;/th&gt;
      &lt;th&gt;项目&lt;/th&gt;
      &lt;th&gt;保存位置&lt;/th&gt;
      &lt;th&gt;存储内容&lt;/th&gt;
      &lt;th&gt;生命周期&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;请求级 / 进程级缓存&lt;/td&gt;
      &lt;td&gt;readFileState&lt;/td&gt;
      &lt;td&gt;进程内 LRU 内存缓存&lt;/td&gt;
      &lt;td&gt;已读取文件的内容快照，以及 mtime、offset、limit、isPartialView 等读取状态&lt;/td&gt;
      &lt;td&gt;仅当前进程有效；LRU 淘汰；进程退出即丢失&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;请求级 / 进程级缓存&lt;/td&gt;
      &lt;td&gt;fileReadCache&lt;/td&gt;
      &lt;td&gt;进程内 Map&lt;/td&gt;
      &lt;td&gt;某些文件读取结果及其 mtime&lt;/td&gt;
      &lt;td&gt;仅当前进程有效；依赖 mtime 失效；进程退出即丢失&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;请求级 / 进程级缓存&lt;/td&gt;
      &lt;td&gt;FileIndex&lt;/td&gt;
      &lt;td&gt;进程内索引对象&lt;/td&gt;
      &lt;td&gt;文件路径和目录路径的模糊检索索引，不包含文件正文&lt;/td&gt;
      &lt;td&gt;仅当前进程有效；可重建；进程退出即丢失&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;请求级 / 进程级缓存&lt;/td&gt;
      &lt;td&gt;ignore 过滤缓存&lt;/td&gt;
      &lt;td&gt;进程内缓存&lt;/td&gt;
      &lt;td&gt;.ignore、.gitgnore 解析后的过滤规则&lt;/td&gt;
      &lt;td&gt;仅当前进程有效；进程退出即丢失&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;请求级 / 进程级缓存&lt;/td&gt;
      &lt;td&gt;GitFileWatcher cache&lt;/td&gt;
      &lt;td&gt;进程内 watcher/cache&lt;/td&gt;
      &lt;td&gt;当前 branch、HEAD、default branch、remote URL 等 git 元信息&lt;/td&gt;
      &lt;td&gt;仅当前进程有效；.git 变化时失效；进程退出即丢失&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;项目级持久化&lt;/td&gt;
      &lt;td&gt;exampleFiles&lt;/td&gt;
      &lt;td&gt;项目配置&lt;/td&gt;
      &lt;td&gt;由 git log 推导出的高频修改文件列表及时间戳&lt;/td&gt;
      &lt;td&gt;跨进程、跨会话保留；按项目保存；过期后重算&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;会话级持久化&lt;/td&gt;
      &lt;td&gt;session transcript&lt;/td&gt;
      &lt;td&gt;~/.claude/projects/…/&lt;sessionId&gt;.jsonl&lt;/sessionId&gt;&lt;/td&gt;
      &lt;td&gt;会话消息、工具结果摘要、快照事件、替换记录、折叠记录等 JSONL 事件流&lt;/td&gt;
      &lt;td&gt;跟随 session 长期存在；用于 resume/continue&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;会话级持久化&lt;/td&gt;
      &lt;td&gt;attribution snapshot&lt;/td&gt;
      &lt;td&gt;~/.claude/projects/…/&lt;sessionId&gt;.jsonl&lt;/sessionId&gt;&lt;/td&gt;
      &lt;td&gt;Claude / 人工改动归因状态&lt;/td&gt;
      &lt;td&gt;跟随 session transcript 存在；恢复会话时加载&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;会话级持久化&lt;/td&gt;
      &lt;td&gt;file-history snapshot&lt;/td&gt;
      &lt;td&gt;~/.claude/projects/…/&lt;sessionId&gt;.jsonl&lt;/sessionId&gt;&lt;/td&gt;
      &lt;td&gt;文件版本快照的索引信息，如文件路径、版本号、关联备份名&lt;/td&gt;
      &lt;td&gt;跟随 session transcript 存在；用于恢复文件历史状态&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;会话级持久化&lt;/td&gt;
      &lt;td&gt;content-replacement&lt;/td&gt;
      &lt;td&gt;~/.claude/projects/…/&lt;sessionId&gt;.jsonl&lt;/sessionId&gt;&lt;/td&gt;
      &lt;td&gt;上下文压缩 / 替换后的映射关系&lt;/td&gt;
      &lt;td&gt;跟随 session transcript 存在；服务长会话恢复&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;会话级持久化&lt;/td&gt;
      &lt;td&gt;context-collapse / related snapshot&lt;/td&gt;
      &lt;td&gt;~/.claude/projects/…/&lt;sessionId&gt;.jsonl&lt;/sessionId&gt;&lt;/td&gt;
      &lt;td&gt;上下文折叠后的提交点、摘要快照及相关恢复信息&lt;/td&gt;
      &lt;td&gt;跟随 session transcript 存在；服务 resume/continue&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;会话级文件实体&lt;/td&gt;
      &lt;td&gt;file-history 备份文件&lt;/td&gt;
      &lt;td&gt;~/.claude/file-history/&lt;sessionId&gt;/&lt;hash&gt;@vN&lt;/hash&gt;&lt;/sessionId&gt;&lt;/td&gt;
      &lt;td&gt;文件某个版本的真实内容副本&lt;/td&gt;
      &lt;td&gt;跟随该 session 的文件历史存在；可在 resume 时复制 / 复用&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;进程级：都是性能缓存，退出即丢。&lt;/li&gt;
  &lt;li&gt;项目级：只有 exampleFiles 这类轻量衍生缓存。&lt;/li&gt;
  &lt;li&gt;会话级：核心都写进 ~/.claude/projects/…/*.jsonl。&lt;/li&gt;
  &lt;li&gt;文件实体级：真正的旧文件内容只在 ~/.claude/file-history/&lt;sessionId&gt;/...。&lt;/sessionId&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;第三阶段工具执行与安全管控驾驭核心&quot;&gt;第三阶段：工具执行与安全管控（驾驭核心）&lt;/h2&gt;

&lt;h3 id=&quot;3-工具的接入与调用-tool-access-and-use&quot;&gt;3. 工具的接入与调用 (Tool Access and Use)&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1775463413/clipboard_1775463409762_44x6c7ixb.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;领域&lt;/th&gt;
      &lt;th&gt;实现方式&lt;/th&gt;
      &lt;th&gt;关键配置项&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;命令 / 工具执行权限&lt;/td&gt;
      &lt;td&gt;先匹配通用工具规则，再进入工具自己的 checkPermissions ()，最后叠加 mode、bypass、dangerous rule 处理&lt;/td&gt;
      &lt;td&gt;permissions.allow、permissions.deny、permissions.ask、permissions.defaultMode、permissions.disableBypassPermissionsMode、permissions.disableAutoMode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;文件系统权限&lt;/td&gt;
      &lt;td&gt;基于路径规则、工作目录边界、敏感路径 / 危险路径检查做 allow/deny/ask；编辑类工具会单独接入文件权限检查&lt;/td&gt;
      &lt;td&gt;permissions.additionalDirectories、permissions.allow/deny/ask&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;外部访问权限&lt;/td&gt;
      &lt;td&gt;WebFetch 做 host/path 级规则；MCP 先做 server 级启用 / 批准 / 企业策略，再到具体 MCP tool 权限&lt;/td&gt;
      &lt;td&gt;allowedMcpServers、deniedMcpServers、enableAllProjectMcpServers、enabledMcpjsonServers、disabledMcpjsonServers、allowManagedMcpServersOnly、allowManagedPermissionRulesOnly、skipWebFetchPreflight&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;相关配置：&lt;a href=&quot;https://www.claude-cn.org/posts/claude-code-complete-guide#_6%EF%B8%8F%E2%83%A3-%E5%AE%89%E5%85%A8%E5%92%8C%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86-%E2%80%8B&quot;&gt;Claude Code 完整使用指南&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;低幻觉&quot;&gt;低幻觉&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;生成 → 执行 → 验证 → 诊断 → 自愈 → 再执行&lt;/li&gt;
  &lt;li&gt;闭环 = 零幻觉、高可靠的根本原因&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;强力提示词&quot;&gt;强力提示词&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/services/compact/prompt.ts:19&lt;/code&gt;：明确要求 Your entire response must be plain text: an &lt;analysis&gt; block followed by a &amp;lt;summary&amp;gt; block.这是最直接的“先分析再给总结”。&lt;/analysis&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/services/compact/prompt.ts:31&lt;/code&gt;：Before providing your final summary, wrap your
analysis in &lt;analysis&gt; tags...，属于硬性两阶段输出。&lt;/analysis&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/commands/security-review.ts:92&lt;/code&gt;：有 ANALYSIS METHODOLOGY，后面分 Phase 1/2/3做仓库研究、对比分析、漏洞评估，明显是先论证再结论。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/commands/security-review.ts:130&lt;/code&gt;：要求 FALSE POSITIVE FILTERING 和 confidence score，不是简单找问题，而是要求证据和置信度。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/memdir/memoryTypes.ts:201&lt;/code&gt;：Before answering the user … verify that the memory is still correct and up-to-date，这是“先验证再回答”。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/coordinator/coordinatorMode.ts:328&lt;/code&gt;：Prove the code works, don’t just confirm it exists，要求给可验证依据，不接受口头确认。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;第四阶段上下文瘦身防爆炸与优化&quot;&gt;第四阶段：上下文瘦身（防爆炸与优化）&lt;/h2&gt;

&lt;h3 id=&quot;4-给上下文瘦身防止撑爆-minimizing-context-bloat&quot;&gt;4. 给上下文瘦身，防止撑爆 (Minimizing Context Bloat)&lt;/h3&gt;

&lt;p&gt;在长会话（比如写代码、改项目）中，AI 会产生大量冗余信息，如果全部塞给 LLM，会：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;超 Token 限制（报错）&lt;/li&gt;
  &lt;li&gt;算力成本飙升&lt;/li&gt;
  &lt;li&gt;AI 注意力分散（记不住重点）
所以，必须做 上下文压缩（Context Compression）。&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;Clip（裁剪）把过长的搜索结果、Shell 日志，截断到合适长度。&lt;/li&gt;
  &lt;li&gt;Deduplicate（去重），删掉旧的、重复的文件读取记录，避免重复信息干扰。&lt;/li&gt;
  &lt;li&gt;Asymmetric detail（非对称细节）。最近的对话保留详细信息，久远的对话只保留摘要。AI 对最近的事记得最清，对很久以前的事只需要大概印象。&lt;/li&gt;
  &lt;li&gt;Compact transcript（精简转录）。经过上面 3 步处理后，得到一个短小、干净、无冗余的历史记录。&lt;/li&gt;
  &lt;li&gt;Prompt assembly（组装）。把精简后的历史，和其他信息（规则、任务、记忆）拼成最终 Prompt，发给 LLM。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;压缩时机&quot;&gt;压缩时机&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;手动执行：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/compact&lt;/code&gt;。/compact 能明显压缩当前会话上下文，让后续对话继续跑下去；但它不会把磁盘上的 transcript JSONL 文件变小。&lt;/li&gt;
  &lt;li&gt;自动执行：系统在上下文快满时自动做的 autoCompact&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;压缩方式&quot;&gt;压缩方式&lt;/h4&gt;

&lt;p&gt;压缩内容：session transcript 
在这条链上插入 compact boundary 和 compact summary&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;之后模型只取 boundary 后面的消息继续工作&lt;/li&gt;
  &lt;li&gt;所以被“压缩”的是 transcript 里的会话历史视图&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    classDef highlight fill:#ff4d4f,stroke:#cf1322,stroke-width:2.5px,font-weight:bold,color:#fff

    A[&quot;Search results&amp;lt;br/&amp;gt;Often larger than needed&quot;] --&amp;gt;|1| B[&quot;Clip&amp;lt;br/&amp;gt;Reduce raw output size&quot;]:::highlight
    C[&quot;Shell logs&amp;lt;br/&amp;gt;Also often larger than needed&quot;] --&amp;gt;|2| B
    Skill[&quot;Skills&quot;] --&amp;gt;|3|B
    D[&quot;Repeated reads&amp;lt;br/&amp;gt;Same file, many turns&quot;] --&amp;gt;|3| E[&quot;Deduplicate&amp;lt;br/&amp;gt;Older read_file events&quot;]:::highlight
    F[&quot;Full transcript&amp;lt;br/&amp;gt;User requests, tools results, LLM responses&quot;] --&amp;gt;|4| G[&quot;Asymmetric detail&amp;lt;br/&amp;gt;Recent rich, older short&quot;]:::highlight
    E --&amp;gt;|5| H[&quot;Compact transcript&amp;lt;br/&amp;gt;Recent, compacted history&quot;]
    G --&amp;gt; H
    B --&amp;gt; H
    H --&amp;gt;|6| I[&quot;Prompt&amp;lt;br/&amp;gt;The assembled prompt&amp;lt;br/&amp;gt;(last section)&quot;]
    I --&amp;gt;|7| J[LLM]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;第五阶段任务委派与复杂场景&quot;&gt;第五阶段：任务委派与复杂场景&lt;/h2&gt;

&lt;h3 id=&quot;6-任务委派与受限子智能体-delegation-with-bounded-subagents&quot;&gt;6. 任务委派与受限子智能体 (Delegation With (Bounded) Subagents)&lt;/h3&gt;

&lt;p&gt;官方文档：&lt;a href=&quot;https://code.claude.com/docs/zh-CN/sub-agents&quot;&gt;创建自定义 subagents&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;配置方式&quot;&gt;配置方式&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude --agents &apos;{
  &quot;code-reviewer&quot;: {
    &quot;description&quot;: &quot;Expert code reviewer. Use proactively after code changes.&quot;,
    &quot;prompt&quot;: &quot;You are a senior code reviewer. Focus on code quality, security, and best practices.&quot;,
    &quot;tools&quot;: [&quot;Read&quot;, &quot;Grep&quot;, &quot;Glob&quot;, &quot;Bash&quot;],
    &quot;model&quot;: &quot;sonnet&quot;
  },
  &quot;debugger&quot;: {
    &quot;description&quot;: &quot;Debugging specialist for errors and test failures.&quot;,
    &quot;prompt&quot;: &quot;You are an expert debugger. Analyze errors, identify root causes, and provide fixes.&quot;
  }
}&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;调用方式&quot;&gt;调用方式&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;自动委托：Claude 根据您请求中的任务描述、subagent 配置中的 description 字段和当前上下文自动委托任务。要鼓励主动委托，在您的 subagent 的 description 字段中包含”use proactively”之类的短语。&lt;/li&gt;
  &lt;li&gt;显示调用
    &lt;ul&gt;
      &lt;li&gt;自然语言：在提示中命名 subagent；Claude 决定是否委托&lt;/li&gt;
      &lt;li&gt;@-mention：保证 subagent 为一个任务运行&lt;/li&gt;
      &lt;li&gt;会话范围：整个会话使用该 subagent 的系统提示、工具限制和模型，通过 –agent 标志或 agent 设置&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;完整架构图&quot;&gt;完整架构图&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    %% 样式定义
    classDef context fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
    classDef memory fill:#fff9c4,stroke:#fbc02d,stroke-width:2px;
    classDef compression fill:#e0f2f1,stroke:#00695c,stroke-width:2px;
    classDef prompt fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
    classDef llm fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
    classDef tools fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
    classDef agents fill:#ffebee,stroke:#c62828,stroke-width:2px;

    subgraph Context_Layer [1. 实时代码仓库上下文]
        A1[Git 上下文&amp;lt;br/&amp;gt;分支/状态/提交]:::context
        A2[工作区摘要&amp;lt;br/&amp;gt;文件结构/内容]:::context
        A3[项目文档&amp;lt;br/&amp;gt;AGENTS.md / README.md]:::context
    end

    subgraph Memory_Layer [5. 结构化的会话记忆]
        B1[会话文件&amp;lt;br/&amp;gt;完整转录]:::memory
        B2[工作记忆&amp;lt;br/&amp;gt;当前任务/笔记]:::memory
    end

    subgraph Compression_Layer [4. 上下文瘦身]
        C1[裁剪&amp;lt;br/&amp;gt;减少原始输出]:::compression
        C2[去重&amp;lt;br/&amp;gt;合并重复读取]:::compression
        C3[非对称细节&amp;lt;br/&amp;gt;近期丰富 / 远期简略]:::compression
    end

    subgraph Prompt_Layer [2. 提示词形态与缓存复用]
        D1[稳定前缀&amp;lt;br/&amp;gt;规则/工具/摘要]:::prompt
        D2[提示词组装&amp;lt;br/&amp;gt;前缀+记忆+转录+请求]:::prompt
    end

    subgraph Execution_Layer [核心执行引擎]
        E1[LLM 大语言模型]:::llm
    end

    subgraph Tool_Layer [3. 工具的接入与调用]
        F1[工具定义&amp;lt;br/&amp;gt;搜索/Shell/文件]:::tools
        F2[沙箱执行&amp;lt;br/&amp;gt;环境隔离]:::tools
        F3[结果处理&amp;lt;br/&amp;gt;格式化/截断]:::tools
    end

    subgraph Agent_Layer [6. 任务委派与受限子智能体]
        G1[规划与分解&amp;lt;br/&amp;gt;拆解复杂任务]:::agents
        G2[子智能体执行&amp;lt;br/&amp;gt;独立上下文]:::agents
        G3[结果聚合&amp;lt;br/&amp;gt;返回主会话]:::agents
    end

    %% 数据流向
    UserRequest --&amp;gt; Memory_Layer
    UserRequest --&amp;gt; Context_Layer
    
    Memory_Layer --&amp;gt; Compression_Layer
    Context_Layer --&amp;gt; Compression_Layer
    
    Compression_Layer --&amp;gt; Prompt_Layer
    Prompt_Layer --&amp;gt; Execution_Layer
    
    Execution_Layer -- 工具调用 --&amp;gt; Tool_Layer
    Tool_Layer -- 观察结果 --&amp;gt; Memory_Layer
    
    Execution_Layer -- 复杂任务委派 --&amp;gt; Agent_Layer
    Agent_Layer -- 子智能体结果 --&amp;gt; Memory_Layer
    
    Execution_Layer -- 最终响应 --&amp;gt; User
&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;更多阅读&quot;&gt;更多阅读&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzY4NzAzOTMxMQ==&amp;amp;mid=2247483770&amp;amp;idx=1&amp;amp;sn=f35fe72584f3a06e415374b93866e52e&amp;amp;chksm=f285a3b7f5525c7728c4786d9cdb2d537175bbc80ab29cdfcf82306cc0ceeb67679d47766b52&amp;amp;mpshare=1&amp;amp;srcid=0405ou8kKlrFZwUPMWG0RKdg&amp;amp;sharer_shareinfo=ca6d0395e3844d726629609a65950860&amp;amp;sharer_shareinfo_first=ca6d0395e3844d726629609a65950860&amp;amp;from=timeline&amp;amp;scene=2&amp;amp;subscene=1&amp;amp;sessionid=1775389781&amp;amp;clicktime=1775393610&amp;amp;enterid=1775393610&amp;amp;ascene=2&amp;amp;fasttmpl_type=0&amp;amp;fasttmpl_fullversion=8198750-zh_CN-zip&amp;amp;fasttmpl_flag=0&amp;amp;realreporttime=1775393610299&amp;amp;devicetype=android-36&amp;amp;version=2800455e&amp;amp;nettype=WIFI&amp;amp;abtest_cookie=AAACAA%3D%3D&amp;amp;lang=zh_CN&amp;amp;countrycode=CN&amp;amp;exportkey=n_ChQIAhIQLrV3Z6FNARBA18j6HgJWhhLZAQIE97dBBAEAAAAAAMF4EGXkL1oAAAAOpnltbLcz9gKNyK89dVj05Fua3wl%2BOMIR6nYzbVRQBjfErWIK0N4guPCG7YeZuqKnrUZN%2FHW874oZ9E%2F8tS49gafo3KWnb6Ut%2F16f8u8Ew23eUaI8YTD%2BF1L7JeKTAP73H%2BCdM3Y7BqzPgZ%2B5t4PUj8UKuj2Fo%2Fsbcz6SbulnaWlp9dXHcZ4%2FbQSOrTwPqrO2EEclzAhjqxAKDoHf1tIi4IZ5verd7%2BJU%2Fn2xordqbyecazhHe6JpAlt%2FopoJaeXvNZE%3D&amp;amp;pass_ticket=STXDxqr1fHzDxQgY62AnLArtheY%2Bt%2BnwvStnOl71NpzsFioMSJ%2BoMzc5Vo6XrORM&amp;amp;wx_header=3&quot;&gt;2026 AI 开发新范式：Harness Engineering（驾驭工程）为何是智能体的决胜点？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://magazine.sebastianraschka.com/p/components-of-a-coding-agent&quot;&gt;HComponents of A Coding Agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Claude Code安装与使用完全指南：从OpenRouter到黄大善人模型</title>
   <link href="https://blog.cyeam.com/ai/2026/04/05/claude-install"/>
   <updated>2026-04-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/04/05/claude-install</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#准备&quot; id=&quot;markdown-toc-准备&quot;&gt;准备&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#安装使用openrouter模型&quot; id=&quot;markdown-toc-安装使用openrouter模型&quot;&gt;安装使用OpenRouter模型&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#配置环境变量&quot; id=&quot;markdown-toc-配置环境变量&quot;&gt;配置环境变量&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#启动&quot; id=&quot;markdown-toc-启动&quot;&gt;启动&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#使用黄大善人模型&quot; id=&quot;markdown-toc-使用黄大善人模型&quot;&gt;使用黄大善人模型&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#接入langsmith&quot; id=&quot;markdown-toc-接入langsmith&quot;&gt;接入LangSmith&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#安装插件&quot; id=&quot;markdown-toc-安装插件&quot;&gt;安装插件&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#配置token&quot; id=&quot;markdown-toc-配置token&quot;&gt;配置Token&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#模型选择&quot; id=&quot;markdown-toc-模型选择&quot;&gt;模型选择&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#增加提示音&quot; id=&quot;markdown-toc-增加提示音&quot;&gt;增加提示音&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#shortcuts&quot; id=&quot;markdown-toc-shortcuts&quot;&gt;Shortcuts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#配置文件&quot; id=&quot;markdown-toc-配置文件&quot;&gt;配置文件&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;准备&quot;&gt;准备&lt;/h1&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://claude.ai/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里需要魔法，绕过Claude登录限制。&lt;/p&gt;

&lt;h1 id=&quot;安装使用openrouter模型&quot;&gt;安装使用OpenRouter模型&lt;/h1&gt;

&lt;p&gt;官方文档：&lt;a href=&quot;https://openrouter.ai/docs/guides/coding-agents/claude-code-integration&quot;&gt;Claude Code&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;配置环境变量&quot;&gt;配置环境变量&lt;/h2&gt;

&lt;p&gt;注意&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ANTHROPIC_API_KEY&lt;/code&gt;要留空，保存后执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;OPENROUTER_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;your-openrouter-api-key&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://openrouter.ai/api&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OPENROUTER_API_KEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/.zshrc &lt;span class=&quot;c&quot;&gt;# 记得要让模型生效&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;启动&quot;&gt;启动&lt;/h2&gt;

&lt;p&gt;如果前面的配置都正确，不会要求登录并且执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/init&lt;/code&gt;命令后会初始化项目，创建CLAUDE.md文件。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/your/project
claude
/status
/init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;使用黄大善人模型&quot;&gt;使用黄大善人模型&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/musistudio/claude-code-router?tab=readme-ov-file&quot;&gt;claude-code-router&lt;/a&gt;支持在本地路由模型、做协议转换。在这里我们用它代理黄大善人的模型。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;注册。移步：&lt;a href=&quot;https://build.nvidia.com&quot;&gt;黄大善人官网&lt;/a&gt;。
    &lt;ol&gt;
      &lt;li&gt;注册好后创建API Key。&lt;/li&gt;
      &lt;li&gt;也可以点击右上角的View Code，能看到完整Url和API Key。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;安装代理&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g @musistudio/claude-code-router&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ccr ui&lt;/code&gt;。强烈建议用这个，比直接改配置省事。&lt;/li&gt;
  &lt;li&gt;启动Claude。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ccr code&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;接入langsmith&quot;&gt;接入LangSmith&lt;/h1&gt;

&lt;h2 id=&quot;安装插件&quot;&gt;安装插件&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/plugin marketplace add langchain-ai/langsmith-claude-code-plugins
/plugin install langsmith-tracing@langsmith-claude-code-plugins
/reload-plugins
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;配置token&quot;&gt;配置Token&lt;/h2&gt;

&lt;p&gt;路径：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/settings.local.json&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;env&quot;: {
    &quot;TRACE_TO_LANGSMITH&quot;: &quot;true&quot;,
    &quot;CC_LANGSMITH_API_KEY&quot;: &quot;&amp;lt;LangSmith API key&amp;gt;&quot;,
    &quot;CC_LANGSMITH_PROJECT&quot;: &quot;my-project&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.langchain.com/langsmith/trace-claude-code&quot;&gt;Trace Claude Code applications&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;模型选择&quot;&gt;模型选择&lt;/h1&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;模型名称&lt;/th&gt;
      &lt;th&gt;别名&lt;/th&gt;
      &lt;th&gt;核心定位&lt;/th&gt;
      &lt;th&gt;适用场景&lt;/th&gt;
      &lt;th&gt;核心优势&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Claude 3.5/4.5 Sonnet（执行力 / 日常首选）&lt;/td&gt;
      &lt;td&gt;十四行诗&lt;/td&gt;
      &lt;td&gt;开发主力&lt;/td&gt;
      &lt;td&gt;日常代码编写、复杂需求理解、功能落地实现、代码重构优化&lt;/td&gt;
      &lt;td&gt;在模型智力与响应速度间达成完美平衡，是绝大多数编码任务的最高性价比选择&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Claude 3.5/4.5 Opus（逻辑王 / 高阶规划）&lt;/td&gt;
      &lt;td&gt;宏大乐章&lt;/td&gt;
      &lt;td&gt;复杂任务攻坚&lt;/td&gt;
      &lt;td&gt;系统架构设计、高复杂度算法开发、大型项目重构、超长上下文深度理解&lt;/td&gt;
      &lt;td&gt;思考逻辑缜密、具备全局统筹能力，是 Claude 系列中推理能力最强的型号&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Claude 3.5/4.5 Haiku（闪电速 / 轻量级）&lt;/td&gt;
      &lt;td&gt;俳句&lt;/td&gt;
      &lt;td&gt;简单辅助、高频轻量任务处理&lt;/td&gt;
      &lt;td&gt;简单样板代码生成、文档内容总结、代码格式化、轻量自动化脚本编写&lt;/td&gt;
      &lt;td&gt;响应速度最快，调用成本极低，适合高频次、低复杂度的辅助需求&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;增加提示音&quot;&gt;增加提示音&lt;/h1&gt;

&lt;p&gt;这个很重要，由于CC能力很强需要后台做很多事情，所以需要提示音来提醒我们任务进度。你可以参考这篇文档调整自己喜欢的提示音&lt;a href=&quot;https://claude-docs.plugins-world.cn/claude-docs/experience/notification-sound.html&quot;&gt;配置任务提示音&lt;/a&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Notification 需要操作确认时播放Glass.aiff&lt;/li&gt;
  &lt;li&gt;Stop 任务完成时播放Ping.aiff&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vi ~/.claude/settings.json

{
  &quot;hooks&quot;: {
    &quot;Notification&quot;: [
      {
        &quot;matcher&quot;: &quot;&quot;,
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;afplay /System/Library/Sounds/Glass.aiff&quot;
          }
        ]
      }
    ],
    &quot;Stop&quot;: [
      {
        &quot;hooks&quot;: [
          {
            &quot;type&quot;: &quot;command&quot;,
            &quot;command&quot;: &quot;afplay /System/Library/Sounds/Ping.aiff&quot;
          }
        ]
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;shortcuts&quot;&gt;Shortcuts&lt;/h1&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Shortcut&lt;/th&gt;
      &lt;th&gt;Action&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+C&lt;/td&gt;
      &lt;td&gt;Cancel current generation or input&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+D&lt;/td&gt;
      &lt;td&gt;Exit Claude Code&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+L&lt;/td&gt;
      &lt;td&gt;Clear terminal screen&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+R&lt;/td&gt;
      &lt;td&gt;Reverse search command history&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Esc + Esc&lt;/td&gt;
      &lt;td&gt;Rewind to previous checkpoint (undo)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Shift+Tab&lt;/td&gt;
      &lt;td&gt;Cycle permission modes (default/plan/yolo)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+G&lt;/td&gt;
      &lt;td&gt;Open input in external editor (vim, etc.)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+O&lt;/td&gt;
      &lt;td&gt;Toggle verbose output&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+T&lt;/td&gt;
      &lt;td&gt;Toggle task list&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ctrl+F&lt;/td&gt;
      &lt;td&gt;Kill all background agents (press twice)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;a href=&quot;https://computingforgeeks.com/claude-code-cheat-sheet/#google_vignette&quot;&gt;Claude Code Cheat Sheet – Commands, Shortcuts, Tips&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;配置文件&quot;&gt;配置文件&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://code.claude.com/docs/zh-CN/settings&quot;&gt;Claude Code 设置&lt;/a&gt;。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>大语言模型选型指南：从架构到场景的全方位对比分析</title>
   <link href="https://blog.cyeam.com/ai/2026/04/04/llm-compare"/>
   <updated>2026-04-04T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/04/04/llm-compare</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#发展史&quot; id=&quot;markdown-toc-发展史&quot;&gt;发展史&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#模型指标&quot; id=&quot;markdown-toc-模型指标&quot;&gt;模型指标&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#模型体量-parameters&quot; id=&quot;markdown-toc-模型体量-parameters&quot;&gt;模型体量 Parameters&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#架构-architecture&quot; id=&quot;markdown-toc-架构-architecture&quot;&gt;架构 Architecture&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#embedding-模型&quot; id=&quot;markdown-toc-embedding-模型&quot;&gt;Embedding 模型&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#蒸馏rag&quot; id=&quot;markdown-toc-蒸馏rag&quot;&gt;蒸馏&amp;amp;RAG&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#模型选择参考&quot; id=&quot;markdown-toc-模型选择参考&quot;&gt;模型选择参考&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;发展史&quot;&gt;发展史&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;史前时代（2017年前） ：基于规则和统计的小模型，只能完成简单任务，长文本处理能力弱。&lt;/li&gt;
  &lt;li&gt;奠基时代（2017-2019） ：Transformer架构诞生，自注意力机制实现并行训练，GPT和BERT开启预训练+微调范式。&lt;/li&gt;
  &lt;li&gt;爆发时代（2020-2022） ：GPT-3证明规模即能力，ChatGPT通过RLHF技术实现对话能力，LLM进入大众视野。&lt;/li&gt;
  &lt;li&gt;多模态与智能体时代（2023至今） ：GPT-4实现图文多模态，开源模型百花齐放，RAG、Agent等技术推动应用落地。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;模型指标&quot;&gt;模型指标&lt;/h1&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Attribute&lt;/th&gt;
      &lt;th&gt;Nemotron Nano 12B 2 VL (free)&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Author&lt;/td&gt;
      &lt;td&gt;nvidia&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Model Size / Parameters&lt;/td&gt;
      &lt;td&gt;7B&lt;/td&gt;
      &lt;td&gt;模型体量 / 参数量。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Context Length&lt;/td&gt;
      &lt;td&gt;128K&lt;/td&gt;
      &lt;td&gt;上下文越大，长文本 / 长对话 / 复杂任务理解能力越强&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hallucination&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;幻觉率&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Architecture&lt;/td&gt;
      &lt;td&gt;hybrid Transformer-Mamba architecture&lt;/td&gt;
      &lt;td&gt;大模型架构&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reasoning&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;推理能力&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Latency (p50)&lt;/td&gt;
      &lt;td&gt;2.09s&lt;/td&gt;
      &lt;td&gt;延迟越低，响应越快&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Throughput (p50)&lt;/td&gt;
      &lt;td&gt;40.0 tok/s&lt;/td&gt;
      &lt;td&gt;吞吐量越高，生成速度越快&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Input Pricing&lt;/td&gt;
      &lt;td&gt;$0 / M tokens&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Output Pricing&lt;/td&gt;
      &lt;td&gt;$0 / M tokens&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Input Modalities&lt;/td&gt;
      &lt;td&gt;image, text, video&lt;/td&gt;
      &lt;td&gt;输入模态，此处均为多模态&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Output Modalities&lt;/td&gt;
      &lt;td&gt;text&lt;/td&gt;
      &lt;td&gt;输出模态&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Quantization&lt;/td&gt;
      &lt;td&gt;unknown&lt;/td&gt;
      &lt;td&gt;量化精度&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Stream cancellation&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;流式取消&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Supports Tools&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;工具调用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;No Prompt Training&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;无提示词训练&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Caching&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;缓存&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;模型体量-parameters&quot;&gt;模型体量 Parameters&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;它决定了模型的知识存储容量、复杂推理能力、泛化能力的理论上限。&lt;/li&gt;
  &lt;li&gt;但「上限高」≠「实际表现好」，就像一个天赋高的学生，不努力（训练差）也考不过努力的普通学生&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;架构-architecture&quot;&gt;架构 Architecture&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;普通大模型（Dense）：一个超级大脑。&lt;/li&gt;
  &lt;li&gt;专家模型（MoE）：一群分工明确的小专家（但对外表现为一个整体）。&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;英文术语&lt;/th&gt;
      &lt;th&gt;中文&lt;/th&gt;
      &lt;th&gt;简单直白解释&lt;/th&gt;
      &lt;th&gt;优点&lt;/th&gt;
      &lt;th&gt;缺点&lt;/th&gt;
      &lt;th&gt;典型模型&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Transformer&lt;/td&gt;
      &lt;td&gt;标准 Transformer 架构&lt;/td&gt;
      &lt;td&gt;目前主流大模型通用架构，靠注意力机制理解上下文&lt;/td&gt;
      &lt;td&gt;逻辑强、推理稳、对话自然&lt;/td&gt;
      &lt;td&gt;越长文本越慢、算力开销大&lt;/td&gt;
      &lt;td&gt;Llama、Qwen、GPT 系列&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mamba (SSM)&lt;/td&gt;
      &lt;td&gt;状态空间模型&lt;/td&gt;
      &lt;td&gt;新一代架构，靠 “状态传递” 处理长文本&lt;/td&gt;
      &lt;td&gt;极快、超长上下文稳定、显存友好&lt;/td&gt;
      &lt;td&gt;逻辑 / 数学略弱于 Transformer&lt;/td&gt;
      &lt;td&gt;Mamba 系列、Jamba&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hybrid Transformer-Mamba&lt;/td&gt;
      &lt;td&gt;混合 Transformer+Mamba&lt;/td&gt;
      &lt;td&gt;一部分层用 Transformer，一部分用 Mamba&lt;/td&gt;
      &lt;td&gt;兼顾推理能力 + 长文本速度&lt;/td&gt;
      &lt;td&gt;结构复杂、训练难度高&lt;/td&gt;
      &lt;td&gt;Nemotron Nano、一些新 VL 模型&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dense&lt;/td&gt;
      &lt;td&gt;稠密模型&lt;/td&gt;
      &lt;td&gt;每次生成都会激活全部参数&lt;/td&gt;
      &lt;td&gt;输出稳定、质量均匀、不忽强忽弱&lt;/td&gt;
      &lt;td&gt;更吃显存、速度一般&lt;/td&gt;
      &lt;td&gt;大部分 7B/13B/12B 模型&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MoE (Mixture of Experts)&lt;/td&gt;
      &lt;td&gt;混合专家模型&lt;/td&gt;
      &lt;td&gt;只激活部分参数做推理&lt;/td&gt;
      &lt;td&gt;速度快、可做大参数量模型&lt;/td&gt;
      &lt;td&gt;可能不稳定、质量波动&lt;/td&gt;
      &lt;td&gt;Gemma 4、Qwen MoE、Mixtral&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Base Model&lt;/td&gt;
      &lt;td&gt;基座模型&lt;/td&gt;
      &lt;td&gt;只预训练过，不会对话&lt;/td&gt;
      &lt;td&gt;泛化强、适合二次训练&lt;/td&gt;
      &lt;td&gt;不能直接聊天&lt;/td&gt;
      &lt;td&gt;原始 Llama、原始 Qwen&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Chat / Instruct Tuned&lt;/td&gt;
      &lt;td&gt;对话 / 指令微调&lt;/td&gt;
      &lt;td&gt;专门优化过，会听话、会聊天&lt;/td&gt;
      &lt;td&gt;好用、对齐人类意图&lt;/td&gt;
      &lt;td&gt;能力受微调数据限制&lt;/td&gt;
      &lt;td&gt;几乎所有 API 模型&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;embedding-模型&quot;&gt;Embedding 模型&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;LLM：负责说话、思考、回答&lt;/li&gt;
  &lt;li&gt;Embedding：负责把内容变成可计算的数字，方便查找和匹配&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;维度区间&lt;/th&gt;
      &lt;th&gt;典型代表模型&lt;/th&gt;
      &lt;th&gt;核心适用场景&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;128-384 维&lt;/td&gt;
      &lt;td&gt;bge-small、m3e-small、text-embedding-3-small&lt;/td&gt;
      &lt;td&gt;轻量场景、移动端部署、海量短文本（评论 / 标题）、对延迟要求极高、成本敏感的业务&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;512-1024 维&lt;/td&gt;
      &lt;td&gt;bge-base、m3e-base、通用多模态 embedding&lt;/td&gt;
      &lt;td&gt;通用 RAG 场景、企业知识库、文档问答、百万级数据量，平衡精度与性能的首选区间&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1024-3072 维&lt;/td&gt;
      &lt;td&gt;bge-large、text-embedding-3-large、gte-large&lt;/td&gt;
      &lt;td&gt;专业领域（法律 / 医疗 / 科研）、复杂长文本、千万级大规模数据、对召回精度有极高要求的场景&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3072 维以上&lt;/td&gt;
      &lt;td&gt;仅特定科研 / 多模态细分场景&lt;/td&gt;
      &lt;td&gt;通用 RAG 几乎不推荐使用，仅极端细分的高精度语义匹配场景有极少量应用&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;蒸馏rag&quot;&gt;蒸馏&amp;amp;RAG&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;知识蒸馏（Knowledge Distillation, KD）
    &lt;ul&gt;
      &lt;li&gt;本质是模型压缩与知识内迁技术，核心是将参数量大、性能强的「教师模型」的知识、推理逻辑与输出分布，通过特定训练机制迁移到轻量的「学生模型」的参数中，把知识固化到模型内部，最终在牺牲少量性能的前提下，实现模型轻量化、推理降本增效。&lt;/li&gt;
      &lt;li&gt;蒸馏后的小模型，推理速度快、成本低，但能力有天花板：它的能力上限无法超过教师模型，甚至会有 10%-15% 的性能损失，尤其在复杂推理、长文本生成等任务上，和大模型的差距会更明显。但优势是推理延迟极低，无需依赖复杂的外部组件，甚至可以离线运行。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;检索增强生成（Retrieval-Augmented Generation, RAG）
    &lt;ul&gt;
      &lt;li&gt;本质是外部知识实时增强技术，核心是不修改模型本身的参数，在生成回答前，先基于用户查询从外部知识库（向量数据库、文档库等）召回相关信息，再将检索结果作为上下文注入 Prompt，让模型基于最新、最精准的外部知识完成生成，核心解决大模型知识过时、生成幻觉、专业知识不足的痛点。&lt;/li&gt;
      &lt;li&gt;RAG 增强后的模型，能力边界可无限拓展，但推理开销更高：它可以通过对接不同的知识库，无限拓展知识覆盖范围，同时不损失模型本身的推理、生成能力，甚至能通过多源信息整合完成更复杂的专业任务。但缺点是每次推理都要执行检索步骤，会增加响应延迟，且长上下文输入会提升推理成本。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;模型选择参考&quot;&gt;模型选择参考&lt;/h1&gt;

&lt;table class=&quot;table table-striped&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;场景名称&lt;/th&gt;
      &lt;th&gt;核心优先级&lt;/th&gt;
      &lt;th&gt;架构选型推荐&lt;/th&gt;
      &lt;th&gt;上下文窗口&lt;/th&gt;
      &lt;th&gt;模型体量（参数量）&lt;/th&gt;
      &lt;th&gt;推理能力要求&lt;/th&gt;
      &lt;th&gt;模态要求&lt;/th&gt;
      &lt;th&gt;避坑指南&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;个人本地部署 / 边缘设备运行&lt;/td&gt;
      &lt;td&gt;体量 &amp;gt; 架构 &amp;gt; 上下文&lt;/td&gt;
      &lt;td&gt;优先Dense 稠密模型，可选混合 Transformer-Mamba，量化友好的纯 Transformer&lt;/td&gt;
      &lt;td&gt;32K 以内（满足日常需求即可）&lt;/td&gt;
      &lt;td&gt;1B-7B，最高不超过 14B&lt;/td&gt;
      &lt;td&gt;基础 - 中等&lt;/td&gt;
      &lt;td&gt;纯文本为主，无需多模态&lt;/td&gt;
      &lt;td&gt;不要盲目选 20B + 大模型，消费级显卡带不动，体验极差；优先选支持 int4/int8 量化的模型&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;日常通用对话 / 轻量问答&lt;/td&gt;
      &lt;td&gt;体验 &amp;gt; 成本 &amp;gt; 体量&lt;/td&gt;
      &lt;td&gt;纯 Transformer、混合架构均可，Dense/MoE 无强制要求&lt;/td&gt;
      &lt;td&gt;8K-32K&lt;/td&gt;
      &lt;td&gt;3B-14B&lt;/td&gt;
      &lt;td&gt;中等&lt;/td&gt;
      &lt;td&gt;纯文本即可&lt;/td&gt;
      &lt;td&gt;不要为了日常对话选 70B + 超大模型，纯纯性能浪费，延迟高还贵&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;超长文档处理 / 长对话 / RAG 检索&lt;/td&gt;
      &lt;td&gt;上下文窗口 &amp;gt; 架构 &amp;gt; 体量&lt;/td&gt;
      &lt;td&gt;优先混合 Transformer-Mamba，次选优化过长文本的纯 Transformer，Dense/MoE 均可&lt;/td&gt;
      &lt;td&gt;最低 128K，优先 256K+，极致场景选 1M+&lt;/td&gt;
      &lt;td&gt;7B-32B（平衡成本与能力）&lt;/td&gt;
      &lt;td&gt;中等 - 强&lt;/td&gt;
      &lt;td&gt;纯文本为主，多模态文档可选图文模态&lt;/td&gt;
      &lt;td&gt;不要选 32K 以下短上下文模型，长文档会断章取义；纯 Mamba 模型长文本虽快，但会丢失关键逻辑&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;代码开发 / 工程化任务&lt;/td&gt;
      &lt;td&gt;推理能力 &amp;gt; 架构 &amp;gt; 工具调用&lt;/td&gt;
      &lt;td&gt;优先纯 Transformer Dense 稠密模型，代码专项优化的架构&lt;/td&gt;
      &lt;td&gt;32K-128K（长代码文件需要）&lt;/td&gt;
      &lt;td&gt;13B-34B，极致场景选 70B+&lt;/td&gt;
      &lt;td&gt;强 - 顶级&lt;/td&gt;
      &lt;td&gt;纯文本为主，可选支持截图读图的多模态&lt;/td&gt;
      &lt;td&gt;不要选纯 Mamba 小模型，代码逻辑、debug 能力远弱于 Transformer 架构；必须验证工具调用（Git、API、终端）兼容性&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;多模态图文 / 视频理解&lt;/td&gt;
      &lt;td&gt;模态能力 &amp;gt; 上下文 &amp;gt; 架构&lt;/td&gt;
      &lt;td&gt;优先混合 Transformer-Mamba 多模态架构，次选带视觉编码器的纯 Transformer&lt;/td&gt;
      &lt;td&gt;64K-128K（长视频 / 多图需要）&lt;/td&gt;
      &lt;td&gt;7B-14B，专业场景选 34B+&lt;/td&gt;
      &lt;td&gt;中等 - 强&lt;/td&gt;
      &lt;td&gt;必须支持图文 + 视频，优先原生多模态&lt;/td&gt;
      &lt;td&gt;不要选文本模型后加视觉插件的 “伪多模态”，读图精度差、幻觉率极高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;复杂推理 / 科研 / 数学逻辑任务&lt;/td&gt;
      &lt;td&gt;推理能力 &amp;gt; 架构 &amp;gt; 体量&lt;/td&gt;
      &lt;td&gt;优先纯 Transformer Dense 稠密模型，推理专项优化的架构&lt;/td&gt;
      &lt;td&gt;32K-128K（复杂逻辑需要长上下文推导）&lt;/td&gt;
      &lt;td&gt;34B-70B+，同架构下越大越好&lt;/td&gt;
      &lt;td&gt;顶级&lt;/td&gt;
      &lt;td&gt;纯文本即可&lt;/td&gt;
      &lt;td&gt;不要选纯 Mamba、MoE 小模型，逻辑严谨性、数学推导能力远弱于大参数量 Dense Transformer 模型&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;垂直领域专用任务（翻译 / 医疗 / 法律）&lt;/td&gt;
      &lt;td&gt;垂直能力 &amp;gt; 架构 &amp;gt; 体量&lt;/td&gt;
      &lt;td&gt;优先垂直场景专项微调的 Dense 模型，通用架构为辅&lt;/td&gt;
      &lt;td&gt;32K-128K（匹配垂直文档长度）&lt;/td&gt;
      &lt;td&gt;7B-14B，专业场景选 34B+&lt;/td&gt;
      &lt;td&gt;中等 - 强（匹配垂直领域逻辑）&lt;/td&gt;
      &lt;td&gt;按需选择&lt;/td&gt;
      &lt;td&gt;不要用通用大模型硬做垂直场景，专业度、准确率远低于专项微调模型，幻觉率极高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;免费测试 / 个人原型开发&lt;/td&gt;
      &lt;td&gt;成本 &amp;gt; 可用性 &amp;gt; 能力&lt;/td&gt;
      &lt;td&gt;无强制要求，优先官方免费开放的成熟架构&lt;/td&gt;
      &lt;td&gt;越高越好&lt;/td&gt;
      &lt;td&gt;无强制要求&lt;/td&gt;
      &lt;td&gt;中等 - 强&lt;/td&gt;
      &lt;td&gt;按需选择&lt;/td&gt;
      &lt;td&gt;不要选免费但有严格调用限额、限速的模型，影响原型开发效率；优先验证免费版是否阉割核心能力&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

</content>
 </entry>
 
 <entry>
   <title>AGENTS.md与SKILL.md：为AI智能体赋能的标准格式详解</title>
   <link href="https://blog.cyeam.com/ai/2026/04/02/agents-skull"/>
   <updated>2026-04-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/04/02/agents-skull</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#agentsmd&quot; id=&quot;markdown-toc-agentsmd&quot;&gt;AGENTS.md&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#跟readmemd的区别&quot; id=&quot;markdown-toc-跟readmemd的区别&quot;&gt;跟README.md的区别&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#兼容性&quot; id=&quot;markdown-toc-兼容性&quot;&gt;兼容性&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#如何使用&quot; id=&quot;markdown-toc-如何使用&quot;&gt;如何使用&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#例子&quot; id=&quot;markdown-toc-例子&quot;&gt;例子&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#skillmd&quot; id=&quot;markdown-toc-skillmd&quot;&gt;SKILL.md&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#skill的结构&quot; id=&quot;markdown-toc-skill的结构&quot;&gt;Skill的结构&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#如何工作&quot; id=&quot;markdown-toc-如何工作&quot;&gt;如何工作&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#例子-1&quot; id=&quot;markdown-toc-例子-1&quot;&gt;例子&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#渐进式拉取skill&quot; id=&quot;markdown-toc-渐进式拉取skill&quot;&gt;渐进式拉取skill&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;agentsmd&quot;&gt;AGENTS.md&lt;/h1&gt;

&lt;p&gt;用于指导编码Agent的简单、开放格式。将 AGENTS.md 视为代理商的README：一个专门、可预测的场所，提供背景和指示，帮助AI编码Agent为您的项目工作。&lt;/p&gt;

&lt;h2 id=&quot;跟readmemd的区别&quot;&gt;跟README.md的区别&lt;/h2&gt;
&lt;p&gt;README.md 是写给人看的：包含快速上手、项目说明与贡献指南。
AGENTS.md 则作为补充，存放编码智能体所需的额外信息（有时是详细上下文）：构建步骤、测试流程与开发规范 —— 这些内容若放进 README 会显得冗余，或对人工贡献者无关紧要。
我们特意将二者分开，目的是：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;为智能体提供一个清晰、固定的指令存放位置&lt;/li&gt;
  &lt;li&gt;让 README 保持简洁，聚焦于人工贡献者&lt;/li&gt;
  &lt;li&gt;提供精准、面向智能体的指引，作为现有 README 与文档的补充
我们没有新增一种专属文件格式，而是选用了对所有人都通用的命名与格式。如果你在开发或使用编码智能体时觉得这份文件有用，欢迎直接沿用。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;兼容性&quot;&gt;兼容性&lt;/h2&gt;

&lt;p&gt;目前支持超过24种Agent和工具，而且CLAUDE.md在我看来也是一回事，另外Trae也兼容这个格式。&lt;/p&gt;

&lt;h2 id=&quot;如何使用&quot;&gt;如何使用&lt;/h2&gt;

&lt;p&gt;放在项目根目录里，或者用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/init&lt;/code&gt;初始化。&lt;/p&gt;

&lt;h2 id=&quot;例子&quot;&gt;例子&lt;/h2&gt;

&lt;p&gt;详细内容可移步官方文档：&lt;a href=&quot;https://agents.md/#examples&quot;&gt;AGENTS.md规范&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Project overview：项目概述&lt;/li&gt;
  &lt;li&gt;Build and test commands：构建与测试命令&lt;/li&gt;
  &lt;li&gt;Code style guidelines：代码风格规范&lt;/li&gt;
  &lt;li&gt;Testing instructions：测试说明&lt;/li&gt;
  &lt;li&gt;Security considerations：安全注意事项&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# AGENTS.md

## Project Overview
项目的整体目标与架构简介

## Build &amp;amp; Commands
用于开发、测试与部署的常用命令

## Code Style
格式化规则、命名规范与最佳实践

## Testing
测试框架、约定及执行指南

## Security
安全注意事项与数据保护策略

## Configuration
环境配置方式与配置管理说明
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;skillmd&quot;&gt;SKILL.md&lt;/h1&gt;

&lt;p&gt;一种简洁、开放的格式，用于为智能体赋予新能力与专业知识。
智能体技能是由说明文档、脚本和资源组成的文件夹，智能体可以发现并使用它们，从而更准确、更高效地完成任务。&lt;/p&gt;

&lt;p&gt;智能体的能力日益增强，但往往缺少可靠完成实际工作所需的上下文信息。技能体系很好地解决了这一问题：它让智能体能够获取流程化知识，以及企业、团队和用户专属的上下文，并可按需加载。具备技能库的智能体，能够根据当前执行的任务灵活扩展自身能力。
面向技能开发者：一次构建能力，即可在多款智能体产品中复用部署。
面向兼容的智能体：支持技能体系，可让终端用户开箱即用地为智能体赋予新能力。
面向团队与企业：将组织知识沉淀为可移植、可版本控制的标准化包。&lt;/p&gt;

&lt;p&gt;智能体技能可以实现哪些能力？&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;领域专业知识：将专业知识打包为可复用的指令，涵盖法律审查流程、数据分析流程等各类专业场景。&lt;/li&gt;
  &lt;li&gt;全新能力：为智能体赋予新技能（例如制作演示文稿、搭建 MCP 服务器、分析数据集）。&lt;/li&gt;
  &lt;li&gt;可复用工作流：将多步骤任务转化为统一、可审计的工作流程。&lt;/li&gt;
  &lt;li&gt;互通兼容性：在不同兼容技能的智能体产品中，复用同一套技能。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;skill的结构&quot;&gt;Skill的结构&lt;/h2&gt;

&lt;p&gt;从本质上讲，一项技能就是一个包含 SKILL.md 文件的文件夹。该文件包含元数据（至少包含名称和描述）以及指导智能体如何执行特定任务的说明。技能还可以打包脚本、模板和参考资料。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;my-skill/
├── SKILL.md          # Required: instructions + metadata
├── scripts/          # Optional: executable code
├── references/       # Optional: documentation
└── assets/           # Optional: templates, resources
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;位置&lt;/th&gt;
      &lt;th&gt;路径&lt;/th&gt;
      &lt;th&gt;适用于&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;个人&lt;/td&gt;
      &lt;td&gt;~/.claude/skills/&lt;skill-name&gt;/SKILL.md&lt;/skill-name&gt;&lt;/td&gt;
      &lt;td&gt;你的所有项目&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;项目&lt;/td&gt;
      &lt;td&gt;.claude/skills/&lt;skill-name&gt;/SKILL.md&lt;/skill-name&gt;&lt;/td&gt;
      &lt;td&gt;仅此项目&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;插件&lt;/td&gt;
      &lt;td&gt;&lt;plugin&gt;/skills/&lt;skill-name&gt;/SKILL.md&lt;/skill-name&gt;&lt;/plugin&gt;&lt;/td&gt;
      &lt;td&gt;启用插件的位置&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;如何工作&quot;&gt;如何工作&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;发现阶段：启动时，智能体仅加载每项可用技能的名称与描述，只需足够判断该技能是否相关即可。&lt;/li&gt;
  &lt;li&gt;激活阶段：当任务与某项技能的描述匹配时，智能体才会将完整的 SKILL.md 指令加载到上下文。&lt;/li&gt;
  &lt;li&gt;执行阶段：智能体按照指令执行任务，并可根据需要按需加载引用文件或执行捆绑的代码。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;例子-1&quot;&gt;例子&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;详细内容可移步官方文档：&lt;a href=&quot;https://agentskills.io/specification&quot;&gt;SKILL.md规范&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;更多例子：&lt;a href=&quot;https://github.com/nextlevelbuilder/ui-ux-pro-max-skill&quot;&gt;UI UX Pro Max&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://code.claude.com/docs/zh-CN/skills#%E7%94%9F%E6%88%90%E8%A7%86%E8%A7%89%E8%BE%93%E5%87%BA&quot;&gt;使用 skills 扩展 Claude - Claude Code Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
name: 技能名称
description: 简要描述这个技能的功能和使用场景

---

# 技能名称

## 描述
描述这个技能的作用。

## 使用场景
描述触发这个技能的条件。

## 指令
清晰的分步说明，告诉智能体具体怎么做。

## 示例 (可选)
输入/输出示例，展示预期效果。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;渐进式拉取skill&quot;&gt;渐进式拉取skill&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    actor User
    participant Agent as Claude Code Agent
    participant Skill as Skill 知识库 (.md)
    participant LLM as Claude LLM

    %% 交互流程
    User-&amp;gt;&amp;gt;Agent: 提出需求（如：生成SVG动画）
    
    Agent-&amp;gt;&amp;gt;Skill: 根据需求检索匹配技能
    Skill--&amp;gt;&amp;gt;Agent: 返回对应技能手册内容
    
    Agent-&amp;gt;&amp;gt;LLM: 注入技能上下文 + 用户需求
    LLM--&amp;gt;&amp;gt;Agent: 要求查询Skill完整信息
    
    Agent-&amp;gt;&amp;gt;LLM: 注入完整 Skill 上下文
    LLM--&amp;gt;&amp;gt;Agent: 要求执行Skill命令（如：sed）
    Agent-&amp;gt;&amp;gt;LLM: 提供执行结果

    LLM--&amp;gt;&amp;gt;Agent: 生成最终结果
    Agent--&amp;gt;&amp;gt;User: 输出最终答案
&lt;/code&gt;&lt;/pre&gt;

</content>
 </entry>
 
 <entry>
   <title>OpenClaw深度解析：模型选择、Skill系统与Heartbeat机制</title>
   <link href="https://blog.cyeam.com/openclaw/2026/03/31/openclaw-skill"/>
   <updated>2026-03-31T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/openclaw/2026/03/31/openclaw-skill</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#准备工作&quot; id=&quot;markdown-toc-准备工作&quot;&gt;准备工作&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#全链路数据流转总流程&quot; id=&quot;markdown-toc-全链路数据流转总流程&quot;&gt;全链路数据流转总流程&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#源码目录&quot; id=&quot;markdown-toc-源码目录&quot;&gt;源码目录&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#四层调用链&quot; id=&quot;markdown-toc-四层调用链&quot;&gt;四层调用链&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#模型选择规则&quot; id=&quot;markdown-toc-模型选择规则&quot;&gt;模型选择规则&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#实际案例&quot; id=&quot;markdown-toc-实际案例&quot;&gt;实际案例&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#选择规则优先级&quot; id=&quot;markdown-toc-选择规则优先级&quot;&gt;选择规则&amp;amp;优先级&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#skill&quot; id=&quot;markdown-toc-skill&quot;&gt;SKILL&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#执行过程&quot; id=&quot;markdown-toc-执行过程&quot;&gt;执行过程&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#计算执行方式activerunqueueaction&quot; id=&quot;markdown-toc-计算执行方式activerunqueueaction&quot;&gt;计算执行方式activeRunQueueAction&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#llm交互案例&quot; id=&quot;markdown-toc-llm交互案例&quot;&gt;LLM交互案例&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#input&quot; id=&quot;markdown-toc-input&quot;&gt;INPUT&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#system&quot; id=&quot;markdown-toc-system&quot;&gt;SYSTEM&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#humanai&quot; id=&quot;markdown-toc-humanai&quot;&gt;HUMAN&amp;amp;AI&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#output输出&quot; id=&quot;markdown-toc-output输出&quot;&gt;OUTPUT（输出）&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#heartbeat&quot; id=&quot;markdown-toc-heartbeat&quot;&gt;HEARTBEAT&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#cli&quot; id=&quot;markdown-toc-cli&quot;&gt;CLI&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#开启心跳&quot; id=&quot;markdown-toc-开启心跳&quot;&gt;开启心跳&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#飞书场景配置&quot; id=&quot;markdown-toc-飞书场景配置&quot;&gt;飞书场景配置&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#流程图&quot; id=&quot;markdown-toc-流程图&quot;&gt;流程图&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#发送结果&quot; id=&quot;markdown-toc-发送结果&quot;&gt;发送结果&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#交互案例&quot; id=&quot;markdown-toc-交互案例&quot;&gt;交互案例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#approval&quot; id=&quot;markdown-toc-approval&quot;&gt;Approval&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#security---安全模式&quot; id=&quot;markdown-toc-security---安全模式&quot;&gt;security - 安全模式&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#ask---审批询问策略&quot; id=&quot;markdown-toc-ask---审批询问策略&quot;&gt;ask - 审批询问策略&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#askfallback---审批超时后的行为&quot; id=&quot;markdown-toc-askfallback---审批超时后的行为&quot;&gt;askFallback - 审批超时后的行为&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;准备工作&quot;&gt;准备工作&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;对话日志目录：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data/.openclaw/agents/&amp;lt;agent_id&amp;gt;/sessions/&amp;lt;run_id&amp;gt;.jsonl&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;OpenRouter并不支持查看完整上下文，这里建议接入&lt;a href=&quot;https://smith.langchain.com/&quot;&gt;LangSmith&lt;/a&gt;库来解决。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;全链路数据流转总流程&quot;&gt;全链路数据流转总流程&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;用户消息 → 对应Channel适配器（协议转换）→ Gateway 接收校验 → 会话锁获取与上下文加载
→ Agent Core 核心推理（Prompt 组装→模型选择→LLM 调用→工具/Skill 调用决策）
→ Skill/Plugin 执行 → 结果回灌 LLM → 多轮推理循环（按需）→ 最终回复生成
→ Gateway 消息封装 → 对应 Channel 适配器 → 用户端接收
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;源码目录&quot;&gt;源码目录&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw/
├── src/
│   ├── agents/                 # Agent 核心执行逻辑（对话处理核心）
│   │   └── pi-embedded-runner/ # 嵌入式 Agent 运行时（核心推理入口）
│   ├── channels/               # 渠道接入层（微信/飞书/API 等适配器）
│   ├── gateway/                # 调度网关层（Cron/Heartbeat/会话管理）
│   ├── core/                   # 核心基础能力（Skill/Plugin/事件/存储）
│   │   ├── skills/             # Skill 技能系统核心
│   │   ├── plugins/            # Plugin 插件管理核心
│   │   └── storage/            # 持久化存储实现
│   ├── extensions/             # 扩展模块（模型路由/钩子等）
│   └── utils/                  # 通用工具函数
├── docs/                       # 官方文档
└── examples/                   # 官方示例代码[[2]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;关键函数/模块&lt;/th&gt;
      &lt;th&gt;文件路径&lt;/th&gt;
      &lt;th&gt;职责说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Channel Adapter&lt;/td&gt;
      &lt;td&gt;src/channels/&lt;/td&gt;
      &lt;td&gt;消息适配器：每个聊天平台对应一个适配器，负责接收特定平台的消息，并将其转换为 OpenClaw 内部的标准化事件格式。这是连接外部世界与 OpenClaw 内部系统的桥梁。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gateway&lt;/td&gt;
      &lt;td&gt;src/gateway/server.impl.ts&lt;/td&gt;
      &lt;td&gt;网关服务：作为系统的神经中枢，Gateway 是一个 WebSocket 服务器，负责管理所有渠道的连接、会话状态以及消息路由。它接收来自 Channel 的标准事件。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Command Queue&lt;/td&gt;
      &lt;td&gt;src/auto-reply/reply/queue.ts&lt;/td&gt;
      &lt;td&gt;指令队列：收到的消息不会立即执行，而是进入一个基于会话（Session）的队列。该队列有三种模式（collect/steer/followup），决定了新消息是排队等待、中断当前任务还是作为后续指令，确保了任务处理的并发与有序。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;agent/agent.wait&lt;/td&gt;
      &lt;td&gt;src/auto-reply/reply/agent-runner.ts&lt;/td&gt;
      &lt;td&gt;执行触发器：队列中的任务最终通过调用 agent() 或 agent.wait() RPC 方法，唤醒 Agent Runtime，正式开始一个完整的 ReAct 执行回合。agent.wait() 会等待任务执行完毕并返回结果。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;四层调用链&quot;&gt;四层调用链&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;层级&lt;/th&gt;
      &lt;th&gt;关键函数&lt;/th&gt;
      &lt;th&gt;文件路径&lt;/th&gt;
      &lt;th&gt;职责说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;L1&lt;/td&gt;
      &lt;td&gt;runReplyAgent&lt;/td&gt;
      &lt;td&gt;src/auto-reply/reply/agent-runner.ts&lt;/td&gt;
      &lt;td&gt;最高层封装：处理队列策略、任务“掌舵”（Steer）检查、最终结果的后处理和使用量上报。它确保一个入站消息能被完整地响应。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;L2&lt;/td&gt;
      &lt;td&gt;runAgentTurnWithFallback&lt;/td&gt;
      &lt;td&gt;src/auto-reply/reply/agent-runner-execution.ts&lt;/td&gt;
      &lt;td&gt;失败回退层：核心职责是“容错”。它包裹了模型调用，当遇到可恢复的错误（如上下文超长、瞬时网络问题、API 限流）时，会自动执行重试、上下文压缩或切换到备用模型等回退策略。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;L3&lt;/td&gt;
      &lt;td&gt;runEmbeddedPiAgent&lt;/td&gt;
      &lt;td&gt;src/agents/pi-embedded-runner/run.ts&lt;/td&gt;
      &lt;td&gt;并发控制与资源准备层：管理执行“车道”（Lane），确保每个会话的任务串行执行。此层还负责解析模型、迭代认证配置（Auth Profile）。&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;L4&lt;/td&gt;
      &lt;td&gt;runEmbeddedAttempt&lt;/td&gt;
      &lt;td&gt;src/agents/pi-embedded-runner/run/attempt.ts&lt;/td&gt;
      &lt;td&gt;单次尝试执行层：这是最核心的执行单元。它负责准备工作区（Workspace）、创建工具集、初始化会话，并最终调用 subscribeEmbeddedPiSession 发起对大语言模型（LLM）的单次请求。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;模型选择规则&quot;&gt;模型选择规则&lt;/h1&gt;

&lt;h2 id=&quot;实际案例&quot;&gt;实际案例&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;agents&quot;: {
    &quot;defaults&quot;: {
        &quot;model&quot;: {
            &quot;primary&quot;: &quot;openrouter/nvidia/nemotron-3-super-120b-a12b:free&quot;,
            &quot;fallbacks&quot;: [&quot;openrouter/nvidia/llama-nemotron-embed-vl-1b-v2:free&quot;]
        },
        &quot;models&quot;: {
            &quot;openrouter/nvidia/nemotron-3-super-120b-a12b:free&quot;: {},
            &quot;openrouter/nvidia/llama-nemotron-embed-vl-1b-v2:free&quot;: {}
        }
    }
},
&quot;models&quot;: {
    &quot;providers&quot;: {
        &quot;openrouter&quot;: {
            &quot;baseUrl&quot;: &quot;https://openrouter.ai/api/v1&quot;,
            &quot;apiKey&quot;: &quot;${OPENROUTER_API_KEY}&quot;,
            &quot;models&quot;: [
                {
                    &quot;id&quot;: &quot;nvidia/nemotron-3-super-120b-a12b:free&quot;,
                    &quot;name&quot;: &quot;Nemotron-3 Super 120B&quot;,
                    &quot;input&quot;: [&quot;text&quot;]
                },
                {
                    &quot;id&quot;: &quot;nvidia/nemotron-nano-12b-v2-vl:free&quot;,
                    &quot;name&quot;: &quot;LLama-Nemotron EmbedEmbedding&quot;,
                    &quot;input&quot;: [&quot;text&quot;, &quot;image&quot;]
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;选择规则优先级&quot;&gt;选择规则&amp;amp;优先级&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;当前请求的模型 （动态传入的 provider/model）；&lt;/li&gt;
  &lt;li&gt;单个智能体的 model.fallbacks；&lt;/li&gt;
  &lt;li&gt;全局 agents.defaults.model.fallbacks；&lt;/li&gt;
  &lt;li&gt;全局 agents.defaults.model.primary （最后的保障）；&lt;/li&gt;
  &lt;li&gt;全局允许列表：agents.defaults.models 作为模型白名单，仅列表内的模型可被调用；&lt;/li&gt;
  &lt;li&gt;提供者内部回退：同一模型提供商内的接口 / 认证失败，会先在提供商内部重试，再切换到下一个模型。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;skill&quot;&gt;SKILL&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;SKILL名称是url链接里的，有很多事重复的，以这个为准，例如https://clawhub.ai/pskoett/self-improving-agent名称是self-improving-agent。&lt;/li&gt;
  &lt;li&gt;可以直接对话里安装SKILL。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1774963371/Screenshot_2026-03-31-19-26-12-991_com.larksuite.suite-edit.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;执行过程&quot;&gt;执行过程&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    autonumber
    title OpenClaw 消息处理时序图
    participant User
    participant Channel
    participant Gateway
    participant CommandQueue as Command Queue
    participant AgentRuntime as Agent Runtime (L1-L4)
    participant Model as Model (LLM)
    participant ToolExecutor as Tool Executor
    participant EventSubscriber as Event Subscriber
    participant Persistence as Persistence (JSONL)

    User-&amp;gt;&amp;gt;Channel: 发送消息
    Channel-&amp;gt;&amp;gt;Gateway: 转发消息
    Gateway-&amp;gt;&amp;gt;CommandQueue: 消息入队 (collect/steer/followup)
    CommandQueue-&amp;gt;&amp;gt;AgentRuntime: agent.wait() 触发执行
    Note over AgentRuntime: L1: runReplyAgent\nL2: runAgentTurnWithFallback\nL3: runEmbeddedPlAgent\nL4: runEmbeddedAttempt
    AgentRuntime-&amp;gt;&amp;gt;Model: 构建 Prompt (上下文+技能)
    Model-&amp;gt;&amp;gt;AgentRuntime: 返回决策 (文本/工具调用)

    alt 直接回复
        AgentRuntime-&amp;gt;&amp;gt;EventSubscriber: 流式事件 (assistant)
        Note over AgentRuntime: buildReplyPayloads()
        EventSubscriber-&amp;gt;&amp;gt;User: 返回最终回复
    else 工具调用
        AgentRuntime-&amp;gt;&amp;gt;ToolExecutor: 执行工具
        ToolExecutor-&amp;gt;&amp;gt;AgentRuntime: 返回工具结果
        AgentRuntime-&amp;gt;&amp;gt;Model: 注入结果, 再次推理
        Model-&amp;gt;&amp;gt;AgentRuntime: 返回最终文本
        AgentRuntime-&amp;gt;&amp;gt;EventSubscriber: 流式事件 (assistant)
        Note over AgentRuntime: buildReplyPayloads()
        EventSubscriber-&amp;gt;&amp;gt;User: 返回最终回复
    end

    AgentRuntime-&amp;gt;&amp;gt;Persistence: 会话持久化 (.JSONL)
    Persistence-&amp;gt;&amp;gt;AgentRuntime: 持久化完成
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;OpenClaw也是用Reasoning and Acting（推理与行动）模式。&lt;/p&gt;

&lt;h2 id=&quot;计算执行方式activerunqueueaction&quot;&gt;计算执行方式activeRunQueueAction&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;判断条件&lt;/th&gt;
      &lt;th&gt;activeRunQueueAction&lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;当前无活跃运行？&lt;/td&gt;
      &lt;td&gt;run-now&lt;/td&gt;
      &lt;td&gt;typingSignals打字信号&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;心跳消息&lt;/td&gt;
      &lt;td&gt;drop&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;应该后续执行 OR messages.queue “steer”&lt;/td&gt;
      &lt;td&gt;enqueue-followup&lt;/td&gt;
      &lt;td&gt;messages.queue默认是collect&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;默认&lt;/td&gt;
      &lt;td&gt;run-now&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;llm交互案例&quot;&gt;LLM交互案例&lt;/h2&gt;

&lt;h3 id=&quot;input&quot;&gt;INPUT&lt;/h3&gt;

&lt;h4 id=&quot;system&quot;&gt;SYSTEM&lt;/h4&gt;
&lt;p&gt;SYSTEM 系统角色 / System Prompt，AI 的 “身份说明书” 和 “行为准则”，由开发者设定，是整个对话的「总纲领」。
定义 AI 的角色、任务、回答风格、约束条件（比如 “你是一个专业的天气助手，只回答北京的天气问题，用口语化表达”）。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;给模型设定上下文背景、规则、格式要求，全程约束模型的输出。&lt;/li&gt;
  &lt;li&gt;只在对话开头出现一次（或少数几次），不会由用户输入。&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;You are a personal assistant running inside OpenClaw.
&lt;span class=&quot;gu&quot;&gt;## Tooling&lt;/span&gt;
Tool availability (filtered by policy):
Tool names are case-sensitive. Call tools exactly as listed.
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; read: Read file contents
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; write: Create or overwrite files
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; edit: Make precise edits to files
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; exec: Run shell commands (pty available for TTY-required CLIs)
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; web_search: Search the web
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; web_fetch: Fetch and extract readable content from a URL
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;humanai&quot;&gt;HUMAN&amp;amp;AI&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;HUMAN。human（用户角色 / Human Message），真实用户的提问 / 输入，代表用户的需求、问题、补充信息，是模型需要响应的核心输入。
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ou_aaaaaa: 明天北京需要带伞吗？
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;AI。AI 角色 / Assistant MessageAI 模型自己的历史回复，也就是上一轮对话的 Output。
    &lt;ul&gt;
      &lt;li&gt;保存对话历史，让模型理解上下文，实现多轮对话的连贯性。&lt;/li&gt;
      &lt;li&gt;比如用户追问 “那后天呢？”，模型需要通过 ai 角色的历史回复，知道上一轮已经回答了明天的天气，才能承接上下文。
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;read{&quot;path&quot;:&quot;/data/.openclaw/workspace/skills/weather/SKILL.md&quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;HUMAN
    &lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;weather&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Get current weather and forecasts (no API key required).&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;homepage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://wttr.in/:help&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;clawdbot&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;emoji&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;🌤️&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;requires&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bins&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;curl&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]}}}&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;gh&quot;&gt;# Weather&lt;/span&gt;
Two free services, no API keys needed.
&lt;span class=&quot;gu&quot;&gt;## wttr.in (primary)&lt;/span&gt;
Quick one-liner:
curl -s &quot;wttr.in/London?format=3&quot;
&lt;span class=&quot;gh&quot;&gt;# Output: London: ⛅️ +8°C&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;AI
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;exec{&quot;command&quot;:&quot;curl -s \&quot;https://api.open-meteo.com/v1/forecast?latitude=39.9042&amp;amp;longitude=116.4074&amp;amp;daily=precipitation_sum&amp;amp;timezone=Asia%2FShanghai\&quot;&quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;HUMAN
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;latitude&quot;:39.875,&quot;longitude&quot;:116.375,&quot;generationtime_ms&quot;:0.042319297790527344,&quot;utc_offset_seconds&quot;:28800,&quot;timezone&quot;:&quot;Asia/Shanghai&quot;,&quot;timezone_abbreviation&quot;:&quot;GMT+8&quot;,&quot;elevation&quot;:47.0,&quot;daily_units&quot;:{&quot;time&quot;:&quot;iso8601&quot;,&quot;precipitation_sum&quot;:&quot;mm&quot;},&quot;daily&quot;:{&quot;time&quot;:[&quot;2026-03-31&quot;,&quot;2026-04-01&quot;,&quot;2026-04-02&quot;,&quot;2026-04-03&quot;,&quot;2026-04-04&quot;,&quot;2026-04-05&quot;,&quot;2026-04-06&quot;],&quot;precipitation_sum&quot;:[1.00,0.00,0.00,0.30,1.50,0.00,0.00]}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;output输出&quot;&gt;OUTPUT（输出）&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;明天北京不需要带伞☀️  
降水量：0.0 mm（干燥）  
可放心出门！ 🌤️
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;定义：这是 AI 模型返回的最终响应结果。这一轮它最后吐出的那一条 AI 回复；&lt;/li&gt;
  &lt;li&gt;内容：就是模型给用户的自然语言回答或结构化数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;heartbeat&quot;&gt;HEARTBEAT&lt;/h1&gt;

&lt;h2 id=&quot;cli&quot;&gt;CLI&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw system event --text &quot;手动触发心跳&quot; --mode now # 手动执行
openclaw system heartbeat last # 查询上一次执行结果
{
    &quot;ts&quot;: 1774963724609,
    &quot;status&quot;: &quot;ok-token&quot;,
    &quot;reason&quot;: &quot;interval&quot;,
    &quot;durationMs&quot;: 157974,
    &quot;channel&quot;: &quot;feishu&quot;,
    &quot;silent&quot;: true,
    &quot;indicatorType&quot;: &quot;ok&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;重点关注&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reason&lt;/code&gt;字段。&lt;/p&gt;

&lt;h2 id=&quot;开启心跳&quot;&gt;开启心跳&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;heartbeat&quot;: {
    &quot;every&quot;: &quot;120m&quot;,
    &quot;model&quot;: &quot;openrouter/minimax/minimax-m2.5:free&quot;,
    &quot;ackMaxChars&quot;: 0,
    &quot;target&quot;: &quot;feishu&quot;,
    &quot;to&quot;: &quot;${FEISHU_CHAT_ID}&quot;
}
// 完整配置
{
    &quot;agents&quot;: {
        &quot;defaults&quot;: {
            &quot;heartbeat&quot;: {
                &quot;every&quot;: &quot;30m&quot;,              // 触发间隔
                &quot;target&quot;: &quot;feishu&quot;,          // 发送通道
                &quot;to&quot;: &quot;chat:oc_xxx&quot;,         // 发送目标 ID
                &quot;prompt&quot;: &quot;自定义提示词&quot;,     // 可选：自定义提示词
                &quot;model&quot;: &quot;特定模型&quot;,          // 可选：专用模型
                &quot;accountId&quot;: &quot;账号ID&quot;,        // 可选：指定账号
                &quot;isolatedSession&quot;: true,      // 可选：独立会话
                &quot;lightContext&quot;: true,         // 可选：轻量上下文
                &quot;includeReasoning&quot;: true,     // 可选：包含思考
                &quot;ackMaxChars&quot;: 300,           // 可选：确认字符限制
                &quot;directPolicy&quot;: &quot;block&quot;       // 可选：私聊策略
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;飞书场景配置&quot;&gt;飞书场景配置&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;场景&lt;/th&gt;
      &lt;th&gt;配置格式示例&lt;/th&gt;
      &lt;th&gt;ID 前缀&lt;/th&gt;
      &lt;th&gt;对应的 receive_id_type&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;群聊（推荐）&lt;/td&gt;
      &lt;td&gt;chat:oc_abc123、group:oc_abc123、channel:oc_abc123、oc_abc123&lt;/td&gt;
      &lt;td&gt;oc_&lt;/td&gt;
      &lt;td&gt;chat_id&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;用户私聊（open_id）&lt;/td&gt;
      &lt;td&gt;user:ou_abc123、dm:ou_abc123、open_id:ou_abc123、ou_abc123&lt;/td&gt;
      &lt;td&gt;ou_&lt;/td&gt;
      &lt;td&gt;open_id&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;用户私聊（user_id）&lt;/td&gt;
      &lt;td&gt;user:abc123、dm:abc123、abc123&lt;/td&gt;
      &lt;td&gt;无前缀&lt;/td&gt;
      &lt;td&gt;user_id&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;邮箱&lt;/td&gt;
      &lt;td&gt;email:user@example.com、user@example.com&lt;/td&gt;
      &lt;td&gt;邮箱格式&lt;/td&gt;
      &lt;td&gt;email&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;流程图&quot;&gt;流程图&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;
graph TB
    subgraph &quot;一、触发时机&quot;
        A1[定时触发&amp;lt;br/&amp;gt;heartbeat.every: 30m]
        A2[手动触发&amp;lt;br/&amp;gt;requestHeartbeatNow]
        A3[事件触发&amp;lt;br/&amp;gt;exec-event/cron-event/wake]
        A1 --&amp;gt; A4[requestHeartbeatNow]
        A2 --&amp;gt; A4
        A3 --&amp;gt; A4
        A4 --&amp;gt; A5[queuePendingWakeReason]
        A5 --&amp;gt; A6[schedule定时器]
    end

    subgraph &quot;二、执行主流程&quot;
        B1[入口: runHeartbeatOnce]
        B2[前置检查&amp;lt;br/&amp;gt;- 全局启用&amp;lt;br/&amp;gt;- 代理启用&amp;lt;br/&amp;gt;- 间隔配置&amp;lt;br/&amp;gt;- 活跃时间&amp;lt;br/&amp;gt;- 队列空闲]
        B3[预飞行检查&amp;lt;br/&amp;gt;resolveHeartbeatPreflight]
        B4[解析发送目标&amp;lt;br/&amp;gt;resolveHeartbeatDeliveryTarget]
        B5[解析提示词&amp;lt;br/&amp;gt;resolveHeartbeatRunPrompt]
        B6[调用LLM&amp;lt;br/&amp;gt;getReplyFromConfig]
        B7[处理LLM回复]
        B8[发送结果]
        B9[清理和收尾]

        B1 --&amp;gt; B2
        B2 --&amp;gt; B3
        B3 --&amp;gt; B4
        B4 --&amp;gt; B5
        B5 --&amp;gt; B6
        B6 --&amp;gt; B7
        B7 --&amp;gt; B8
        B8 --&amp;gt; B9
    end

    subgraph &quot;三、HEARTBEAT.md处理&quot;
        C1[文件位置&amp;lt;br/&amp;gt;workspaceDir/HEARTBEAT.md]
        C2[预飞行读取&amp;lt;br/&amp;gt;检查是否有效为空]
        C3{有效为空?}
        C4[是: skipReason=empty-heartbeat-file]
        C5[否: 继续执行]
        C6[提示词引用&amp;lt;br/&amp;gt;Read HEARTBEAT.md]
        C7[LLM读取并处理]
        
        C1 --&amp;gt; C2
        C2 --&amp;gt; C3
        C3 --&amp;gt;|是| C4
        C3 --&amp;gt;|否| C5
        C5 --&amp;gt; C6
        C6 --&amp;gt; C7
    end

    subgraph &quot;B3 预飞行检查详情&quot;
        D1[解析会话信息]
        D2[检查系统事件队列]
        D3[读取HEARTBEAT.md]
        D4[判断触发原因类型]
        
        D1 --&amp;gt; D2
        D2 --&amp;gt; D3
        D3 --&amp;gt; D4
    end

    subgraph &quot;B6 调用LLM详情&quot;
        E1[构建请求上下文]
        E2[Body: 提示词+时间线]
        E3[OriginatingChannel/To]
        E4[Provider: heartbeat]
        E5[执行智能体调用]
        E6[获取回复结果]
        
        E1 --&amp;gt; E2
        E1 --&amp;gt; E3
        E1 --&amp;gt; E4
        E2 --&amp;gt; E5
        E3 --&amp;gt; E5
        E4 --&amp;gt; E5
        E5 --&amp;gt; E6
    end

    subgraph &quot;B7 处理LLM回复详情&quot;
        F1[解析回复负载]
        F2[标准化回复&amp;lt;br/&amp;gt;移除HEARTBEAT_TOKEN]
        F3{需要跳过?}
        F4[仅HEARTBEAT_OK → 跳过]
        F5[内容为空且无媒体 → 跳过]
        F6[24小时重复 → 跳过]
        F7[继续发送]
        
        F1 --&amp;gt; F2
        F2 --&amp;gt; F3
        F3 --&amp;gt;|是| F4
        F3 --&amp;gt;|是| F5
        F3 --&amp;gt;|是| F6
        F3 --&amp;gt;|否| F7
    end

    %% 连接关系
    A6 --&amp;gt; B1
    B3 --&amp;gt; D1
    D3 --&amp;gt; C1
    B6 --&amp;gt; E1
    B7 --&amp;gt; F1
    C5 --&amp;gt; B5
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;发送结果&quot;&gt;发送结果&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;status&lt;/th&gt;
      &lt;th&gt;reason&lt;/th&gt;
      &lt;th&gt;result&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;disabled&lt;/td&gt;
      &lt;td&gt;心跳功能已禁用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;quiet-hours&lt;/td&gt;
      &lt;td&gt;当前处于非活跃时间段&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;requests-in-flight&lt;/td&gt;
      &lt;td&gt;请求队列忙碌，跳过本次心跳&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;empty-heartbeat-file&lt;/td&gt;
      &lt;td&gt;HEARTBEAT.md 文件内容为空&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;no-target&lt;/td&gt;
      &lt;td&gt;未配置心跳发送目标&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;unknown-account&lt;/td&gt;
      &lt;td&gt;目标账号不存在&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;dm-blocked&lt;/td&gt;
      &lt;td&gt;私聊被对方阻止&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;alerts-disabled&lt;/td&gt;
      &lt;td&gt;警报功能已禁用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;duplicate&lt;/td&gt;
      &lt;td&gt;心跳内容重复，跳过发送&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;skipped&lt;/td&gt;
      &lt;td&gt;not-due&lt;/td&gt;
      &lt;td&gt;未到下一次心跳执行时间&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ok-empty&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;执行成功，但无回复内容&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ok-token&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;执行成功，返回 HEARTBEAT_OK 标识&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;sent&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;心跳消息发送成功&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;failed&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;心跳消息发送失败&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Reason&lt;/th&gt;
      &lt;th&gt;触发方式&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;interval&lt;/td&gt;
      &lt;td&gt;定时触发&lt;/td&gt;
      &lt;td&gt;配置 heartbeat.every: 30m 的定时触发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;wake&lt;/td&gt;
      &lt;td&gt;外部唤醒&lt;/td&gt;
      &lt;td&gt;CLI –mode now、服务重启、Push 通知等&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;hook&lt;/td&gt;
      &lt;td&gt;Webhook&lt;/td&gt;
      &lt;td&gt;HTTP webhook 触发（hook:wake）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;cron&lt;/td&gt;
      &lt;td&gt;Cron 任务&lt;/td&gt;
      &lt;td&gt;Cron 定时任务触发（cron:*）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;exec-event&lt;/td&gt;
      &lt;td&gt;执行事件&lt;/td&gt;
      &lt;td&gt;Shell 命令执行完成后触发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;manual&lt;/td&gt;
      &lt;td&gt;手动&lt;/td&gt;
      &lt;td&gt;其他手动触发方式&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;retry&lt;/td&gt;
      &lt;td&gt;重试&lt;/td&gt;
      &lt;td&gt;心跳失败重试&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;other&lt;/td&gt;
      &lt;td&gt;其他&lt;/td&gt;
      &lt;td&gt;未识别的触发方式&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;交互案例&quot;&gt;交互案例&lt;/h2&gt;

&lt;p&gt;与SKILL区别不大，提示词如下：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.
When reading HEARTBEAT.md, use workspace file /data/.openclaw/workspace/HEARTBEAT.md (exact case). Do not read docs/heartbeat.md.
Current time: Tuesday, March 31st, 2026 — 04:15 (UTC) / 2026-03-31 04:15 UTC
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;approval&quot;&gt;Approval&lt;/h1&gt;

&lt;h2 id=&quot;security---安全模式&quot;&gt;security - 安全模式&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;值&lt;/th&gt;
      &lt;th&gt;代码作用&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;“deny”&lt;/td&gt;
      &lt;td&gt;默认拒绝所有&lt;/td&gt;
      &lt;td&gt;最严格，默认拒绝所有命令执行&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“allowlist”&lt;/td&gt;
      &lt;td&gt;只允许 allowlist 中的命令&lt;/td&gt;
      &lt;td&gt;平衡安全和灵活，需要手动配置 allowlist&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“full”&lt;/td&gt;
      &lt;td&gt;允许所有命令&lt;/td&gt;
      &lt;td&gt;最宽松，完全关闭安全检查&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;ask---审批询问策略&quot;&gt;ask - 审批询问策略&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;值&lt;/th&gt;
      &lt;th&gt;代码作用&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;“off”&lt;/td&gt;
      &lt;td&gt;从不询问审批&lt;/td&gt;
      &lt;td&gt;完全跳过审批流程，直接根据安全模式决定&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“on-miss”&lt;/td&gt;
      &lt;td&gt;不在 allowlist 中时询问&lt;/td&gt;
      &lt;td&gt;只有当命令不在 allowlist 中时才弹出审批询问&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“always”&lt;/td&gt;
      &lt;td&gt;总是询问审批&lt;/td&gt;
      &lt;td&gt;无论命令是否在 allowlist 中，都会弹出审批询问&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;askfallback---审批超时后的行为&quot;&gt;askFallback - 审批超时后的行为&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;值&lt;/th&gt;
      &lt;th&gt;代码作用&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;“deny”&lt;/td&gt;
      &lt;td&gt;超时后拒绝执行&lt;/td&gt;
      &lt;td&gt;最安全，超时后默认拒绝命令&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“allowlist”&lt;/td&gt;
      &lt;td&gt;超时后检查 allowlist&lt;/td&gt;
      &lt;td&gt;超时后，如果命令在 allowlist 中就允许&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“full”&lt;/td&gt;
      &lt;td&gt;超时后直接允许&lt;/td&gt;
      &lt;td&gt;最宽松，超时后无条件允许命令&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

</content>
 </entry>
 
 <entry>
   <title>OpenClaw部署实战：在fly.io上快速搭建AI智能助手</title>
   <link href="https://blog.cyeam.com/openclaw/2026/03/30/openclaw-deploy"/>
   <updated>2026-03-30T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/openclaw/2026/03/30/openclaw-deploy</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#quick-start&quot; id=&quot;markdown-toc-quick-start&quot;&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#部署流程&quot; id=&quot;markdown-toc-部署流程&quot;&gt;部署流程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#运行效果&quot; id=&quot;markdown-toc-运行效果&quot;&gt;运行效果&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#常用命令&quot; id=&quot;markdown-toc-常用命令&quot;&gt;常用命令&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#flyio命令&quot; id=&quot;markdown-toc-flyio命令&quot;&gt;fly.io命令&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#openclaw命令&quot; id=&quot;markdown-toc-openclaw命令&quot;&gt;OpenClaw命令&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#斜杠命令&quot; id=&quot;markdown-toc-斜杠命令&quot;&gt;斜杠命令&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;quick-start&quot;&gt;Quick Start&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;下载OpenClaw脚手架&lt;a href=&quot;https://github.com/cyeam/cyeamclaw&quot;&gt;cyeamclaw&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;申请fly应用和外挂存储，参考官方文档&lt;a href=&quot;https://docs.openclaw.ai/install/fly&quot;&gt;fly.io&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;在fly.io或.env中部署密钥，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fly deploy&lt;/code&gt;命令部署。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;部署流程&quot;&gt;部署流程&lt;/h1&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    subgraph &quot;运行([processes]start-services.sh)&quot;
        feishu --&amp;gt; gateway
        gateway --&amp;gt; openclaw_json[(openc claw\n.json)]
        gateway --&amp;gt; Runtime
        Runtime --&amp;gt; models[models\nsecrets]
        Runtime --&amp;gt; skills[skills\nSKILL.md, CONF]
        Runtime --&amp;gt; cron
    end

    subgraph &quot;部署([env][mounts])&quot;
        env_vars[环境变量\n配置路径路径、启动参数]
        secrets[秘钥\n.env、fly Secrets]
        mounts[挂载磁盘\n/data]
    end

    subgraph &quot;环境&quot;
        openclaw_dir[OPENCLAW 目录=/data/.openclaw]
        cli_path[可执行cli /usr/bin/ /usr/local/bin]
    end

    subgraph &quot;打包([build]Dockerfile)&quot;
        compile_env[编译环境变量]
        set_cn[设置中文]
        sync_json[同步openclaw.json]
        add_perm[增加执行权限]
        install_openclaw[安装openclaw]
        install_skill_deps[安装skill依赖]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在fly.io部署最核心的是fly.toml文件，它定义了应用的编译信息&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[build]&lt;/code&gt;、环境变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[env]&lt;/code&gt;、硬盘挂载&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[mounts]&lt;/code&gt;、启动配置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[processes]&lt;/code&gt;等。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;[build]用Dockerfile文件实现，构建包含 OpenClaw 全量运行依赖的 Docker 镜像，本阶段最终产出：可直接部署的 OpenClaw 标准化 Docker 镜像。：
    &lt;ol&gt;
      &lt;li&gt;配置编译环境变量：在 Dockerfile 中预设构建所需的基础环境参数，比如 Node.js 运行时版本、软件源、系统架构适配参数等，确保跨环境构建的一致性。&lt;/li&gt;
      &lt;li&gt;设置系统中文环境：安装中文字体、配置zh_CN.UTF-8系统编码，解决 OpenClaw 运行时的中文乱码、 emoji 显示异常问题。&lt;/li&gt;
      &lt;li&gt;同步 openclaw.json 主配置文件：将提前编写好的 OpenClaw 核心配置文件打包到镜像内，或预设配置模板，确保服务启动时可直接加载核心规则。&lt;/li&gt;
      &lt;li&gt;增加执行权限：给启动脚本start-services.sh、技能执行脚本等核心文件授予可执行权限，避免容器启动时因权限不足报错。&lt;/li&gt;
      &lt;li&gt;安装 OpenClaw 主程序：通过 npm 全局安装 OpenClaw CLI，将可执行文件部署到/usr/bin/、/usr/local/bin/系统路径（对应图表「环境」模块的可执行 CLI 路径），确保全局可调用。&lt;/li&gt;
      &lt;li&gt;安装 Skill 技能依赖：提前安装自定义技能所需的系统依赖、语言运行时依赖，避免运行时技能加载失败。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;[env]定义环境变量，磁盘挂载。OpenClaw运行时信息，包括记忆、日志不能放在容器中，否则会在每次部署时被覆盖，需要放在挂载硬盘&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data&lt;/code&gt;上。
    &lt;ol&gt;
      &lt;li&gt;持久化工作目录准备：确定 OpenClaw 核心工作目录为/data/.openclaw，该目录用于存储会话数据、技能文件、运行日志、模型密钥等核心数据，需提前准备对应的持久化存储资源。&lt;/li&gt;
      &lt;li&gt;执行需要的cli放在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/bin&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/bin&lt;/code&gt;，确保全局可调用。&lt;/li&gt;
      &lt;li&gt;环境变量注入：通过容器环境变量，配置 OpenClaw 的核心运行参数，主要是OpenClaw和Skill的配置文件路径、模型并发限制等。
        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;OPENCLAW_HOME = &quot;/data&quot;
OPENCLAW_STATE_DIR = &quot;/data/.openclaw&quot;
OPENCLAW_CONFIG_PATH = &quot;/data/.openclaw/openclaw.json&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;敏感密钥安全管理：通过.env文件、fly Secrets 等安全方式，注入模型 API 密钥、飞书渠道凭证、第三方服务 Token 等敏感信息，避免硬编码到镜像中造成泄露。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;[processes]从start-services.sh作为入口启动服务，核心是openclaw.json配置文件。
    &lt;ol&gt;
      &lt;li&gt;默认日志在/tmp/openclaw.log。&lt;/li&gt;
      &lt;li&gt;openclaw工作目录功能如下，workspace里的文件调教方式可参考&lt;a href=&quot;https://developer.aliyun.com/article/1715278&quot;&gt;《搞懂这7个配置文件让你的OpenClaw变智能助手》&lt;/a&gt;：&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;环境变量&lt;/th&gt;
      &lt;th&gt;作用&lt;/th&gt;
      &lt;th&gt;优先级&lt;/th&gt;
      &lt;th&gt;默认值&lt;/th&gt;
      &lt;th&gt;示例&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;OPENCLAW_HOME&lt;/td&gt;
      &lt;td&gt;用户主目录，用于解析 ~ 路径&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;$HOME&lt;/td&gt;
      &lt;td&gt;/data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OPENCLAW_STATE_DIR&lt;/td&gt;
      &lt;td&gt;状态/配置目录，存储所有文件&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;$OPENCLAW_HOME/.openclaw&lt;/td&gt;
      &lt;td&gt;/data/.openclaw&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/data/.openclaw/
├── openclaw.json                 # 主配置文件（JSON/JSON5）
├── exec-approvals.json           # 执行审批配置文件（JSON/JSON5）
├── workspace/                    # 你的 AI “灵魂”文件夹（推荐 git 版本控制）
│   ├── SOUL.md                   # 人格设定（语气、风格）
│   ├── USER.md                   # 你的个人信息（让 AI 更懂你）
│   ├── MEMORY.md                 # 长期记忆（手动可编辑）
│   ├── IDENTITY.md               # Agent 名称、形象
│   ├── AGENTS.md                 # 多 Agent 路由规则
│   ├── BOOT.md                   # 启动提示词
│   ├── HEARTBEAT.md              # 每日检查清单
│   └── skills/                   # 已安装技能（每个技能一个子文件夹）
├── agents/&amp;lt;cid&amp;gt;/                 # 每个 Agent 的独立状态
├── memory/&amp;lt;cid&amp;gt;.sqlite           # 向量记忆库
├── credentials/                  # API Key、OAuth（旧版）
├── skills/                       # 全局技能包
└── secrets.json                  # 加密凭证（可选）
└── devices/                      # 设备
│   ├── paired.json               # 已配对设备列表
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;运行效果&quot;&gt;运行效果&lt;/h1&gt;

&lt;p&gt;cron执行效果如下，每天定时推送热门新闻。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw cron add \
  --name &quot;每日热榜推送&quot; \
  --cron &quot;0 0 * * *&quot; \
  --tz &quot;Asia/Shanghai&quot; \
  --session isolated \
  --message &quot;使用 daily-hot-push 技能获取今日热榜TOP10并推送&quot; \
  --announce \
  --channel feishu \
  --to &quot;ou_aaaaa&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1774882140/clipboard_1774882131026_vp8i2e6w4.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;常用命令&quot;&gt;常用命令&lt;/h1&gt;

&lt;h2 id=&quot;flyio命令&quot;&gt;fly.io命令&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flyctl secrets list -a cyeamclaw
fly machine start 2865110a372e48
fly logs -i 2865110a372e48
fly deploy -a cyeamclaw &amp;amp;&amp;amp; sleep 5 &amp;amp;&amp;amp; fly machine start 2865110a372e48 &amp;amp;&amp;amp; fly logs -i 2865110a372e48
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;openclaw命令&quot;&gt;OpenClaw命令&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw --version
openclaw logs -follow
openclaw pairing list
openclaw pairing list feishu
openclaw gateway restart
openclaw channels list
openclaw cron list --all
openclaw config set agents.sandbox.mode all
openclaw config set tools.deny &apos;[&quot;group:web&quot;,&quot;browser&quot;]&apos;
openclaw approvals get
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;斜杠命令&quot;&gt;斜杠命令&lt;/h2&gt;

&lt;p&gt;官方文档&lt;a href=&quot;https://docs.openclaw.ai/zh-CN/tools/slash-commands&quot;&gt;斜杠命令&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/model
/model list
/model openrouter/nvidia/nemotron-nano-12b-v2-vl:free
/verbose full
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>最近切换域名服务器，为了查问题用了很多方法，这里记录下</title>
   <link href="https://blog.cyeam.com/css/2026/03/21/dns-checker"/>
   <updated>2026-03-21T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/css/2026/03/21/dns-checker</id>
   <content type="html">
&lt;hr /&gt;

&lt;p&gt;Chrome浏览器报错如下，中间走了很多网路，TLS配置、QUIC、服务器限流等等，确实有知识盲区，领取Chrome报错也太不清楚了。。。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;检查网络连接
检查代理服务器和防火墙
ERR_CONNECTION_CLOSED 访问网页有问题
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;确定是不是真有问题
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;https://downforeveryoneorjustme.com/&quot;&gt;https://downforeveryoneorjustme.com/&lt;/a&gt;;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://ping.chinaz.com/www.cyeam.com&quot;&gt;Ping&lt;/a&gt;;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;查询域名服务器，选择NS类型
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;https://linux.die.net/man/8/dig&quot;&gt;ITDog&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;while true; do dig cyeam.com NS +short; sleep 20; done&lt;/code&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;查询解析结果，AA、AAAA
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;https://linux.die.net/man/8/dig&quot;&gt;ITDog&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;while true; do dig cyeam.com AAAA +short; sleep 20; done&lt;/code&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;用&lt;a href=&quot;https://www.ip138.com/&quot;&gt;IP138&lt;/a&gt;检测解析出来的结果是否正确，拿着IP查询所在地，是Cloudflare还是其他服务器；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;检查浏览器DNS解析结果
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://net-internals/#dns&lt;/code&gt; 输入域名cyeam.com查询&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因为是偶发问题很难复现，最后问题就是出现在第5步，当出问题时查了下域名记录，结果如下，其中多了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ech_config_list&lt;/code&gt;，这就是问题所在，该记录携带了 Cloudflare 的 ECH 加密配置和 Cloudflare 边缘节点 IP，Chrome 优先用这个 Cloudflare 节点发起连接；但我们无法正常使用ECH，收到请求后直接关闭了 TCP 连接。其他人也需要了&lt;a href=&quot;https://v2ex.com/t/1077702&quot;&gt;类似问题&lt;/a&gt;，Cloudflare无法在页面关闭，需要用命令行。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Resolved IP addresses of &quot;note.cyeam.com&quot;: [&quot;1.1.1.1&quot;].
Alternative endpoint: {&quot;alpns&quot;:[&quot;h2&quot;,&quot;http/1.1&quot;],&quot;ech_config_list&quot;:&quot;fdsafafdafadsfadsfdsa&quot;,&quot;ip_endpoints&quot;:[&quot;1.1.1.1&quot;]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>用Playwright扫描渲染问题</title>
   <link href="https://blog.cyeam.com/css/2026/03/17/playright"/>
   <updated>2026-03-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/css/2026/03/17/playright</id>
   <content type="html">
&lt;hr /&gt;

&lt;p&gt;按照依赖包：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install playwright --save
npx playwright install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;保存下面的脚本，用命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node scan.js&lt;/code&gt;运行即可。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-node&quot;&gt;const { chromium, devices } = require(&apos;playwright&apos;);
const https = require(&apos;https&apos;);
const fs = require(&apos;fs&apos;);
const path = require(&apos;path&apos;);

// --- 配置区域 ---
const CONFIG = {
  urls: [
// &apos;https://www.cyeam.com/tool/ddl2gostruct&apos;,
// &apos;https://www.cyeam.com/tool/json2ddl&apos;,
 
    &apos;https://www.cyeam.com/sitemap.xml&apos;,
   ],
  outputDir: &apos;./scan-results&apos;,
  timeout: 10000,
  headless: true,
  devicesToTest: [
    { name: &apos;Mobile-iPhone-15&apos;, preset: &apos;iPhone 15&apos; },
    { name: &apos;Desktop-MacBook-Pro-16&apos;, preset: &apos;MacBook Pro 16&apos; },
  ],
  maxSitemapUrls: 300,
  retryTimes: 3 // 重试次数
};

// --- 辅助函数：带重试的page.goto ---
const gotoWithRetry = async (page, url, options, maxRetries) =&amp;gt; {
  let lastError;
  for (let retry = 1; retry &amp;lt;= maxRetries; retry++) {
    try {
      if (retry &amp;gt; 1) {
        console.log(`   🔄 重试(${retry}/${maxRetries})访问: ${url}`);
      }
      await page.goto(url, options);
      return; // 成功则直接返回
    } catch (error) {
      lastError = error;
      if (error.message.includes(&apos;Timeout&apos;) || error.message.includes(&apos;timeout&apos;)) {
        if (retry === maxRetries) {
          throw new Error(`[Retry Failed] 超过${maxRetries}次重试仍无法访问: ${url}，错误：${error.message}`);
        }
        await new Promise(resolve =&amp;gt; setTimeout(resolve, 1000));
      } else {
        throw error;
      }
    }
  }
  throw lastError;
};

// --- 辅助函数：原生解析Sitemap ---
const fetchSitemapUrls = (sitemapUrl) =&amp;gt; {
  return new Promise((resolve, reject) =&amp;gt; {
    https.get(sitemapUrl, (res) =&amp;gt; {
      let xml = &apos;&apos;;
      // 1. 处理编码问题（避免chunk拼接乱码）
      res.setEncoding(&apos;utf8&apos;);
      
      res.on(&apos;data&apos;, (chunk) =&amp;gt; xml += chunk);
      res.on(&apos;end&apos;, () =&amp;gt; {
        try {
          // 2. 替换正则匹配方式：用matchAll（ES2020+），避免lastIndex陷阱
          const urlRegex = /&amp;lt;loc&amp;gt;(.*?)&amp;lt;\/loc&amp;gt;/g;
          // 兼容写法：如果环境不支持matchAll，用while循环+重置lastIndex
          const urls = [];
          let match;
          // 重置正则lastIndex，确保从头匹配
          urlRegex.lastIndex = 0;
          while ((match = urlRegex.exec(xml)) !== null) {
            if (match[1]) {
              urls.push(match[1].trim()); // 去除首尾空格，避免无效URL
            }
          }

          // 3. 优化域名过滤逻辑：匹配主域名（而非完整hostname）
          const baseUrlObj = new URL(sitemapUrl);
          // 提取主域名（如从www.cyeam.com提取cyeam.com）
          const baseDomain = baseUrlObj.hostname.split(&apos;.&apos;).slice(-2).join(&apos;.&apos;);
          
          const filteredUrls = urls.filter(url =&amp;gt; {
            try {
              const urlObj = new URL(url);
              // 匹配主域名（支持所有子域名：www/blog/note/game.cyeam.com）
              const urlDomain = urlObj.hostname.split(&apos;.&apos;).slice(-2).join(&apos;.&apos;);
              return urlDomain === baseDomain;
            } catch (e) {
              console.warn(&apos;无效URL:&apos;, url, e.message);
              return false;
            }
          });

          // 4. 最终结果：按CONFIG截断（但先确保CONFIG值足够大）
          const result = filteredUrls.slice(0, CONFIG.maxSitemapUrls);
          console.log(`提取结果：总匹配${urls.length}个 → 过滤后${filteredUrls.length}个 → 最终${result.length}个`);
          resolve(result);
        } catch (e) {
          reject(new Error(`解析XML失败：${e.message}`));
        }
      });
    })
    // 处理请求错误（如超时、连接失败）
    .on(&apos;error&apos;, (err) =&amp;gt; reject(new Error(`请求Sitemap失败：${err.message}`)))
    // 处理超时
    .setTimeout(10000, () =&amp;gt; reject(new Error(&apos;请求Sitemap超时&apos;)));
  });
};

// --- 辅助函数：初始化目录 ---
const initDirs = (deviceName) =&amp;gt; {
  const dir = path.join(CONFIG.outputDir, deviceName);
  const shotDir = path.join(dir, &apos;screenshots&apos;);
  if (!fs.existsSync(shotDir)) fs.mkdirSync(shotDir, { recursive: true });
  return { dir, shotDir };
};

// --- 核心逻辑 ---
(async () =&amp;gt; {
  try {
    let urls = [];
    let isSitemapMode = false;

    if (CONFIG.urls.length === 1 &amp;amp;&amp;amp; CONFIG.urls[0].toLowerCase().includes(&apos;sitemap.xml&apos;)) {
      isSitemapMode = true;
      const sitemapUrl = CONFIG.urls[0];
      console.log(`🔍 检测到sitemap模式，正在解析: ${sitemapUrl}`);
      urls = await fetchSitemapUrls(sitemapUrl);
      console.log(`✅ 从sitemap解析出 ${urls.length} 个URL\n`);
    } else {
      urls = CONFIG.urls;
      console.log(`📋 使用手动配置的URL列表，共 ${urls.length} 个URL\n`);
    }

    if (urls.length === 0) {
      console.log(&apos;❌ 未找到可检测的URL，请检查配置！&apos;);
      return;
    }

    for (const deviceConfig of CONFIG.devicesToTest) {
      console.log(`\n========== 正在测试设备: ${deviceConfig.name} ==========`);
      const { dir, shotDir } = initDirs(deviceConfig.name);
      const results = [];

      const browser = await chromium.launch({ headless: CONFIG.headless });
      
      let context;
      if (deviceConfig.preset) {
        const device = devices[deviceConfig.preset];
        context = await browser.newContext({ ...device });
      } else {
        context = await browser.newContext({ viewport: deviceConfig.viewport });
      }

      const page = await context.newPage();

      for (const url of urls) {
        // 1. 初始化结果对象，新增timeCost字段
        const pageResult = { url, errors: [], screenshot: null, timeCost: 0 };
        const safeFilename = url.replace(/[^a-zA-Z0-9]/g, &apos;_&apos;);
        
        // 2. 记录访问开始时间（毫秒级）
        const startTime = Date.now();

        try {
          page.on(&apos;pageerror&apos;, err =&amp;gt; pageResult.errors.push(`[JS] ${err.message}`));
          
          // 访问页面（包含重试逻辑）
          await gotoWithRetry(page, url, { waitUntil: &apos;load&apos;, timeout: CONFIG.timeout }, CONFIG.retryTimes);

          // 检查横向滚动条
          const hasHorizontalScroll = await page.evaluate(() =&amp;gt; {
            return document.documentElement.scrollWidth &amp;gt; document.documentElement.clientWidth;
          });
          if (hasHorizontalScroll) {
            pageResult.errors.push(&apos;[Layout] 检测到横向滚动条，内容溢出屏幕&apos;);
          }

          // 检查裂图
          const brokenImages = await page.evaluate(() =&amp;gt; {
            const imgs = Array.from(document.images);
            return imgs
              .filter(img =&amp;gt; !img.alt.includes(&apos;大图预览&apos;))
              .filter(img =&amp;gt; img.naturalWidth === 0)
              .map(img =&amp;gt; {
                const getXPath = (element) =&amp;gt; {
                  if (element.id !== &apos;&apos;) return `//*[@id=&quot;${element.id}&quot;]`;
                  if (element === document.body) return &apos;/html/body&apos;;
                  
                  let ix = 0;
                  const siblings = element.parentNode.childNodes;
                  for (let i = 0; i &amp;lt; siblings.length; i++) {
                    const sibling = siblings[i];
                    if (sibling === element) return `${getXPath(element.parentNode)}/${element.tagName.toLowerCase()}[${ix + 1}]`;
                    if (sibling.nodeType === 1 &amp;amp;&amp;amp; sibling.tagName === element.tagName) ix++;
                  }
                  return getXPath(element.parentNode);
                };

                return {
                  src: img.src,
                  alt: img.alt,
                  html: img.outerHTML,
                  xpath: getXPath(img)
                };
              });
          });

          if (brokenImages.length &amp;gt; 0) {
            for (const brokenImg of brokenImages) {
              const errMsg = `[Layout] 裂图详情：
                - 链接: ${brokenImg.src}
                - Alt文本: ${brokenImg.alt || &apos;无&apos;}
                - HTML代码: ${brokenImg.html}
                - XPath路径: ${brokenImg.xpath}`;
              pageResult.errors.push(errMsg);
            }
          }

          // 截图保存
          const screenshotPath = path.join(deviceConfig.name, &apos;screenshots&apos;, `${safeFilename}.png`);
          await page.screenshot({ path: path.join(CONFIG.outputDir, screenshotPath), fullPage: true });
          pageResult.screenshot = screenshotPath;

        } catch (e) {
          pageResult.errors.push(`[Critical] ${e.message}`);
        } finally {
          // 3. 计算总耗时（无论成功/失败，都会执行）
          pageResult.timeCost = Date.now() - startTime;
        }

        results.push(pageResult);
        
        // 4. 控制台输出时展示耗时
        if (pageResult.errors.length &amp;gt; 0) {
          console.log(`❌ ${url} (耗时: ${pageResult.timeCost}ms)`);
          pageResult.errors.forEach(err =&amp;gt; console.log(`   - ${err}`));
        } else {
          console.log(`✅ ${url} (耗时: ${pageResult.timeCost}ms)`);
        }
      }

      // 保存检测报告（包含耗时字段）
      fs.writeFileSync(path.join(dir, &apos;report.json&apos;), JSON.stringify(results, null, 2));
      await browser.close();
    }

    console.log(`\n🎉 扫描完成！结果已保存至: ${path.resolve(CONFIG.outputDir)}`);
  } catch (error) {
    console.error(&apos;💥 运行出错:&apos;, error);
  }
})();
&lt;/code&gt;&lt;/pre&gt;

</content>
 </entry>
 
 <entry>
   <title>手机端页面横向滚动条排查方法</title>
   <link href="https://blog.cyeam.com/css/2026/03/16/overflow"/>
   <updated>2026-03-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/css/2026/03/16/overflow</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#复现&quot; id=&quot;markdown-toc-复现&quot;&gt;复现&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#定位&quot; id=&quot;markdown-toc-定位&quot;&gt;定位&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;AI教我学习前端系列。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;移动端因为页面较窄，很容易由于内部元素配置有问题导致出现横向滚动条。但手机端排查难度又比较大，还是需要在PC端排查。&lt;/p&gt;

&lt;h2 id=&quot;复现&quot;&gt;复现&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;在桌面Chrome打开调试页面，切换成移动端模式。&lt;/li&gt;
  &lt;li&gt;我的情况要复杂些，桌面调试一开始无法复现问题，是分辨率对不上的问题，遂又搭建了&lt;a href=&quot;https://www.cyeam.com/tool/pixel&quot;&gt;设备信息检测工具&lt;/a&gt;。如下图所示：&lt;/li&gt;
  &lt;li&gt;在浏览器中填入问题设备的像素，问题复现。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773673609/image.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;定位&quot;&gt;定位&lt;/h2&gt;

&lt;p&gt;如果是简单的页面，通过Elements工具就能发现问题了，可以把鼠标放到每个Element上面看，Chrome自然会提示宽度。如果宽度超出了第一步获取到的数值，那么恭喜你，你找到问题元素了。
但如果页面比较复杂，元素嵌套比较深，通过Elements工具就比较麻烦了。这时可以考虑通过Chrome的Console工具来排查。在Console中输入以下代码，回车执行：&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 查找所有溢出视口的元素（带定位+高亮）&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;allElements&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;overflowElements&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
  
  &lt;span class=&quot;c1&quot;&gt;// 先清除之前的高亮（避免干扰）&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;[data-overflow-highlight]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;removeAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data-overflow-highlight&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;allElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBoundingClientRect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 检查元素是否超出视口宽度（排除不可见元素，减少干扰）&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerWidth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 1. 给溢出元素加红色边框（可视化高亮）&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;border&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2px solid red !important&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;boxShadow&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;0 0 10px red !important&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;data-overflow-highlight&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// 2. 构造定位信息（方便查找）&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`#&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 有id直接用#id&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 处理多class，转为.类名1.类名2&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\s&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;selector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tagName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 无class/id用标签名&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// 3. 收集信息（包含元素本身，关键！）&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;overflowElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;元素引用&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 核心：Console点击可跳转到Elements面板&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;标签名&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tagName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;类名&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;无&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;选择器&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;元素宽度&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;px`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;元素右边界&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;px`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;视口宽度&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;px`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;溢出宽度&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;px`&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 超出多少&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 输出表格（可点击「元素引用」列跳转到Elements）&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;overflowElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  
  &lt;span class=&quot;c1&quot;&gt;// 提示信息&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;%c✅ 溢出元素已标红边框，Console表格中点击「元素引用」可直接定位到Elements面板&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;color: green; font-weight: bold&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;%c⚠️ 刷新页面可清除红色边框&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;color: orange; font-weight: bold&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果有问题，所显示一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt;，点击展开它，点击「元素引用」会跳到Elements面板，这就是有问题的元素，右击这个元素选择「Scroll into View」，鼠标放上去看宽度即可诊断。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773674853/clipboard_1773674845492_xacl8ivuz.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>药不能停</title>
   <link href="https://blog.cyeam.com/ai/2026/03/14/medicine"/>
   <updated>2026-03-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/03/14/medicine</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#说明&quot; id=&quot;markdown-toc-说明&quot;&gt;说明&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#使用指南&quot; id=&quot;markdown-toc-使用指南&quot;&gt;使用指南&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#添加药品&quot; id=&quot;markdown-toc-添加药品&quot;&gt;添加药品&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#删除药品&quot; id=&quot;markdown-toc-删除药品&quot;&gt;删除药品&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#查看服药历史&quot; id=&quot;markdown-toc-查看服药历史&quot;&gt;查看服药历史&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#常见问题-faq&quot; id=&quot;markdown-toc-常见问题-faq&quot;&gt;常见问题 FAQ&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#注意事项&quot; id=&quot;markdown-toc-注意事项&quot;&gt;注意事项&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#changelog&quot; id=&quot;markdown-toc-changelog&quot;&gt;ChangeLog&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#v101-2026-04-02&quot; id=&quot;markdown-toc-v101-2026-04-02&quot;&gt;v1.0.1 (2026-04-02)&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#build&quot; id=&quot;markdown-toc-build&quot;&gt;Build&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#feat&quot; id=&quot;markdown-toc-feat&quot;&gt;Feat&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#fix&quot; id=&quot;markdown-toc-fix&quot;&gt;Fix&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#v100-2026-03-14&quot; id=&quot;markdown-toc-v100-2026-03-14&quot;&gt;v1.0.0 (2026-03-14)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;text-center my-5&quot;&gt;
    &lt;a href=&quot;https://www.pgyer.com/yaobunengting&quot; class=&quot;btn btn-primary btn-lg py-3 px-5&quot;&gt;
        &lt;i class=&quot;bi bi-download me-2&quot;&gt;&lt;/i&gt;
        立即下载-药不能停
    &lt;/a&gt;
&lt;/div&gt;

&lt;h1 id=&quot;说明&quot;&gt;说明&lt;/h1&gt;

&lt;p&gt;本应用用于帮助用户快速掌握药品管理、每日提醒、历史记录、数据备份等全部功能。
所有数据均保存在手机本地，不联网、不上传。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;功能模块&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
      &lt;th&gt;产品截图&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;药品管理&lt;/td&gt;
      &lt;td&gt;添加 / 删除药品名称、剂量、每日提醒时间&lt;/td&gt;
      &lt;td&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773501865/Screenshot_2026-03-14-23-22-09-734_com.cyeam.medicine-edit.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;每日定时提醒&lt;/td&gt;
      &lt;td&gt;按设定时间准时提醒，每天自动重复&lt;/td&gt;
      &lt;td&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773540130/Screenshot_2026-03-15-10-00-17-057_com.larus.nova-edit.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;服药历史记录&lt;/td&gt;
      &lt;td&gt;查看所有已提醒、已确认服药的记录&lt;/td&gt;
      &lt;td&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773502038/Screenshot_2026-03-14-23-26-08-093_com.cyeam.medicine-edit.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;使用指南&quot;&gt;使用指南&lt;/h1&gt;

&lt;h2 id=&quot;添加药品&quot;&gt;添加药品&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;点击主界面右下角 + 按钮&lt;/li&gt;
  &lt;li&gt;填写：
    &lt;ul&gt;
      &lt;li&gt;药品名称（必填）&lt;/li&gt;
      &lt;li&gt;服药剂量（可选）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;点击 选择时间，设置每日提醒时刻&lt;/li&gt;
  &lt;li&gt;点击 保存&lt;/li&gt;
  &lt;li&gt;系统自动设置每日重复闹钟&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;删除药品&quot;&gt;删除药品&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;在药品列表找到对应药品&lt;/li&gt;
  &lt;li&gt;点击删除按钮&lt;/li&gt;
  &lt;li&gt;系统自动取消该药品的所有提醒&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;查看服药历史&quot;&gt;查看服药历史&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;点击主界面 右上角菜单（三个点）&lt;/li&gt;
  &lt;li&gt;选择 服药历史&lt;/li&gt;
  &lt;li&gt;可查看：
    &lt;ul&gt;
      &lt;li&gt;药品名称&lt;/li&gt;
      &lt;li&gt;提醒时间&lt;/li&gt;
      &lt;li&gt;服药状态（已服药）&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;常见问题-faq&quot;&gt;常见问题 FAQ&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Q1：提醒只响一次，之后不提醒？
A：通常是被系统杀后台。
请开启：自启动 + 后台运行 + 关闭电池优化。&lt;/li&gt;
  &lt;li&gt;Q2：收不到通知？
A：检查：
APP 通知权限是否开启
手机是否开启静音 / 勿扰模式&lt;/li&gt;
  &lt;li&gt;Q3：APP 升级后数据不见了？
A：正常覆盖升级不会丢数据。
如丢失，点击 恢复数据 即可从备份找回。&lt;/li&gt;
  &lt;li&gt;Q4：手机重启后提醒失效？
A：不会。
APP 已支持开机自动恢复所有闹钟。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;注意事项&quot;&gt;注意事项&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;卸载 APP 会清空本地数据，卸载前请务必备份。&lt;/li&gt;
  &lt;li&gt;提醒依赖系统闹钟，请勿使用 “清理内存” 类工具强制关闭 APP。&lt;/li&gt;
  &lt;li&gt;所有数据仅存在本地，隐私安全。&lt;/li&gt;
  &lt;li&gt;最低支持 Android 8.0（API 26）。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;changelog&quot;&gt;ChangeLog&lt;/h1&gt;

&lt;p&gt;&lt;a name=&quot;v1.0.1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;v101-2026-04-02&quot;&gt;&lt;a href=&quot;https://github.com/mnhkahn/medicine/compare/v1.0.0...v1.0.1&quot;&gt;v1.0.1&lt;/a&gt; (2026-04-02)&lt;/h2&gt;

&lt;h3 id=&quot;build&quot;&gt;Build&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;更新应用版本号为v1.0.1&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;feat&quot;&gt;Feat&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;analytics:&lt;/strong&gt; 集成百度移动统计SDK&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;i18n:&lt;/strong&gt; 添加多语言支持并国际化UI字符串&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;ui:&lt;/strong&gt; 添加未来提醒功能并优化界面&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;fix&quot;&gt;Fix&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;MedicineAdapter:&lt;/strong&gt; 使用资源字符串显示剂量标签&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;v1.0.0&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;v100-2026-03-14&quot;&gt;v1.0.0 (2026-03-14)&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Added
    &lt;ul&gt;
      &lt;li&gt;支持本地保存药品和服药记录&lt;/li&gt;
      &lt;li&gt;支持查询服药历史&lt;/li&gt;
      &lt;li&gt;支持推送功能&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Geek新闻 每日AI热点新闻</title>
   <link href="https://blog.cyeam.com/ai/2026/03/12/geek-news"/>
   <updated>2026-03-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/03/12/geek-news</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#需求&quot; id=&quot;markdown-toc-需求&quot;&gt;需求&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#方案&quot; id=&quot;markdown-toc-方案&quot;&gt;方案&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mcp-实现&quot; id=&quot;markdown-toc-mcp-实现&quot;&gt;MCP 实现&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#传输方式对比&quot; id=&quot;markdown-toc-传输方式对比&quot;&gt;传输方式对比&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mcp-切换官方go-sdk&quot; id=&quot;markdown-toc-mcp-切换官方go-sdk&quot;&gt;MCP 切换官方Go SDK&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;text-center my-5&quot;&gt;
    &lt;a href=&quot;https://geek.cyeam.com/geek&quot; class=&quot;btn btn-primary btn-lg py-3 px-5&quot;&gt;
        &lt;i class=&quot;bi bi-download me-2&quot;&gt;&lt;/i&gt;
        立即体验-GeekNews
    &lt;/a&gt;
&lt;/div&gt;

&lt;h1 id=&quot;需求&quot;&gt;需求&lt;/h1&gt;
&lt;ol&gt;
  &lt;li&gt;新增微信公众号RSS，微信防爬虫力度非常强，需要做适配。只展示标题、链接即可；&lt;/li&gt;
  &lt;li&gt;其他RSS支持标题、链接、内容简介（基于AI总结）;&lt;/li&gt;
  &lt;li&gt;基于MCP搭建，每天定时更新AI最新动态。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;方案&quot;&gt;方案&lt;/h1&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
    %% Web端
    subgraph Web端
        A[拉取JSON数据] --&amp;gt; B[页面渲染]
    end

    %% 应用层
    subgraph 应用层
        GenNews[定时拉取新闻数据] --&amp;gt; D[保存新闻JSON]
        D --&amp;gt; E[刷新缓存]
    end
    
    %% MCP服务核心层
    subgraph MCP服务
        TechNews[并发获取新闻] --&amp;gt; H[时间排序&amp;amp;截取模块]
        H --&amp;gt; J[LLM总结精简内容]
        
        
    end
    
    %% 数据源
    subgraph 数据存储
        DS1[新闻数据RSS]
        DS2[当天新闻JSON文件]
    end

    LLM[LLM]
   

    %% 链路连接
    A --&amp;gt; DS2
    GenNews --&amp;gt;|title/img/desc|TechNews
    TechNews --&amp;gt; DS1
    D --&amp;gt; DS2
    J --&amp;gt; LLM
    
    A ~~~ GenNews
    E ~~~ DS2
    E ~~~ TechNews

&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;mcp-实现&quot;&gt;MCP 实现&lt;/h1&gt;

&lt;h2 id=&quot;传输方式对比&quot;&gt;传输方式对比&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;对比维度&lt;/th&gt;
      &lt;th&gt;stdio（标准输入输出）&lt;/th&gt;
      &lt;th&gt;Streamable HTTP（新版官方推荐）&lt;/th&gt;
      &lt;th&gt;HTTP with SSE（旧版已弃用）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;核心通信机制&lt;/td&gt;
      &lt;td&gt;客户端启动服务器子进程，通过 stdin/stdout 双向传输换行分隔的 JSON-RPC 消息，stderr 用于日志Model Context Protocol&lt;/td&gt;
      &lt;td&gt;单一 HTTP 端点同时支持 POST/GET；客户端 POST 发请求，支持同步 JSON 响应或 SSE 流式推送，内置会话管理与断点续传&lt;/td&gt;
      &lt;td&gt;双端点架构：SSE GET 端点建立长连接收服务端消息，HTTP POST 端点发客户端消息，服务端通过 SSE 单向推送结果&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;部署模式&lt;/td&gt;
      &lt;td&gt;本地父子进程，仅限同一台机器&lt;/td&gt;
      &lt;td&gt;独立服务进程，支持本地 / 局域网 / 云端跨网络跨机器部署&lt;/td&gt;
      &lt;td&gt;独立服务进程，支持跨网络跨机器部署&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;生命周期&lt;/td&gt;
      &lt;td&gt;与客户端进程强绑定，客户端退出则服务器子进程终止&lt;/td&gt;
      &lt;td&gt;服务端独立运行，与客户端完全解耦，支持会话生命周期管理&lt;/td&gt;
      &lt;td&gt;服务端独立运行，与客户端解耦&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;多客户端能力&lt;/td&gt;
      &lt;td&gt;不支持，1 个客户端对应 1 个独立服务器进程&lt;/td&gt;
      &lt;td&gt;原生支持多客户端并发连接，可负载均衡&lt;/td&gt;
      &lt;td&gt;支持多客户端连接&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;连接恢复能力&lt;/td&gt;
      &lt;td&gt;进程退出即连接终止，无恢复能力&lt;/td&gt;
      &lt;td&gt;原生支持 SSE 流断点续传，通过 Last-Event-ID 重放丢失消息Model Context Protocol&lt;/td&gt;
      &lt;td&gt;断连后需重建 SSE 连接，无官方续传机制&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;性能表现&lt;/td&gt;
      &lt;td&gt;本地进程间通信，延迟极低（通常 &amp;lt; 1ms），无网络栈开销，资源占用最小&lt;/td&gt;
      &lt;td&gt;受网络带宽 / 延迟影响，长连接模式下延迟可控，支持流式传输优化&lt;/td&gt;
      &lt;td&gt;受网络影响，长连接占用固定资源，断连开销大&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;安全性&lt;/td&gt;
      &lt;td&gt;天然安全，不暴露网络端口，无远程攻击面，依赖操作系统进程隔离&lt;/td&gt;
      &lt;td&gt;需自行配置 HTTPS 加密、Origin 校验、身份认证、防火墙策略，有网络暴露风险Model Context Protocol&lt;/td&gt;
      &lt;td&gt;同左，需额外的安全加固&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;mcp-切换官方go-sdk&quot;&gt;MCP 切换官方Go SDK&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-Go&quot;&gt;// 服务端
server1 := mcp.NewServer(&amp;amp;mcp.Implementation{Name: &quot;greeter1&quot;}, nil)
mcp.AddTool(server1, &amp;amp;mcp.Tool{Name: &quot;greet1&quot;, Description: &quot;say hi&quot;}, SayHi)
handler := mcp.NewStreamableHTTPHandler(func(request *http.Request) *mcp.Server {
    return server1
}, nil)
app.Handle(&quot;/mcp&quot;, http.HandlerFunc(handler.ServeHTTP))
l, err := net.Listen(&quot;tcp&quot;, &quot;:1031&quot;)
if err != nil {
    logger.Errorf(&quot;Listen: %v&quot;, err)
    return
}
app.Serve(l)

// 客户端
client := mcp.NewClient(&amp;amp;mcp.Implementation{Name: &quot;mcp-client&quot;, Version: &quot;v1.0.0&quot;}, nil)
ctx := context.Background()
transport := &amp;amp;mcp.StreamableClientTransport{Endpoint: &quot;http://localhost:1031/mcp&quot;}
session, err := client.Connect(ctx, transport, nil)
if err != nil {
    log.Fatal(err)
}
defer session.Close()

params := &amp;amp;mcp.CallToolParams{
    Name:      &quot;greet1&quot;,
    Arguments: map[string]any{&quot;name&quot;: &quot;you&quot;},
}
res, err := session.CallTool(ctx, params)
if err != nil {
    log.Fatalf(&quot;CallTool failed: %v&quot;, err)
}
for _, a := range res.Content {
    buf, _ := a.MarshalJSON()
    fmt.Println(string(buf))
}
&lt;/code&gt;&lt;/pre&gt;

</content>
 </entry>
 
 <entry>
   <title>Mermaid语法整理</title>
   <link href="https://blog.cyeam.com/markdown/2026/03/10/mermaid"/>
   <updated>2026-03-10T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/markdown/2026/03/10/mermaid</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#一流程图flowchart&quot; id=&quot;markdown-toc-一流程图flowchart&quot;&gt;一、流程图（Flowchart）&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-基础规则&quot; id=&quot;markdown-toc-1-基础规则&quot;&gt;1. 基础规则&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-核心元素&quot; id=&quot;markdown-toc-2-核心元素&quot;&gt;2. 核心元素&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#1布局方向&quot; id=&quot;markdown-toc-1布局方向&quot;&gt;（1）布局方向&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#2节点样式&quot; id=&quot;markdown-toc-2节点样式&quot;&gt;（2）节点样式&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#3连线类型&quot; id=&quot;markdown-toc-3连线类型&quot;&gt;（3）连线类型&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-完整示例&quot; id=&quot;markdown-toc-3-完整示例&quot;&gt;3. 完整示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#二时序图sequence-diagram&quot; id=&quot;markdown-toc-二时序图sequence-diagram&quot;&gt;二、时序图（Sequence Diagram）&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-基础规则-1&quot; id=&quot;markdown-toc-1-基础规则-1&quot;&gt;1. 基础规则&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-核心元素-1&quot; id=&quot;markdown-toc-2-核心元素-1&quot;&gt;2. 核心元素&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#1参与者定义&quot; id=&quot;markdown-toc-1参与者定义&quot;&gt;（1）参与者定义&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#2消息连线核心&quot; id=&quot;markdown-toc-2消息连线核心&quot;&gt;（2）消息连线（核心）&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#3逻辑块&quot; id=&quot;markdown-toc-3逻辑块&quot;&gt;（3）逻辑块&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-完整示例-1&quot; id=&quot;markdown-toc-3-完整示例-1&quot;&gt;3. 完整示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#三甘特图gantt-chart&quot; id=&quot;markdown-toc-三甘特图gantt-chart&quot;&gt;三、甘特图（Gantt Chart）&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-基础规则-2&quot; id=&quot;markdown-toc-1-基础规则-2&quot;&gt;1. 基础规则&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-核心元素-2&quot; id=&quot;markdown-toc-2-核心元素-2&quot;&gt;2. 核心元素&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#1日期格式常用&quot; id=&quot;markdown-toc-1日期格式常用&quot;&gt;（1）日期格式（常用）&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#2任务定义格式&quot; id=&quot;markdown-toc-2任务定义格式&quot;&gt;（2）任务定义格式&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-完整示例-2&quot; id=&quot;markdown-toc-3-完整示例-2&quot;&gt;3. 完整示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#四饼图pie-chart&quot; id=&quot;markdown-toc-四饼图pie-chart&quot;&gt;四、饼图（Pie Chart）&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-基础规则-3&quot; id=&quot;markdown-toc-1-基础规则-3&quot;&gt;1. 基础规则&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-完整示例&quot; id=&quot;markdown-toc-2-完整示例&quot;&gt;2. 完整示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#五类图class-diagram&quot; id=&quot;markdown-toc-五类图class-diagram&quot;&gt;五、类图（Class Diagram）&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-基础规则-4&quot; id=&quot;markdown-toc-1-基础规则-4&quot;&gt;1. 基础规则&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-核心元素-3&quot; id=&quot;markdown-toc-2-核心元素-3&quot;&gt;2. 核心元素&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#1访问修饰符&quot; id=&quot;markdown-toc-1访问修饰符&quot;&gt;（1）访问修饰符&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#2类间关系核心&quot; id=&quot;markdown-toc-2类间关系核心&quot;&gt;（2）类间关系（核心）&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-完整示例-3&quot; id=&quot;markdown-toc-3-完整示例-3&quot;&gt;3. 完整示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#六状态图state-diagram&quot; id=&quot;markdown-toc-六状态图state-diagram&quot;&gt;六、状态图（State Diagram）&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#1-基础规则-5&quot; id=&quot;markdown-toc-1-基础规则-5&quot;&gt;1. 基础规则&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2-完整示例-1&quot; id=&quot;markdown-toc-2-完整示例-1&quot;&gt;2. 完整示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#七mermaid-通用使用技巧&quot; id=&quot;markdown-toc-七mermaid-通用使用技巧&quot;&gt;七、Mermaid 通用使用技巧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Mermaid 是一款基于 JavaScript 的图表绘制工具，通过简单的文本描述即可生成流程图、时序图、甘特图等多种图表。以下整理了最常用的几种图表的核心语法。&lt;/p&gt;

&lt;h2 id=&quot;一流程图flowchart&quot;&gt;一、流程图（Flowchart）&lt;/h2&gt;
&lt;p&gt;最常用的图表类型，用于描述业务流程、执行逻辑，支持多方向布局和复杂子图/分支。&lt;/p&gt;

&lt;h3 id=&quot;1-基础规则&quot;&gt;1. 基础规则&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;图表开头用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flowchart&lt;/code&gt;（旧版&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph&lt;/code&gt;也兼容），后跟布局方向，无方向默认从上到下；&lt;/li&gt;
  &lt;li&gt;节点用唯一标识定义，标识与显示文本用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]/()/{}/(())&lt;/code&gt;分隔，对应不同节点样式；&lt;/li&gt;
  &lt;li&gt;连线用箭头/符号表示，可添加连线说明文本；&lt;/li&gt;
  &lt;li&gt;子图用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subgraph&lt;/code&gt;定义，用于分组逻辑，子图可独立指定方向。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-核心元素&quot;&gt;2. 核心元素&lt;/h3&gt;
&lt;h4 id=&quot;1布局方向&quot;&gt;（1）布局方向&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;含义&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;TD/TB&lt;/td&gt;
      &lt;td&gt;从上到下（Top Down/Top Bottom，最常用）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LR&lt;/td&gt;
      &lt;td&gt;从左到右（Left Right）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;BT&lt;/td&gt;
      &lt;td&gt;从下到上（Bottom Top）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RL&lt;/td&gt;
      &lt;td&gt;从右到左（Right Left）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;2节点样式&quot;&gt;（2）节点样式&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;节点类型&lt;/th&gt;
      &lt;th&gt;示例&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;id[文本]&lt;/td&gt;
      &lt;td&gt;矩形（普通流程节点，最常用）&lt;/td&gt;
      &lt;td&gt;start[开始]&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;id(文本)&lt;/td&gt;
      &lt;td&gt;圆角矩形（开始/结束节点）&lt;/td&gt;
      &lt;td&gt;end(结束)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;id{文本}&lt;/td&gt;
      &lt;td&gt;菱形（判断/分支节点）&lt;/td&gt;
      &lt;td&gt;judge{是否符合条件？}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;id((文本))&lt;/td&gt;
      &lt;td&gt;圆形（起止/重要节点）&lt;/td&gt;
      &lt;td&gt;core((核心步骤))&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;id&amp;gt;文本&lt;/td&gt;
      &lt;td&gt;右向旗帜形&lt;/td&gt;
      &lt;td&gt;flag&amp;gt;备注&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;3连线类型&quot;&gt;（3）连线类型&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;连线样式&lt;/th&gt;
      &lt;th&gt;示例（带说明）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;-&amp;gt;&lt;/td&gt;
      &lt;td&gt;实线箭头（最常用）&lt;/td&gt;
      &lt;td&gt;A-&amp;gt;B&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;–&amp;gt;&lt;/td&gt;
      &lt;td&gt;虚线箭头&lt;/td&gt;
      &lt;td&gt;A–&amp;gt;B&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-.-&lt;/td&gt;
      &lt;td&gt;点线箭头&lt;/td&gt;
      &lt;td&gt;A-.-B&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;==&amp;gt;&lt;/td&gt;
      &lt;td&gt;粗线箭头&lt;/td&gt;
      &lt;td&gt;A==&amp;gt;B&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-&amp;gt;|文本|&lt;/td&gt;
      &lt;td&gt;实线箭头+说明&lt;/td&gt;
      &lt;td&gt;A-&amp;gt;| 执行操作 | B&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;~~~&lt;/td&gt;
      &lt;td&gt;隐藏线，用于控制布局，不展示实际连线&lt;/td&gt;
      &lt;td&gt;A~~~B&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;3-完整示例&quot;&gt;3. 完整示例&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
A[Hard] --&amp;gt;|Text| B(Round)
B --&amp;gt; C{Decision}
C --&amp;gt;|One| D[Result 1]
C --&amp;gt;|Two| E[Result 2]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;二时序图sequence-diagram&quot;&gt;二、时序图（Sequence Diagram）&lt;/h2&gt;

&lt;p&gt;用于描述不同参与者之间的交互过程，按时间顺序展示消息传递，适合接口调用、系统交互等场景。&lt;/p&gt;

&lt;h3 id=&quot;1-基础规则-1&quot;&gt;1. 基础规则&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;图表开头用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sequenceDiagram&lt;/code&gt;，无需指定方向，默认从左到右、从上到下按时间流；&lt;/li&gt;
  &lt;li&gt;先定义参与者（Actor/Participant），参与者可自定义别名；&lt;/li&gt;
  &lt;li&gt;消息连线按交互关系选择，可添加消息文本，支持循环、条件、并行等逻辑块。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-核心元素-1&quot;&gt;2. 核心元素&lt;/h3&gt;
&lt;h4 id=&quot;1参与者定义&quot;&gt;（1）参与者定义&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    actor U as 用户  # 角色参与者（人形图标）
    participant S as 服务端  # 普通参与者（矩形图标）
    participant D as 数据库
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
  &lt;li&gt;actor：带人形图标的参与者（如用户、管理员）；&lt;/li&gt;
  &lt;li&gt;participant：普通矩形参与者（如系统、服务、数据库）；&lt;/li&gt;
  &lt;li&gt;as：为参与者设置别名，简化后续语法。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;2消息连线核心&quot;&gt;（2）消息连线（核心）&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;连线含义&lt;/th&gt;
      &lt;th&gt;适用场景&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;A-&amp;gt;B: 文本&lt;/td&gt;
      &lt;td&gt;实线无箭头，单向消息&lt;/td&gt;
      &lt;td&gt;普通通知 / 调用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A-»B: 文本&lt;/td&gt;
      &lt;td&gt;实线有箭头，单向同步调用&lt;/td&gt;
      &lt;td&gt;核心交互（最常用）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A–»B: 文本&lt;/td&gt;
      &lt;td&gt;虚线有箭头，单向异步调用&lt;/td&gt;
      &lt;td&gt;异步接口 / 消息队列&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A–x B: 文本&lt;/td&gt;
      &lt;td&gt;虚线叉号，调用失败&lt;/td&gt;
      &lt;td&gt;异常反馈&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;A-&amp;gt;B\B-&amp;gt;A: 文本&lt;/td&gt;
      &lt;td&gt;双向箭头，互相交互&lt;/td&gt;
      &lt;td&gt;双向通信&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;3逻辑块&quot;&gt;（3）逻辑块&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;逻辑块类型&lt;/th&gt;
      &lt;th&gt;示例（带说明）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;loop&lt;/td&gt;
      &lt;td&gt;循环块&lt;/td&gt;
      &lt;td&gt;loop 重复执行 { 操作 }&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;alt/else&lt;/td&gt;
      &lt;td&gt;条件分支（二选一）&lt;/td&gt;
      &lt;td&gt;alt 条件1 { 操作1 } else { 操作2 }&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;alt&lt;/td&gt;
      &lt;td&gt;条件块&lt;/td&gt;
      &lt;td&gt;alt 条件1 { 操作1 } alt 条件2 { 操作2 }&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;par&lt;/td&gt;
      &lt;td&gt;并行块&lt;/td&gt;
      &lt;td&gt;par 并行操作1 { 操作1 } and 并行操作2 { 操作2 }&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;3-完整示例-1&quot;&gt;3. 完整示例&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    actor U as 前端用户
    participant S as 后端服务
    participant D as 数据库
    U-&amp;gt;&amp;gt;S: 提交登录请求(账号/密码)
    S-&amp;gt;&amp;gt;S: 密码加密处理
    alt 密码加密成功
        S-&amp;gt;&amp;gt;D: 查询用户信息
        D--&amp;gt;&amp;gt;S: 返回用户数据
        S-&amp;gt;&amp;gt;S: 验证账号密码是否匹配
        opt 匹配成功
            S-&amp;gt;&amp;gt;S: 生成Token
            S--&amp;gt;&amp;gt;U: 登录成功(返回Token)
        end
        opt 匹配失败
            S--x U: 登录失败(提示账号密码错误)
        end
    else 密码加密失败
        S--x U: 登录失败(提示系统异常)
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;三甘特图gantt-chart&quot;&gt;三、甘特图（Gantt Chart）&lt;/h2&gt;
&lt;p&gt;用于项目进度管理，按时间维度展示任务的开始 / 结束时间、持续时长、任务归属和完成状态，适合项目排期、里程碑规划。&lt;/p&gt;

&lt;h3 id=&quot;1-基础规则-2&quot;&gt;1. 基础规则&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;图表开头用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gantt&lt;/code&gt;，无需指定方向，默认从左到右、从上到下按时间流；&lt;/li&gt;
  &lt;li&gt;先定义任务（Task），任务可自定义名称、开始时间、持续时长、任务归属和完成状态；&lt;/li&gt;
  &lt;li&gt;必选日期格式（dateFormat）和标题（title）；&lt;/li&gt;
  &lt;li&gt;用section划分任务模块（如需求、开发、测试）；&lt;/li&gt;
  &lt;li&gt;任务连线按任务依赖关系选择，可添加任务描述，支持循环、条件、并行等逻辑块。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-核心元素-2&quot;&gt;2. 核心元素&lt;/h3&gt;

&lt;h4 id=&quot;1日期格式常用&quot;&gt;（1）日期格式（常用）&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;格式说明&lt;/th&gt;
      &lt;th&gt;示例&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;YYYY-MM-DD&lt;/td&gt;
      &lt;td&gt;年 - 月 - 日（最常用）&lt;/td&gt;
      &lt;td&gt;2025-01-01&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MM-DD&lt;/td&gt;
      &lt;td&gt;月 - 日（默认当年）&lt;/td&gt;
      &lt;td&gt;01-01&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HH:mm&lt;/td&gt;
      &lt;td&gt;时：分（默认当天）&lt;/td&gt;
      &lt;td&gt;09:00&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;2任务定义格式&quot;&gt;（2）任务定义格式&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;任务名 : 标识, 开始时间, 结束时间
# 或（按持续时长）
任务名 : 标识, 开始时间, +持续时间（d=天，h=小时，m=分钟）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;标识：可选，用于任务关联，可省略；&lt;/li&gt;
  &lt;li&gt;特殊标记：done（已完成任务）、crit（关键任务）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3-完整示例-2&quot;&gt;3. 完整示例&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;gantt
    section Section
    Completed :done,    des1, 2014-01-06,2014-01-08
    Active        :active,  des2, 2014-01-07, 3d
    Parallel 1   :         des3, after des1, 1d
    Parallel 2   :         des4, after des1, 1d
    Parallel 3   :         des5, after des3, 1d
    Parallel 4   :         des6, after des4, 1d
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;四饼图pie-chart&quot;&gt;四、饼图（Pie Chart）&lt;/h2&gt;
&lt;p&gt;用于展示数据占比，语法极简，适合快速呈现分类数据的比例关系。&lt;/p&gt;

&lt;h3 id=&quot;1-基础规则-3&quot;&gt;1. 基础规则&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;图表开头用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pie&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;必选标题（title）；&lt;/li&gt;
  &lt;li&gt;数据行格式：”分类名” : 数值（数值支持整数 / 浮点数，会自动计算占比）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-完整示例&quot;&gt;2. 完整示例&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;pie
    title 网站流量来源占比
    &quot;搜索引擎&quot; : 65
    &quot;社交媒体&quot; : 20
    &quot;直接访问&quot; : 10
    &quot;外部链接&quot; : 5
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;五类图class-diagram&quot;&gt;五、类图（Class Diagram）&lt;/h2&gt;
&lt;p&gt;用于面向对象设计，描述类、接口、对象之间的关系（继承、关联、聚合等），适合架构设计、代码建模。&lt;/p&gt;

&lt;h3 id=&quot;1-基础规则-4&quot;&gt;1. 基础规则&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;图表开头用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;classDiagram&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;类的定义：class 类名 { 属性; 方法 }，支持访问修饰符（公有 / 私有 / 受保护）；&lt;/li&gt;
  &lt;li&gt;用符号表示类之间的关系，箭头指向被关联 / 继承的类。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-核心元素-3&quot;&gt;2. 核心元素&lt;/h3&gt;

&lt;h4 id=&quot;1访问修饰符&quot;&gt;（1）访问修饰符&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;含义&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;+&lt;/td&gt;
      &lt;td&gt;公有（public，最常用）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-&lt;/td&gt;
      &lt;td&gt;私有（private）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#&lt;/td&gt;
      &lt;td&gt;受保护（protected）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;2类间关系核心&quot;&gt;（2）类间关系（核心）&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;语法&lt;/th&gt;
      &lt;th&gt;关系含义&lt;/th&gt;
      &lt;th&gt;适用场景&lt;/th&gt;
      &lt;th&gt;示例&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;—&amp;gt;&lt;/td&gt;
      &lt;td&gt;继承（泛化）&lt;/td&gt;
      &lt;td&gt;子类 –&amp;gt; 父类&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;–&amp;gt;&lt;/td&gt;
      &lt;td&gt;关联&lt;/td&gt;
      &lt;td&gt;类之间的普通依赖&lt;/td&gt;
      &lt;td&gt;学生 –&amp;gt; 课程&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;*–&lt;/td&gt;
      &lt;td&gt;聚合&lt;/td&gt;
      &lt;td&gt;整体包含部分，部分可独立存在&lt;/td&gt;
      &lt;td&gt;班级 *– 学生&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;o–&lt;/td&gt;
      &lt;td&gt;组合&lt;/td&gt;
      &lt;td&gt;整体包含部分，部分不可独立存在&lt;/td&gt;
      &lt;td&gt;人 o– 心脏&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;..&amp;gt;&lt;/td&gt;
      &lt;td&gt;实现&lt;/td&gt;
      &lt;td&gt;类实现接口&lt;/td&gt;
      &lt;td&gt;实现类..&amp;gt; 接口&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;3-完整示例-3&quot;&gt;3. 完整示例&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;classDiagram
Class01 &amp;lt;|-- AveryLongClass : Cool
&amp;lt;&amp;lt;Interface&amp;gt;&amp;gt; Class01
Class09 --&amp;gt; C2 : Where am I?
Class09 --* C3
Class09 --|&amp;gt; Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
class Class10 {
  &amp;lt;&amp;lt;service&amp;gt;&amp;gt;
  int id
  size()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;六状态图state-diagram&quot;&gt;六、状态图（State Diagram）&lt;/h2&gt;
&lt;p&gt;用于描述对象的状态变化过程，展示状态之间的转换条件和触发事件，适合生命周期、状态机设计（如订单状态、设备状态）。&lt;/p&gt;

&lt;h3 id=&quot;1-基础规则-5&quot;&gt;1. 基础规则&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;图表开头用stateDiagram（或stateDiagram-v2，新版更友好）；&lt;/li&gt;
  &lt;li&gt;用[*]表示初始状态 / 结束状态；&lt;/li&gt;
  &lt;li&gt;状态之间用–&amp;gt; 连接，可添加触发条件 / 事件。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-完整示例-1&quot;&gt;2. 完整示例&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;stateDiagram-v2
    [*] --&amp;gt; 待支付
    待支付 --&amp;gt; 待发货 : 支付成功
    待支付 --&amp;gt; 已取消 : 用户取消/超时未支付
    待发货 --&amp;gt; 待收货 : 商家发货
    待收货 --&amp;gt; 已完成 : 用户确认收货
    待收货 --&amp;gt; 售后中 : 用户申请退款/换货
    售后中 --&amp;gt; 已完成 : 售后处理结束
    已取消 --&amp;gt; [*]
    已完成 --&amp;gt; [*]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;七mermaid-通用使用技巧&quot;&gt;七、Mermaid 通用使用技巧&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;代码块包裹：所有语法必须写在mermaid和 ``` 之间，平台才会渲染；&lt;/li&gt;
  &lt;li&gt;注释：用%%添加注释，注释内容不会渲染；&lt;/li&gt;
  &lt;li&gt;空格 / 换行：语法对空格、换行不敏感，可合理换行让代码更易读；&lt;/li&gt;
  &lt;li&gt;别名 / 唯一标识：节点、参与者的标识必须唯一，建议用简单英文 / 拼音，避免中文；&lt;/li&gt;
  &lt;li&gt;布局控制：流程图中子图可通过subgraph 子图名 [方向]指定独立方向；调整子图位置可通过「定义顺序 + 隐形连接（-.-&amp;gt;/—）」引导排版；&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>这一年AI编程总结</title>
   <link href="https://blog.cyeam.com/ai/2026/03/06/ai-coding-summary"/>
   <updated>2026-03-06T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2026/03/06/ai-coding-summary</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#总结&quot; id=&quot;markdown-toc-总结&quot;&gt;总结&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#ai使用流程&quot; id=&quot;markdown-toc-ai使用流程&quot;&gt;AI使用流程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#药不能停&quot; id=&quot;markdown-toc-药不能停&quot;&gt;药不能停&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;近一年我在很多场景都使用AI辅助开发，我最明显的想法是确实能够通过AI高效的解决了。先后用过这些：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;TRAE：基于vscode开发，所以没啥上手成本，对于整个项目理解能力一般，编码调整较大很难基于Diff判断代码修改是否符合预期。我一般当做编辑器使用，顺便使用自动提示功能。另外代码提交时自动生成commit message这个好评。&lt;/li&gt;
  &lt;li&gt;豆包：知识面较广知道的东西比较多，但编码水平稍逊色于DeepSeek；&lt;/li&gt;
  &lt;li&gt;DeepSeek：编码质量最好的一款，不知道为啥没有PC端，而且TRAE也支持开源的DeepSeek，说明差异主要在于调度大模型的流程上&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI编码近一年发展速度还是比较明显的，从之前代码都跑不通到今天能提供比较完整的样例。尤其是前端领域，我这种小白已经在AI的辅助下搭建了非常多工具，如果你之前也留意过我的网站，这一年已经实现了游戏、记事本这种复杂的工具了，整个站点的复杂度、精致程度向前迈了一大步。
几点经验：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;AI目前对于大型项目、上下文理解还不足，但是做单体页面、函数开发能力非常强，使用的时候也可以注意下；&lt;/li&gt;
  &lt;li&gt;人和AI是经理和指挥者的关系，整个开发过程中的思路、逻辑输出、问题排查更多得依赖人来分析以及给出思路；&lt;/li&gt;
  &lt;li&gt;AI的优势是知识面广，经常能给一些意想不到的解法，这方面是远胜于搜索引擎的。比如前端样式方面，它会给我讲解BootStrap响应式断点，以及匹配优先级；&lt;/li&gt;
  &lt;li&gt;CR的效果特别好，未使用变量、疑似Panic、逻辑错误都能指出来。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这就是AI教我BootStrap响应式断点知识🐶：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;断点前缀&lt;/th&gt;
      &lt;th&gt;对应屏幕宽度&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;sm-&lt;/td&gt;
      &lt;td&gt;≥576px&lt;/td&gt;
      &lt;td&gt;小屏（平板/手机横屏）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;md-&lt;/td&gt;
      &lt;td&gt;≥768px&lt;/td&gt;
      &lt;td&gt;中屏（平板/小屏 PC）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;lg-&lt;/td&gt;
      &lt;td&gt;≥992px&lt;/td&gt;
      &lt;td&gt;大屏（常规 PC）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;xl-&lt;/td&gt;
      &lt;td&gt;≥1200px&lt;/td&gt;
      &lt;td&gt;超大屏&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;ai使用流程&quot;&gt;AI使用流程&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;先聊一些具体且关键的问题
    &lt;ol&gt;
      &lt;li&gt;当AI能准确回答问题，执行下一步&lt;/li&gt;
      &lt;li&gt;当AI不能准确回答问题，指出错误纠正它&lt;/li&gt;
      &lt;li&gt;如果纠正还不行，换一个Agent试试。。。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;前一步成功意味着基础的上下文和任务说清楚了，继续迭代提出新要求
    &lt;ol&gt;
      &lt;li&gt;在这个过程中我需要验证每次返回的结果，指出问题&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;药不能停&quot;&gt;药不能停&lt;/h3&gt;

&lt;p&gt;基于豆包的&lt;strong&gt;&lt;em&gt;专家&lt;/em&gt;&lt;/strong&gt;模式开发了提醒吃药工具，代码比较简单，发布在&lt;a href=&quot;https://github.com/mnhkahn/medicine&quot;&gt;GitHub&lt;/a&gt;。因为我也不会开发安卓，属于一步一步问的，比如这段代码应该放在哪个目录哪个文件，回答的都很靠谱。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn-app-icon2.pgyer.com/f/0/1/7/c/f017c08f211fff9d8cd3c2fd3742090c?x-oss-process=image/resize,m_lfit,h_120,w_120/format,jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;
发布到了&lt;a href=&quot;https://www.pgyer.com/yaobunengting&quot;&gt;蒲公英&lt;/a&gt;，大家可以尝试使用。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>资损常见治理思路</title>
   <link href="https://blog.cyeam.com/monitor/2025/10/13/assets-loss"/>
   <updated>2025-10-13T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/monitor/2025/10/13/assets-loss</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#治理思路&quot; id=&quot;markdown-toc-治理思路&quot;&gt;治理思路&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#评估完整性&quot; id=&quot;markdown-toc-评估完整性&quot;&gt;评估完整性&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;维度&lt;/th&gt;
      &lt;th&gt;一致性&lt;/th&gt;
      &lt;th&gt;卡单&lt;/th&gt;
      &lt;th&gt;幂等（重复执行无副作用）&lt;/th&gt;
      &lt;th&gt;合理性&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;核心定义&lt;/td&gt;
      &lt;td&gt;多数据源 / 存储节点间的数据 “最终一致” 或 “强一致”，重点关注状态字段不一致问题&lt;/td&gt;
      &lt;td&gt;业务流程在关键节点停滞（未按预期向下流转），表现为 “状态卡住”&lt;/td&gt;
      &lt;td&gt;同一请求（或重复请求）多次执行，产生的业务结果与 “仅执行一次” 完全一致，避免重复扣钱、重复发货等副作用&lt;/td&gt;
      &lt;td&gt;业务逻辑符合业务规则、赔付原则、财务合规&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;典型场景&lt;/td&gt;
      &lt;td&gt;稳定性问题：1. 强一致性：分布式事务；2. 最终一致性：MQ、重试；3. 跨数据源数据不一致；4. 跨服务数据同步&lt;/td&gt;
      &lt;td&gt;业务类问题：1. 任务下发失败；2. 回调丢失；3. 资源锁死（如分布式锁未释放导致流程阻塞）；&lt;/td&gt;
      &lt;td&gt;技术问题：1. 重复支付（用户多次点击支付按钮导致重复扣款）；2. MQ 消息重复消费（如重试机制导致消息重复投递）；3. 接口重试（如前端超时重试、网关重试导致重复执行业务逻辑）；&lt;/td&gt;
      &lt;td&gt;业务、技术问题：1. 边界值违规（如零空负）；2. 财务合规违规（极大、极小）；3. 逻辑冲突（如 “已取消订单” 仍能发起退款）；4. 规则不合理，增值服务开通了错误的的国家&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;核心工作内容&lt;/td&gt;
      &lt;td&gt;1. 一致性方案设计；2. 不一致监控；3. 不一致数据修复（修复接口可重试）&lt;/td&gt;
      &lt;td&gt;1. SLA 计算；2. 流程链路追踪（埋点监控关键节点状态流转）；3. 阻塞原因洞察、归因；4. 异常单据修复&lt;/td&gt;
      &lt;td&gt;1. 幂等标识设计；2. 悲观锁；3. DB 幂等校验；4. 不幂等数据处理，迁移新库&lt;/td&gt;
      &lt;td&gt;1. 规则线上化；2. 边界值测试（覆盖极端场景，如 0 金额、超大数据量）；3. 灰度；4. 仿真；&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;影响面&lt;/td&gt;
      &lt;td&gt;涉及单量大，修复方式简单&lt;/td&gt;
      &lt;td&gt;影响用户体验&lt;/td&gt;
      &lt;td&gt;一旦下游无法兜底拦截会产生直接资损，优化成本高&lt;/td&gt;
      &lt;td&gt;大部分为 Bug，需要对业务有深刻理解，修数难度大&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;治理思路&quot;&gt;治理思路&lt;/h3&gt;

&lt;p&gt;推荐按照一致性、幂等、卡单、合理性的顺序进行治理。优先保证系统质量，再拔高监控解决细分场景的问题。&lt;/p&gt;

&lt;h3 id=&quot;评估完整性&quot;&gt;评估完整性&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;一致性：重点关注系统间数据一致性，监控系统间数据传输质量。需要列举核心系统调用来评估；&lt;/li&gt;
  &lt;li&gt;幂等：评估系统是否对重复请求有正确的处理机制，避免重复执行导致的副作用。需要列举核心单据来评估；&lt;/li&gt;
  &lt;li&gt;卡单：评估系统是否对关键节点的状态流转有监控和修复机制，避免业务流程停滞。需要通过单据核心状态&amp;amp;动作来评估；&lt;/li&gt;
  &lt;li&gt;合理性：评估系统是否符合业务规则、赔付原则、财务合规等要求，下钻到细分场景监控，监控代码 Bug。需要梳理下游使用的重点字段来评估，如订单金额、支付状态、退款金额等。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>用AI搭建一个数独游戏</title>
   <link href="https://blog.cyeam.com/ai/2025/06/22/sudoku"/>
   <updated>2025-06-22T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/06/22/sudoku</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#惊艳的开始基本框架&quot; id=&quot;markdown-toc-惊艳的开始基本框架&quot;&gt;惊艳的开始——基本框架&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#优化调整难度大样式唯一解&quot; id=&quot;markdown-toc-优化调整难度大样式唯一解&quot;&gt;优化调整难度大——样式、唯一解&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#交互优化无法指望提示关联框标记&quot; id=&quot;markdown-toc-交互优化无法指望提示关联框标记&quot;&gt;交互优化无法指望——提示、关联框标记&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#最后总结&quot; id=&quot;markdown-toc-最后总结&quot;&gt;最后总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;说来惭愧，大学学习都就是游戏开发，但是自己也没做过什么游戏，最后还是需要AI来帮助。&lt;/p&gt;

&lt;h3 id=&quot;惊艳的开始基本框架&quot;&gt;惊艳的开始——基本框架&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;帮我生成一个数独网页游戏，支持简单、困难、专家三种模式，能够按照模式随机生成新游戏，支持通过cookie记录游戏进度，支持提醒
&lt;a href=&quot;https://www.doubao.com/thread/we62248b80277869c&quot;&gt;执行过程&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;一开始的试用非常惊艳，一句话就直接实现了，成品效果非常成熟。&lt;/p&gt;

&lt;h3 id=&quot;优化调整难度大样式唯一解&quot;&gt;优化调整难度大——样式、唯一解&lt;/h3&gt;

&lt;p&gt;游戏我也试玩了几把，可以跑起来。但是我发现一些问题：比如样式有点问题，格子之间间隔太大；比如题目有问题，解不唯一。&lt;strong&gt;这个时候直接写提示词让AI调整代码，难度就很大。&lt;/strong&gt;我先后试了豆包、Trae都太行，而且会越改越乱。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;最后没有办法，自己读代码理解逻辑，分析如何调整。&lt;/strong&gt;这个时候你会发现，你分析到一个优化点，就新开一个回话，告诉他要解唯一，他就会生成一个新的代码，新老代码需要自己来融合。我这里用了DeepSeek的逻辑，豆包的思路也差不多，都是回溯法，但是跑出来运行结果不对。&lt;/p&gt;

&lt;h3 id=&quot;交互优化无法指望提示关联框标记&quot;&gt;交互优化无法指望——提示、关联框标记&lt;/h3&gt;

&lt;p&gt;除了基本功能，后面还想升级页面UI：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;选中一个数字后，高亮潜在冲突的表格；&lt;/li&gt;
  &lt;li&gt;自动填充可能的候选值；&lt;/li&gt;
  &lt;li&gt;某个数字用完后，置灰；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些功能就没有用AI了，实现出来的基本就不可用，要么是我找的现成的例子，要么是自己实现的（当让我也不会写前端，请教了AI）。&lt;/p&gt;

&lt;h3 id=&quot;最后总结&quot;&gt;最后总结&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;AI的优势在于知识面广，啥也知道，但是不一定对，自己要能够识别；&lt;/li&gt;
  &lt;li&gt;AI 不擅长改写，擅长从头搭建一个简单实现，所以Trae我自己用起来效果也一般，因为本身Trae都是要修改现有逻辑；
问题识别到了，要修改的内容也就知道，修改剔除逻辑和验证逻辑；&lt;/li&gt;
  &lt;li&gt;我也在怀疑是不是我不会写提示词，或者用法有问题，这方面谁可以指导下？&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>LangSmith介绍以及Golang的介绍方法</title>
   <link href="https://blog.cyeam.com/ai/2025/05/29/langchain"/>
   <updated>2025-05-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/05/29/langchain</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#langsmith&quot; id=&quot;markdown-toc-langsmith&quot;&gt;LangSmith&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#效果&quot; id=&quot;markdown-toc-效果&quot;&gt;效果&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#python-接入&quot; id=&quot;markdown-toc-python-接入&quot;&gt;Python 接入&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#golang-接入&quot; id=&quot;markdown-toc-golang-接入&quot;&gt;Golang 接入&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#初始化&quot; id=&quot;markdown-toc-初始化&quot;&gt;初始化&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#写入请求和响应&quot; id=&quot;markdown-toc-写入请求和响应&quot;&gt;写入请求和响应&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;langsmith&quot;&gt;LangSmith&lt;/h3&gt;

&lt;p&gt;LangSmith是一个用于管理和监控机器学习模型生命周期的平台，它可以帮助开发者更好地监控和优化他们的模型。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可观察性。可以支持分析、追踪，并且可以基于这些数据配置看板和报警；&lt;/li&gt;
  &lt;li&gt;评估。评估您的应用在生产流量中的表现。&lt;/li&gt;
  &lt;li&gt;Prompt 工程。通过版本控制迭代升级你的提示词。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;特性也可以看下面这个图，完整资料移步&lt;a href=&quot;https://docs.smith.langchain.com/?_gl=1*13mwwef*_ga*MTM1NDY2MTAwMy4xNzQ4MDc4MjY5*_ga_47WX3HKKY2*czE3NDg0ODAwNDEkbzkkZzEkdDE3NDg0ODAyODQkajYwJGwwJGgw&quot;&gt;官网&lt;/a&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1748528362/ls-diagram-5be7dd68b135f573a7b0e163692e6800_t0heck.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;效果&quot;&gt;效果&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1748528659/langsmith_ece94p.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;看到这个效果基本就知道LangSmith的强大了，它能完整记录跟LLM交互的过程以及相应内容。&lt;/p&gt;

&lt;h4 id=&quot;python-接入&quot;&gt;Python 接入&lt;/h4&gt;

&lt;p&gt;Python 默认就可以打开，不需要额外的代码，你可以直接设定好环境变量或者通过如下代码配置。&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LANGCHAIN_TRACING_V2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LANGSMITH_ENDPOINT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://api.smith.langchain.com&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LANGSMITH_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;你申请到的Key&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LANGSMITH_PROJECT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;你创建的项目名称&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;golang-接入&quot;&gt;Golang 接入&lt;/h3&gt;

&lt;p&gt;Golang 本身langchaingo包不支持，我看ISSUE里作者也提到他没想到如何像Python一样自动记录，我目前找到一个十来个Star的包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/devalexandre/langsmithgo&lt;/code&gt;。&lt;/p&gt;

&lt;h4 id=&quot;初始化&quot;&gt;初始化&lt;/h4&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smith&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;langsmithgo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Client&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Setenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LANGSMITH_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;你申请到的Key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Setenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LANGSMITH_PROJECT_NAME&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;你创建的项目名称&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;smith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;langsmithgo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;langsmithgo.NewClient: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;写入请求和响应&quot;&gt;写入请求和响应&lt;/h4&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;context&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;

	&lt;span class=&quot;s&quot;&gt;&quot;github.com/devalexandre/langsmithgo&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/google/uuid&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/mnhkahn/gogogo/logger&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/tmc/langchaingo/llms&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SmithStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;runId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;langsmithgo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RunPayload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;RunID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;runId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;&quot;langsmithgo-chain&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;RunType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;langsmithgo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Inputs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
			&lt;span class=&quot;s&quot;&gt;&quot;prompt&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;smith.Run: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runId&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SmithEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PatchRun&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;langsmithgo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RunPayload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Outputs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
			&lt;span class=&quot;s&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;smith.PatchRun: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>通过OpenAI对接LLM——搭建Geek头条</title>
   <link href="https://blog.cyeam.com/ai/2025/05/21/openai"/>
   <updated>2025-05-21T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/05/21/openai</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#ai-agent-与-llm-关系&quot; id=&quot;markdown-toc-ai-agent-与-llm-关系&quot;&gt;AI Agent 与 LLM 关系&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#一般架构&quot; id=&quot;markdown-toc-一般架构&quot;&gt;一般架构&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mcp-中-toolspromptsresources的区别&quot; id=&quot;markdown-toc-mcp-中-toolspromptsresources的区别&quot;&gt;MCP 中 Tools、Prompts、Resources的区别&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#流程&quot; id=&quot;markdown-toc-流程&quot;&gt;流程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#geek-头条实现&quot; id=&quot;markdown-toc-geek-头条实现&quot;&gt;Geek 头条实现&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#openai-对接-llm&quot; id=&quot;markdown-toc-openai-对接-llm&quot;&gt;OpenAI 对接 LLM&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#模型申请&quot; id=&quot;markdown-toc-模型申请&quot;&gt;模型申请&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#初始化-openai&quot; id=&quot;markdown-toc-初始化-openai&quot;&gt;初始化 OpenAI&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#获取新闻&quot; id=&quot;markdown-toc-获取新闻&quot;&gt;获取新闻&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#获取提示词&quot; id=&quot;markdown-toc-获取提示词&quot;&gt;获取提示词&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#调用llm&quot; id=&quot;markdown-toc-调用llm&quot;&gt;调用LLM&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#finish_reason&quot; id=&quot;markdown-toc-finish_reason&quot;&gt;finish_reason&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#渲染&quot; id=&quot;markdown-toc-渲染&quot;&gt;渲染&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#最后&quot; id=&quot;markdown-toc-最后&quot;&gt;最后&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;ai-agent-与-llm-关系&quot;&gt;AI Agent 与 LLM 关系&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1747838637/a4f1ca3e01124cfe866c7233c185fc54_tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAg56a75byA5Zyw55CD6KGo6Z2iXzk5_q75_esisk6.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;LLM 扮演了 Agent 的 “大脑”，在 Agent 这个系统中提供推理、规划等能力。——&lt;a href=&quot;https://juejin.cn/post/7481220690671632447&quot;&gt;《LLM、Prompt、AI Agent、RAG… 一网打尽大模型热门概念》&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;一般架构&quot;&gt;一般架构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1747838958/whiteboard_exported_image_wggwfe.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;AI Agent 中 LLM 是最核心的部分，实际工程应用中，还需要建设好周边工具，提供「作业」、「记忆」等能力，结合 LLM 一套建设完整的智能体。&lt;/p&gt;

&lt;h4 id=&quot;mcp-中-toolspromptsresources的区别&quot;&gt;MCP 中 Tools、Prompts、Resources的区别&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Primitive&lt;/th&gt;
      &lt;th&gt;Control&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Prompts&lt;/td&gt;
      &lt;td&gt;User-controlledPrompts&lt;br /&gt;像是提前配置好的，需要指定后执行，不需要每次填写&lt;/td&gt;
      &lt;td&gt;Interactive templates invoked by user choice&lt;/td&gt;
      &lt;td&gt;Slash commands, menu options&lt;br /&gt;/prompts&lt;br /&gt;/prompt&lt;br /&gt;generate_search_prompt topic=history num_papers=2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Resources&lt;/td&gt;
      &lt;td&gt;Application-controlled应用选择怎么用，如果需要LLM使用还是需要提供Tools&lt;/td&gt;
      &lt;td&gt;Contextual data attached and managed by the client&lt;/td&gt;
      &lt;td&gt;File contents, git history&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tools&lt;/td&gt;
      &lt;td&gt;Model-controlled&lt;/td&gt;
      &lt;td&gt;Functions exposed to the LLM to take actions&lt;/td&gt;
      &lt;td&gt;API POST requests, file writing&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;流程&quot;&gt;流程&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1747839257/whiteboard_exported_image-2_i1bbut.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;geek-头条实现&quot;&gt;Geek 头条实现&lt;/h3&gt;

&lt;p&gt;Geek 头条的流程：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tech_news&lt;/code&gt;工具，获取最新科技新闻；&lt;/li&gt;
  &lt;li&gt;调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tech_news_prompt&lt;/code&gt;工具生成提示词；&lt;/li&gt;
  &lt;li&gt;调用LLM，生成总结。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;openai-对接-llm&quot;&gt;OpenAI 对接 LLM&lt;/h4&gt;

&lt;p&gt;OpenAI除了做模型，也提供了标准API，我们可以直接调用API来调用LLM。&lt;/p&gt;

&lt;h5 id=&quot;模型申请&quot;&gt;模型申请&lt;/h5&gt;

&lt;p&gt;我申请的是&lt;a href=&quot;https://cloud.siliconflow.cn/&quot;&gt;siliconflow&lt;/a&gt;的模型，它支持免费使用 Qwen/Qwen2-7B-Instruct 和 deepseek-ai/DeepSeek-R1-Distill-Qwen-7B 等多种模型。&lt;/p&gt;

&lt;p&gt;需要用到的包：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get github.com/openai/openai-go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;初始化-openai&quot;&gt;初始化 OpenAI&lt;/h5&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;aiClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithBaseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://api.siliconflow.cn/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithAPIKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ApiKey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// defaults to os.LookupEnv(&quot;OPENAI_API_KEY&quot;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;获取新闻&quot;&gt;获取新闻&lt;/h5&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;callToolReq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;callToolReq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tech_news&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;callResp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callToolReq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;获取提示词&quot;&gt;获取提示词&lt;/h5&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;getPromptReq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetPromptRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;getPromptReq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tech_news_prompt&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;getPromptReq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arguments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;news&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newsLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://github.com/ThinkInAIXYZ/deepchat/releases/tag/v0.1.1,https://tonybai.com/2025/05/20/post-quantum-cryptography-in-go/,https://tonybai.com/2025/05/19/shardedvalue-per-cpu-proposal/ [I] 2025/05/21 21:20:35 new_agent.go:67: https://github.com/ThinkInAIXYZ/deepchat/releases/tag/v0.1.1,https://tonybai.com/2025/05/20/post-quantum-cryptography-in-go/,https://tonybai.com/2025/05/19/shardedvalue-per-cpu-proposal/
分别总结这些网页，使用网页标题，不需要做对比，并为每个标题附带上文档链接。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;调用llm&quot;&gt;调用LLM&lt;/h5&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ChatCompletionNewParams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ChatCompletionMessageParamUnion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;promptMsg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 提示词，UserMessage代表说用户说的话&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;deepseek-ai/DeepSeek-R1-Distill-Qwen-7B&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 选择的模型&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Seed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openai&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chatCompletion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;aiClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Chat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Completions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;User（用户）：代表与 AI 进行对话的人类。是对话的发起者和推动者，通过提出问题、表达需求或提供指令来引导对话的方向。&lt;/li&gt;
  &lt;li&gt;Assistant（助手）：代表 AI 本身，是对用户进行回应的角色。根据系统设定和用户输入生成相应的回复，为用户提供信息、回答问题、完成任务或提供建议等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;finish_reason&quot;&gt;finish_reason&lt;/h5&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chatCompletion.Choices[0].FinishReason&lt;/code&gt;字段保存了 LLM 停止&amp;amp;返回客户的的原因。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;原因标识&lt;/th&gt;
      &lt;th&gt;含义解释&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;stop&lt;/td&gt;
      &lt;td&gt;模型到达自然停止点或遇到提供的停止序列&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;length&lt;/td&gt;
      &lt;td&gt;达到请求中指定的最大 token 数量&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;content_filter&lt;/td&gt;
      &lt;td&gt;内容因内容过滤器标记被省略&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;tool_calls&lt;/td&gt;
      &lt;td&gt;模型调用了工具&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;function_call（deprecated）&lt;/td&gt;
      &lt;td&gt;模型调用了函数（已弃用 ）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h5 id=&quot;渲染&quot;&gt;渲染&lt;/h5&gt;

&lt;p&gt;因为生成的内容是markdown格式，这里用到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/gomarkdown/markdown&lt;/code&gt;包来将markdown渲染成html，&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MDToHTML&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;md&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// create markdown parser with extensions&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;extensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CommonExtensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AutoHeadingIDs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoEmptyLineBeforeBlock&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewWithExtensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;md&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// create HTML renderer with extensions&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;htmlFlags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CommonFlags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HrefTargetBlank&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RendererOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;htmlFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;renderer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewRenderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;renderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;默认html只能按照文本展示&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;，&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;需要用template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HTML用作安全渲染&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;。&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;```go
res[&quot;news&quot;] = template.HTML(string(views))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;最后&quot;&gt;最后&lt;/h3&gt;

&lt;p&gt;效果查询：&lt;a href=&quot;https://www.cyeam.com/geek&quot;&gt;Geek 头条&lt;/a&gt;。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;为什么是用markdown而不是更结构化的比如json呢？尝试了很多提示词，并不能严格按照期望的格式生成json，经常会有自由发挥的字段，总结的内容也很离谱，还不如用简单的提示词效果好。另外猜测模型本身也是基于markdown训练的，所以对json支持并不友好。&lt;/li&gt;
  &lt;li&gt;关于流程，目前我了解到的是也有流程框架&lt;a href=&quot;https://github.com/tmc/langchaingo/&quot;&gt;LangChainGo&lt;/a&gt;，它和MCP之间也有&lt;a href=&quot;https://github.com/i2y/langchaingo-mcp-adapter&quot;&gt;Adaptar&lt;/a&gt;来做适配对接，接下来详细学学看。&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Git Comments 提交规范</title>
   <link href="https://blog.cyeam.com/server/2025/05/14/git-comments"/>
   <updated>2025-05-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/server/2025/05/14/git-comments</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-提交信息格式&quot; id=&quot;markdown-toc-1-提交信息格式&quot;&gt;&lt;strong&gt;1. 提交信息格式&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-常用提交类型&quot; id=&quot;markdown-toc-2-常用提交类型&quot;&gt;&lt;strong&gt;2. 常用提交类型&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-提交信息示例&quot; id=&quot;markdown-toc-3-提交信息示例&quot;&gt;&lt;strong&gt;3. 提交信息示例&lt;/strong&gt;&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#示例-1新增功能&quot; id=&quot;markdown-toc-示例-1新增功能&quot;&gt;示例 1：新增功能&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#示例-2修复-bug&quot; id=&quot;markdown-toc-示例-2修复-bug&quot;&gt;示例 2：修复 bug&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#示例-3文档更新&quot; id=&quot;markdown-toc-示例-3文档更新&quot;&gt;示例 3：文档更新&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;Git 代码提交规范对于团队协作和项目维护至关重要，它能够确保提交历史清晰、可追溯，便于后续的代码审查、问题追踪以及版本发布。以下是一些常见的 Git 代码提交规范和最佳实践：&lt;/p&gt;

&lt;h3 id=&quot;1-提交信息格式&quot;&gt;&lt;strong&gt;1. 提交信息格式&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;一个完整的提交信息通常包含三个部分：&lt;strong&gt;标题（Header）&lt;/strong&gt;、&lt;strong&gt;正文（Body）&lt;/strong&gt; 和 &lt;strong&gt;页脚（Footer）&lt;/strong&gt;，各部分之间用空行分隔。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;类型&amp;gt;(&amp;lt;范围&amp;gt;): &amp;lt;简短描述&amp;gt;
// 空行
[可选] 详细描述
// 空行
[可选] 关联的 Issue 或 Breaking Change
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;标题（Header）&lt;/strong&gt;：
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;类型（Type）&lt;/strong&gt;：必须，描述提交的类型（如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feat&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docs&lt;/code&gt; 等）。&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;范围（Scope）&lt;/strong&gt;：可选，说明修改的具体模块或功能（如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui&lt;/code&gt; 等）。&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;简短描述（Description）&lt;/strong&gt;：必须，用简洁的语言概括变更内容（不超过 50 个字符，首字母小写，结尾不加标点）。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;正文（Body）&lt;/strong&gt;：
    &lt;ul&gt;
      &lt;li&gt;可选，详细说明修改的动机、背景和实现细节，每行不超过 72 个字符。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;页脚（Footer）&lt;/strong&gt;：
    &lt;ul&gt;
      &lt;li&gt;可选，用于关联 Issue（如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fixes #123&lt;/code&gt;）或声明重大变更（如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BREAKING CHANGE: ...&lt;/code&gt;）。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-常用提交类型&quot;&gt;&lt;strong&gt;2. 常用提交类型&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;以下是常见的提交类型及其含义：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;类型&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feat&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;新增功能&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fix&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;修复 bug&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docs&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;文档更新（如 README、注释等）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;style&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;代码格式调整（不影响功能，如空格、缩进）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refactor&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;代码重构（既不修复 bug 也不添加功能）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;添加或修改测试用例&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chore&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;构建流程或辅助工具的变动（如配置文件）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;性能优化&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;CI/CD 配置或脚本更新&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;revert&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;回滚之前的提交&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;3-提交信息示例&quot;&gt;&lt;strong&gt;3. 提交信息示例&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;示例-1新增功能&quot;&gt;示例 1：新增功能&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;feat(user): 添加用户注册验证码功能

新增了验证码生成、发送和验证逻辑，
使用 Redis 存储验证码，有效期 5 分钟。

Fixes #456
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;示例-2修复-bug&quot;&gt;示例 2：修复 bug&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fix(api): 修复用户登录时密码加密错误

由于密码加密算法使用错误，导致部分用户无法登录。
现已更正为使用 bcrypt 进行加密。

Fixes #789
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;示例-3文档更新&quot;&gt;示例 3：文档更新&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docs: 更新 API 文档中的用户接口说明

补充了用户注册、登录和获取信息接口的参数说明，
添加了错误码对照表。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;遵循这些规范可以让你的 Git 提交历史更加清晰、专业，提高团队协作效率。根据项目需求，你可以在此基础上调整或扩展规范。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>MCP Resource学习，用Go搭建一个Demo</title>
   <link href="https://blog.cyeam.com/ai/2025/05/05/mcp-resource"/>
   <updated>2025-05-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/05/05/mcp-resource</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#概述&quot; id=&quot;markdown-toc-概述&quot;&gt;概述&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#协议消息&quot; id=&quot;markdown-toc-协议消息&quot;&gt;协议消息&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#数据流&quot; id=&quot;markdown-toc-数据流&quot;&gt;数据流&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#资源类型&quot; id=&quot;markdown-toc-资源类型&quot;&gt;资源类型&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#资源定义&quot; id=&quot;markdown-toc-资源定义&quot;&gt;资源定义&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#uri-scheme&quot; id=&quot;markdown-toc-uri-scheme&quot;&gt;URI Scheme&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#错误码&quot; id=&quot;markdown-toc-错误码&quot;&gt;错误码&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#go-实现&quot; id=&quot;markdown-toc-go-实现&quot;&gt;Go 实现&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#测试工具&quot; id=&quot;markdown-toc-测试工具&quot;&gt;测试工具&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;概述&quot;&gt;概述&lt;/h3&gt;
&lt;p&gt;MCP 协议提供了标准协议来让服务端向客户端暴露资源内容，例如文件、数据库，每个资源使用唯一标识&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI&lt;/code&gt;来区分。&lt;/p&gt;

&lt;p&gt;本文介绍最新版本&lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-03-26/server/resources#https%3A%2F%2F&quot;&gt;2025-03-26&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;协议消息&quot;&gt;协议消息&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Listing Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;客户端发送&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources/list&lt;/code&gt;请求查询可用的资源，该操作支持分页。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reading Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;发送&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources/read&lt;/code&gt;请求检索资源内容。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Resource Templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resources/templates/list&lt;/code&gt;允许向客户端暴露参数化资源。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;List Changed Notification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当资源发生变更时，服务器会发送&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notifications/resources/list_changed&lt;/code&gt;通知。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Subscriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;协议支持订阅资源变更，可以支持订阅指定资源当发生变更时会收到通知。&lt;/p&gt;

&lt;h3 id=&quot;数据流&quot;&gt;数据流&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1746441901/8addd27e-4e07-4d59-a5b3-d5dd8a25f065.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;资源类型&quot;&gt;资源类型&lt;/h3&gt;

&lt;h4 id=&quot;资源定义&quot;&gt;资源定义&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;uri: 资源的唯一标识符&lt;/li&gt;
  &lt;li&gt;name: 资源的名称&lt;/li&gt;
  &lt;li&gt;description: 【可选】资源描述&lt;/li&gt;
  &lt;li&gt;mimeType: 【可选】资源的MIME类型，常见的有：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text/plain&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text/html&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image/jpeg&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image/png&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/json&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/pdf&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/octet-stream&lt;/code&gt;等&lt;/li&gt;
  &lt;li&gt;size: 【可选】资源大小，单位为字节&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;uri-scheme&quot;&gt;URI Scheme&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;https://&lt;/li&gt;
  &lt;li&gt;file://&lt;/li&gt;
  &lt;li&gt;git://&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;错误码&quot;&gt;错误码&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;资源不存在: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-32002&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;内部错误：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-32603&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;go-实现&quot;&gt;Go 实现&lt;/h3&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerAllGoResources&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RegisterResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;MimeType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resourceHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadResourceRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadResourceResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Link&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;URI&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewReadResourceResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ResourceContents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextResourceContents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;n&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
					&lt;span class=&quot;n&quot;&gt;MimeType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource not found&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;测试工具&quot;&gt;测试工具&lt;/h3&gt;

&lt;p&gt;MCP官方提供的&lt;a href=&quot;https://mcp-docs.cn/docs/tools/inspector&quot;&gt;Inspector&lt;/a&gt;，支持本地部署调试，启动命令：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx @modelcontextprotocol/inspector https://www.cyeam.com/sse
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1746452045/5d7de112-db70-44b6-8803-e11fe5fff54c.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>用Go基于stdio通信搭建本地MCP工具</title>
   <link href="https://blog.cyeam.com/ai/2025/04/29/mcp-stdio"/>
   <updated>2025-04-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/04/29/mcp-stdio</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#概述&quot; id=&quot;markdown-toc-概述&quot;&gt;概述&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#实现&quot; id=&quot;markdown-toc-实现&quot;&gt;实现&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#解析env&quot; id=&quot;markdown-toc-解析env&quot;&gt;解析Env&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#返回处理&quot; id=&quot;markdown-toc-返回处理&quot;&gt;返回处理&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#部署&quot; id=&quot;markdown-toc-部署&quot;&gt;部署&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#环境变量&quot; id=&quot;markdown-toc-环境变量&quot;&gt;环境变量&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#运行步骤&quot; id=&quot;markdown-toc-运行步骤&quot;&gt;运行步骤&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#测试代码&quot; id=&quot;markdown-toc-测试代码&quot;&gt;测试代码&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h2 id=&quot;概述&quot;&gt;概述&lt;/h2&gt;
&lt;p&gt;本文将基于Go语言开发的MCP（Multi - Cloud Processing）服务器，主要功能是将文件上传到Cloudinary云存储服务。服务器通过MCP协议接收文件路径请求，并将对应文件上传到Cloudinary，最后返回文件的安全访问链接。&lt;/p&gt;

&lt;h2 id=&quot;实现&quot;&gt;实现&lt;/h2&gt;

&lt;p&gt;本身SSE和Stdio的实现方式区别不大，只需要将transport改成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NewStdioServerTransport&lt;/code&gt;。上传逻辑使用包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/cloudinary/cloudinary-go/v2&lt;/code&gt;。详细使用方式参考&lt;a href=&quot;https://cloudinary.com/documentation/go_quick_start&quot;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;解析env&quot;&gt;解析Env&lt;/h3&gt;

&lt;p&gt;由于是命令行调用，参数需要通过环境变量解析获取。&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cloud&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;cloud&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;key&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;secret&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;返回处理&quot;&gt;返回处理&lt;/h3&gt;

&lt;p&gt;本次场景使用文本格式返回，还支持图片、音频等。&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;部署&quot;&gt;部署&lt;/h2&gt;
&lt;h3 id=&quot;环境变量&quot;&gt;环境变量&lt;/h3&gt;
&lt;p&gt;运行项目前，需要设置以下环境变量：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud&lt;/code&gt;: Cloudinary的云名称。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key&lt;/code&gt;: Cloudinary的API密钥。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secret&lt;/code&gt;: Cloudinary的API密钥密码。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;密钥从&lt;a href=&quot;https://console.cloudinary.com/settings/api-keys&quot;&gt;这里&lt;/a&gt;获取。&lt;/p&gt;

&lt;h3 id=&quot;运行步骤&quot;&gt;运行步骤&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;确保Go环境已正确安装（版本1.23.1及以上）。&lt;/li&gt;
  &lt;li&gt;设置所需的环境变量。&lt;/li&gt;
  &lt;li&gt;在项目根目录下执行以下命令运行项目：&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;gitee.com/cyeam/cloudinary_mcp@latest
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;image_upload&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;stdio&quot;&lt;/span&gt;,
      &lt;span class=&quot;s2&quot;&gt;&quot;command&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;cloudinary&quot;&lt;/span&gt;,
      &lt;span class=&quot;s2&quot;&gt;&quot;args&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;,
      &lt;span class=&quot;s2&quot;&gt;&quot;env&quot;&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;cloud&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;cyeam&quot;&lt;/span&gt;,
        &lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;,
        &lt;span class=&quot;s2&quot;&gt;&quot;secret&quot;&lt;/span&gt;: &lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;测试代码&quot;&gt;测试代码&lt;/h3&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;context&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/pkg&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;

	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/client&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/protocol&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/transport&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;transportClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewStdioClientTransport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cloudinary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithStdioClientOptionLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pkg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DebugLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithStdioClientOptionEnv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cloud=cyeam&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;key=key1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;secret=password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create transport client: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// Initialize MCP client&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transportClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create MCP client: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// Get available tools&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListTools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to list tools: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tool Name: %+v, Description: %s, Required: %+v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InputSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cloudinary&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;Arguments&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
					&lt;span class=&quot;s&quot;&gt;&quot;file_path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/Users/cyeam/Downloads/abc.jpg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to call tool: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tool Response: %+v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>如何用自己的http server搭建一个线上MCP服务？</title>
   <link href="https://blog.cyeam.com/ai/2025/04/28/mcp-server"/>
   <updated>2025-04-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/04/28/mcp-server</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#mcp-server-初始化&quot; id=&quot;markdown-toc-mcp-server-初始化&quot;&gt;MCP Server 初始化&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#超时设置&quot; id=&quot;markdown-toc-超时设置&quot;&gt;超时设置&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#代码调用&quot; id=&quot;markdown-toc-代码调用&quot;&gt;代码调用&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#用-trae-调试&quot; id=&quot;markdown-toc-用-trae-调试&quot;&gt;用 Trae 调试&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;前文讲解了MCP协议并使用Go搭建了一个简单的Demo，本文将介绍如何将其部署到线上。&lt;/p&gt;

&lt;h2 id=&quot;mcp-server-初始化&quot;&gt;MCP Server 初始化&lt;/h2&gt;

&lt;p&gt;和前面的Demo一样，需要初始化MCP Server，因为要集成到现有 HTTP 服务中，不能直接启动 Server 而是要把Handler注册到 app 中。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSseHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HandleSSE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse/message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSseHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HandleMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;app 包是我自研的框架，完整内容可以移步&lt;a href=&quot;http://github.com/mnhkahn/gogogo&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;完整代码：&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InitMCPServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// Create SSE transport server&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;transportServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSseHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewSSEServerTransportAndHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse/message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithSSEServerTransportAndHandlerOptionLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pkg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DebugLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// Initialize MCP server&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transportServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// Register time query tool&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;current_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Get current time for specified timezone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create tool: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RegisterTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleTimeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSseHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HandleSSE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse/message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newSseHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HandleMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;超时设置&quot;&gt;超时设置&lt;/h2&gt;

&lt;p&gt;由于是 SSE 协议，需要放开超时时间，否则会报错：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context deadline exceeded&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;需要使用没有超时时间的方式启动：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.ServeDefault(l)&lt;/code&gt;。不能设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadTimeout&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteTimeout&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;代码调用&quot;&gt;代码调用&lt;/h2&gt;

&lt;p&gt;实际测试的时候AI不一定靠谱，问多了就不真正请求了，所以用代码请求调试靠谱些。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;context&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;

    &lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/client&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/protocol&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/transport&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;// Create SSE transport client&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;transportClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewSSEClientTransport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://www.cyeam.com/sse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create transport client: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// Initialize MCP client&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transportClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create MCP client: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// Get available tools&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListTools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to list tools: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tool Name: %+v, Description: %s, Required: %+v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InputSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;current_time&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Arguments&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&quot;timezone&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Asia/Shanghai&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to call tool: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tool Response: %+v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;用-trae-调试&quot;&gt;用 Trae 调试&lt;/h2&gt;

&lt;p&gt;配置：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;current_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://www.cyeam.com/sse&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;命令：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;使用mcp服务告诉我北京时间&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;结果：
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1745852839/1753e5c1-0453-47e4-8202-85d51d05d8e5.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>validator 包使用方法</title>
   <link href="https://blog.cyeam.com/go/2025/04/25/go_validator"/>
   <updated>2025-04-25T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/go/2025/04/25/go_validator</id>
   <content type="html">&lt;hr /&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valiader&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/go-playground/validator/v10&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/stretchr/testify/assert&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;os&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;testing&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;validator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rules&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;NotNull&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;LargerThanZero&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;gt=0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;Enum&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;           &lt;span class=&quot;s&quot;&gt;&quot;required,oneof=1 2 3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestMain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RegisterStructValidationMapRules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Struct&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;NotNull&lt;/span&gt;        &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;LargerThanZero&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Enum&lt;/span&gt;           &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// https://pkg.go.dev/github.com/go-playground/validator/v10#section-readme&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestValidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// 正常值放过&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Struct&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NotNull&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;not null string&quot;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LargerThanZero&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// 异常值报错&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NotNull&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LargerThanZero&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;9&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>MCP 模型上下文协议初探，用Go快速构建一个 MCP Server</title>
   <link href="https://blog.cyeam.com/ai/2025/04/17/mcp_hello_world"/>
   <updated>2025-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ai/2025/04/17/mcp_hello_world</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#整体架构&quot; id=&quot;markdown-toc-整体架构&quot;&gt;整体架构&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#通信方式&quot; id=&quot;markdown-toc-通信方式&quot;&gt;通信方式&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#编码方式&quot; id=&quot;markdown-toc-编码方式&quot;&gt;编码方式&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#request&quot; id=&quot;markdown-toc-request&quot;&gt;Request&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#response&quot; id=&quot;markdown-toc-response&quot;&gt;Response&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#notifications&quot; id=&quot;markdown-toc-notifications&quot;&gt;Notifications&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#go语言实现&quot; id=&quot;markdown-toc-go语言实现&quot;&gt;Go语言实现&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#server&quot; id=&quot;markdown-toc-server&quot;&gt;Server&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#客户端&quot; id=&quot;markdown-toc-客户端&quot;&gt;客户端&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#最后&quot; id=&quot;markdown-toc-最后&quot;&gt;最后&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#扩展阅读&quot; id=&quot;markdown-toc-扩展阅读&quot;&gt;扩展阅读&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;Model Context Protocol (MCP)是一个开源协议，正如 USB-C 为将你的设备连接到各种外围设备和配件提供了一种标准化的方式一样，MCP 也为将人工智能模型连接到不同的数据源和工具提供了一种标准化的方式。大语言模型常常需要与数据和工具集成，而 MCP 能够提供：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;一份不断扩充的预构建集成列表，你的大语言模型可直接接入这些集成；&lt;/li&gt;
  &lt;li&gt;在不同大语言模型提供商和供应商之间切换的灵活性；&lt;/li&gt;
  &lt;li&gt;在你的基础设施内保障数据安全。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;整体架构&quot;&gt;整体架构&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;MCP Hosts：诸如 Claude 桌面应用程序、集成开发环境（IDE）或希望通过 MCP 访问数据的人工智能工具等程序。本文使用的是VSCode里面的插件Cline。&lt;/li&gt;
  &lt;li&gt;MCP Clients: 与服务器保持一对一连接的协议客户端。&lt;/li&gt;
  &lt;li&gt;MCP Servers：通过MCP协议提供的轻量级服务。&lt;/li&gt;
  &lt;li&gt;Local Data Sources：你的计算机上 MCP 服务器能够安全访问的文件、数据库和服务。&lt;/li&gt;
  &lt;li&gt;Remote Services：MCP 服务器可以连接的通过互联网可用的外部系统（例如，通过应用程序编程接口（API））。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;通信方式&quot;&gt;通信方式&lt;/h2&gt;

&lt;p&gt;MCP协议支持两种数据通信方式：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1744897827/640.webp_az9ha1.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;本地通信
    &lt;ul&gt;
      &lt;li&gt;对于本地进程，使用stdio传输方式，就是常见的进程输入输出；&lt;/li&gt;
      &lt;li&gt;对于同一机器上的通信而言效率较高；&lt;/li&gt;
      &lt;li&gt;进程管理简单&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;远程通信
    &lt;ul&gt;
      &lt;li&gt;对于需要 HTTP 兼容性的场景，使用服务器发送事件（SSE）技术；&lt;/li&gt;
      &lt;li&gt;需考虑包括身份验证和授权等方面的安全问题；&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;编码方式&quot;&gt;编码方式&lt;/h2&gt;

&lt;p&gt;为了灵活支持复杂命令，MCP协议使用JSON-RPC数据格式。&lt;/p&gt;

&lt;h3 id=&quot;request&quot;&gt;Request&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  jsonrpc: &quot;2.0&quot;;
  id: string | number;
  method: string;
  params?: {
    [key: string]: unknown;
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;请求必须包含一个字符串或整数类型的标识符（ID）。&lt;/li&gt;
  &lt;li&gt;与基础的 JSON-RPC 不同，该标识符不能为 null。&lt;/li&gt;
  &lt;li&gt;在同一会话中，请求者此前一定不能使用过该请求标识符。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;response&quot;&gt;Response&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  jsonrpc: &quot;2.0&quot;;
  id: string | number;
  result?: {
    [key: string]: unknown;
  }
  error?: {
    code: number;
    message: string;
    data?: unknown;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;响应必须包含与所对应请求相同的标识符（ID）。&lt;/li&gt;
  &lt;li&gt;响应进一步细分为成功结果或错误两类。必须设置结果或错误其中之一，响应绝不能同时设置两者。&lt;/li&gt;
  &lt;li&gt;结果可以遵循任何 JSON 对象结构，而错误至少必须包含一个错误代码和一条错误消息。&lt;/li&gt;
  &lt;li&gt;错误代码必须是整数。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;notifications&quot;&gt;Notifications&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  jsonrpc: &quot;2.0&quot;;
  method: string;
  params?: {
    [key: string]: unknown;
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通知是从客户端发送到服务器，或者从服务器发送到客户端的一种单向消息。接收方不能发送响应。&lt;/p&gt;

&lt;h2 id=&quot;go语言实现&quot;&gt;Go语言实现&lt;/h2&gt;

&lt;h3 id=&quot;server&quot;&gt;Server&lt;/h3&gt;

&lt;p&gt;代码类似于 HTTP 服务，也需要写Handler。例子里是在本地启动，日志等级为Debug方便观察响应流程。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;

	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/pkg&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/protocol&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/server&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/ThinkInAIXYZ/go-mcp/transport&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeRequest&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Timezone&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;timezone&quot; description:&quot;timezone&quot; required:&quot;true&quot;`&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// Use field tag to describe input schema&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// Create SSE transport server&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;transportServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewSSEServerTransport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;127.0.0.1:8080&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithSSEServerTransportOptionLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pkg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DebugLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create transport server: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// Initialize MCP server&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transportServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create MCP server: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// Register time query tool&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;current_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Get current time for specified timezone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create tool: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RegisterTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleTimeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// Start server&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Server failed to start: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleTimeRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeReq&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeRequest&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VerifyAndUnmarshal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RawArguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeReq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoadLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeReq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Timezone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;invalid timezone: %v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CallToolResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;In&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;客户端&quot;&gt;客户端&lt;/h3&gt;

&lt;p&gt;VSCode安装插件Cline，登录，配置MCP服务器。Trae也支持了，在AI对话框的设置添加下面的配置即可。&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;current_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;timeout&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:8080/sse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;transportType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;disabled&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输入问题：「告诉我北京时间」，等待即可。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1744899675/%E6%88%AA%E5%B1%8F2025-04-17_22.20.39_s2frsr.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;最后&quot;&gt;最后&lt;/h2&gt;

&lt;p&gt;MCP协议最大的应用场景在于AI编程，本地文件、数据库资源都可以作为Local Data Sources引入进来支持高效开发，Trae要赶紧跟上了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;扩展阅读&quot;&gt;扩展阅读&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/docs/concepts/architecture&quot;&gt;Core architecture&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html&quot;&gt;Server-Sent Events 教程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/O2iKdLxaAFlPkUtQZHQfKg&quot;&gt;几十行代码轻松打造属于自己的MCP服务器&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>OTP原理分析</title>
   <link href="https://blog.cyeam.com/server/2025/04/15/otp"/>
   <updated>2025-04-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/server/2025/04/15/otp</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#生成方式&quot; id=&quot;markdown-toc-生成方式&quot;&gt;生成方式&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#原理&quot; id=&quot;markdown-toc-原理&quot;&gt;原理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#go语言实现&quot; id=&quot;markdown-toc-go语言实现&quot;&gt;Go语言实现&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#生成二维码&quot; id=&quot;markdown-toc-生成二维码&quot;&gt;生成二维码&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#验证otp&quot; id=&quot;markdown-toc-验证otp&quot;&gt;验证OTP&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;OTP 是一次性密码（One-Time Password）的缩写，它是一种用于身份验证的安全机制，下次登录或进行身份验证时需要生成新的密码，从而大大提高了安全性。&lt;/p&gt;

&lt;h2 id=&quot;生成方式&quot;&gt;生成方式&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;基于时间同步 TOTP（Time - based One - Time Password）：服务器和用户的设备（如手机令牌）都有精确的时钟，它们通过网络进行时间同步。服务器根据当前时间以及用户的密钥，按照特定的算法生成一个一次性密码。用户的设备也使用相同的算法和密钥，基于本地时间生成相同的密码。由于双方时间同步，生成的 OTP 在特定时间内是一致的。例如，每隔 30 秒或 60 秒，OTP 就会更新一次。&lt;/li&gt;
  &lt;li&gt;基于事件同步：这种方式是基于特定的事件来生成 OTP，而不是时间。例如，用户每次按下令牌上的按钮，或者进行特定的操作（如在手机应用中进行特定手势操作），令牌会根据一个密钥和当前的事件计数生成一个新的 OTP。服务器端也会记录用户的事件计数，并使用相同的算法和密钥生成相应的密码，以验证用户身份。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;常用的是基于时间同步的方式，可以安装 Google 提供的 App Authenticator 来生成 OTP。&lt;/p&gt;

&lt;h2 id=&quot;原理&quot;&gt;原理&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;初始化：生成唯一密钥&quot;] --&amp;gt;|1. 生成160位随机数（十六进制）| B[&quot;密钥存储&quot;]
    B --&amp;gt; B1[&quot;服务器端存储密钥&quot;]
    B --&amp;gt; B2[&quot;用户设备（手机令牌）存储密钥&quot;]

    C[&quot;OTP生成阶段&quot;] --&amp;gt; C1[&quot;计算当前时间戳（秒级，从1970-01-01 00:00:00起）&quot;]
    C1 --&amp;gt; C2[&quot;时间戳处理：时间戳 // 固定时间间隔（如30秒），得到整数&quot;]
    C2 --&amp;gt; C3[&quot;生成哈希值：以【处理后的时间戳+密钥】为输入，通过SHA-1计算，得到20字节二进制哈希值&quot;]
    C3 --&amp;gt; C4[&quot;生成6位OTP&quot;]
    C4 --&amp;gt; C41[&quot;取哈希值最后1字节的低4位作为偏移量&quot;]
    C41 --&amp;gt; C42[&quot;从偏移量开始取4个字节，转换为32位整数&quot;]
    C42 --&amp;gt; C43[&quot;整数对1000000取模，得到6位OTP&quot;]

    D[&quot;OTP验证阶段&quot;] --&amp;gt; D1[&quot;用户设备生成OTP并提交至服务器&quot;]
    D1 --&amp;gt; D2[&quot;服务器端按相同算法（同C1-C4）生成OTP&quot;]
    D2 --&amp;gt; D3[&quot;对比服务器端OTP与用户提交的OTP&quot;]
    D3 --&amp;gt;|一致| D4[&quot;身份验证通过&quot;]
    D3 --&amp;gt;|不一致| D5[&quot;身份验证失败&quot;]

    B1 --&amp;gt; C
    B2 --&amp;gt; D1
    C --&amp;gt; D
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
  &lt;li&gt;生成密钥：首先，需要为用户生成一个唯一的密钥（Key），这个密钥通常是一个长度为 160 位的随机数，以十六进制表示。例如：3132333435363738393031323334353637383930。这个密钥会同时存储在服务器端和用户的设备（如手机令牌）中。&lt;/li&gt;
  &lt;li&gt;计算时间戳：计算当前的时间戳，通常以秒为单位。例如，当前时间是 2024 年 1 月 1 日 12:00:00，对应的时间戳（从 1970 年 1 月 1 日 00:00:00 到当前时间的秒数）可能是1704115200。&lt;/li&gt;
  &lt;li&gt;时间戳处理：将时间戳除以一个固定的时间间隔（T），得到一个整数。这个时间间隔通常是 30 秒或 60 秒，这里假设时间间隔为 30 秒。则1704115200 // 30 = 56803840。&lt;/li&gt;
  &lt;li&gt;生成哈希值：将处理后的时间戳和密钥作为输入，使用哈希函数（如 SHA - 1）进行计算，得到一个哈希值。计算得到的哈希值是一个 20 字节的二进制数据，例如：
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;\x03\xcd&amp;gt;\xa9\x84\x9c\x94\x13\x1b\x80\xcd\x0c\xe6\x18\x1d\x0b\x87\x05\x9b\x93
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;生成 OTP：从哈希值中提取特定的字节，根据这些字节计算出一个数字，再将这个数字转换为指定长度的 OTP。通常是取哈希值的最后一个字节的低 4 位作为偏移量，从哈希值中偏移量开始的 4 个字节作为一个 32 位的整数，然后对这个整数取模1000000（得到 6 位数字的 OTP）。例如，哈希值的最后一个字节是\x93，低 4 位是0011，对应的十进制数是 3。从哈希值的第 3 个字节开始取 4 个字节\xcd&amp;gt;\xa9\x84，转换为 32 位整数是3405691780，对1000000取模得到569178，这就是最终生成的 OTP。&lt;/li&gt;
  &lt;li&gt;验证 OTP：当用户进行身份验证时，服务器和用户的设备都会按照相同的算法和密钥计算出一个 OTP。服务器和用户的设备会比较这两个 OTP 是否一致，如果一致，则表示身份验证通过。如果不一致，则表示身份验证失败。&lt;/li&gt;
  &lt;li&gt;配置信息一般是通过二维码同步到App上，二维码的内容如下：&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;otpauth://totp/cyeam.com:test@cyeam.com?algorithm=SHA1&amp;amp;digits=6&amp;amp;issuer=cyeam.com&amp;amp;period=30&amp;amp;secret=AAAW7EIEWF3U7S5EX4LRU4TVKZSCTOJNNR62UXBGIEJOUDG54W7Q
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;go语言实现&quot;&gt;Go语言实现&lt;/h2&gt;

&lt;h3 id=&quot;生成二维码&quot;&gt;生成二维码&lt;/h3&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;bytes&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;image/png&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;os&quot;&lt;/span&gt;

	&lt;span class=&quot;s&quot;&gt;&quot;github.com/pquerna/otp&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/pquerna/otp/totp&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;otp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InitOtp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;totp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Generate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;totp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GenerateOpts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Issuer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;      &lt;span class=&quot;s&quot;&gt;&quot;cyeam.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;AccountName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;test@cyeam.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;SecretSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;m&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;nb&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;optLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Buffer&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;nb&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;png&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;c&quot;&gt;// display the QR code to the user.&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Issuer:       %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Issuer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Key:          %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Account Name: %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AccountName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Secret:       %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Writing PNG to qr-code.png....&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qr-code.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0644&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Please add your TOTP to your OTP Application now!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;验证otp&quot;&gt;验证OTP&lt;/h3&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OptValidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passcode&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;otp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;valid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;totp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>2024年的总结</title>
   <link href="https://blog.cyeam.com/notebook/2025/01/06/2024summary"/>
   <updated>2025-01-06T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2025/01/06/2024summary</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#调岗&quot; id=&quot;markdown-toc-调岗&quot;&gt;调岗&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#超出预期&quot; id=&quot;markdown-toc-超出预期&quot;&gt;超出预期&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#我理解的架构师&quot; id=&quot;markdown-toc-我理解的架构师&quot;&gt;我理解的架构师&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h1 id=&quot;调岗&quot;&gt;调岗&lt;/h1&gt;

&lt;p&gt;去年有一个比较大的身份转变：我调岗了。调整的原因一个是我自己想动动了，干得太久了，一个是新岗位也需要人，借机就过来了。新岗位和之前有所不同，需要去横向支持架构工作。对这个事情我一开始还有点犯怵，之前虽然也搞架构，但都是给自己做，给几百人的团队做架构师，之前没有这种类型的经验，这里面还牵扯到公信力和专业性的问题。&lt;/p&gt;

&lt;h1 id=&quot;超出预期&quot;&gt;超出预期&lt;/h1&gt;
&lt;p&gt;到今天为止，我自己觉得是超出预期的，超出我自己预期。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;架构项目A：确保架构顺利升级，完善了全流程监控，最近集中召回了多起线上问题。架构升级项目，尤其是ToB的项目，技术成果很难拿，目前来看拿到了；&lt;/li&gt;
  &lt;li&gt;探索项目B：业务那边和老板那边的事情，都有交代，自己也进去帮助业务梳理了一些事情和思路，并推进业务按照我的思路执行。能让业务按照技术思路做，很难得。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;我理解的架构师&quot;&gt;我理解的架构师&lt;/h1&gt;

&lt;p&gt;架构师岗位设立的初衷，是为了帮助部门负责人解决技术层面看到的问题，这里面有技术设计问题，也有需求问题引起的架构问题，总之，是要技术驱动主动往前推。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;方案问题，这个好办，方案拿出来review下，该调整链路就调整，该调整模型就调整，对我来说难度不大；&lt;/li&gt;
  &lt;li&gt;需求问题，这个会复杂很多，因为它还会好办业务需求、产品需求两层。
    &lt;ul&gt;
      &lt;li&gt;要推进这个事情，首要是梳理清楚业务流程、生产关系。我的经验是一开始找一些case去接触，如果case没找好，大不了被喷一顿，但是人是要认识下的，流程也是要了解下的，从点到线再到面，一步步把业务情况摸清楚，这样技术才不会被动。&lt;/li&gt;
      &lt;li&gt;如果要解决需求问题，对于沟通、信息拉齐上也有一些要求，能基于了解到的分工，把大家串联起来，相当于自己来做业务在某个场景的POC，要置身其中，用业务语言也大家交流。目前大家遇到问题咨询我的时候，我会冲淡路由的角色，把对应的负责人拉进来；&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;边界问题，我帮大家解决了一些架构问题后，后续类似的问题或者大家觉得关联的问题都会拉我进来一起聊。如果双方有分歧，架构师得做到中立，确保自己输出的架构理念跟老板的一致，这样也能树立好自己的威信。如果吃不准，需要找老板确认。我前前后后也支持了几次架构仲裁，目前看我的判断OK，这点也超出我的预期，说实话没有团队这个包袱设计方案要轻松和清晰很多。&lt;/li&gt;
  &lt;li&gt;人的问题，架构治理完后，怎么保证后续不劣化？我目前的做法是整理好流程说明，比如报警后怎么处理，该找谁，去哪个群反馈。目前看负责的同学执行得很好。架构治理得做到这一步；&lt;/li&gt;
  &lt;li&gt;汇报问题，这个其实很重要，甚至我认为是最重要的。前面提到，架构师是帮助老板解决问题的，那么我们遇到问题是需要及时反馈的，包括我们的想法，每个子团队的想法，都需要同步到位，哪怕挨喷。今年能拿到这个结果，我觉得跟老板给空间、时间都有关系。另外，我定期找他同步进展沟通想法也帮助很大，我目前推进事情、交流想法越来越遛，跟老板的背书和支持都离不开关系，需要多去争取。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>线上问题处理SOP</title>
   <link href="https://blog.cyeam.com/monitor/2024/11/26/fatal_sop"/>
   <updated>2024-11-26T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/monitor/2024/11/26/fatal_sop</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#问题处理原则&quot; id=&quot;markdown-toc-问题处理原则&quot;&gt;问题处理原则&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#一般处理流程&quot; id=&quot;markdown-toc-一般处理流程&quot;&gt;一般处理流程&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#问题发现初步排查&quot; id=&quot;markdown-toc-问题发现初步排查&quot;&gt;问题发现&amp;amp;初步排查&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#拉群同步&quot; id=&quot;markdown-toc-拉群同步&quot;&gt;拉群同步&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#线上止损&quot; id=&quot;markdown-toc-线上止损&quot;&gt;线上止损&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#bug解决&quot; id=&quot;markdown-toc-bug解决&quot;&gt;Bug解决&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#总结复盘&quot; id=&quot;markdown-toc-总结复盘&quot;&gt;总结复盘&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#报警&quot; id=&quot;markdown-toc-报警&quot;&gt;报警&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#业务报警&quot; id=&quot;markdown-toc-业务报警&quot;&gt;业务报警&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#技术报警&quot; id=&quot;markdown-toc-技术报警&quot;&gt;技术报警&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#报警--监控&quot; id=&quot;markdown-toc-报警--监控&quot;&gt;报警 &amp;amp; 监控&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#响应率&quot; id=&quot;markdown-toc-响应率&quot;&gt;响应率&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;开发是个苦逼的差使，除了保证日常需求如期上线，还需要维护服务运行、保证稳定性，一旦出点问题都是麻烦事。而体现技术价值的也在这里，这是少有的可以技术主导推进的事情。&lt;/p&gt;

&lt;h2 id=&quot;问题处理原则&quot;&gt;问题处理原则&lt;/h2&gt;

&lt;p&gt;5分钟内响应，半小时止损，当天修复完成，3天内复盘。&lt;/p&gt;

&lt;p&gt;考核故障的指标不是单看问题大小，最核心指标其实是影响时长，5min内处理完和1个小时处理完完全不是一个量级。举个当年在京东的例子，有一次早上刚上班，空调页面panic了，接到报警开始处理，前后有了差不多3min吧，最后啥事没有，也不需要发故障通报，就是因为有预案、处理快。&lt;/p&gt;

&lt;h2 id=&quot;一般处理流程&quot;&gt;一般处理流程&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;问题发现&amp;amp;初步排查 -&amp;gt; 问题同步 -&amp;gt; 线上止损 -&amp;gt; Bug解决 -&amp;gt; 总结复盘&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;问题发现初步排查&quot;&gt;问题发现&amp;amp;初步排查&lt;/h3&gt;
&lt;p&gt;发现问题后需要初步定位问题，确定影响面。确定是已知问题还是新增问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;定位问题原因、故障模块&lt;/li&gt;
  &lt;li&gt;确定问题开始时间，是否有对应上线操作&lt;/li&gt;
  &lt;li&gt;确定影响数据量&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这步非常关键，和故障的等级强关联，如果有提前准备预案辅助定位分析缩小范围，那么处理就会快很多；当然，处理的人也很关键，需要冷静+果断，尤其是预案不足的情况，不管用什么方案要尽快产出止损方案。&lt;/p&gt;

&lt;h3 id=&quot;拉群同步&quot;&gt;拉群同步&lt;/h3&gt;

&lt;p&gt;如果影响面较大、问题难定位、或者需要拉人决策，需要拉群、拉会同步信息（业务相关RD、PM、TL、QA）。&lt;/p&gt;

&lt;p&gt;不同的角色要有分工：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;技术专注在故障止损上，QA提前准备上线流程、回归case；&lt;/li&gt;
  &lt;li&gt;业务根据影响面评估进线方案、话术、法务风险；&lt;/li&gt;
  &lt;li&gt;产品需要配合安抚，一方面帮助业务整理故障话术，一方面帮技术隔离无效沟通，让技术专注解决问题；&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;线上止损&quot;&gt;线上止损&lt;/h3&gt;

&lt;p&gt;如果确定了止损方案就立刻实施。一般都是上线引入的，回滚上线操作、配置操作、数据库操作。&lt;/p&gt;

&lt;p&gt;如果一下子无法完美止损，先尝试部分止损，例如MysQL打满时 kill 慢 SQL，先处理一部分；比如服务端负载被打满，先加限流保证负载降下来，一部分流程能成功。&lt;/p&gt;

&lt;h3 id=&quot;bug解决&quot;&gt;Bug解决&lt;/h3&gt;

&lt;p&gt;技术来主导，但是QA把控好质量，建好工单跟进不能不了了之。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;确认是否需要二次数据修复；&lt;/li&gt;
  &lt;li&gt;默认需要当天修复上线，如果问题较小或者确保不会再发生，可以周知后排期修复；&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;总结复盘&quot;&gt;总结复盘&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;时间线梳理；&lt;/li&gt;
  &lt;li&gt;问题定位工具和方案；&lt;/li&gt;
  &lt;li&gt;如果第一时间是通过Oncall渠道等非技术报警反馈的问题，说明报警设计待优化，需要复盘业务报警，需要确保业务报警及时性；&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;报警&quot;&gt;报警&lt;/h2&gt;

&lt;p&gt;报警等级要怎么划分？按照报警分业务报警和技术报警来看。&lt;/p&gt;

&lt;h3 id=&quot;业务报警&quot;&gt;业务报警&lt;/h3&gt;

&lt;p&gt;业务报警的含义是业务受影响要高优报警 Critical，例如下单失败率过高，下单单量环比下降50%。
制定规则：前期需要根据业务场景制定好规则，所有业务报警都需要高优响应和处理。&lt;/p&gt;

&lt;h3 id=&quot;技术报警&quot;&gt;技术报警&lt;/h3&gt;

&lt;p&gt;技术视角的报警，例如网关限流、CPU负载高，它算是业务报警的下钻报警，处理业务报警时要参考技术报警来分析定位。如果只有技术报警而未影响实际业务，我们也就不需要太着急了。
制定规则：按照业务报警拆解，优先对核心接口监控，其次考虑横向展开。&lt;/p&gt;

&lt;h3 id=&quot;报警--监控&quot;&gt;报警 &amp;amp; 监控&lt;/h3&gt;

&lt;p&gt;报警、监控一般是一起做，不过真有问题看监控肯定来不及，优先要把下钻辅助分析的制作为报警。&lt;/p&gt;

&lt;h3 id=&quot;响应率&quot;&gt;响应率&lt;/h3&gt;

&lt;p&gt;如果只报警不响应，那么报警毫无意义，需要定期Review响应率，业务报警需要100% 5min内响应，技术报警需要95%响应。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;需要制定好值班计划，并宣导及时响应的要求，如果没有及时响应需要预警backup；&lt;/li&gt;
  &lt;li&gt;也需要关注噪音，核心报警可以按照核心系统数量来估算，系统动线上的关键节点有多少，就对应做多少业务报警，切记不要太多。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Github Actions 初探，代码发布成功后发送微信推送</title>
   <link href="https://blog.cyeam.com/monitor/2024/10/30/github_action"/>
   <updated>2024-10-30T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/monitor/2024/10/30/github_action</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#代码自动部署以flyio为例&quot; id=&quot;markdown-toc-代码自动部署以flyio为例&quot;&gt;代码自动部署（以fly.io为例）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#微信通知&quot; id=&quot;markdown-toc-微信通知&quot;&gt;微信通知&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#飞书通知&quot; id=&quot;markdown-toc-飞书通知&quot;&gt;飞书通知&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;先看图感受下，当部署完成后可以在微信里收到推送。Github Actions支持自动触发，执行命令。本文不会讲解具体语法，可以参考阮一峰的博客&lt;a href=&quot;https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html&quot;&gt;《GitHub Actions 入门教程》&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&quot;代码自动部署以flyio为例&quot;&gt;代码自动部署（以fly.io为例）&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;在代码仓库里执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fly launch --no-deploy&lt;/code&gt;创建部署文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fly.toml&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;执行命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fly tokens create deploy -x 999999h&lt;/code&gt;创建部署密钥；&lt;/li&gt;
  &lt;li&gt;访问Github网址，点击自己的项目-&amp;gt;Settings-&amp;gt;Secrets and variables-&amp;gt;Actions-&amp;gt;Repository secrets-&amp;gt;New repository secret，填写部署密钥。Name为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLY_API_TOKEN&lt;/code&gt;，Secret为第二步生成的内容；&lt;/li&gt;
  &lt;li&gt;在代码仓库里，在这个目录里创建问题：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/fly.yml&lt;/code&gt;，把下面的内容粘贴进去；&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;name: Fly Deploy
on:
  push:
    branches:
      - master    # change to main if needed
jobs:
  deploy:
    name: Deploy app
    runs-on: ubuntu-latest
    concurrency: deploy-group    # optional: ensure only one action runs at a time
    steps:
      - uses: actions/checkout@v4
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: $
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;简单介绍下语法：
    &lt;ul&gt;
      &lt;li&gt;on/push，当master分支代码提交后触发执行；&lt;/li&gt;
      &lt;li&gt;jobs/deploy，触发后执行的内容。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runs-on&lt;/code&gt;指运行环境，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;steps&lt;/code&gt;指运行指令，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env&lt;/code&gt;加载之前第三部设置的环境变量，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uses&lt;/code&gt;指引入了三方的工具包，这个例子是使用fly官方的部署包；&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;完整例子移步&lt;a href=&quot;https://fly.io/docs/launch/continuous-deployment-with-github-actions/&quot;&gt;Continuous Deployment with Fly.io and GitHub Actions&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&quot;微信通知&quot;&gt;微信通知&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;重复上面第三步，新建一个Name为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WECHAT_WORK_BOT_WEBHOOK&lt;/code&gt;的密钥，值为微信通知的Webhook地址，用企业微信创建一个即可（拉个微信群添加机器人后会展示出来）；&lt;/li&gt;
  &lt;li&gt;在目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github/workflows&lt;/code&gt;里新建文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notify.yml&lt;/code&gt;，粘贴下面的内容：&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;name: Watch Workflow Status
on:
  workflow_run:
    workflows: [ &quot;Fly Deploy&quot; ]
    types: [ completed ]
env:
  WECHAT_WORK_BOT_WEBHOOK: $

jobs:
  notify:
    runs-on: ubuntu-latest
    steps:
      - id: prep
        uses: hocgin/action-env@main
      - name: WeChat Work Notification
        uses: chf007/action-wechat-work@master
        env:
          WECHAT_WORK_BOT_WEBHOOK: $
        with:
          msgtype: markdown
          content: &quot;**【[$]($)】** \n
          📌 $ \n
          🏃 [@$]($)\n
          🕐 &amp;lt;font color=\&quot;comment\&quot;&amp;gt;$&amp;lt;/font&amp;gt; \n
          🔧 &amp;lt;font color=\&quot;warning\&quot;&amp;gt;$ -&amp;gt; $&amp;lt;/font&amp;gt; \n
          🏆 &amp;lt;font color=\&quot;comment\&quot;&amp;gt;$ / $&amp;lt;/font&amp;gt; \n
          📝 提交信息: $ \n
          \n
          [查看更多]($)
          &quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;on/workflow_run 指在部署完成后执行这个脚本，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workflows&lt;/code&gt;里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fly Deploy&lt;/code&gt;是前面第一步的工作流名称，如果你用的不是fly，按照自己情况修改；&lt;/li&gt;
  &lt;li&gt;jobs 复用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chf007/action-wechat-work@master&lt;/code&gt;包的能力，发送markdown格式的消息，能使用的变量可以参考这个&lt;a href=&quot;https://github.com/hocgin/action-env/blob/main/action.yml&quot;&gt;文件&lt;/a&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;飞书通知&quot;&gt;飞书通知&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;name: Watch Workflow Status
on:
  workflow_run:
    workflows: [ &quot;Fly Deploy&quot; ]
    types: [ completed ]
env:
  # 飞书机器人 Webhook 地址（需在 Secrets 中配置 LARK_BOT_WEBHOOK）
  LARK_BOT_WEBHOOK: $

jobs:
  notify:
    runs-on: ubuntu-latest
    steps:
      - id: prep
        uses: hocgin/action-env@main

      - name: Send Lark notification
        env:
          LARK_WEBHOOK: $
        run: |
          # 使用 jq 构建飞书 post 消息
          jq -n \
            --arg repo_full_name &quot;$&quot; \
            --arg repo_html_url &quot;$&quot; \
            --arg conclusion &quot;$&quot; \
            --arg sender &quot;$&quot; \
            --arg sender_html_url &quot;$&quot; \
            --arg action_trigger_at &quot;$&quot; \
            --arg source_branch &quot;$&quot; \
            --arg target_branch &quot;$&quot; \
            --arg env &quot;$&quot; \
            --arg version &quot;$&quot; \
            --arg commit_body &quot;$&quot; \
            --arg repo_homepage &quot;$&quot; \
            &apos;{
              msg_type: &quot;post&quot;,
              content: {
                post: {
                  zh_cn: {
                    title: &quot;Cyeam站点部署通知（Fly Deploy）&quot;,
                    content: [
                      [ { tag: &quot;a&quot;, text: &quot;【\($repo_full_name)】&quot;, href: $repo_html_url } ],
                      [ { tag: &quot;text&quot;, text: (if $conclusion == &quot;success&quot; then &quot;☀️☀️☀️☀️☀️&quot; else &quot;🌧️🌧️🌧️🌧️🌧️&quot; end) } ],
                      [ { tag: &quot;a&quot;, text: &quot;@\($sender)&quot;, href: $sender_html_url } ],
                      [ { tag: &quot;text&quot;, text: &quot;🕐 \($action_trigger_at)&quot; } ],
                      [ { tag: &quot;text&quot;, text: &quot;🔧 \($source_branch // &quot;∅&quot;) -&amp;gt; \($target_branch // &quot;∅&quot;)&quot; } ],
                      [ { tag: &quot;text&quot;, text: &quot;🏆 \($env // &quot;未知版本&quot;) / \($version // &quot;未知版本&quot;)&quot; } ],
                      [ { tag: &quot;text&quot;, text: &quot;📝 提交信息: \($commit_body // &quot;&quot;)&quot; } ],
                      [ { tag: &quot;a&quot;, text: &quot;查看更多&quot;, href: ($repo_homepage // $repo_html_url) } ]
                    ]
                  }
                }
              }
            }&apos; | curl -X POST -H &quot;Content-Type: application/json&quot; -d @- $LARK_WEBHOOK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>代码隔离大法</title>
   <link href="https://blog.cyeam.com/idea/2024/04/02/split_code"/>
   <updated>2024-04-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/idea/2024/04/02/split_code</id>
   <content type="html">&lt;hr /&gt;

&lt;p&gt;现在系统架构大多采用分层的形式web-&amp;gt;handler-&amp;gt;service-&amp;gt;dao-&amp;gt;model
每次调用都是从前到后发起，每一层沉淀必要的代码能力，当有新需求迭代某个模块即可，无需重新大规模开发。
但经常也会有一些问题发生，一单这些代码不够solid，出现了一些劣化问题，就会使得整个项目迭代难度、稳定性保障难度变大。尤其是每层表现均不够稳定、分层不够清晰的时候，改造难度会更大。这次我面临的就是底层模型的调整，这样上面几层多少也会受影响，改造工作量非常大。
解决这个问题，有两个思路：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;每一层收敛新老模型兼容逻辑&lt;/li&gt;
  &lt;li&gt;从web到model重新开发，做代码隔离&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;前者看起来技术含量高，后者反而显得很low，但是我推荐隔离的方式。
&lt;strong&gt;&lt;em&gt;大的系统改造，一定要确保上线后遇到任何问题都有兜底的处理办法&lt;/em&gt;&lt;/strong&gt;。一但上线后系统有异常，会有非常多的数据清洗、系统止损工作，而方式1每一层的梳理兼容工作，会把整个项目的难度提升多个数量级，一但有一个点没梳理到，将会面临不可预期的问题，另外，回归的挑战也非常大。而方式2，恰恰可以做到对主干流程侵入降到最低，甚至可以做降级、灰度开关，一点一点放量控制影响面。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>团队间互卷怎么解决？</title>
   <link href="https://blog.cyeam.com/idea/2024/03/16/how_to_deal_with_overlap"/>
   <updated>2024-03-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/idea/2024/03/16/how_to_deal_with_overlap</id>
   <content type="html">&lt;hr /&gt;

&lt;p&gt;这些年没少经历踩脚的事情，要么人家来卷我，要么我领了任务去卷别人。&lt;/p&gt;

&lt;p&gt;为啥会踩脚？
说白了就是两个团队核心目标一致，都想拿同一个结果。上面两边老板没能拉齐，只能安排下去抢。说白了，层层下放。而且卷地盘这种事情确实一线好操作，需求那么多，很容易通过一些case收一些东西。但是两边团队实力本身也不会差特别多，所以结果大多是两边互相收一些东西，整体架构乱得一批。&lt;/p&gt;

&lt;p&gt;我们要如何应对？
大家如果在团队里，本身也有屁股问题，要说客观视角也都是相对的，所以谁遇到也会头疼。说说我的想法：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;一般开卷，都是通过一些方案来聊起的。往往一开始大家也不知道对面想做啥，底线是什么，通过一次次方案、交互、流程的交锋来试探双方的真实想法，讲究的就是一个反应速度。&lt;/li&gt;
  &lt;li&gt;探到想法，两边想法没冲突还好，有冲突就得解，不解项目都得卡死。&lt;/li&gt;
  &lt;li&gt;怎么解冲突？如果不是触及自己底线，可以适当让步，当然我们自己也要给自己争取些资源。&lt;/li&gt;
  &lt;li&gt;如果还解决不了，上升，让两边负责人去聊。&lt;/li&gt;
  &lt;li&gt;当然，还有一些骚操作，找合作伙伴去聊，比如产品、业务。上游同学说话非常有效果。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;按照康威定律，组织架构决定系统架构。如果系统有overlap，那一定是组织分工有问题。说实话高层不一定很快想清楚这种问题的，也依赖一线反馈，所以我们遇到这种问题也积极些，卷确实让人恶心，但问题还是要推动解决。而且针对这些问题，多完整得梳理下全局情况，让大家都有机会，自己也能成长。但也防不住对方藏材料恶心你。。。&lt;/p&gt;

&lt;p&gt;所以说要根治，离不开整个团队负责人的管理方法和手段，确保大家分工明确，都有发展空间。如果大家发现自己团队搞不定，趁早撤。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>如何做平台？</title>
   <link href="https://blog.cyeam.com/idea/2024/03/15/how_to_build_platform"/>
   <updated>2024-03-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/idea/2024/03/15/how_to_build_platform</id>
   <content type="html">&lt;hr /&gt;

&lt;p&gt;平台的初衷是为了沉淀一些常见能力，当新业务启动时可以快速复用。&lt;/p&gt;

&lt;p&gt;理想是好的，但实际落地难度非常大。新业务很多是在尝试和探索，可能做了半年就停了，平台还没成型。此时又开始一个新业务，但要平台能力不同又需要重新做。如此往复，到最后往往乱七八糟，平台不像平台，开发效率低，强行复用问题也多。&lt;/p&gt;

&lt;p&gt;这个时候大家又会觉得平台是个累赘，还不如小而美快速支持业务。但是这种情况下容易偏case推进，也不能在发展受阻的时候把之前优质的经验分享给业务。&lt;/p&gt;

&lt;p&gt;从上面的描述也能看出来存在两条路，每条路都有优劣，要在合适的契机选择好路线。选很关键，一般发展早期糙快猛支持业务，成熟期需要精细化运营的时候做平台。千万不要既这又那，不是说选了行业先行就不做平台，选了平台就可以慢点做业务，而且说要有侧重，选了哪个哪个优先。四不像系统就是既这又那的过程中做烂的。&lt;/p&gt;

&lt;p&gt;要选好，并且排除阻力，就需要高层支持，如果说我们负责的系统和路线不匹配，千万别纠结，四不像系统是最可怕的。&lt;/p&gt;

&lt;p&gt;最后，平台路线到底要咋做？&lt;/p&gt;

&lt;p&gt;平台切记不是为了做平台而做，他是孵化出来的。所以平台团队也是要去做业务的，尤其是复杂的业务场景，在支持业务的工程中逐步沉淀，而不是上下分层，搞成行业和平台。一开始我们是看不清楚平台核心目标的，而且业务一旦放缓，之前定的架构全部作废。理想的做法是收拢多行业垂直业务，逐步沉淀平台系统。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>一些管理方面的思考</title>
   <link href="https://blog.cyeam.com/idea/2023/09/28/some_thoughts_of_management"/>
   <updated>2023-09-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/idea/2023/09/28/some_thoughts_of_management</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#如何适应新的位置&quot; id=&quot;markdown-toc-如何适应新的位置&quot;&gt;如何适应新的位置？&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#冒充者综合症&quot; id=&quot;markdown-toc-冒充者综合症&quot;&gt;冒充者综合症&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#需要做好三件事&quot; id=&quot;markdown-toc-需要做好三件事&quot;&gt;需要做好三件事&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#看清全局&quot; id=&quot;markdown-toc-看清全局&quot;&gt;看清全局&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#做过才能写规划么&quot; id=&quot;markdown-toc-做过才能写规划么&quot;&gt;做过才能写规划么？&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#怎么规划&quot; id=&quot;markdown-toc-怎么规划&quot;&gt;怎么规划？&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#为什么有人会觉得你的产出不足事情简单&quot; id=&quot;markdown-toc-为什么有人会觉得你的产出不足事情简单&quot;&gt;为什么有人会觉得你的产出不足，事情简单？&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#需要哪些人&quot; id=&quot;markdown-toc-需要哪些人&quot;&gt;需要哪些人？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#这些人要如何运转&quot; id=&quot;markdown-toc-这些人要如何运转&quot;&gt;这些人要如何运转？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#最后&quot; id=&quot;markdown-toc-最后&quot;&gt;最后&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#关于方法论&quot; id=&quot;markdown-toc-关于方法论&quot;&gt;关于方法论&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#除了前面提到的还有啥要做的&quot; id=&quot;markdown-toc-除了前面提到的还有啥要做的&quot;&gt;除了前面提到的还有啥要做的？&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#为什么要总结规划对自己的收益&quot; id=&quot;markdown-toc-为什么要总结规划对自己的收益&quot;&gt;为什么要总结规划，对自己的收益？&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h1 id=&quot;如何适应新的位置&quot;&gt;如何适应新的位置？&lt;/h1&gt;
&lt;p&gt;随着自己能力的提升，工作的积累，会安排越来越多的相关工作，我们就需要承担更多的任务。这是一个循序渐进的过程，和传统企业的「任命」不同。不过，随着我们承担工作越来越多，量变也会引起质变，突然有一天，你会发现我们需要带实习生、分配工作、写规划、甚至要去吵架聊分工。
越到后面你会发现自己承担的责任越大，压力也越大。而且由于并不是官方任命，缺少了专门的培训流程。这里其实体感会很差，突然要承担一个之前没太接触或者培训过的事情。&lt;/p&gt;

&lt;h2 id=&quot;冒充者综合症&quot;&gt;冒充者综合症&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;冒名顶替症候群（英语：Impostor syndrome），亦称为冒名顶替现象（英语：impostor phenomenon）、骗子症候群（英语：fraud syndrome）。这个名称是在1978年由临床心理学家克兰斯博士（英语：Pauline R. Clance）与因墨斯（英语：Suzanne A. Imes）所提出，用以指称出现在成功人士身上的一种现象。
患有冒名顶替症候群的人无法将自己的成功归因于自己的能力，并总是担心有朝一日会被他人识破自己其实是骗子这件事。他们坚信自己的成功并非源于自己的努力或能力，而是凭借著运气、良好的时机，或别人误以为他们能力很强、很聪明，才导致他们的成功 。即使现实环境中的证据指明，他们确实具备优秀才能，他们还是认为自己只是骗子，不值得获得成功。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里推荐阅读《硬核晋升》，作者一步一步讲了自己晋升或者提升scope后的思考和解法。&lt;/p&gt;

&lt;h2 id=&quot;需要做好三件事&quot;&gt;需要做好三件事&lt;/h2&gt;
&lt;p&gt;前面也提到，互联网的提升往往是被动且缺少培训的，那我们也就不绕弯子了，带团队或者小方向，都算管理的职责了，可以分为三件事：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;初衷：团队想要实现的成果，或者也可以称为「为什么」，就是我们常说的规划。为什么要做这件事，为什么要这个月做这件事？我们需要保证团队知道成功是什么，都认可这个目标，都认为可以实现。&lt;/li&gt;
  &lt;li&gt;人员：需要谁来完成初衷，团队人力是否满足？技能是否满足？不满足接下来要怎么办？&lt;/li&gt;
  &lt;li&gt;流程：团队要如何一起工作，要协调好成员。能够控制好自己的团队，培养健康、高效的团队文化。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;看清全局&quot;&gt;看清全局&lt;/h1&gt;
&lt;p&gt;大部分人认为的规划，是要指出来一条路，这里就需要我们已经是这个领域的专家了。XX业务应该这样做：1. XXX；2. XXX；3. XXX。&lt;/p&gt;

&lt;h2 id=&quot;做过才能写规划么&quot;&gt;做过才能写规划么？&lt;/h2&gt;

&lt;p&gt;我们要开始着手规划的时候，往往接手的业务，或者一些细节，我们是不了解的。前面提到的冒充者综合征，自己虚的点往往是这个。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;压根没做过。团队新开了个方向，直接安排去支持；&lt;/li&gt;
  &lt;li&gt;之前虽然做过类似业务，但是这次很难照搬。
所以，不管做没做过，实际上都需要我们从头开始了解业务情况作出适当的规划。恰恰照搬是最不可取的，参考云仓。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;怎么规划&quot;&gt;怎么规划？&lt;/h3&gt;
&lt;p&gt;自己的定位更像是做顾问，我们接手了一个新的项目，需要需求方说清楚自己的目标和要求，我们自己有一套方法论，可以快速了解全局情况并给出解法，最后需要有一个逻辑自洽的PPT来做汇报。
关键的几件事：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;收集业务目标
需求生产的顺序是业务运营-&amp;gt;产品-&amp;gt;开发。整个运营其实是一个完整运转的「企业」。业务的流转本身是闭环的，但有时需要自研系统来提升关键环节的效率和收益。常见的一个误区是教业务做事，这是因为业务和产品的分工看起来没有那么「干净」，出现了混合汇报的情况。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;前面也提到，业务本身是闭环的，所以我们不可能去教业务做事情；但是可以去教产品做事，本身产研是不分家的。所以对于业务的一些规划和思考，没有必要等产品转达，我们作为实现方可以直接沟通获取一手消息，也避免信息差过大影响规划方向。今年前半年我重点做的就是这个方面。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;看清业务全貌
业务团队的研发一定要懂业务，要明白我们到底做出来的东西是怎么支持业务运转的，这个非常重要。而且我们能够用自己的语言来描述我们的业务架构（当然了，如果你对口的业务非常猛，直接白嫖即可，但也要提醒下，业务的首要任务是GMV，并不是架构）。这块也需要多少了解下友商的情况，不用太细，可以帮助我们判断后续的走势；
架构的话基于金字塔模型来梳理，用MECE原则搞一张大图，一般是以业务动线、业务模块等套路来表达。我们能从准确表达业务发展的思路：为什么去年重点是做A？A做得怎么样？有了A能力得沉淀，接下来要做什么？为什么做这个？
业务全貌不是单纯说自己负责的部分。因为产研和业务不是1:1，所以要看清楚全貌要看业务团队负责的内容。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;提炼技术目标
能回答好前面的问题后，基于此来推导技术规划。大家一开始着手规划的时候往往是从3开始考虑，很容易没想法最后写不下去，1、2做得越好，3越明确和清晰。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;
&lt;p&gt;这里推荐阅读《麦肯锡结构化战略思维》。&lt;/p&gt;

&lt;h2 id=&quot;为什么有人会觉得你的产出不足事情简单&quot;&gt;为什么有人会觉得你的产出不足，事情简单？&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;业务复杂，规划复杂
比较理想的情况，一般是业务增长比较好的时候，我们能跟着业务蹭一波。最好有技术沉淀，比如把脉、调度引擎、对账框架、主数据。&lt;/li&gt;
  &lt;li&gt;业务简单，规划复杂
这种情况说明能力更强，去年云仓就有这种趋势，从一开始没有目标但又非常忙到后来收了无忧目标清晰游刃有余。&lt;/li&gt;
  &lt;li&gt;业务复杂，规划看起来简单
这种情况其实最常见，电商本身还是在增长的，大家都很忙，沉浸在倒排期中，最后没有时间思考没啥沉淀。&lt;/li&gt;
  &lt;li&gt;业务简单，规划看起来简单
同3。
我不认为有简单的业务，业务团队的规模摆在那里，我们看到的东西少，可能是同步给我们的少；也可能是东西多但没我们想要的。思考和深度就非常重要了。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;需要哪些人&quot;&gt;需要哪些人？&lt;/h1&gt;

&lt;p&gt;有了目标、事项、里程碑，就需要有人来干。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;如果现有人能力能够满足，我们需要给每个人明确事情和目标。总体策略：扬长避短，让大家做擅长的事情并提高一定的要求来提升一些方面的能力；&lt;/li&gt;
  &lt;li&gt;如果现有人力&amp;amp;能力不足，需要做好招聘，招聘候选人的画像要很明确；&lt;/li&gt;
  &lt;li&gt;除了考虑单个人，人多了之后，还需要考虑组织建设。我们要分几个方向（一般建议和规划里的目标对齐）？每个方向的负责人是谁？负责人需要做什么？负责人需要提升哪些？&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;
&lt;p&gt;这部分推荐阅读《硬核晋升》。&lt;/p&gt;

&lt;h1 id=&quot;这些人要如何运转&quot;&gt;这些人要如何运转？&lt;/h1&gt;
&lt;p&gt;关键目标就是提效，需要1+1&amp;gt;2，而不是人越多越乱。
内部：解决一线问题，提升需求流转效率。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;定容会，做好需求安排人力安排，不能单纯压力下放；&lt;/li&gt;
  &lt;li&gt;周会是非常重要的手段，定期同步进展解决需求推进卡点；&lt;/li&gt;
  &lt;li&gt;问题Review会，培养团队质量意识，沉淀处理办法、规范。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;外部：做好协同&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;业务间：定期走一线；&lt;/li&gt;
  &lt;li&gt;产研间，周会；&lt;/li&gt;
  &lt;li&gt;研发间：规划&amp;amp;总结会，同步好业务价值；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;另外，为了能拿关键结果，专事专办：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;大型项目技术O管理；&lt;/li&gt;
  &lt;li&gt;专项成立FT，例如稳定性小组、专项小组；&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;
&lt;p&gt;这部分推荐阅读《卓有成效的管理者》、《增长黑客》。&lt;/p&gt;

&lt;h1 id=&quot;最后&quot;&gt;最后&lt;/h1&gt;
&lt;h2 id=&quot;关于方法论&quot;&gt;关于方法论&lt;/h2&gt;
&lt;p&gt;方法论不可能完整，关键还是要思考沉淀，不断优化、丰富自己的方法。这也没有那么高端，把自己做事情的方法看作提高流程，不断优化丰富工具。
另外，也不能光想，也要去了解世面上其它人的思考方式。&lt;/p&gt;

&lt;h2 id=&quot;除了前面提到的还有啥要做的&quot;&gt;除了前面提到的还有啥要做的？&lt;/h2&gt;
&lt;p&gt;后续我能看到的方法，要去考虑团队增长。按传统企业发展来看，做好一个业务只需要维护即可，要预留人力甚至招聘都是建立在业务增长的前提下。在业务规划已经支持比较好的情况下，一旦发现业务苗头不对，基于高层的远景和规划做好前瞻性布局。&lt;/p&gt;

&lt;h2 id=&quot;为什么要总结规划对自己的收益&quot;&gt;为什么要总结规划，对自己的收益？&lt;/h2&gt;
&lt;p&gt;面对一系列复杂问题理清楚思路，给出思路，解决阶段性问题，让自己和团队更「躺」。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>【Go实现】熔断机制</title>
   <link href="https://blog.cyeam.com/framework/2020/03/01/circuitbreaker"/>
   <updated>2020-03-01T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/framework/2020/03/01/circuitbreaker</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;目录&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;什么是熔断&quot;&gt;什么是熔断？&lt;/h3&gt;

&lt;p&gt;熔断是指在下游发生错误时上游主动关闭或限制对下游的请求。&lt;/p&gt;

&lt;h3 id=&quot;原理&quot;&gt;原理&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;通常熔断器分为三个时期: CLOSED，OPEN，HALFOPEN&lt;/li&gt;
  &lt;li&gt;RPC 正常时，为 CLOSED；&lt;/li&gt;
  &lt;li&gt;当 RPC 错误增多时，熔断器会被触发, 进入 OPEN；&lt;/li&gt;
  &lt;li&gt;OPEN 后经过一定的冷却时间，熔断器变为 HALFOPEN；&lt;/li&gt;
  &lt;li&gt;HALFOPEN 时会对下游进行一些有策略的访问, 然后根据结果决定是变为 CLOSED，还是 OPEN；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;总得来说三个状态的转换大致如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773795225/clipboard_1773795222320_vj2qg21fz.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;go-实现&quot;&gt;Go 实现&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/rubyist/circuitbreaker&quot;&gt;https://github.com/rubyist/circuitbreaker&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;isallowed-是否允许请求根据当前状态判断&quot;&gt;IsAllowed 是否允许请求，根据当前状态判断&lt;/h4&gt;

&lt;h5 id=&quot;close-允许&quot;&gt;CLOSE 允许&lt;/h5&gt;

&lt;h5 id=&quot;open&quot;&gt;OPEN&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;在 CoolingTimeout 冷却时间内，不允许&lt;/li&gt;
  &lt;li&gt;过了冷却时间，状态变为 HALFOPEN，允许访问&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;halfopen&quot;&gt;HALFOPEN&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;在 DetectTimeout 检测时间内，允许访问&lt;/li&gt;
  &lt;li&gt;否则不允许&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;atomic.StoreInt32((*int32)(&amp;amp;b.state), int32(HALFOPEN))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;trip-判断是否达到熔断限额可以自定义&quot;&gt;trip 判断是否达到熔断限额（可以自定义）&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type TripFunc func(Metricser) bool
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;ThresholdTripFunc 错误阈值&lt;/li&gt;
  &lt;li&gt;ConsecutiveTripFunc 连续错误超过阈值&lt;/li&gt;
  &lt;li&gt;RateTripFunc 根据最少访问数和错误率判断&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;metricser-访问统计包括成功数失败数超时数错误率采样数连续错误数&quot;&gt;Metricser 访问统计，包括成功数、失败数、超时数、错误率、采样数、连续错误数&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Metricser interface {
   Fail()    // records a failure
   Succeed() // records a success
   Timeout() // records a timeout

   Failures() int64    // return the number of failures
   Successes() int64   // return the number of successes
   Timeouts() int64    // return the number of timeouts
   ConseErrors() int64 // return the consecutive errors recently
   ErrorRate() float64 // rate = (timeouts + failures) / (timeouts + failures + successes)
   Samples() int64     // (timeouts + failures + successes)
   Counts() (successes, failures, timeouts int64)

   Reset()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;window-实现类&quot;&gt;window 实现类&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type window struct {
   sync.RWMutex
   oldest  int32     // oldest bucket index
   latest  int32     // latest bucket index
   buckets []bucket // buckets this window holds

   bucketTime time.Duration // time each bucket holds
   bucketNums int32         // the numbe of buckets
   inWindow   int32         // the number of buckets in the window

   allSuccess int64
   allFailure int64
   allTimeout int64

   conseErr int64
}

type bucket struct {
   failure int64
   success int64
   timeout int64
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;用环形队列实现动态统计。把一个连续的时间切成多个小份，每一个 bucket 保存 BucketTime 的统计数据，BucketTime * BucketNums 是统计的时间区间。&lt;/p&gt;

&lt;p&gt;每 BucketTime，会有一个 bucket 过期&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if w.inWindow == w.bucketNums {
   // the lastest covered the oldest(latest == oldest)
   oldBucket := &amp;amp;w.buckets[w.oldest]
   atomic.AddInt64(&amp;amp;w.allSuccess, -oldBucket.Successes())
   atomic.AddInt64(&amp;amp;w.allFailure, -oldBucket.Failures())
   atomic.AddInt64(&amp;amp;w.allTimeout, -oldBucket.Timeouts())
   w.oldest++
   if w.oldest &amp;gt;= w.bucketNums {
      w.oldest = 0
   }
} else {
   w.inWindow++
}

w.latest++
if w.latest &amp;gt;= w.bucketNums {
   w.latest = 0
}
(&amp;amp;w.buckets[w.latest]).Reset()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;panel-metricser-的容器&quot;&gt;Panel Metricser 的容器&lt;/h4&gt;

&lt;h4 id=&quot;panelstatechangehandler-熔断事件&quot;&gt;PanelStateChangeHandler 熔断事件&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type PanelStateChangeHandler func(key string, oldState, newState State, m Metricser)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;缺陷&quot;&gt;缺陷&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;所有 breaker 公用同一个 BucketTime，统计周期不支持更新&lt;/li&gt;
  &lt;li&gt;冷却时间不支持动态更新&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>使用 Golang 生成 PDF</title>
   <link href="https://blog.cyeam.com/go/2019/06/26/golang-pdf"/>
   <updated>2019-06-26T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/go/2019/06/26/golang-pdf</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#gofpdf&quot; id=&quot;markdown-toc-gofpdf&quot;&gt;gofpdf&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#加载字体&quot; id=&quot;markdown-toc-加载字体&quot;&gt;加载字体&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#绘制矩形并填充颜色&quot; id=&quot;markdown-toc-绘制矩形并填充颜色&quot;&gt;绘制矩形并填充颜色&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#写字&quot; id=&quot;markdown-toc-写字&quot;&gt;写字&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#增加图片&quot; id=&quot;markdown-toc-增加图片&quot;&gt;增加图片&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#简单的svg&quot; id=&quot;markdown-toc-简单的svg&quot;&gt;简单的svg&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#条形码二维码&quot; id=&quot;markdown-toc-条形码二维码&quot;&gt;条形码、二维码&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#简单的-html&quot; id=&quot;markdown-toc-简单的-html&quot;&gt;简单的 HTML&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#居中&quot; id=&quot;markdown-toc-居中&quot;&gt;居中&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#wkhtmltopdf&quot; id=&quot;markdown-toc-wkhtmltopdf&quot;&gt;wkhtmltopdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;这是一个例子，可以先感受下：&lt;a href=&quot;https://www.cyeam.com/tool/arithmetic?utm_source=blog&quot;&gt;口算大通关&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;gofpdf&quot;&gt;gofpdf&lt;/h3&gt;

&lt;p&gt;仓库：&lt;a href=&quot;https://github.com/jung-kurt/gofpdf&quot;&gt;https://github.com/jung-kurt/gofpdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一开始就发现了这个包，纯 Go 实现，用起来很友好。&lt;/p&gt;

&lt;p&gt;举几个简单的样例：&lt;/p&gt;

&lt;h5 id=&quot;加载字体&quot;&gt;加载字体&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pdf.AddUTF8Font(&quot;NotoSansSC&quot;, &quot;&quot;, &quot;./resource/font/NotoSansSC-Regular.ttf&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;绘制矩形并填充颜色&quot;&gt;绘制矩形并填充颜色&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pdf.SetFillColor(0, 0, 0)
pdf.Rect(30, 0, 26.5, 14, &quot;FD&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;写字&quot;&gt;写字&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pdf.Text(32, 9, &quot;字字字&quot;)
// 多行文字，根据宽度自动换行
func TextMultiLine(pdf *gofpdf.Fpdf, text string, x, y, width, heightPerLine float64) {
	texts := pdf.SplitText(text, width)

	i := 0
	for _, text := range texts {
		_y := y + (heightPerLine * float64(i))
		pdf.Text(x, _y, text)
		i++
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;增加图片&quot;&gt;增加图片&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pdf.Image(&quot;./resource/image/logo.jpeg&quot;, 2, 1.5, 26, 10, false, &quot;&quot;, 0, &quot;&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;简单的svg&quot;&gt;简单的svg&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sig, err = gofpdf.SVGBasicParse(templateStr)
if err == nil {
	scale := 100 / sig.Wd
	scaleY := 113 / sig.Ht
	if scale &amp;gt; scaleY {
		scale = scaleY
	}
	pdf.SetLineWidth(0.25)
	pdf.SetDrawColor(0, 0, 0)
	pdf.SetXY(0, 0)
	pdf.SVGBasicWrite(&amp;amp;sig, scale)
} else {
	pdf.SetError(err)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;条形码二维码&quot;&gt;条形码、二维码&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;key = barcode.RegisterCode128(pdf, &quot;barcode&quot;)
width = 55
height = 6.7
barcode.BarcodeUnscalable(pdf, key, 39, 110.5, &amp;amp;width, &amp;amp;height, false)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;简单的-html&quot;&gt;简单的 HTML&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;htmlStr := `You can now easily print text mixing different styles: &amp;lt;b&amp;gt;bold&amp;lt;/b&amp;gt;, ` +
	`&amp;lt;i&amp;gt;italic&amp;lt;/i&amp;gt;, &amp;lt;u&amp;gt;underlined&amp;lt;/u&amp;gt;, or &amp;lt;b&amp;gt;&amp;lt;i&amp;gt;&amp;lt;u&amp;gt;all at once&amp;lt;/u&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/b&amp;gt;!&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;` +
	`&amp;lt;center&amp;gt;You can also center text.&amp;lt;/center&amp;gt;` +
	`&amp;lt;right&amp;gt;Or align it to the right.&amp;lt;/right&amp;gt;` +
	`You can also insert links on text, such as ` +
	`&amp;lt;a href=&quot;http://www.fpdf.org&quot;&amp;gt;www.fpdf.org&amp;lt;/a&amp;gt;, or on an image: click on the logo.`
html := pdf.HTMLBasicNew()
html.Write(lineHt, htmlStr)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;居中&quot;&gt;居中&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pdf.SetXY(40, 117.8)
pdf.CellFormat(60, 2, &quot;center&quot;, &quot;&quot;, 0,
	&quot;CM&quot;, false, 0, &quot;&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;优势：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;纯 Go 实现，使用方便；&lt;/li&gt;
  &lt;li&gt;性能好 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BenchmarkSvg-4    100      15806543 ns/op&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;支持格式多，除了上面的例子，还支持根据简单 HTML 绘制 PDF；&lt;/li&gt;
  &lt;li&gt;非常完善的样例，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fpdf_test.go&lt;/code&gt;里面有所有接口的生成样例，可以直接看效果；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;缺陷：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;svg 支持的比较简单。只支持最外层是path，path内只支持d，其他的比如坐标转换都不支持。其实大部分svg都是包含坐标转换的，这个不太友好。可以通过&lt;a href=&quot;https://github.com/methodofaction/Method-Draw&quot;&gt;Method-Draw&lt;/a&gt;将svg简化压缩一下。&lt;/li&gt;
  &lt;li&gt;html 支持的比较简单。上面的例子写得比较明白了，支持居中、加粗、超链接这种。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;wkhtmltopdf&quot;&gt;wkhtmltopdf&lt;/h3&gt;

&lt;p&gt;网上经常能找到在线html转pdf的在线小工具，其实都是基于c语言的实现wkhtmltopdf做的。这个工具功能很强大，甚至可以解析&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt;引入的jQuery外网包。Go 语言是通过cgo bind实现的包，&lt;a href=&quot;https://github.com/adrg/go-wkhtmltopdf&quot;&gt;https://github.com/adrg/go-wkhtmltopdf&lt;/a&gt;，功能非常强大，缺点是生成得太慢。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Docker(Linux) 环境下如何配置 host</title>
   <link href="https://blog.cyeam.com/go/2019/03/18/hostswitch"/>
   <updated>2019-03-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/go/2019/03/18/hostswitch</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#linux-系统如何配置-host&quot; id=&quot;markdown-toc-linux-系统如何配置-host&quot;&gt;Linux 系统如何配置 host？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#docker-里配置没起作用&quot; id=&quot;markdown-toc-docker-里配置没起作用&quot;&gt;Docker 里配置没起作用&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#etcnsswitchconf&quot; id=&quot;markdown-toc-etcnsswitchconf&quot;&gt;/etc/nsswitch.conf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;linux-系统如何配置-host&quot;&gt;Linux 系统如何配置 host？&lt;/h3&gt;

&lt;p&gt;如果你有管理员权限&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo vi /etc/hosts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;增加你想配置的 host，保持并退出&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;127.0.0.1 cyeam.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ping&lt;/code&gt;命令来检测一下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ping cyeam.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大部分情况到这里就结束了。&lt;/p&gt;

&lt;h3 id=&quot;docker-里配置没起作用&quot;&gt;Docker 里配置没起作用&lt;/h3&gt;

&lt;p&gt;测试俊哥在自己的 Docker 里搭起了我的服务，但是发现发 http 请求没有按照预想的用配置好的 host 发。但是我在自己的 Mac 上面配 host 是可以的。下面讲一下原因，特别感谢俊哥的研究。&lt;/p&gt;

&lt;h3 id=&quot;etcnsswitchconf&quot;&gt;/etc/nsswitch.conf&lt;/h3&gt;

&lt;p&gt;Linux 系统解析域名，支持本地文件配置，也支持通过域名服务器查询。本地文件配置的方式就是第一步提到的 /etc/hosts。而域名解析服务器的配置则在 /etc/resolv.conf 中。此外，还有一个重要的文件 /etc/nsswitch.conf，它可以用来配置域名解析的优先级。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hosts:          files dns
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我的开发机上面是这样，意思是先读取文件，如果文件里没有从 dns 服务器查询。&lt;/p&gt;

&lt;p&gt;而我们的情况是 docker 里面没有这个文件，当系统没有这个文件时，Go 的表现是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// If /etc/nsswitch.conf doesn&apos;t exist or doesn&apos;t specify any
// sources for &quot;hosts&quot;, assume Go&apos;s DNS will work fine.
if os.IsNotExist(nss.err) || (nss.err == nil &amp;amp;&amp;amp; len(srcs) == 0) {
	if c.goos == &quot;solaris&quot; {
		// illumos defaults to &quot;nis [NOTFOUND=return] files&quot;
		return fallbackOrder
	}
	if c.goos == &quot;linux&quot; {
		// glibc says the default is &quot;dns [!UNAVAIL=return] files&quot;
		// https://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html.
		return hostLookupDNSFiles
	}
	return hostLookupFilesDNS
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;它会按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dns [!UNAVAIL=return] files&lt;/code&gt;的默认值来查询。也就是先查询 dns，如果 dns 服务器不可用，再查询本地 hosts 文件。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The system takes the action associated with the STATUS (return) if the DNS method does not return UNAVAIL (!UNAVAIL)—that is, if DNS returns SUCCESS, NOTFOUND, or TRYAGAIN. As a consequence, the following method (files) is used only when the DNS server is unavailable: If the DNS server is not unavailable (read the two negatives as “is available”), the search returns the domain name or reports that the domain name was not found. The search uses the files method (check the local /etc/hosts file) only if the server is not available.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;p&gt;一个想当然的问题最后引出了这么多不了解的新知识，真好。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Go http 如何发送 multipart/form-data (发送文件)？</title>
   <link href="https://blog.cyeam.com/go/2019/03/15/form-post"/>
   <updated>2019-03-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/go/2019/03/15/form-post</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#通过multipartform-data提交数据&quot; id=&quot;markdown-toc-通过multipartform-data提交数据&quot;&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipart/form-data&lt;/code&gt;提交数据&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#通过-createformfile-发送文件&quot; id=&quot;markdown-toc-通过-createformfile-发送文件&quot;&gt;通过 CreateFormFile 发送文件&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;通过multipartform-data提交数据&quot;&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipart/form-data&lt;/code&gt;提交数据&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bodyBuf := &amp;amp;bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
_ = bodyWriter.WriteField(&quot;param&quot;, string(param))
defer bodyWriter.Close()

req, err := http.NewRequest(&quot;POST&quot;, callbackUrl, bodyBuf)
if err != nil {
	return nil, err
}
req.Header.Set(&quot;Content-Type&quot;, bodyWriter.FormDataContentType())
req.WithContext(ctx)

resp, err := http.DefaultClient.Do(req)
if err != nil {
	return nil, err
}
defer resp.Body.Close()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type&lt;/code&gt;头很重要，丢了的话服务端无法正常解析。&lt;/p&gt;

&lt;h3 id=&quot;通过-createformfile-发送文件&quot;&gt;通过 CreateFormFile 发送文件&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipart.Writer&lt;/code&gt;支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CreateFormFile&lt;/code&gt;，写入文件名就能实现上传文件了。&lt;/p&gt;

&lt;p&gt;http 的 POST 方法可以通过 body 发送数据，而数据的编码格式是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type&lt;/code&gt;来定义，常见的类型是&lt;a href=&quot;https://stackoverflow.com/questions/23714383/what-are-all-the-possible-values-for-http-content-type-header&quot;&gt;What are all the possible values for HTTP “Content-Type” header?&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Go module 如何发布 v2 及以上版本？</title>
   <link href="https://blog.cyeam.com/go/2019/03/12/go-version"/>
   <updated>2019-03-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/go/2019/03/12/go-version</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#go-module-版本号&quot; id=&quot;markdown-toc-go-module-版本号&quot;&gt;Go module 版本号&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#go-项目如何升级-v2&quot; id=&quot;markdown-toc-go-项目如何升级-v2&quot;&gt;Go 项目如何升级 v2？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#incompatible&quot; id=&quot;markdown-toc-incompatible&quot;&gt;incompatible&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#v000-20190312205133-abcdefghijklm&quot; id=&quot;markdown-toc-v000-20190312205133-abcdefghijklm&quot;&gt;v0.0.0-20190312205133-abcdefghijklm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tag-删除了重建为什么没效果&quot; id=&quot;markdown-toc-tag-删除了重建为什么没效果&quot;&gt;tag 删除了重建为什么没效果？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;用上 go mod 之后，依赖包都是通过版本打 tag 的形式确定版本号。比如 `	github.com/mnhkahn/gogogo v1.0.9`。每次都改动都是在累加低位的版本号，一直这么用也挺安逸的。突然有一天，我的一个底层包需要大改，导致和之前的版本彻底不兼容，这种情况下如何设置版本号，如何能让调用方成功接入？&lt;/p&gt;

&lt;h3 id=&quot;go-module-版本号&quot;&gt;Go module 版本号&lt;/h3&gt;

&lt;p&gt;先讲一下 Go 在用的版本号协议&lt;a href=&quot;https://semver.org/&quot;&gt;semver (Semantic Versioning)&lt;/a&gt;。它定义的版本号格式是：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;vMAJOR.MINOR.PATCH&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;MAJOR 主版本号，如果有大的版本更新，导致 API 和之前版本不兼容。我们遇到的就是这个问题。&lt;/li&gt;
  &lt;li&gt;MINOR 次版本号，当你做了向下兼容的新 feature。&lt;/li&gt;
  &lt;li&gt;PATCH 修订版本号，当你做了向下兼容的修复 bug fix。&lt;/li&gt;
  &lt;li&gt;v 所有版本号都是 v 开头。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;比如我们用的 Go 语言，目前是 1.12.0。它还是 Go 1，每次升级都保证是兼容的，12的版本号是新 feature，而最末尾的版本号是修复。说明当前的版本上了之后还没有修复过问题。&lt;/p&gt;

&lt;p&gt;我这次也是搞了一个不兼容的更新，所以需要升级到 v2.0.0。&lt;/p&gt;

&lt;h3 id=&quot;go-项目如何升级-v2&quot;&gt;Go 项目如何升级 v2？&lt;/h3&gt;

&lt;p&gt;假设你的项目已经支持 go module 了。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;修改 go.mod 第一行，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;那行最后加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v2&lt;/code&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module github.com/mnhkahn/aaa/v2&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;对于不兼容的改动（除了 v0 和 v1），都必须显示得修改 import 的路径。所以我们的引用需要改成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import &quot;github.com/mnhkahn/aaa/v2/config&quot;&lt;/code&gt;。在所有的地方都需要修改，包括自己的包内和调用方包。&lt;/li&gt;
  &lt;li&gt;底层包的更新有个小工具可以帮助快速实现&lt;a href=&quot;https://github.com/marwan-at-work/mod&quot;&gt;mod&lt;/a&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GO111MODULE=on go get github.com/marwan-at-work/mod/cmd/mod&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod upgrade&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;代码提交之后需要打新 tag，v2.0.0。&lt;/li&gt;
  &lt;li&gt;调用方修改引用代码，需要加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2&lt;/code&gt;，和第二步提到的一样。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get github.com/mnhkahn/aaa/v2&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;incompatible&quot;&gt;incompatible&lt;/h3&gt;

&lt;p&gt;有时候你能在 go.mod 文件中发现不兼容的标记，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v3.2.1+incompatible&lt;/code&gt;，这是因为这个依赖包没有使用 go module，并且它通过 git 打了 tag。&lt;/p&gt;

&lt;h3 id=&quot;v000-20190312205133-abcdefghijklm&quot;&gt;v0.0.0-20190312205133-abcdefghijklm&lt;/h3&gt;

&lt;p&gt;对于没有打 tag 的仓库，go.mod 就会很丑陋，它的格式是&lt;a href=&quot;https://golang.org/cmd/go/#hdr-Pseudo_versions&quot;&gt;pseudo-version&lt;/a&gt;。它的含义是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v0.0.0-yyyymmddhhmmss-abcdefabcdef&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;tag-删除了重建为什么没效果&quot;&gt;tag 删除了重建为什么没效果？&lt;/h3&gt;

&lt;p&gt;困扰了我很久的一个问题。有一个 tag v2.0.0 的代码有问题，我删除了这个 tag，新建了一个好的版本，但是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;依然报错，困扰了很久，一直以为是 v2 的版本号写错了。后来才发现是 go 有本地缓存，缓存在 $GOPATH/pkg/mod/cache 下面，把里面的内容清掉，重新获取即可。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;更多阅读：&lt;a href=&quot;https://github.com/golang/go/wiki/Modules&quot;&gt;Go 1.11 Modules&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Mac 环境下 git 如何自动补全</title>
   <link href="https://blog.cyeam.com/tool/2018/11/12/git-completion"/>
   <updated>2018-11-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/tool/2018/11/12/git-completion</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#安装-bash-completion&quot; id=&quot;markdown-toc-安装-bash-completion&quot;&gt;安装 bash-completion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#安装-git-completionbash&quot; id=&quot;markdown-toc-安装-git-completionbash&quot;&gt;安装 git-completion.bash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;安装-bash-completion&quot;&gt;安装 bash-completion&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew install bash-completion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将下面代码添加到~/.bash_profile（如果没有该文件，新建一个）。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# git auto completition
if [ -f ~/.git-completion.bash ]; then
  . ~/.git-completion.bash
fi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;安装-git-completionbash&quot;&gt;安装 git-completion.bash&lt;/h3&gt;

&lt;p&gt;这个文件不能随便安装，网上的教程都是用git参数的master分支，这是不对的，这个版本需要和当前系统安装的git版本对应。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;看看自己的版本是什么，我的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.17.1&lt;/code&gt;，就需要用这个&lt;a href=&quot;https://raw.githubusercontent.com/git/git/v2.17.1/contrib/completion/git-completion.bash&quot;&gt;版本的脚本&lt;/a&gt;。将脚本报存到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.git-completion.bash&lt;/code&gt;里面，然后执行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;source ~/.git-completion.bash
source ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就可以咯，按下Tab键就可以提示啦。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git che
checkout      cherry        cherry-pick
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;更多阅读：&lt;a href=&quot;https://apple.stackexchange.com/questions/327817/git-completion-bash-producing-error-on-macos-sierra-10-12-6&quot;&gt;.git-completion.bash producing error on macOS Sierra 10.12.6&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>在这个大促的日子聊一聊我的大促经历</title>
   <link href="https://blog.cyeam.com/ctalk/2018/11/11/1111"/>
   <updated>2018-11-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ctalk/2018/11/11/1111</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;目录&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;之前在某东待了 3 年半，经历了 4 个 618，3 个双十一。之前所在的团队是&lt;a href=&quot;https://list.jd.com/list.html?cat=9987,653,655&quot;&gt;京东 PC 端三级列表页&lt;/a&gt;，关于我们项目的架构介绍，可以参考&lt;a href=&quot;https://www.sunyuantao.com/syt-jd-list.html&quot;&gt;这篇文章&lt;/a&gt;。每年两次大促，每年也需要为这两次大促进行长达一个月的准备。&lt;/p&gt;

&lt;h3 id=&quot;压测&quot;&gt;压测&lt;/h3&gt;

&lt;p&gt;压测不光是要大促的时候压，平时每个月都会压。每个月压单机，压的链接是线上抓取的真实访问链接，我们线下找一台机器。压测单机会根据不同的并发进行压，一般从 5 并发开始，逐渐增加，直到 TP99 下降到不可接受为止。每次压测都会和之前的进行对比，如果发现性能下降，需要定位原因和优化。&lt;/p&gt;

&lt;p&gt;大促压测每年集中在 5 月和 10 月，会在夜里进行。整个项目组的所有机器（或者一个机房的所有机器）进行压测，从入口开始压。大促前的压测标准是之前大促峰值的流量，能够能扛得住之前峰值的倍数，能达标就行，不能的话需要优化或者加机器（主要还是加机器）。&lt;/p&gt;

&lt;p&gt;全链路加测。这个是最近开始搞的，全公司的同一个夜里一起加班压。这个会模拟当时的实时流量，并不会像单独压测的时候那么狠。&lt;/p&gt;

&lt;h3 id=&quot;性能&quot;&gt;性能&lt;/h3&gt;

&lt;p&gt;对于性能最重要的指标还是 TP99，公司对性能其实有不成文的规定，TP99 要小于 200ms。如果大于这个值，用户会明显觉得卡顿。&lt;/p&gt;

&lt;h3 id=&quot;禁止读库禁止跨机房调用&quot;&gt;禁止读库、禁止跨机房调用&lt;/h3&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;&lt;/p&gt;

&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;

&lt;p&gt;线上只读服务，比如我们列表服务，禁止直连数据库。用户的任何操作都不能发生数据库操作。平时我们经常会写这种接口：取缓存读数据，没有的话去读库，然后把数据设置到缓存里。这种方案只适用于小流量的情况，一旦数据库来个慢查询或者数据量变大，所有接口都会挂，所有哦。这也算是雪崩的一种吧。&lt;/p&gt;

&lt;p&gt;跨机房调用一个是慢，一个是这样的话就失去了双活的意义了。不过我们的降级方案里有跨机房方案，不过常规方案里要禁止。&lt;/p&gt;

&lt;h3 id=&quot;监控&quot;&gt;监控&lt;/h3&gt;

&lt;p&gt;监控非常重要。程序接口和关键模块都需要加监控，包括性能和流量监控，报警阈值也需要调整成一个合理的值。发送异常问题需要能立刻报警。&lt;/p&gt;

&lt;p&gt;除了接口监控，还需要有实例监控，防止机器或者服务挂了自己没有感知影响到了用户。一般我会选择监听端口获取发起一个 HTTP 请求来检测存活。之前就遇到一个问题，重启 nginx 失败了，没有设置报警，导致刷新页面会偶尔跳错误页，害得我会滚代码第二天才重新上。&lt;/p&gt;

&lt;p&gt;还有一种监控是针对页面的，定时调用指定页面，并渲染页面，渲染完成后通过抓取页面上的 HTML 信息检测业务是否正确。回归测试中非常有用。&lt;/p&gt;

&lt;h3 id=&quot;服务降级&quot;&gt;服务降级&lt;/h3&gt;

&lt;p&gt;依赖的模块有时候会发生异常，比如 Redis，每过几个月就会来一次，一般原因是某个分片过大，分片过大性能会下降。还有一些情况，比如硬件维修，这个时候需要摘机器等。&lt;/p&gt;

&lt;p&gt;方案有很多，比如双机房，双集群，多组 Redis 等。当时我们设计了十几套降级方案，尽量把所有的降级操作都压缩到最小。&lt;/p&gt;

&lt;h3 id=&quot;兜底&quot;&gt;兜底&lt;/h3&gt;

&lt;p&gt;上面说了，nginx 出错会跳错误页，而不是 nginx 的默认错误页。而我们的要求是三级列表页能根据每个类目跳兜底页面。也就是说，兜底的时候用户有可能并不会发现我们兜底了。这样可以保证在后端服务发生任何异常的时候都能给用户一个友好的页面。&lt;/p&gt;

&lt;p&gt;对于一个电商而且是上市技术公司来说，兜底是非常重要的，它会影响到公司的股价。如果发生时间很长的故障，而且又引发一些舆论效应的话，损失的就不是那些订单了，而是市值。分享一个真实的事情：有一次我的接口出了问题，没有返回正常的数据，前端接口没有兼容，在用户端看到了 nginx 的 404 页面，当时要求第一个要修复的是兜底方案。&lt;/p&gt;

&lt;h3 id=&quot;事故&quot;&gt;事故&lt;/h3&gt;

&lt;p&gt;写到这里，线上紧急问题的处理方法已经有了，自动的有兜底，手动的是报警+降级操作。可以这么说，这几个部分是影响绩效的最重要因素。线上问题不管多么严重，衡量级别的最重要因素是影响时间，处理得快，比如十五分钟内，问题不算大；如果超过半个小时，事故级别就会往上走了。&lt;/p&gt;

&lt;p&gt;处理事故最重要的，还是要快。自己操作时需要权衡，如何快速恢复。处理问题最差的方案是改代码。如果实在找不到问题，可以考虑强制更改数据库或者缓存来解决。紧急问题处理的快慢主要还是看平时项目预留了多少调试的方案和快速定位问题的辅助工具。真发生紧急问题一般会比较着急，没办法集中注意力，所以前面提到的调试工具或者降级方案就显得很重要了。&lt;/p&gt;

&lt;h3 id=&quot;值班&quot;&gt;值班&lt;/h3&gt;

&lt;p&gt;进入 6 月份和 11 月份就要封版了，那个时候也没啥需求了，主要就是值班。一般 1 号是秒杀日，那天凌晨需要值班。移动端的流量很大，移动是未来啊。有时候每个夜里都得留人值班，不是每次都这样，这个主要是领导决定。17 和 10 号夜里一定得留在公司值班，夜里也不能回家，2 点流量下来后需要到公司附近的酒店休息，早上 8 点前回来，因为早上 8 点有秒杀。&lt;/p&gt;

&lt;p&gt;连夜值班我还是比较喜欢的，可以换调休，第二天还可以休息一天。一个组的会经常大促一起开房，大家的基情是其它公司比不了的。&lt;/p&gt;

&lt;p&gt;每次值班领导都会给买吃的，各种吃的，还有饮料，我最喜欢的还是那个豚骨汤泡面，在每个值班的夜里，泡一碗，周围都是这个味，最后大家都开始吃这个了。&lt;/p&gt;

&lt;p&gt;一般 17 号或 10 的时候就需要进值班室了，值班室挺挤的，大家在那里呆一宿，全是味。在那边有基础服务的人，出了问题方便解决。0 点的量真的很大，那个时候大家都很紧张。&lt;/p&gt;

&lt;p&gt;讲一个真实的值班情况：那天我像往常一样在值班，那是我第一次负责我们组所有的大促服务稳定和降级策略，所有准备工作都像往常一样过完了，能想到的意外情况也都做了准备，但还是发生了意外。10 号晚上 23:20，流量突然可以激增，是平时流量的几倍，触发了报警。看到流量还在上涨，就得找上涨的源头。我们的服务除了给自己的列表用，其它部门也会调用，我们把请求量大的都做了单独的监控，但是这次的流量上涨却没找到源头。这个平时量太小，被我忽略了。没办法，只能上 nginx 去看 access log，找出流量来源。发生这种情况，都很意外，不过我们服务性能很好（曾经有一度可以单台抗所有量），我定位起来也就没有很紧张了。后来找到了调用方，他们在大促前更新了缓存服务，这个服务并不是那么可靠，导致 10 号晚上缓存被击穿，流量回源，全到我了这里。平时 QPS 应该是个零点几的样子，那天晚上那么短的时间 QPS 有几十万，还好我们服务扛得住，否则的话只能把这个来源降级了。&lt;/p&gt;

&lt;p&gt;很多情况下问题就是这么来的，你觉得可以没什么，优化了一把，跑得也正常，大促极端情况下就会被击穿了，下游服务都扛住了，如果下游依赖的很多服务因为流量突然上涨没有扛住，问题就很严重了。&lt;/p&gt;

&lt;h3 id=&quot;最后&quot;&gt;最后&lt;/h3&gt;

&lt;p&gt;今年双 11 是第一次没有亲自参与的大促，我自己也没买什么，感觉大促气氛不像以前那么浓烈了，大家是什么感觉呢？&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>通过两个例子介绍一下 Golang For Range 循环原理</title>
   <link href="https://blog.cyeam.com/golang/2018/10/30/for-interals"/>
   <updated>2018-10-30T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/10/30/for-interals</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#下面的代码是死循环么&quot; id=&quot;markdown-toc-下面的代码是死循环么&quot;&gt;下面的代码是死循环么？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#语法糖&quot; id=&quot;markdown-toc-语法糖&quot;&gt;语法糖&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#下面的代码有什么问题么&quot; id=&quot;markdown-toc-下面的代码有什么问题么&quot;&gt;下面的代码有什么问题么？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#切片for-range原理&quot; id=&quot;markdown-toc-切片for-range原理&quot;&gt;切片For Range原理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#其它语法糖&quot; id=&quot;markdown-toc-其它语法糖&quot;&gt;其它语法糖&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#map&quot; id=&quot;markdown-toc-map&quot;&gt;map&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#channel&quot; id=&quot;markdown-toc-channel&quot;&gt;channel&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#数组&quot; id=&quot;markdown-toc-数组&quot;&gt;数组&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#字符串&quot; id=&quot;markdown-toc-字符串&quot;&gt;字符串&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;下面的代码是死循环么&quot;&gt;下面的代码是死循环么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func main() {
	v := []int{1, 2, 3}
	for i := range v {
		v = append(v, i)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面的代码先初始化了一个内容为1、2、3的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;，然后遍历这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;，然后给这个切片追加元素。随着遍历的进行，数组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v&lt;/code&gt;也在逐渐增大，那么这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环是一个死循环么？&lt;/p&gt;

&lt;p&gt;答案是否。只会遍历三次，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v&lt;/code&gt;的结果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, 1, 2]&lt;/code&gt;。并不是死循环，原因就在于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for range&lt;/code&gt;实现的时候用到了语法糖。&lt;/p&gt;

&lt;h3 id=&quot;语法糖&quot;&gt;语法糖&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;语法糖（Syntactic sugar），也译为糖衣语法，是由英国计算机科学家彼得·蘭丁发明的一个术语，指计算机语言中添加的某种语法，这种语法对语言的功能没有影响，但是更方便程序员使用。 语法糖让程序更加简洁，有更高的可读性。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;对于切片的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for range&lt;/code&gt;，它的底层代码就是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;//   for_temp := range
//   len_temp := len(for_temp)
//   for index_temp = 0; index_temp &amp;lt; len_temp; index_temp++ {
//           value_temp = for_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到，在遍历之前就获取的切片的长度&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;len_temp := len(for_temp)&lt;/code&gt;，遍历的次数不会随着切片的变化而变化，上面的代码自然不会是死循环了。&lt;/p&gt;

&lt;h3 id=&quot;下面的代码有什么问题么&quot;&gt;下面的代码有什么问题么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)

for index, value := range slice {
	myMap[index] = &amp;amp;value
}
fmt.Println(&quot;=====new map=====&quot;)
for k, v := range myMap {
	fmt.Printf(&quot;%d =&amp;gt; %d\n&quot;, k, *v)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这也是实际编码中有可能会遇到的问题，循环切片，把切片值的地址保存到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myMap&lt;/code&gt;中，这样的操作结果是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;=====new map=====
0 =&amp;gt; 3
1 =&amp;gt; 3
2 =&amp;gt; 3
3 =&amp;gt; 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果完全一样，都是最后一次遍历的值。通过上面的底层代码看下，遍历后的值赋给了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;，而在我们的例子中，会把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;的地址保存到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myMap&lt;/code&gt;的值中。这里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;是个「全局变量」，所以赋完值之后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myMap&lt;/code&gt;里面所有的值都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;，所以结构都是一样的而且是最后一个值。&lt;/p&gt;

&lt;p&gt;注意，这里必须是保存指针才会有问题，如果直接保存的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;，因为 Golang 是值拷贝，所以值会重新复制再保存，这种情况下结果就会是正确的了。&lt;/p&gt;

&lt;h3 id=&quot;切片for-range原理&quot;&gt;切片For Range原理&lt;/h3&gt;

&lt;p&gt;总结一下，通过For Range遍历切片，&lt;strong&gt;&lt;em&gt;首先，计算遍历次数（切片长度）；每次遍历，都会把当前遍历到的值存放到一个全局变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index&lt;/code&gt;中。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;其它语法糖&quot;&gt;其它语法糖&lt;/h3&gt;

&lt;p&gt;另外，For Range 不光支持切片。其它的语法糖底层代码。&lt;/p&gt;

&lt;h4 id=&quot;map&quot;&gt;map&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Lower a for range over a map.
// The loop we generate:
//   var hiter map_iteration_struct
//   for mapiterinit(type, range, &amp;amp;hiter); hiter.key != nil; mapiternext(&amp;amp;hiter) {
//           index_temp = *hiter.key
//           value_temp = *hiter.val
//           index = index_temp
//           value = value_temp
//           original body
//   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;channel&quot;&gt;channel&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Lower a for range over a channel.
// The loop we generate:
//   for {
//           index_temp, ok_temp = &amp;lt;-range
//           if !ok_temp {
//                   break
//           }
//           index = index_temp
//           original body
//   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;数组&quot;&gt;数组&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Lower a for range over an array.
// The loop we generate:
//   len_temp := len(range)
//   range_temp := range
//   for index_temp = 0; index_temp &amp;lt; len_temp; index_temp++ {
//           value_temp = range_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;字符串&quot;&gt;字符串&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Lower a for range over a string.
// The loop we generate:
//   len_temp := len(range)
//   var next_index_temp int
//   for index_temp = 0; index_temp &amp;lt; len_temp; index_temp = next_index_temp {
//           value_temp = rune(range[index_temp])
//           if value_temp &amp;lt; utf8.RuneSelf {
//                   next_index_temp = index_temp + 1
//           } else {
//                   value_temp, next_index_temp = decoderune(range, index_temp)
//           }
//           index = index_temp
//           value = value_temp
//           // original body
//   }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5384&quot;&gt;完整底层代码。&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;推荐阅读：&lt;a href=&quot;https://garbagecollected.org/2017/02/22/go-range-loop-internals/&quot;&gt;Go Range Loop Internals&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>如何在Mac上快速切换Host</title>
   <link href="https://blog.cyeam.com/tool/2018/10/26/host"/>
   <updated>2018-10-26T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/tool/2018/10/26/host</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#切换-host&quot; id=&quot;markdown-toc-切换-host&quot;&gt;切换 Host&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#which-host&quot; id=&quot;markdown-toc-which-host&quot;&gt;Which Host&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#断开现有链接&quot; id=&quot;markdown-toc-断开现有链接&quot;&gt;断开现有链接&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#一键断开链接&quot; id=&quot;markdown-toc-一键断开链接&quot;&gt;一键断开链接&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;切换-host&quot;&gt;切换 Host&lt;/h3&gt;

&lt;p&gt;我们会经常遇到需要切换 Host 的场景，最原始的方法是直接修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt;。不过这个方法太原始了，一般我们都会分测试场景分不同的 Host 组。这个时候就需要一个工具来保存多个分组的 Host，我用的是&lt;a href=&quot;https://github.com/2ndalpha/gasmask&quot;&gt;Gas Mask&lt;/a&gt;。这种工具很多，功能也都差不多，大家可以选择自己习惯的。&lt;/p&gt;

&lt;h3 id=&quot;which-host&quot;&gt;Which Host&lt;/h3&gt;

&lt;p&gt;大部分人也就到上一步就结束了，其实很多时候也会有问题，我们会经常发现 Host 没有生效。&lt;/p&gt;

&lt;p&gt;我们这个时候需要做的就是明确到底 Host 指到哪里了，切换 Host 到底成功了没有。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1540536947/cyeam/WX20181026-145514_2x.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;借助 Chrome 的调试工具也可以看到具体这次访问的 Host 到底是什么，如上图。不过这个方法多少有些繁琐，推荐大家一个插件：&lt;a href=&quot;https://chrome.google.com/webstore/detail/which-host/hjecimglpgbbajfigibmieancoegaema&quot;&gt;Which host&lt;/a&gt;，一个京东前端大神开发的。具体效果就和封面图的一样。&lt;/p&gt;

&lt;h3 id=&quot;断开现有链接&quot;&gt;断开现有链接&lt;/h3&gt;

&lt;p&gt;那么配了 Host 为啥有时候不是立刻生效呢？原因在于 Chrome 和之前的 Host 还保持着链接，切换 Host 并不会释放这个链接，这种情况下访问的有可能还是之前的 Host。&lt;/p&gt;

&lt;p&gt;Chrome 本身提供了清空链接的功能，把当前保持的链接清空了就能解决这个问题。在浏览器输入：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://net-internals/#sockets&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1540537363/cyeam/WX20181026-150146_2x.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在打开的页面依次点击 Close idle sockets 和 Fulsh socket pools 就能释放链接了。你可以把这个地址保持到书签栏来提高速度。&lt;/p&gt;

&lt;h3 id=&quot;一键断开链接&quot;&gt;一键断开链接&lt;/h3&gt;

&lt;p&gt;不过这个还是很慢，我们需要打开一个标签页，点击这个书签，再点击两个情况按钮，再回到之前的测试页面，需要点5次呢。&lt;/p&gt;

&lt;p&gt;后来我又找到一个插件：&lt;a href=&quot;https://chrome.google.com/webstore/detail/flush-dns-close-sockets/mlmlfmdmhdplgecgmiihhfjodokajeel&quot;&gt;Flush DNS &amp;amp; close sockets&lt;/a&gt;。它可以帮助我们一键解决这个问题。不过用这个插件还是会有个问题，那就是需要增加 Chrome 的启动参数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--enable-net-benchmarking&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在Mac上通过下面几行命令就可以解决：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mv &quot;Google Chrome&quot; Google.real
touch &quot;Google Chrome&quot;
printf &apos;#!/bin/bash\ncd &quot;/Applications/Google Chrome.app/Contents/MacOS&quot;\n&quot;/Applications/Google Chrome.app/Contents/MacOS/Google.real&quot; --enable-net-benchmarking &quot;$@&quot;\n&apos; &amp;gt; &quot;Google Chrome&quot;
chmod 755 &quot;Google Chrome&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大功告成。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>为 Go module 搭建私服</title>
   <link href="https://blog.cyeam.com/golang/2018/09/27/athens"/>
   <updated>2018-09-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/09/27/athens</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#私服安装&quot; id=&quot;markdown-toc-私服安装&quot;&gt;私服安装&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#启动&quot; id=&quot;markdown-toc-启动&quot;&gt;启动&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#打包脚本&quot; id=&quot;markdown-toc-打包脚本&quot;&gt;打包脚本&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#goproxy&quot; id=&quot;markdown-toc-goproxy&quot;&gt;GOPROXY&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#get-goproxyvlist-返回所有已知的当前-module-的版本号每行一条&quot; id=&quot;markdown-toc-get-goproxyvlist-返回所有已知的当前-module-的版本号每行一条&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/list 返回所有已知的当前 module 的版本号，每行一条&lt;/module&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#get-goproxyvinfo-返回-json-格式的版本元数据&quot; id=&quot;markdown-toc-get-goproxyvinfo-返回-json-格式的版本元数据&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/&lt;version&gt;.info 返回 JSON 格式的版本元数据&lt;/version&gt;&lt;/module&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#get-goproxyvmod-返回这个-module-版本的-gomod-文件&quot; id=&quot;markdown-toc-get-goproxyvmod-返回这个-module-版本的-gomod-文件&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/&lt;version&gt;.mod 返回这个 module 版本的 go.mod 文件&lt;/version&gt;&lt;/module&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#get-goproxyvzip-返回这个-module-对应版本的-zip-压缩包&quot; id=&quot;markdown-toc-get-goproxyvzip-返回这个-module-对应版本的-zip-压缩包&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/&lt;version&gt;.zip 返回这个 module 对应版本的 zip 压缩包。&lt;/version&gt;&lt;/module&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#athens-的实现&quot; id=&quot;markdown-toc-athens-的实现&quot;&gt;Athens 的实现&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;之前介绍了&lt;a href=&quot;https://blog.cyeam.com/golang/2018/09/18/go111-pack&quot;&gt;如何在线上环境打包&lt;/a&gt;，这样能解决问题，但是由于是借助代理下载依赖包，打包的过程偏慢，我自己的感觉打一个项目需要2分钟所有。而且还有一个严重问题，那就是一旦下载失败，打包就失败了，还得重试。这个体验很不好。关于 Go module 打包，我感觉未来的发展方向还是和 Java 的一样，得自己整私服，这样打包会很快，而且也安全。我们打包从私服下载，私服如果缓存了当前版本的包，直接返回；否则私服去下载对应版本的代码。&lt;/p&gt;

&lt;h3 id=&quot;私服安装&quot;&gt;私服安装&lt;/h3&gt;

&lt;p&gt;首先你需要安装 Go1.11。&lt;/p&gt;

&lt;p&gt;我用的是&lt;a href=&quot;https://github.com/gomods/athens/&quot;&gt;Athens&lt;/a&gt;，雅典娜。&lt;/p&gt;

&lt;p&gt;首先下载代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/gomods/athens
cd athens
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;即使用私服还是得设置代理：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export HTTP_PROXY=10.244.255.3:7766
export HTTPS_PROXY=10.244.255.3:7766
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译安装二进制文件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd cmd/proxy
go install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&quot;启动&quot;&gt;启动&lt;/h3&gt;

&lt;p&gt;因为是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go install&lt;/code&gt;安装，所以会被安到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GOBIN&lt;/code&gt;里，它会是全局的可以直接调用。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./proxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是这样太简陋了，我用 &lt;a href=&quot;https://supervisord.org/installing.html&quot;&gt;Supervisor&lt;/a&gt; 来做进程守护，配置文件如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[program:proxy]
command=/path/to/proxy -config_file=/path/to/github.com/gomods/athens/config.dev.toml
environment=HTTP_PROXY=&quot;10.244.255.3:7766&quot;,HTTPS_PROXY=&quot;10.244.255.3:7766&quot;
stdout_logfile=/tmp/proxy.log
stderr_logfile=/tmp/proxy.log
autostart=true
autorestart=true
startsecs=5
priority=1
stopasgroup=true
illasgroup=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;打包脚本&quot;&gt;打包脚本&lt;/h3&gt;

&lt;p&gt;之前是四行脚本，这次变了两行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export GO111MODULE=on
export GOPROXY=http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打包开始后，私服的日志能看到类似于这样的日志：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;handler: GET /github.com/spf13/afero/@v/v1.1.1.zip [200]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而打包日志是这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go: downloading github.com/spf13/pflag v1.0.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果是第一次下载，会有可能超时：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go: gopkg.in/tomb.v1@v1.0.0-20141024135613-dd632973f1e7: unexpected status (http://10.244.255.3:7766/gopkg.in/tomb.v1/@v/v1.0.0-20141024135613-dd632973f1e7.info): 500 Internal Server Error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样没事，稍等一会就会好，可以把上面的链接放到浏览器里面刷一下，能刷出来结果那说明下载好了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
	&quot;Version&quot;: &quot;v1.0.0-20141024135613-dd632973f1e7&quot;,
	&quot;Time&quot;: &quot;2014-10-24T13:56:13Z&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;goproxy&quot;&gt;GOPROXY&lt;/h3&gt;

&lt;p&gt;其实最核心的是上面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOPROXY&lt;/code&gt;，这个是 Go 官方的代理设置，和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP_PROXY&lt;/code&gt;不一样哦。&lt;/p&gt;

&lt;p&gt;可以使用命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go help goproxy&lt;/code&gt;查看详细介绍，也可以看&lt;a href=&quot;https://golang.org/pkg/cmd/go/internal/help/&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Go module 支持通过代理的方式下载，如果环境变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOPROXY&lt;/code&gt;设置了，所有的包都会从这个代理下载。&lt;/p&gt;

&lt;p&gt;代理基于 HTTP 协议的 GET 方法，请求的时候没有参数，所以只要是符合固定的规则，任何服务器都可以做代理服务器。比如一个静态文件服务器。&lt;/p&gt;

&lt;p&gt;规则是：&lt;/p&gt;

&lt;h4 id=&quot;get-goproxyvlist-返回所有已知的当前-module-的版本号每行一条&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/list 返回所有已知的当前 module 的版本号，每行一条&lt;/module&gt;&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /github.com/mnhkahn/gogogo/@v/list
v1.0.0
v1.0.1
v1.0.2
v1.0.3
v1.0.4
v1.0.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;get-goproxyvinfo-返回-json-格式的版本元数据&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/&lt;version&gt;.info 返回 JSON 格式的版本元数据&lt;/version&gt;&lt;/module&gt;&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /github.com/mnhkahn/gogogo/@v/v1.0.5.info
{
	&quot;Version&quot;: &quot;v1.0.5&quot;,
	&quot;Time&quot;: &quot;2018-09-26T02:47:43Z&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;元数据的 Go 结构体定义：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Info struct {
    Version string    // version string
    Time    time.Time // commit time
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;get-goproxyvmod-返回这个-module-版本的-gomod-文件&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/&lt;version&gt;.mod 返回这个 module 版本的 go.mod 文件&lt;/version&gt;&lt;/module&gt;&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /github.com/mnhkahn/gogogo/@v/v1.0.5.mod
module github.com/mnhkahn/gogogo

require (
	github.com/BurntSushi/toml v0.3.0 // indirect
	github.com/ChimeraCoder/gojson v1.0.0
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/magiconair/properties v1.8.0
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/sasbury/mini v0.0.0-20161224193750-64bd399395db
	github.com/stretchr/testify v1.2.2
	golang.org/x/net v0.0.0-20180826012351-8a410e7b638d
	gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3
	gopkg.in/yaml.v2 v2.2.1 // indirect
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;get-goproxyvzip-返回这个-module-对应版本的-zip-压缩包&quot;&gt;GET $GOPROXY/&lt;module&gt;/@v/&lt;version&gt;.zip 返回这个 module 对应版本的 zip 压缩包。&lt;/version&gt;&lt;/module&gt;&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /github.com/mnhkahn/gogogo/@v/v1.0.5.mod
v1.0.5.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;所有的包名会被编码成小写。如果有大写字母，前面加感叹号。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;github.com/Azure =&amp;gt; github.com/!azure
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;athens-的实现&quot;&gt;Athens 的实现&lt;/h3&gt;

&lt;p&gt;下载的时候会读取这些环境变量：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func PrepareEnv(gopath string) []string {
	pathEnv := fmt.Sprintf(&quot;PATH=%s&quot;, os.Getenv(&quot;PATH&quot;))
	httpProxy := fmt.Sprintf(&quot;HTTP_PROXY=%s&quot;, os.Getenv(&quot;HTTP_PROXY&quot;))
	httpsProxy := fmt.Sprintf(&quot;HTTPS_PROXY=%s&quot;, os.Getenv(&quot;HTTPS_PROXY&quot;))
	noProxy := fmt.Sprintf(&quot;NO_PROXY=%s&quot;, os.Getenv(&quot;NO_PROXY&quot;))
	gopathEnv := fmt.Sprintf(&quot;GOPATH=%s&quot;, gopath)
	cacheEnv := fmt.Sprintf(&quot;GOCACHE=%s&quot;, filepath.Join(gopath, &quot;cache&quot;))
	disableCgo := &quot;CGO_ENABLED=0&quot;
	enableGoModules := &quot;GO111MODULE=on&quot;
	...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP_PROXY&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTPS_PROXY&lt;/code&gt;是私服下载包的时候会用到的代理设置，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NO_PROXY&lt;/code&gt;可以加不走代理的白名单：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export NO_PROXY=gopkg.in,$NO_PROXY
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这些环境变量会被作为临时环境变量用于代码的下载。而下载依赖包的逻辑：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cmd := exec.Command(goBinaryName, &quot;mod&quot;, &quot;download&quot;, fullURI)
cmd.Env = PrepareEnv(gopath)
cmd.Dir = repoRoot
o, err := cmd.CombinedOutput()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;实际执行时就是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP_PROXY=10.244.255.3:7766 HTTPS_PROXY=10.244.255.3:7766 GOPATH=/tmp/athens167327692 GOCACHE=/tmp/athens167327692/cache go mod download golang.org/x/text@v0.3.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>增加4 行代码，实现使用 Go module 在线上环境打包</title>
   <link href="https://blog.cyeam.com/golang/2018/09/18/go111-pack"/>
   <updated>2018-09-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/09/18/go111-pack</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#初始化&quot; id=&quot;markdown-toc-初始化&quot;&gt;初始化&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#下载外网代码&quot; id=&quot;markdown-toc-下载外网代码&quot;&gt;下载外网代码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#下载内网私有代码&quot; id=&quot;markdown-toc-下载内网私有代码&quot;&gt;下载内网私有代码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#详细说说-insteadof&quot; id=&quot;markdown-toc-详细说说-insteadof&quot;&gt;详细说说 insteadOf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#总结&quot; id=&quot;markdown-toc-总结&quot;&gt;总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;初始化&quot;&gt;初始化&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go mod init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;把生成好的 go.mod 和 go.sum 提交到代码仓库。&lt;/p&gt;

&lt;p&gt;如果要开启 module，需要设置环境变量：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export GO111MODULE=&quot;on&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;下载外网代码&quot;&gt;下载外网代码&lt;/h3&gt;

&lt;p&gt;执行打包脚本，发现报了一堆报错：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go: golang.org/x/sys@v0.0.0-20180909124046-d0be0721c37e: unrecognized import path &quot;golang.org/x/sys&quot; (https fetch: Get https://golang.org/x/sys?go-get=1: dial tcp 216.239.37.1:443: connect: connection timed out)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;下载超时，这也是能够预料到的。不过可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;replace&lt;/code&gt;来解决，比如上面的 Go 扩展包，它没法下载，而它正好在 GitHub 上面有代码镜像，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;replace&lt;/code&gt;就可以到 GitHub 上面下载了。详细用法可以参考&lt;a href=&quot;https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive&quot;&gt;When should I use the replace directive?&lt;/a&gt;。不过我并没有用这个方法，这里就不展开说了。&lt;/p&gt;

&lt;p&gt;我的方法是使用代理。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export http_proxy=10.244.255.3:7766
export https_proxy=10.244.255.3:7766
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;下载内网私有代码&quot;&gt;下载内网私有代码&lt;/h3&gt;

&lt;p&gt;再次执行打包脚本，因为有了代理，刚才的报错没有了，不过又有了新的报错：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd .; git clone https://github.com/examplesite/myprivaterepo /Users/tom/go/src/github.com/examplesite/myprivaterepo
Cloning into &apos;/Users/tom/go/src/github.com/examplesite/myprivaterepo&apos;...
fatal: could not read Username for &apos;https://github.com&apos;: terminal prompts disabled
package github.com/examplesite/myprivaterepo: exit status 128
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个报错也不难看懂，下载内网包的时候没有授权，需要输入密码，而 Go 会屏蔽输入密码的操作，导致下载失败。&lt;/p&gt;

&lt;p&gt;打开密码输入也很简单：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;env GIT_TERMINAL_PROMPT=1 go get xxxx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不过这个并不是我想要的方法。一个原因是通过打包脚本输密码不方便，还有一个原因是把开发者个人密码写到打包脚本里也并不合适。&lt;/p&gt;

&lt;p&gt;Git 支持两种授权方式，一种是 HTTPS，一种是 SSH。刚才的问题就是通过 HTTPS 下载代码的时候引起的，而通过 SSH 校验权限的话并不需要密码。那么问题来了，为什么&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;是通过 HTTPS 下载代码而不是 SSH 呢？&lt;/p&gt;

&lt;p&gt;Go 官方也给出了解释：&lt;a href=&quot;https://golang.org/doc/faq#git_https&quot;&gt;Why does “go get” use HTTPS when cloning a repository? &lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;公司一般喜欢只暴露80端口（HTTP）和433端口（HTTPS），其它端口会被屏蔽，包括9418（git）和22（SSH）。当使用 HTTPS 而不是 HTTP 协议时，git 会强制执行证书验证，预防中间人、窃听和篡改攻击。所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;使用 HTTPS 协议来保证安全。&lt;/p&gt;

&lt;p&gt;其实还有一个原因，Github 本身也推荐使用 HTTPS，大家去克隆代码的时候默认的连接都是 HTTPS 的。&lt;/p&gt;

&lt;p&gt;如果是通过 HTTPS 下载私有代码，可以在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HOME/.netrc&lt;/code&gt;文件中配置密码用于授权。如果是 GitHub 用户，密码可以是&lt;a href=&quot;https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/&quot;&gt;access token&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;machine github.com login USERNAME password APIKEY
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;也是可以通过 SSH 协议实现授权的。git 也支持替换的功能，可以把 HTTPS 替换成 SSH。比如要访问 Github 的私有仓库，可以把下面的代码放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt;里面：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[url &quot;ssh://git@github.com/&quot;]
	insteadOf = https://github.com/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也可以通过一条命令实现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config --global url.&quot;git@git.cyeam.com:&quot;.insteadOf &quot;https://code.cyeam.com/&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SSH 授权需要用到私钥，这里就不展开讲了。默认情况下是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HOME/.ssh/id_rsa&lt;/code&gt;的文件。&lt;/p&gt;

&lt;h3 id=&quot;详细说说-insteadof&quot;&gt;详细说说 insteadOf&lt;/h3&gt;

&lt;p&gt;如果你都没有填错的话，代码就可以正常下载并编译了。&lt;/p&gt;

&lt;p&gt;但是我实际操作的时候真是费了特别大的劲，网上的搜索结果都是上面的一条替换命令，再也没有多说了，但是有一个符号错了就会有问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;insteadOf&lt;/code&gt;其实就是字符串替换&lt;/em&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;比方说我的仓库，HTTPS 连接：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://github.com/mnhkahn/gogogo.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SSH 连接：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git@github.com:mnhkahn/gogogo.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这两个协议区别就是一个是 https 开头，而一个是 git（注意：是git，不是你的用户名）。中间是域名，自己的私有仓库就换成仓库的域名。后面的仓库地址，这部分是完全一样的。而域名和仓库地址中间的符号，这里也有区别，一个是斜线，一个是冒号。&lt;/p&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;在编译脚本里加下面的4行代码，就能通过 Go1.11 实现自动下载依赖包了，代理自己搭哦。推荐&lt;a href=&quot;https://portal.shadowsocks.to/aff.php?aff=5842&quot;&gt;这个&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export GO111MODULE=&quot;on&quot;
export http_proxy=10.244.255.3:7766
export https_proxy=10.244.255.3:7766

# 内网似有包需要通过ssh下载
git config --global url.&quot;git@git.cyeam.com:&quot;.insteadOf &quot;https://code.cyeam.com/&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>fmt 源码分析——fmt 如何进行格式化？</title>
   <link href="https://blog.cyeam.com/golang/2018/09/10/fmt"/>
   <updated>2018-09-10T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/09/10/fmt</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#format&quot; id=&quot;markdown-toc-format&quot;&gt;format&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#verb-占位符&quot; id=&quot;markdown-toc-verb-占位符&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verb&lt;/code&gt; 占位符。&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#宽度&quot; id=&quot;markdown-toc-宽度&quot;&gt;宽度&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#精度&quot; id=&quot;markdown-toc-精度&quot;&gt;精度&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#标记&quot; id=&quot;markdown-toc-标记&quot;&gt;标记&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fmtstate-和-fmtformatter&quot; id=&quot;markdown-toc-fmtstate-和-fmtformatter&quot;&gt;fmt.State 和 fmt.Formatter&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#常见类型的格式化方法&quot; id=&quot;markdown-toc-常见类型的格式化方法&quot;&gt;常见类型的格式化方法&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#指针-p类型-t&quot; id=&quot;markdown-toc-指针-p类型-t&quot;&gt;指针 %p，类型 %T&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#数字&quot; id=&quot;markdown-toc-数字&quot;&gt;数字&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#万能通用格式v&quot; id=&quot;markdown-toc-万能通用格式v&quot;&gt;万能通用格式，%v&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#异常&quot; id=&quot;markdown-toc-异常&quot;&gt;异常&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fmtstringer&quot; id=&quot;markdown-toc-fmtstringer&quot;&gt;fmt.Stringer&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#一个-fmtformatter-例子&quot; id=&quot;markdown-toc-一个-fmtformatter-例子&quot;&gt;一个 fmt.Formatter 例子&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#完整流程&quot; id=&quot;markdown-toc-完整流程&quot;&gt;完整流程&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#格式解析把fmtstate接口要用到的数据解析完成&quot; id=&quot;markdown-toc-格式解析把fmtstate接口要用到的数据解析完成&quot;&gt;格式解析，把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.State&lt;/code&gt;接口要用到的数据解析完成&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#在func-p-pp-printargarg-interface-verb-rune中进行格式化转换编码&quot; id=&quot;markdown-toc-在func-p-pp-printargarg-interface-verb-rune中进行格式化转换编码&quot;&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func (p *pp) printArg(arg interface{}, verb rune)&lt;/code&gt;中进行格式化转换编码；&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#如果对象值是空直接打印&quot; id=&quot;markdown-toc-如果对象值是空直接打印&quot;&gt;如果对象值是空，直接打印&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#如果是指针或者类型格式化调用反射实现&quot; id=&quot;markdown-toc-如果是指针或者类型格式化调用反射实现&quot;&gt;如果是指针或者类型格式化，调用反射实现&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#格式化数据&quot; id=&quot;markdown-toc-格式化数据&quot;&gt;格式化数据&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#默认转换策略-pprintvaluereflectvalueoff-verb-0&quot; id=&quot;markdown-toc-默认转换策略-pprintvaluereflectvalueoff-verb-0&quot;&gt;默认转换策略 p.printValue(reflect.ValueOf(f), verb, 0)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#总结&quot; id=&quot;markdown-toc-总结&quot;&gt;总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;format&quot;&gt;format&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt&lt;/code&gt;包虽然不建议用来打印日志，但是格式化字符串确实是必不可少的，比如打印日志的时候。先详细介绍一下格式化的格式&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format&lt;/code&gt;由百分号&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt;开始，后面的部分可以分为四部分：&lt;/p&gt;

&lt;h4 id=&quot;verb-占位符&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verb&lt;/code&gt; 占位符。&lt;/h4&gt;

&lt;p&gt;完整的格式可以参考&lt;a href=&quot;https://golang.org/pkg/fmt/#hdr-Printing&quot;&gt;Go 文档&lt;/a&gt;，下面我大概列几个：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%v  通过默认格式打印
%t  用于布尔类型，打印true或者false
%d  以10进制格式打印数字
%c  将数据转换成 Unicode 里面的字符打印
%x  以16进制格式打印数字
%e  科学计数法表示
%f  以10进制表示浮点数
%s  字符串
%p  指针，以0x开头的16进制地址
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还有Go语言自己定义的类型：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%#v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;宽度&quot;&gt;宽度&lt;/h4&gt;

&lt;p&gt;比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%3c&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt;是占位符，表示把整数转成 Unicode 字符展示，而前面的3就是宽度了。&lt;/p&gt;

&lt;p&gt;源码如下，看到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num = num*10 + int(s[newi]-&apos;0&apos;)&lt;/code&gt;很熟悉有没有，就是一个把字符转成整形的方法。那么&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%3c&lt;/code&gt;返回的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num&lt;/code&gt;就是3了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
	if start &amp;gt;= end {
		return 0, false, end
	}
	for newi = start; newi &amp;lt; end &amp;amp;&amp;amp; &apos;0&apos; &amp;lt;= s[newi] &amp;amp;&amp;amp; s[newi] &amp;lt;= &apos;9&apos;; newi++ {
		if tooLarge(num) {
			return 0, false, end // Overflow; crazy long number most likely.
		}
		num = num*10 + int(s[newi]-&apos;0&apos;)
		isnum = true
	}
	return
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打印下面的语句：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fmt.Printf(&quot;%3c\n&quot;, &apos;a&apos;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;控制输出3位，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;输出占用一位，前面需要补两个0。在源码中，是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pad&lt;/code&gt;实现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (f *fmt) pad(b []byte) {
	...
	width := f.wid - utf8.RuneCount(b)
	if !f.minus {
		// left padding
		f.writePadding(width)
		f.buf.Write(b)
	} else {
		// right padding
		f.buf.Write(b)
		f.writePadding(width)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面的代码&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width&lt;/code&gt;就是2，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writePadding&lt;/code&gt;会打印对应宽度的空格。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;如果打印的内容很长，比如有10位，而宽度只设置了3位，会展示完整的数字还是只显示3位呢？&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;答案是完整展示，当&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt;小于等于0，直接返回，而打印内容方面，不受影响：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (f *fmt) writePadding(n int) {
	if n &amp;lt;= 0 { // No padding bytes needed.
		return
	}
	...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;精度&quot;&gt;精度&lt;/h4&gt;

&lt;p&gt;比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%3.2f&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt;是占位符，表示浮点数展示。3表示宽度，而小数点后面的2则是精度。精度在浮点数的格式化中会用到。精度的控制是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv&lt;/code&gt;包的字符串转换函数来实现的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;标记&quot;&gt;标记&lt;/h4&gt;

&lt;p&gt;除了宽度和精度，还有标记可以用来控制输出。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+      总打印数值的正负号；对于%q（%+q）保证只输出ASCII编码的字符。 
-      在右侧而非左侧填充空格（左对齐该区域）
#      备用格式：为八进制添加前导 0（%#o）      Printf(&quot;%#U&quot;, &apos;中&apos;)      U+4E2D
       为十六进制添加前导 0x（%#x）或 0X（%#X），为 %p（%#p）去掉前导 0x；
       如果可能的话，%q（%#q）会打印原始 （即反引号围绕的）字符串；
       如果是可打印字符，%U（%#U）会写出该字符的
       Unicode 编码形式（如字符 x 会被打印成 U+0078 &apos;x&apos;）。
&apos; &apos;    (空格)为数值中省略的正负号留出空白（% d）；
       以十六进制（% x, % X）打印字符串或切片时，在字节之间用空格隔开
0      填充前导的0而非空格；对于数字，这会将填充移到正负号之后
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;fmtstate-和-fmtformatter&quot;&gt;fmt.State 和 fmt.Formatter&lt;/h4&gt;

&lt;p&gt;上面提到的占位符、宽度、精度和标记，除了占位符，剩下的3个在解析后被保存到了接口&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.State&lt;/code&gt;里面。这个接口还增加了一个函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;用于写入数据。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type State interface {
	// Write is the function to call to emit formatted output to be printed.
	Write(b []byte) (n int, err error)
	// Width returns the value of the width option and whether it has been set.
	Width() (wid int, ok bool)
	// Precision returns the value of the precision option and whether it has been set.
	Precision() (prec int, ok bool)

	// Flag reports whether the flag c, a character, has been set.
	Flag(c int) bool
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;它会在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Formatter&lt;/code&gt;接口中被用到。参数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt;就是占位符，这些终于都凑齐了。这个接口用来自定义格式化方法，你可以在自己的结构体中实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Format&lt;/code&gt;函数来实现自动调用解析。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Formatter interface {
	Format(f State, c rune)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;常见类型的格式化方法&quot;&gt;常见类型的格式化方法&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func (p *pp) printArg(arg interface{}, verb rune)&lt;/code&gt;是底层真正进行转换的函数。&lt;/p&gt;

&lt;h4 id=&quot;指针-p类型-t&quot;&gt;指针 %p，类型 %T&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (p *pp) printArg(arg interface{}, verb rune) {
	...
	// Special processing considerations.
	// %T (the value&apos;s type) and %p (its address) are special; we always do them first.
	switch verb {
	case &apos;T&apos;:
		p.fmt.fmtS(reflect.TypeOf(arg).String())
		return
	case &apos;p&apos;:
		p.fmtPointer(reflect.ValueOf(arg), &apos;p&apos;)
		return
	}
	...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于类型和指针的转换，有现成的方法调用，而这两个转换都是通过反射实现。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;这里并没有判断是否调用用户自定义的 Format 函数，说明所有类型打印内存地址和类型都只能通过上面的代码实现，不能自定义。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;数字&quot;&gt;数字&lt;/h4&gt;

&lt;p&gt;数字支持多种进制，16进制、8进制、4进制、2进制、10进制。在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmtInteger&lt;/code&gt;中通过求余法实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;switch base {
	case 10:
		for u &amp;gt;= 10 {
			i--
			next := u / 10
			buf[i] = byte(&apos;0&apos; + u - next*10)
			u = next
		}
	...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;万能通用格式v&quot;&gt;万能通用格式，%v&lt;/h4&gt;

&lt;p&gt;万能格式其实也有映射关系：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int, int8 etc.:          %d
uint, uint8 etc.:        %d, %#x if printed with %#v
float32, complex64, etc: %g
string:                  %s
chan:                    %p
pointer:                 %p
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一般结构体会用到这种打印方式。如果是结构体：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if p.fmt.sharpV {
	p.buf.WriteString(f.Type().String())
}
p.buf.WriteByte(&apos;{&apos;)
for i := 0; i &amp;lt; f.NumField(); i++ {
	if i &amp;gt; 0 {
		if p.fmt.sharpV {
			p.buf.WriteString(commaSpaceString)
		} else {
			p.buf.WriteByte(&apos; &apos;)
		}
	}
	if p.fmt.plusV || p.fmt.sharpV {
		if name := f.Type().Field(i).Name; name != &quot;&quot; {
			p.buf.WriteString(name)
			p.buf.WriteByte(&apos;:&apos;)
		}
	}
	p.printValue(getField(f, i), verb, depth+1)
}
p.buf.WriteByte(&apos;}&apos;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过反射拿到字段 Field 和内容，如果格式是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%+v&lt;/code&gt;，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p.fmt.plusV&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;，这样会打印字段名称。&lt;/p&gt;

&lt;h4 id=&quot;异常&quot;&gt;异常&lt;/h4&gt;

&lt;p&gt;转换的时候还会有异常捕获，这个在 Go 源码中不多见：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;defer p.catchPanic(p.arg, verb)
p.fmtString(v.String(), verb)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果在转换的时候发生异常&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;panic&lt;/code&gt;，并不会发生异常，转换后的结果会是这个样子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type data struct {
	A string
	B int
}

func (d *data) String() string {
	panic(&quot;implement me&quot;)
}

func main() {
	d := &amp;amp;data{&quot;1&quot;, 2}
	fmt.Printf(&quot;%s\n&quot;, d) // prints: %!s(PANIC=implement me)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%!s(PANIC=implement me)&lt;/code&gt;，会有 PANIC 的字样。还有一个地方很有趣，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String()&lt;/code&gt;方法并没有按要求返回字符串，只有一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;panic&lt;/code&gt;，这样可以编译过。&lt;/p&gt;

&lt;h4 id=&quot;fmtstringer&quot;&gt;fmt.Stringer&lt;/h4&gt;

&lt;p&gt;顺道介绍一下&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stringer&lt;/code&gt;接口，上面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;对象就实现了这个方法。如果是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%s&lt;/code&gt;打印，或者直接调用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Println&lt;/code&gt;，这时候会判断这个对象是否实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stringer&lt;/code&gt;接口，如果实现了，就调用对象的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;方法，上一节的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;就是这个例子。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Stringer interface {
	String() string
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;一个-fmtformatter-例子&quot;&gt;一个 fmt.Formatter 例子&lt;/h4&gt;

&lt;p&gt;还是针对上面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;类型，我实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Formatter&lt;/code&gt;接口：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (d *data) Format(f fmt.State, c rune) {
	switch c {
	case &apos;v&apos;: // &amp;amp;{1 2}
		buf, err := json.Marshal(d)
		if err != nil {
			panic(err)
		}
		f.Write(buf)
	case &apos;s&apos;:
		f.Write([]byte(d.String()))
	case &apos;x&apos;, &apos;X&apos;:
		//case &apos;p&apos;:
		v := reflect.ValueOf(d)
		f.Write([]byte{&apos;(&apos;})
		f.Write([]byte(v.Type().String()))
		f.Write([]byte{&apos;)&apos;, &apos;(&apos;})
		u := v.Pointer()
		f.Write([]byte(strconv.FormatUint(uint64(u), 16)))
		f.Write([]byte{&apos;)&apos;})
	default:
		f.Write([]byte(&quot;http://cyeam.com&quot;))
	}
}

d := &amp;amp;data{&quot;1&quot;, 2}
fmt.Printf(&quot;v %v\n&quot;, d)
fmt.Printf(&quot;s %s\n&quot;, d)
fmt.Printf(&quot;p %p\n&quot;, d)
fmt.Printf(&quot;T %T\n&quot;, d)
fmt.Printf(&quot;b %b\n&quot;, d)
fmt.Printf(&quot;o %o\n&quot;, d)
fmt.Printf(&quot;x %x\n&quot;, d)
fmt.Printf(&quot;d %d\n&quot;, d)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果如下：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;v {“A”:”1”,”B”:2}
s {“A”:”1”,”B”:2}
p 0xc00006c020
T &lt;em&gt;main.data
b http://cyeam.com
o http://cyeam.com
x (&lt;/em&gt;main.data)(c00006c020)
d http://cyeam.com&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;o&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt;我没有实现，所以返回的是一个默认值；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v&lt;/code&gt;是返回的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;编码；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;在前面也介绍了，它并不会调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Format&lt;/code&gt;，所以虽然我并没有实现这两个占位符，但是结果是对的；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;手写一个基于反射的实现，能返回变量名称和地址。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;完整流程&quot;&gt;完整流程&lt;/h3&gt;

&lt;h4 id=&quot;格式解析把fmtstate接口要用到的数据解析完成&quot;&gt;格式解析，把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.State&lt;/code&gt;接口要用到的数据解析完成&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (p *pp) doPrintf(format string, a []interface{}) {
	...
	// Do we have flags?
	// 解析格式串中的标记
	for ; i &amp;lt; end; i++ {
		c := format[i]
		switch c {
		case &apos;#&apos;:
			p.fmt.sharp = true
		case &apos;0&apos;:
			p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
		case &apos;+&apos;:
			p.fmt.plus = true
		case &apos;-&apos;:
			p.fmt.minus = true
			p.fmt.zero = false // Do not pad with zeros to the right.
		case &apos; &apos;:
			p.fmt.space = true
		default:
	}
	...
	// Do we have width?
	if i &amp;lt; end &amp;amp;&amp;amp; format[i] == &apos;*&apos; {
	...
	} else {
		// 解析了格式串中的宽度内容
		p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end)
		if afterIndex &amp;amp;&amp;amp; p.fmt.widPresent { // &quot;%[3]2d&quot;
			p.goodArgNum = false			
		}
	}
	...
	// Do we have precision?
	if i+1 &amp;lt; end &amp;amp;&amp;amp; format[i] == &apos;.&apos; {
		...
		// 解析了格式串中的精度内容
		p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end)
		...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;在func-p-pp-printargarg-interface-verb-rune中进行格式化转换编码&quot;&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func (p *pp) printArg(arg interface{}, verb rune)&lt;/code&gt;中进行格式化转换编码；&lt;/h4&gt;

&lt;h4 id=&quot;如果对象值是空直接打印&quot;&gt;如果对象值是空，直接打印&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if arg == nil {
	switch verb {
	case &apos;T&apos;, &apos;v&apos;:
		p.fmt.padString(nilAngleString)
	default:
		p.badVerb(verb)
	}
	return
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;如果是指针或者类型格式化调用反射实现&quot;&gt;如果是指针或者类型格式化，调用反射实现&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;switch verb {
case &apos;T&apos;:
	p.fmt.fmtS(reflect.TypeOf(arg).String())
	return
case &apos;p&apos;:
	p.fmtPointer(reflect.ValueOf(arg), &apos;p&apos;)
	return
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;格式化数据&quot;&gt;格式化数据&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;switch f := arg.(type) {
	case bool:
		p.fmtBool(f, verb)
	case float32:
		p.fmtFloat(float64(f), 32, verb)
	case float64:
	...
	default:
		if !p.handleMethods(verb) {
			// Need to use reflection, since the type had no
			// interface methods that could be used for formatting.
			p.printValue(reflect.ValueOf(f), verb, 0)
		}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;每种内置类型都有自己的格式化实现，这样就&lt;strong&gt;&lt;em&gt;避免了反射&lt;/em&gt;&lt;/strong&gt;；&lt;/li&gt;
  &lt;li&gt;如果不是内置类型，判断是否实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Formatter&lt;/code&gt;接口，如果实现了调用此接口；&lt;/li&gt;
  &lt;li&gt;如果需要转成字符串，而对象实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stringer&lt;/code&gt;接口，调用其&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;方法转换；&lt;/li&gt;
  &lt;li&gt;上面两个逻辑在函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func (p *pp) handleMethods(verb rune) (handled bool)&lt;/code&gt;中，如果能通过接口实现转换，返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;并格式化数据，否则返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;；(其实还有一些细节的逻辑，例如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GoStringer&lt;/code&gt;，我就不展开细说了)&lt;/li&gt;
  &lt;li&gt;如果通过上面的转换失败，则需要使用默认转换策略。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;默认转换策略-pprintvaluereflectvalueoff-verb-0&quot;&gt;默认转换策略 p.printValue(reflect.ValueOf(f), verb, 0)&lt;/h4&gt;

&lt;p&gt;默认转换就是通过反射实现，以结构体为例，如果反射出来是结构体，那就遍历所有字段打印，逻辑和上面提到的万能转换里提到的差不多。&lt;/p&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;从格式化的完整流程中可以发现，底层格式化算法是有对性能优化的，那就是通过对每种内置对象单独编写格式化实现来规避反射来提高性能。&lt;/p&gt;

&lt;p&gt;实际工作中经常需要对系统内复杂结构进行格式化，那么为这些对象实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Formatter&lt;/code&gt;接口也算是一种提升性能的有效方式。&lt;/p&gt;

&lt;p&gt;本文涉及的完整代码请看&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/formatter/formatter.go&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>godoc 介绍以及 Golang 注释规范</title>
   <link href="https://blog.cyeam.com/golang/2018/09/03/godoc"/>
   <updated>2018-09-03T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/09/03/godoc</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#命令&quot; id=&quot;markdown-toc-命令&quot;&gt;命令&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#代码中注释生成文档&quot; id=&quot;markdown-toc-代码中注释生成文档&quot;&gt;代码中注释生成文档&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#package&quot; id=&quot;markdown-toc-package&quot;&gt;Package&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#变量和函数&quot; id=&quot;markdown-toc-变量和函数&quot;&gt;变量和函数&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#bug&quot; id=&quot;markdown-toc-bug&quot;&gt;BUG&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#deprecated&quot; id=&quot;markdown-toc-deprecated&quot;&gt;Deprecated&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#链接-url-自动转成-html-的-a-标签&quot; id=&quot;markdown-toc-链接-url-自动转成-html-的-a-标签&quot;&gt;链接 URL 自动转成 HTML 的 a 标签&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#注释自动生成&quot; id=&quot;markdown-toc-注释自动生成&quot;&gt;注释自动生成&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#docgo&quot; id=&quot;markdown-toc-docgo&quot;&gt;doc.go&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#标题和段落&quot; id=&quot;markdown-toc-标题和段落&quot;&gt;标题和段落：&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#代码&quot; id=&quot;markdown-toc-代码&quot;&gt;代码&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#example_packagename_testgo&quot; id=&quot;markdown-toc-example_packagename_testgo&quot;&gt;example_PackageName_test.go&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#最后&quot; id=&quot;markdown-toc-最后&quot;&gt;最后&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;命令&quot;&gt;命令&lt;/h3&gt;

&lt;p&gt;golang 官方有有文档自动生成网站，地址是 godoc.org，比如：logger 的&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/gogogo/logger&quot;&gt;文档&lt;/a&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;godoc&lt;/code&gt;也可以在本地启动：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;godoc -http=:6060
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动之后浏览器访问 localhost:6060，就能看到文档首页了。如果想看自己代码的文档，后面输入包的路径即可，比如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http://localhost:6060/pkg/github.com/mnhkahn/gogogo/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;代码或者注释文档的修改可以实时更新，不需要重启服务。&lt;/p&gt;

&lt;h3 id=&quot;代码中注释生成文档&quot;&gt;代码中注释生成文档&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;godoc&lt;/code&gt;支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;var&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func&lt;/code&gt;这些代码生成文档，而且只会对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public&lt;/code&gt;变量(首字母大写)自动生成，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private&lt;/code&gt;变量则不会。&lt;/p&gt;

&lt;h4 id=&quot;package&quot;&gt;Package&lt;/h4&gt;

&lt;p&gt;在自动生成的时候，比如说对于包，&lt;strong&gt;&lt;em&gt;对包介绍的内容用的就是包名上面的注释&lt;/em&gt;&lt;/strong&gt;（变量和函数同理）。如果是多个包，就会把多个包的注释放在一起，按照文件名字母顺序排序。&lt;/p&gt;

&lt;p&gt;如果多个注释中间有一个空行，那么&lt;strong&gt;&lt;em&gt;只会算挨着变量位置的注释&lt;/em&gt;&lt;/strong&gt;，其它的会被丢弃。&lt;/p&gt;

&lt;p&gt;包生成的内容会放到文档的「Overview」里面。举个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// package gogogo

/*
A web framework includes app server, logger, panicer, util and so on.
 */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-175717.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;变量和函数&quot;&gt;变量和函数&lt;/h4&gt;

&lt;p&gt;和包类似，常量会放到「Constants」里，变量会放到「Variables」里，后面跟的是函数。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-180159.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;bug&quot;&gt;BUG&lt;/h4&gt;

&lt;p&gt;如果代码中有 bug，可以使用注释：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BUG(who): xxx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;它会被识别为一个 bug，可以在文档中的「Bugs」中看到。&lt;/p&gt;

&lt;h4 id=&quot;deprecated&quot;&gt;Deprecated&lt;/h4&gt;

&lt;p&gt;顺便提一句，弃用注释：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Deprecated: xxx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个注释不会体现在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;godoc&lt;/code&gt;中，但是还是挺有用的，Goland可以识别它并作出提示。&lt;/p&gt;

&lt;h4 id=&quot;链接-url-自动转成-html-的-a-标签&quot;&gt;链接 URL 自动转成 HTML 的 a 标签&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// SetFlag sets log flags. For more information, see the sdk https://golang.org/pkg/log/#pkg-constants.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-181240.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;注释自动生成&quot;&gt;注释自动生成&lt;/h4&gt;

&lt;p&gt;有一个自动生成注释的工具&lt;a href=&quot;https://github.com/Gnouc/gocmt&quot;&gt;gocmt&lt;/a&gt;。安装和使用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get -u github.com/Gnouc/gocmt
gocmt -i $FilePath$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个命令可以结合 Goland 的「External Tools」使用，检测文件是否改动，实时生成注释。&lt;/p&gt;

&lt;h3 id=&quot;docgo&quot;&gt;doc.go&lt;/h3&gt;

&lt;p&gt;如果包注释超过3行，可以把注释都迁移到doc.go文件中。&lt;/p&gt;

&lt;p&gt;多行注释自然需要支持一些复杂的格式，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;godoc&lt;/code&gt;支持的并不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Markdown&lt;/code&gt;这种熟悉的格式，下面详细说明一下。&lt;/p&gt;

&lt;h4 id=&quot;标题和段落&quot;&gt;标题和段落：&lt;/h4&gt;

&lt;p&gt;先看看效果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-182016.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果&lt;strong&gt;&lt;em&gt;首字母是大写，并且结尾没有标点符号，是标题&lt;/em&gt;&lt;/strong&gt;。结尾有标点的自然是段落了。&lt;/p&gt;

&lt;h4 id=&quot;代码&quot;&gt;代码&lt;/h4&gt;

&lt;p&gt;注释中的代码也可以转成代码块。如果是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;//&lt;/code&gt;来注释，因为默认注释和正文中间是一个空格，那么多整一个空格（最少多一个）就会被识别为代码了。而段注释也同理，比常规注释多一个空格就行，不过我喜欢用 tab。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-183309.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;example_packagename_testgo&quot;&gt;example_PackageName_test.go&lt;/h3&gt;

&lt;p&gt;例子非常重要，基本上项目就是通过每个包的例子搭起来的。&lt;/p&gt;

&lt;p&gt;例子文件需要创建一个新文件，格式是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example_PackageName_test.go&lt;/code&gt;。不加 example 的前缀也可以，不过我觉得还是加上好一些，不加感觉和单元测试文件一样。包名也有要求，是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PackageName_test&lt;/code&gt;。在这个文件中加函数，函数名的格式是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExampleFuncName&lt;/code&gt;。&lt;strong&gt;&lt;em&gt;不加函数名的话是包级别示例。加函数名的话是函数级别的示例。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;包级别的例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Example() {
	logger.Info(&quot;hello, world.&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-184620.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;包级别的示例放在文档的开头处，而函数级别的示例放在函数后面，函数名得保持一致哦。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func ExampleNewLogger() {
    w := os.Stdout
    flag := log.Llongfile
    l := logger.NewWriterLogger(w, flag, 3)
    l.Info(&quot;hello, world&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/WX20180903-184841.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;最后&quot;&gt;最后&lt;/h3&gt;

&lt;p&gt;我们写代码都不太爱写注释，每当使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;golint&lt;/code&gt;这些工具检测代码的时候，就会有一堆的错误提示。看了今天的文章，是不是愿意写注释了呢？&lt;/p&gt;

&lt;p&gt;第二部分也是一个 Go 语言写注释的规范说明，大家可以参考这个。&lt;/p&gt;

&lt;p&gt;本文涉及的代码可以从&lt;a href=&quot;https://github.com/mnhkahn/gogogo&quot;&gt;这里&lt;/a&gt;下载。赶紧 star 起来啊。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;更多阅读：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://elliot.land/post/godoc-tips-tricks&quot;&gt;GODOC: TIPS &amp;amp; TRICKS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jianshu.com/p/b91c4400d4b2&quot;&gt;GoDoc的使用&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Go 语言实现的一个简单的重试方法</title>
   <link href="https://blog.cyeam.com/golang/2018/08/27/retry"/>
   <updated>2018-08-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/08/27/retry</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#代码&quot; id=&quot;markdown-toc-代码&quot;&gt;代码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#重试的逻辑&quot; id=&quot;markdown-toc-重试的逻辑&quot;&gt;重试的逻辑&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#示例&quot; id=&quot;markdown-toc-示例&quot;&gt;示例&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#github&quot; id=&quot;markdown-toc-github&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;代码&quot;&gt;代码&lt;/h3&gt;

&lt;p&gt;简单的东西就不废话了，直接上代码。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Retry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Warnf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;retry func error: %s. attemps #%d after %s.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Retry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attempts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NoRetryError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;接收三个参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;attempts：最多重试次数；&lt;/li&gt;
  &lt;li&gt;sleep：调用失败后的等待时间；&lt;/li&gt;
  &lt;li&gt;fn：重试的函数。函数的类型是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func() error&lt;/code&gt;，如果你的重试函数定义并不是这样，可以通过闭包包一下。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;重试的逻辑&quot;&gt;重试的逻辑&lt;/h3&gt;

&lt;p&gt;最多重试&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attempts&lt;/code&gt;次，如果调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fn&lt;/code&gt;返回错误，等待&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sleep&lt;/code&gt;的时间，而下次错误重试就需要等待两倍的时间了。还有一点是错误的类型，常规错误会重试，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stop&lt;/code&gt;类型的错误会中断重试，这也提供了一种中断机制。&lt;/p&gt;

&lt;h3 id=&quot;示例&quot;&gt;示例&lt;/h3&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;test error every time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;errFn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Retry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Millisecond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;github&quot;&gt;GitHub&lt;/h3&gt;

&lt;p&gt;完整的代码放到了 GitHub 上面，&lt;a href=&quot;https://github.com/mnhkahn/gogogo&quot;&gt;地址&lt;/a&gt;。欢迎大家 Star。&lt;/p&gt;

&lt;iframe src=&quot;http:s//ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=gogogo&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;https://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=gogogo&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;https://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;完整文档：&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/gogogo/util&quot;&gt;godoc&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;更多阅读：&lt;a href=&quot;https://upgear.io/blog/simple-golang-retry-function/&quot;&gt;Simple Golang Retry Function&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>介绍一下Json的Number(二)</title>
   <link href="https://blog.cyeam.com/golang/2018/08/22/json-number"/>
   <updated>2018-08-22T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/08/22/json-number</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#如何将json中的数字和字符串都解析为数字&quot; id=&quot;markdown-toc-如何将json中的数字和字符串都解析为数字&quot;&gt;如何将JSON中的数字和字符串都解析为数字？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#原理&quot; id=&quot;markdown-toc-原理&quot;&gt;原理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#usenumber&quot; id=&quot;markdown-toc-usenumber&quot;&gt;UseNumber&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;如何将json中的数字和字符串都解析为数字&quot;&gt;如何将JSON中的数字和字符串都解析为数字？&lt;/h3&gt;

&lt;p&gt;接着&lt;a href=&quot;https://blog.cyeam.com/golang/2016/05/02/jsonnumber&quot;&gt;《介绍一下Json的Number》
&lt;/a&gt;继续写，可以两篇文章一起读。&lt;/p&gt;

&lt;p&gt;有一些情况下，JSON 解码需要适配多种类型，比如一串数字会被存成整数或者字符串两种格式，类型是不确定的，这个时候如何解析？&lt;/p&gt;

&lt;p&gt;通用一点的方法我们可以自定义类型，自己实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MarshalJSON&lt;/code&gt;方法来实现负责逻辑的解析，具体方法可以参考&lt;a href=&quot;https://blog.cyeam.com/json/2014/08/04/go_json&quot;&gt;《Golang——json数据处理》&lt;/a&gt;，但是对于数字，就不用这么复杂了，Go 已经提供了一&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Number&lt;/code&gt;来帮助我们。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var raw = []byte(`{&quot;a&quot;:1}`)
var raw_string = []byte(`{&quot;a&quot;:&quot;2&quot;}`)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;定义了两个 JSON 串，一个是整形，一个是字符串。如何将这两个数据解析到同一种类型中呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type S3 struct {
	A json.Number `json:&quot;a&quot;`
}

s3 := new(S3)
err = json.Unmarshal(raw, s3)
log.Println(err, s3.A)

s31 := new(S3)
err = json.Unmarshal(raw_string, s31)
log.Println(err, s31.A)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将对象&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;的类型定义成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json.Number&lt;/code&gt;就可以实现对两种数据类型的解析。&lt;/p&gt;

&lt;h3 id=&quot;原理&quot;&gt;原理&lt;/h3&gt;

&lt;p&gt;我们知道，解析 JSON 的时候其实是对 JSON 串的遍历，其实所有遍历出来的值都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;类型，然后根据识别目标解析对象字段类型，或者识别&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;数据的内容转换格式。比如，如果数据被解析到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;上，把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;转换为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;；如果被解析到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interface{}&lt;/code&gt;上，就只能通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;的类型来转换了，数字会被统一处理能&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;，这个有个问题，就是会丢精度。而通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Number&lt;/code&gt;解析时，值会被直接保存为字符串类型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String() string { return string(n) }

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
	return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
	return strconv.ParseInt(string(n), 10, 64)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Int64&lt;/code&gt;还是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;就可以通过用户自己的情况选择了。&lt;/p&gt;

&lt;p&gt;本文涉及的代码可以从&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/jsonnumber/main.go&quot;&gt;这里&lt;/a&gt;下载。&lt;/p&gt;

&lt;h3 id=&quot;usenumber&quot;&gt;UseNumber&lt;/h3&gt;

&lt;p&gt;有时候我们想偷懒，并不想自己定义结构体，还是想使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]interface{}&lt;/code&gt;来解析 JSON，可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UseNumber&lt;/code&gt;方法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;decoder := json.NewDecoder(bytes.NewBufferString(`{&quot;10000000000&quot;:10000000000,&quot;111&quot;:1}`))
decoder.UseNumber()
var obj map[string]interface{}
decoder1.Decode(&amp;amp;obj)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>strings.Builder 源码分析</title>
   <link href="https://blog.cyeam.com/golang/2018/08/16/strings-builder"/>
   <updated>2018-08-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/08/16/strings-builder</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#stringsbuilder-和-bytesbuffer-接口设计上基本一致&quot; id=&quot;markdown-toc-stringsbuilder-和-bytesbuffer-接口设计上基本一致&quot;&gt;strings.Builder 和 bytes.Buffer 接口设计上基本一致&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#底层实现&quot; id=&quot;markdown-toc-底层实现&quot;&gt;底层实现&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#string-方法有门道&quot; id=&quot;markdown-toc-string-方法有门道&quot;&gt;String() 方法有门道&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#不允许复制&quot; id=&quot;markdown-toc-不允许复制&quot;&gt;不允许复制&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#线程不安全&quot; id=&quot;markdown-toc-线程不安全&quot;&gt;线程不安全&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;众所周知，Go 里面的字符串是常量，对字符串的修改会重新申请内存地址。为了优化这个，我之前都是用 bytes.Buffer 代替字符串的拼接等操作。这种方案避免了字符串修改过程中的内存申请，但是最后从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;转成字符串时会重新内存申请，这个无法避免。从 Go 1.10 开始，提供了更友好性能更好的方法 strings.Builder。&lt;/p&gt;

&lt;h3 id=&quot;stringsbuilder-和-bytesbuffer-接口设计上基本一致&quot;&gt;strings.Builder 和 bytes.Buffer 接口设计上基本一致&lt;/h3&gt;

&lt;p&gt;支持的方法是 bytes.Buffer 的子集，仔细看了一下，它实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Writer&lt;/code&gt;接口，而 bytes.Buffer 实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Reader&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Writer&lt;/code&gt;两个接口。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;type Builder
    func (b *Builder) Grow(n int)
    func (b *Builder) Len() int
    func (b *Builder) Reset()
    func (b *Builder) String() string
    func (b *Builder) Write(p []byte) (int, error)
    func (b *Builder) WriteByte(c byte) error
    func (b *Builder) WriteRune(r rune) (int, error)
    func (b *Builder) WriteString(s string) (int, error)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Writer interface {
        Write(p []byte) (n int, err error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&quot;底层实现&quot;&gt;底层实现&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;它底层还是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;保存数据的，这和 bytes.Buffer 是一致的。&lt;/p&gt;

&lt;p&gt;如果写入数据，就是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;后面追加内容：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (b *Builder) Write(p []byte) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, p...)
	return len(p), nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;追加内容也有讲究，因为底层是 slice，追加数据时有可能引起 slice 扩容。一般的优化方案是为 slice 初始化合理的空间，避免多次扩容复制。Builder 也提供了预分配内存的方法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (b *Builder) grow(n int) {
	buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
	copy(buf, b.buf)
	b.buf = buf
}

func (b *Builder) Grow(n int) {
	b.copyCheck()
	if n &amp;lt; 0 {
		panic(&quot;strings.Builder.Grow: negative count&quot;)
	}
	if cap(b.buf)-len(b.buf) &amp;lt; n {
		b.grow(n)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意扩容的容量和 slice 直接扩容两倍的方式略有不同，它是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2*cap(b.buf)+n&lt;/code&gt;，之前容量的两倍加n。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果容量是10，长度是5，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grow(3)&lt;/code&gt;结果是什么？当前容量足够使用，没有任何操作；&lt;/li&gt;
  &lt;li&gt;如果容量是10，长度是5，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grow(7)&lt;/code&gt;结果是什么？剩余空间是5，不满足7个扩容空间，底层需要扩容。扩容的时候按照之前容量的两倍再加n的新容量扩容，结果是2*10+7=27。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;string-方法有门道&quot;&gt;String() 方法有门道&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (b *Builder) String() string {
	return *(*string)(unsafe.Pointer(&amp;amp;b.buf))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;返回当前数据的字符串，先获取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;地址，然后转成字符串指针，然后再取地址。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;从 ptype 输出的结构来看，string 可看做 [2]uintptr，而 [ ]byte 则是 [3]uintptr，这便于我们编写代码，无需额外定义结构类型。如此，str2bytes 只需构建 [3]uintptr{ptr, len, len}，而 bytes2str 更简单，直接转换指针类型，忽略掉 cap 即可。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1773795374/clipboard_1773795371602_temlk6qxy.webp&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;详细可以参考雨痕的&lt;a href=&quot;https://segmentfault.com/a/1190000005006351&quot;&gt;【Go性能优化技巧 1/10】&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;不允许复制&quot;&gt;不允许复制&lt;/h3&gt;

&lt;p&gt;还是再看一下 Builder 的底层数据，它还有个字段&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addr&lt;/code&gt;，是一个指向 Builder 的指针。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;默认情况是它会指向自己：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addr&lt;/code&gt;和当前指针所指地址不同，会引发&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;panic&lt;/code&gt;异常。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (b *Builder) copyCheck() {
	if b.addr == nil {
		// This hack works around a failing of Go&apos;s escape analysis
		// that was causing b to escape and be heap allocated.
		// See issue 23382.
		// TODO: once issue 7921 is fixed, this should be reverted to
		// just &quot;b.addr = b&quot;.
		b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
	} else if b.addr != b {
		panic(&quot;strings: illegal use of non-zero Builder copied by value&quot;)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copyCheck&lt;/code&gt;用来保证复制后不允许修改的逻辑。仔细看下源码，如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addr&lt;/code&gt;是空，也就是没有数据的时候是可以被复制后修改的，一旦那边有数据了，就不能这么搞了。在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grow&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteByte&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteString&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteRune&lt;/code&gt;这五个函数里都有这个检查逻辑。&lt;/p&gt;

&lt;h3 id=&quot;线程不安全&quot;&gt;线程不安全&lt;/h3&gt;

&lt;p&gt;这个包并不是线程安全的，整个例子看看：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;strings&quot;
	&quot;sync&quot;
	&quot;sync/atomic&quot;
)

func main() {
	var b strings.Builder
	var n int32
	var wait sync.WaitGroup
	for i := 0; i &amp;lt; 1000; i++ {
		wait.Add(1)
		go func() {
			atomic.AddInt32(&amp;amp;n, 1)
			b.WriteString(&quot;1&quot;)
			wait.Done()
		}()
	}
	wait.Wait()
	fmt.Println(len(b.String()), n)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;902 1000&lt;/code&gt;，并不都是1000。如果想保证线程安全，需要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteString&lt;/code&gt;的时候加锁。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;strings&quot;
	&quot;sync&quot;
	&quot;sync/atomic&quot;
)

func main() {
	var b strings.Builder
	var n int32
	var wait sync.WaitGroup
	var lock sync.Mutex
	for i := 0; i &amp;lt; 1000; i++ {
		wait.Add(1)

		go func() {
			atomic.AddInt32(&amp;amp;n, 1)

			lock.Lock()
			b.WriteString(&quot;1&quot;)
			lock.Unlock()
			wait.Done()
		}()
	}
	wait.Wait()

	fmt.Println(len(b.String()), n)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://medium.com/@thuc/8-notes-about-strings-builder-in-golang-65260daae6e9&quot;&gt;7 notes about strings.builder in Golang&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>【译】在 Go 语言中使用猴子补丁</title>
   <link href="https://blog.cyeam.com/golang/2018/08/07/monkey-patch"/>
   <updated>2018-08-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/08/07/monkey-patch</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#go-语言中函数值如何工作&quot; id=&quot;markdown-toc-go-语言中函数值如何工作&quot;&gt;Go 语言中函数值如何工作&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#运行时替换函数&quot; id=&quot;markdown-toc-运行时替换函数&quot;&gt;运行时替换函数&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#封装到库中&quot; id=&quot;markdown-toc-封装到库中&quot;&gt;封装到库中&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#结论&quot; id=&quot;markdown-toc-结论&quot;&gt;结论&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;很多人认为&lt;strong&gt;猴子补丁&lt;/strong&gt;（A &lt;a href=&quot;https://en.wikipedia.org/wiki/Monkey_patch&quot;&gt;monkey patch&lt;/a&gt; is a way for a program to extend or modify supporting system software locally (affecting only the running instance of the program). 指可以在运行时动态修改或扩展程序的一种方法）是那些语言，比如 Ruby 和 Python 才有的东西。这并不对，计算机只是愚蠢的机器而我们总能让他们按照我们的想法工作！让我们来看看 Go 的函数如何工作，再看看我们如何在运行时修改它们。这篇文章将会使用 Intel 的汇编语法，所以我假设你了解过它或者在阅读的过程中参考&lt;a href=&quot;https://software.intel.com/en-us/articles/introduction-to-x64-assembly&quot;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果你对猴子补丁的原理没有兴趣，只想使用猴子补丁，可以直接移步到&lt;a href=&quot;https://github.com/bouk/monkey&quot;&gt;代码仓库&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;看看下面的代码反编译之后的结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

func a() int { return 1 }

func main() {
  print(a())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;编译完成后通过&lt;a href=&quot;https://hopperapp.com/&quot;&gt;Hopper&lt;/a&gt;查看，上面的代码将会展示下面的汇编代码：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/y2yaYfm.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我将参考屏幕左侧显示的各种指令的地址。&lt;/p&gt;

&lt;p&gt;我们的代码从过程&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.main&lt;/code&gt;开始，指令 0x2010 到 0x2026 初始化了栈。你可以参考这些&lt;a href=&quot;https://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite&quot;&gt;扩展阅读&lt;/a&gt;，下面的文章将会忽略那些代码。&lt;/p&gt;

&lt;p&gt;0x202a 行调用了函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.a&lt;/code&gt;，0x2000 行简单得把 0x1 压入栈返回。0x202f 到 0x2037 行把值传给了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime.printint&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;够简单了！现在咱们一起看看 Go 里面的函数值是如何实现的。&lt;/p&gt;

&lt;h3 id=&quot;go-语言中函数值如何工作&quot;&gt;Go 语言中函数值如何工作&lt;/h3&gt;

&lt;p&gt;看下面的代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
  &quot;fmt&quot;
  &quot;unsafe&quot;
)

func a() int { return 1 }

func main() {
  f := a
  fmt.Printf(&quot;0x%x\n&quot;, *(*uintptr)(unsafe.Pointer(&amp;amp;f)))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在第11行把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;赋值给了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt;，这就意味着调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f()&lt;/code&gt;将会调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;。接下来用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt;包读取出存在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt;里面的值。如果你是有 C 语言背景的程序员你可能会认为简单得把指向函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;的指针打印出来将会得到 0x2000（就是上面汇编里面看到的地址）。当我运行上面的代码得到了 0x102c38，这个地址相差了十万八千里！反编译后，这是第11行的代码：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/nAF7zmI.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里引用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.a.f&lt;/code&gt;，我们看看那个位置，可以发现：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/e26F32n.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;啊哈！&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.a.f&lt;/code&gt;在 0x102c38 并且包含值 0x2000，它正好是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.a&lt;/code&gt;的地址。看起来&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt;并不是指向函数的指针，而是指向函数的指针的指针。让我们修改代码证实：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main
 
import (
  &quot;fmt&quot;
  &quot;unsafe&quot;
)
 
func a() int { return 1 }
 
func main() {
  f := a
  fmt.Printf(&quot;0x%x\n&quot;, **(**uintptr)(unsafe.Pointer(&amp;amp;f)))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;和我们期望的一样，将会打印 0x2000。在&lt;a href=&quot;https://github.com/golang/go/blob/e9d9d0befc634f6e9f906b5ef7476fbd7ebd25e3/src/runtime/runtime2.go#L75-L78&quot;&gt;这里&lt;/a&gt;我们也能找到一些线索。Go 语言的函数值包含了额外的信息，这是闭包和绑定实例实现的方式。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type funcval struct {
	fn uintptr
	// variable-size, fn-specific data here
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接下来看看调用函数值的实现。把代码改成下面这样，给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt;赋值之后调用它。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

func a() int { return 1 }

func main() {
	f := a
	f()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;反编译后可以得到下面的结果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/bQr6Nbr.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.a.f&lt;/code&gt;加载到寄存器&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdx&lt;/code&gt;里，然后把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdx&lt;/code&gt;寄存器指向的地址存入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbx&lt;/code&gt;里，最后调用。函数的地址值总是会加载到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdx&lt;/code&gt;寄存器里面，当代码调用的时候可以用来加载一些可能会用到的额外信息。这里的额外信息是指向绑定的实例和匿名函数闭包的指针。如果你想了解更多我建议你深入研究一下反编译代码！&lt;/p&gt;

&lt;p&gt;让我们用新的知识实现 Go 语言里面的猴子补丁。&lt;/p&gt;

&lt;h3 id=&quot;运行时替换函数&quot;&gt;运行时替换函数&lt;/h3&gt;

&lt;p&gt;我们是想实现的是让下面的代码打印出来2:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

func a() int { return 1 }
func b() int { return 2 }

func main() {
	replace(a, b)
	print(a())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如何实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;replace&lt;/code&gt;?我们需要修改函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;，让它跳转到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;的代码，跳过执行它自己的代码。实际上，我们需要通过这种方法来实现替换，加载函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;到寄存器&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdx&lt;/code&gt;，然后执行时跳转到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdx&lt;/code&gt;上面。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mov rdx, main.b.f ; 48 C7 C2 ?? ?? ?? ??
jmp [rdx] ; FF 22
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我在汇编代码旁边附上了相应的机器码（你可以用&lt;a href=&quot;https://defuse.ca/online-x86-assembler.htm&quot;&gt;这种&lt;/a&gt;在线汇编工具来模拟测试）。编写一个生成上面汇编代码的函数就很简单了，类似于下面这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func assembleJump(f func() int) []byte {
  funcVal := *(*uintptr)(unsafe.Pointer(&amp;amp;f))
  return []byte{
    0x48, 0xC7, 0xC2,
    byte(funcval &amp;gt;&amp;gt; 0),
    byte(funcval &amp;gt;&amp;gt; 8),
    byte(funcval &amp;gt;&amp;gt; 16),
    byte(funcval &amp;gt;&amp;gt; 24), // MOV rdx, funcVal
    0xFF, 0x22,          // JMP [rdx]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就能把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;的函数体指向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;了！下面的代码尝试复制机器代码到函数题上。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;syscall&quot;
	&quot;unsafe&quot;
)

func a() int { return 1 }
func b() int { return 2 }

func rawMemoryAccess(b uintptr) []byte {
	return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:]
}

func assembleJump(f func() int) []byte {
	funcVal := *(*uintptr)(unsafe.Pointer(&amp;amp;f))
	return []byte{
		0x48, 0xC7, 0xC2,
		byte(funcVal &amp;gt;&amp;gt; 0),
		byte(funcVal &amp;gt;&amp;gt; 8),
		byte(funcVal &amp;gt;&amp;gt; 16),
		byte(funcVal &amp;gt;&amp;gt; 24), // MOV rdx, funcVal
		0xFF, 0x22,          // JMP [rdx]
	}
}

func replace(orig, replacement func() int) {
	bytes := assembleJump(replacement)
	functionLocation := **(**uintptr)(unsafe.Pointer(&amp;amp;orig))
	window := rawMemoryAccess(functionLocation)
	
	copy(window, bytes)
}

func main() {
	replace(a, b)
	print(a())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行上面的代码并不会工作，结果会是 segementation fault 段错误。这是因为加载后的二进制文件&lt;a href=&quot;https://en.wikipedia.org/wiki/Segmentation_fault#Writing_to_read-only_memory&quot;&gt;默认不允许修改&lt;/a&gt;。我们可以使用系统调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mprotect&lt;/code&gt;来关掉这个保护，这个最终版的代码终于可以像期望的那样，通过调用替换后的函数来打印出来 2。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;syscall&quot;
	&quot;unsafe&quot;
)

func a() int { return 1 }
func b() int { return 2 }

func getPage(p uintptr) []byte {
	return (*(*[0xFFFFFF]byte)(unsafe.Pointer(p &amp;amp; ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()]
}

func rawMemoryAccess(b uintptr) []byte {
	return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:]
}

func assembleJump(f func() int) []byte {
	funcVal := *(*uintptr)(unsafe.Pointer(&amp;amp;f))
	return []byte{
		0x48, 0xC7, 0xC2,
		byte(funcVal &amp;gt;&amp;gt; 0),
		byte(funcVal &amp;gt;&amp;gt; 8),
		byte(funcVal &amp;gt;&amp;gt; 16),
		byte(funcVal &amp;gt;&amp;gt; 24), // MOV rdx, funcVal
		0xFF, 0x22,          // JMP rdx
	}
}

func replace(orig, replacement func() int) {
	bytes := assembleJump(replacement)
	functionLocation := **(**uintptr)(unsafe.Pointer(&amp;amp;orig))
	window := rawMemoryAccess(functionLocation)
	
	page := getPage(functionLocation)
	syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
	
	copy(window, bytes)
}

func main() {
	replace(a, b)
	print(a())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;封装到库中&quot;&gt;封装到库中&lt;/h3&gt;

&lt;p&gt;我把上面的代码&lt;a href=&quot;https://github.com/bouk/monkey&quot;&gt;封装到了一个易用的库&lt;/a&gt;中。它支持32位，关闭补丁，对实例打方法补丁。我在 README 中写了一些例子。&lt;/p&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;p&gt;有志者事竟成！我们可以在运行时修改程序了，它能让我们做一些很酷的事情，例如猴子补丁。&lt;/p&gt;

&lt;p&gt;我希望你读了本文之后能有所收获，我玩得很开心！&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=9290917&quot;&gt;Hacker News&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.reddit.com/r/golang/comments/30try1/monkey_patching_in_go/&quot;&gt;Reddit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://bouk.co/blog/monkey-patching-in-go/&quot;&gt;原文地址&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 中整数转字符串</title>
   <link href="https://blog.cyeam.com/golang/2018/06/20/go-itoa"/>
   <updated>2018-06-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/06/20/go-itoa</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#fmtsprintf&quot; id=&quot;markdown-toc-fmtsprintf&quot;&gt;fmt.Sprintf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#strconvitoa&quot; id=&quot;markdown-toc-strconvitoa&quot;&gt;strconv.Itoa&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#strconvformatint&quot; id=&quot;markdown-toc-strconvformatint&quot;&gt;strconv.FormatInt&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#format-的实现&quot; id=&quot;markdown-toc-format-的实现&quot;&gt;Format 的实现&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#0-99的两位整数&quot; id=&quot;markdown-toc-0-99的两位整数&quot;&gt;[0, 99)的两位整数&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#10进制转换&quot; id=&quot;markdown-toc-10进制转换&quot;&gt;10进制转换&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#2481632进制的转换&quot; id=&quot;markdown-toc-2481632进制的转换&quot;&gt;2、4、8、16、32进制的转换。&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#常规情况&quot; id=&quot;markdown-toc-常规情况&quot;&gt;常规情况&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#sprintf-的实现&quot; id=&quot;markdown-toc-sprintf-的实现&quot;&gt;Sprintf 的实现&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#性能对比&quot; id=&quot;markdown-toc-性能对比&quot;&gt;性能对比&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;整形转字符串经常会用到，本文讨论一下 Golang 提供的这几种方法。基于 go1.10.1&lt;/p&gt;

&lt;h3 id=&quot;fmtsprintf&quot;&gt;fmt.Sprintf&lt;/h3&gt;

&lt;p&gt;fmt 包应该是最常见的了，从刚开始学习 Golang 就接触到了，写 ‘hello, world’ 就得用它。它还支持格式化变量转为字符串。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Sprintf(format string, a ...interface{}) string
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Sprintf formats according to a format specifier and returns the resulting string.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fmt.Sprintf(&quot;%d&quot;, a)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%d&lt;/code&gt;代表十进制整数。&lt;/p&gt;

&lt;h3 id=&quot;strconvitoa&quot;&gt;strconv.Itoa&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Itoa(i int) string
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Itoa is shorthand for FormatInt(int64(i), 10).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;strconv.Itoa(a)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;strconvformatint&quot;&gt;strconv.FormatInt&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func FormatInt(i int64, base int) string
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;FormatInt returns the string representation of i in the given base, for 2 &amp;lt;= base &amp;lt;= 36. The result uses the lower-case letters ‘a’ to ‘z’ for digit values &amp;gt;= 10.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;参数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;是要被转换的整数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base&lt;/code&gt;是进制，例如2进制，支持2到36进制。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;strconv.Format(int64(a), 10)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;format-的实现&quot;&gt;Format 的实现&lt;/h3&gt;

&lt;h4 id=&quot;0-99的两位整数&quot;&gt;[0, 99)的两位整数&lt;/h4&gt;

&lt;p&gt;对于小的（小于等于100）十进制正整数有加速优化算法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if fastSmalls &amp;amp;&amp;amp; 0 &amp;lt;= i &amp;amp;&amp;amp; i &amp;lt; nSmalls &amp;amp;&amp;amp; base == 10 {
	return small(int(i))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;加速的原理是提前算好100以内非负整数转换后的字符串。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const smallsString = &quot;00010203040506070809&quot; +
    &quot;10111213141516171819&quot; +
    &quot;20212223242526272829&quot; +
    &quot;30313233343536373839&quot; +
    &quot;40414243444546474849&quot; +
    &quot;50515253545556575859&quot; +
    &quot;60616263646566676869&quot; +
    &quot;70717273747576777879&quot; +
    &quot;80818283848586878889&quot; +
    &quot;90919293949596979899&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看出来，转换后的结果是从1到99都有，而且每个结果只占两位。当然个人数的情况还得特殊处理，个位数结果只有一位。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func small(i int) string {
    off := 0
    if i &amp;lt; 10 {
        off = 1
    }
    return smallsString[i*2+off : i*2+2]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果被转换的数字是个位数，那么偏移量变成了1，默认情况是0。&lt;/p&gt;

&lt;p&gt;只支持2到36进制的转换。36进制是10个数字加26个小写字母，超过这个范围无法计算。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var a [64 + 1]byte 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;整形最大64位，加一位是因为有个符号。转换计算时，要分10进制和非10进制的情况。&lt;/p&gt;

&lt;h4 id=&quot;10进制转换&quot;&gt;10进制转换&lt;/h4&gt;

&lt;p&gt;10进制里，两位两位转换，为什么这么干？两位数字时100以内非负整数转换可以用上面的特殊情况加速。很有意思。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;us := uint(u)
for us &amp;gt;= 100 {
	is := us % 100 * 2
	us /= 100
	i -= 2
	a[i+1] = smallsString[is+1]
	a[i+0] = smallsString[is+0]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2481632进制的转换&quot;&gt;2、4、8、16、32进制的转换。&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const digits = &quot;0123456789abcdefghijklmnopqrstuvwxyz&quot;

var shifts = [len(digits) + 1]uint{
    1 &amp;lt;&amp;lt; 1: 1,
    1 &amp;lt;&amp;lt; 2: 2,
    1 &amp;lt;&amp;lt; 3: 3,
    1 &amp;lt;&amp;lt; 4: 4,
    1 &amp;lt;&amp;lt; 5: 5,
}

if s := shifts[base]; s &amp;gt; 0 {
	// base is power of 2: use shifts and masks instead of / and %
	b := uint64(base)
	m := uint(base) - 1 // == 1&amp;lt;&amp;lt;s - 1
	for u &amp;gt;= b {
		i--
		a[i] = digits[uint(u)&amp;amp;m]
		u &amp;gt;&amp;gt;= s
	}
	// u &amp;lt; base
	i--
	a[i] = digits[uint(u)]
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过循环求余实现。进制的转换也是这种方式。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for u &amp;gt;= b {
    i--
    a[i] = uint(u)&amp;amp;m
    u &amp;gt;&amp;gt;= s
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面的代码实现了进制的转换。而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;digits[uint(u)&amp;amp;m]&lt;/code&gt;实现了转换后的结果再转成字符。&lt;/p&gt;

&lt;h4 id=&quot;常规情况&quot;&gt;常规情况&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;b := uint64(base)
for u &amp;gt;= b {
	i--
	q := u / b
	a[i] = digits[uint(u-q*b)]
	u = q
}
// u &amp;lt; base
i--
a[i] = digits[uint(u)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;依然是循环求余来实现。这段代码更像是给人看的。和上面2的倍数的进制转换的区别在于，上面的代码把除法&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;换成了右移（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&amp;gt;&lt;/code&gt;）&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt;位，把求余&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%&lt;/code&gt;换成了逻辑与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt;操作。&lt;/p&gt;

&lt;h3 id=&quot;sprintf-的实现&quot;&gt;Sprintf 的实现&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;switch f := arg.(type) {
    case bool:
        p.fmtBool(f, verb)
    case float32:
        p.fmtFloat(float64(f), 32, verb)
    case float64:
        p.fmtFloat(f, 64, verb)
    case complex64:
        p.fmtComplex(complex128(f), 64, verb)
    case complex128:
        p.fmtComplex(f, 128, verb)
    case int:
        p.fmtInteger(uint64(f), signed, verb)
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;判断类型，如果是整数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;类型，不需要反射，直接计算。支持的都是基础类型，其它类型只能通过反射实现。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sprintf&lt;/code&gt;支持的进制只有10&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%d&lt;/code&gt;、16&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;、8&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;o&lt;/code&gt;、2&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;这四种，其它的会包&lt;em&gt;fmt: unknown base; can’t happen&lt;/em&gt;异常。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;switch base {
case 10:
	for u &amp;gt;= 10 {
		i--
		next := u / 10
		buf[i] = byte(&apos;0&apos; + u - next*10)
		u = next
	}
case 16:
	for u &amp;gt;= 16 {
		i--
		buf[i] = digits[u&amp;amp;0xF]
		u &amp;gt;&amp;gt;= 4
	}
case 8:
	for u &amp;gt;= 8 {
		i--
		buf[i] = byte(&apos;0&apos; + u&amp;amp;7)
		u &amp;gt;&amp;gt;= 3
	}
case 2:
	for u &amp;gt;= 2 {
		i--
		buf[i] = byte(&apos;0&apos; + u&amp;amp;1)
		u &amp;gt;&amp;gt;= 1
	}
default:
	panic(&quot;fmt: unknown base; can&apos;t happen&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、8、16进制和之前&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FormatInt&lt;/code&gt;差不多，而10进制的性能差一些，每次只能处理一位数字，而不像&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FormatInt&lt;/code&gt;一次处理两位。&lt;/p&gt;

&lt;h3 id=&quot;性能对比&quot;&gt;性能对比&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var smallInt = 35
var bigInt = 999999999999999

func BenchmarkItoa(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := strconv.Itoa(smallInt)
        _ = val
    }
}

func BenchmarkItoaFormatInt(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := strconv.FormatInt(int64(smallInt), 10)
        _ = val
    }
}

func BenchmarkItoaSprintf(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := fmt.Sprintf(&quot;%d&quot;, smallInt)
        _ = val
    }
}

func BenchmarkItoaBase2Sprintf(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := fmt.Sprintf(&quot;%b&quot;, smallInt)
        _ = val
    }
}

func BenchmarkItoaBase2FormatInt(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := strconv.FormatInt(int64(smallInt), 2)
        _ = val
    }
}

func BenchmarkItoaBig(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := strconv.Itoa(bigInt)
        _ = val
    }
}

func BenchmarkItoaFormatIntBig(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := strconv.FormatInt(int64(bigInt), 10)
        _ = val
    }
}

func BenchmarkItoaSprintfBig(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        val := fmt.Sprintf(&quot;%d&quot;, bigInt)
        _ = val
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;压测有三组对比，小于100的情况，大数字的情况，还有二进制的情况。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BenchmarkItoa-8                 	300000000	         4.58 ns/op	       0 B/op	       0 allocs/op
BenchmarkItoaFormatInt-8        	500000000	         3.07 ns/op	       0 B/op	       0 allocs/op
BenchmarkItoaBase2Sprintf-8     	20000000	        86.4 ns/op	      16 B/op	       2 allocs/op
BenchmarkItoaBase2FormatInt-8   	50000000	        30.2 ns/op	       8 B/op	       1 allocs/op
BenchmarkItoaSprintf-8          	20000000	        83.5 ns/op	      16 B/op	       2 allocs/op
BenchmarkItoaBig-8              	30000000	        44.6 ns/op	      16 B/op	       1 allocs/op
BenchmarkItoaFormatIntBig-8     	30000000	        43.9 ns/op	      16 B/op	       1 allocs/op
BenchmarkItoaSprintfBig-8       	20000000	       108 ns/op	      24 B/op	       2 allocs/op
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sprintf&lt;/code&gt;在所有情况中都是最差的，还是别用这个包了。&lt;/li&gt;
  &lt;li&gt;小于100的情况会有加速，不光是性能上的加速，因为结果是提前算好的，也不需要申请内存。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FormatInt&lt;/code&gt;10进制性能最好，其它的情况差一个数量级。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Itoa&lt;/code&gt;虽然只是封装了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FormatInt&lt;/code&gt;，对于性能还是有一些影响的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本文涉及的代码可以从&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/itoa/itoa_test.go&quot;&gt;这里&lt;/a&gt;下载。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.kancloud.cn/kancloud/the-way-to-go/72523&quot;&gt;11.4 类型判断：type-switch - Go 入门指南&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Go 项目下载依赖</title>
   <link href="https://blog.cyeam.com/golang/2018/06/19/gogetdep"/>
   <updated>2018-06-19T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/06/19/gogetdep</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#如何自动下载所有依赖包&quot; id=&quot;markdown-toc-如何自动下载所有依赖包&quot;&gt;如何自动下载所有依赖包？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#代码无法下载怎么办&quot; id=&quot;markdown-toc-代码无法下载怎么办&quot;&gt;代码无法下载怎么办？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;如何自动下载所有依赖包&quot;&gt;如何自动下载所有依赖包？&lt;/h3&gt;

&lt;p&gt;大部分情况下大家下载 Go 项目都是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;命令，它除了会下载指定的项目代码，还会去下载这个项目所依赖的所有项目。&lt;/p&gt;

&lt;p&gt;但是有的时候我们的项目由于各种原因并不是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;下载的，是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt;下载的，这样代码下下来就没有依赖包了，没办法编译通过的。这样的话怎么办呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get -d -v ./...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt;标志只下载代码包，不执行安装命令；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v&lt;/code&gt;打印详细日志和调试日志。这里加上这个标志会把每个下载的包都打印出来；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./...&lt;/code&gt;这个表示路径，代表当前目录下所有的文件。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&quot;代码无法下载怎么办&quot;&gt;代码无法下载怎么办？&lt;/h3&gt;

&lt;p&gt;我们会经常用到扩展包，很巧的是扩展包国内无法访问。&lt;/p&gt;

&lt;p&gt;可以从&lt;a href=&quot;https://golangtc.com/download/package&quot;&gt;Golang 中国&lt;/a&gt;下载，它提供了这个服务，好人一生平安。&lt;/p&gt;

&lt;p&gt;此外，扩展包还被放在了 GitHub 上面，比如&lt;a href=&quot;https://github.com/golang/tools&quot;&gt;tools&lt;/a&gt;，直接下载下来，放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GOPATH/src/golang.org/x/&lt;/code&gt;下面即可。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Slice 小技巧</title>
   <link href="https://blog.cyeam.com/golang/2018/06/18/slicetricks"/>
   <updated>2018-06-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2018/06/18/slicetricks</id>
   <content type="html">&lt;p&gt;本文翻译自&lt;a href=&quot;https://github.com/golang/go/wiki/SliceTricks&quot;&gt;SliceTricks&lt;/a&gt;。我会追加一些我的理解。官方给出的例子代码很漂亮，建议大家多看看，尤其是利用多个切片共享底层数组的功能就地操作很有意思。&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#appendvector&quot; id=&quot;markdown-toc-appendvector&quot;&gt;AppendVector&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#copy&quot; id=&quot;markdown-toc-copy&quot;&gt;Copy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#cut&quot; id=&quot;markdown-toc-cut&quot;&gt;Cut&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#delete&quot; id=&quot;markdown-toc-delete&quot;&gt;Delete&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#delete-without-preserving-order-不保留顺序的删除&quot; id=&quot;markdown-toc-delete-without-preserving-order-不保留顺序的删除&quot;&gt;Delete without preserving order 不保留顺序的删除&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#expand&quot; id=&quot;markdown-toc-expand&quot;&gt;Expand&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#extend&quot; id=&quot;markdown-toc-extend&quot;&gt;Extend&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#insert&quot; id=&quot;markdown-toc-insert&quot;&gt;Insert&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#insertvector&quot; id=&quot;markdown-toc-insertvector&quot;&gt;InsertVector&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#popshift&quot; id=&quot;markdown-toc-popshift&quot;&gt;Pop/Shift&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pop-back&quot; id=&quot;markdown-toc-pop-back&quot;&gt;Pop Back&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#push&quot; id=&quot;markdown-toc-push&quot;&gt;Push&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#push-frontunshift&quot; id=&quot;markdown-toc-push-frontunshift&quot;&gt;Push Front/Unshift&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#additional-tricks-附加技巧&quot; id=&quot;markdown-toc-additional-tricks-附加技巧&quot;&gt;Additional Tricks 附加技巧&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#filtering-without-allocating-不申请内存过滤数据&quot; id=&quot;markdown-toc-filtering-without-allocating-不申请内存过滤数据&quot;&gt;Filtering without allocating 不申请内存过滤数据&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#reversing-反转&quot; id=&quot;markdown-toc-reversing-反转&quot;&gt;Reversing 反转&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#shuffling-随机&quot; id=&quot;markdown-toc-shuffling-随机&quot;&gt;Shuffling 随机&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container/vector&lt;/code&gt;包在 Go 1中被删除了，因为引入了内置函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;，它再加上内置函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;基本上可以代替这个包的功能。&lt;/p&gt;

&lt;h4 id=&quot;appendvector&quot;&gt;AppendVector&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;支持两个参数，第一个是被追加的 slice，第二个参数是追加到后面的数据。第二个参数是变长参数，可以传多值。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;copy&quot;&gt;Copy&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;// or&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;函数把数据 a 复制到 b 中。它有个坑，复制数据的长度取决于 b 的当前长度，如果 b 没有初始化，那么并不会发生复制操作。所以复制的第一行需要初始化长度。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;复制还有一种替代方案，利用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;的多值情况来追加。但是这样会有一个问题，追加的时候是追加到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]T(nil)&lt;/code&gt;里面，默认初始长度是0，每追加一个元素需要检测当前长度是否满足，如果不满足就要扩容，每次扩容扩当前容量的一倍（详细原理可以查看 slice 的内部实现）。这么操作的话如果 a 长度是3，第一种方法复制出来长度和容量都是3，而第二种方法长度是3，容量却是4。如果只是单纯复制我推荐第一种。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;cut&quot;&gt;Cut&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;把 [i, j)中间的元素剪切掉。slice 的切片都是前开后闭原则。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;delete&quot;&gt;Delete&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;// or&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;删除位置 i 的元素。第一种，利用剪切的方式删除。因为只是在删除，这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;操作并不会引起底层数据的扩容。只不过 i 之后的数据发生了更新。此时长度减小1，容量不变。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;第二种方式，利用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;方法实现。将 i 后面所有的数据迁移，然后删除最后一位数据。将 i+1 到 len(a) 的数据复制到 i 到 len(a)-1的位置上。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;方法的返回值是复制的元素长度，所以这里又被直接用来截断，将 a 的最后一位没有被删除的数据删除。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这两种操作的底层结果一致。被删除元素的后面元素都需要被复制。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;delete-without-preserving-order-不保留顺序的删除&quot;&gt;Delete without preserving order 不保留顺序的删除&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;删除第 i 位的元素，把最后一位放到第 i 位上，然后把最后一位元素删除。这种方式底层并没有发生复制操作。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt; 如果 slice 的类型是指向结构体的指针，或者是结构体 slice 里面包含指针，这些数据在被删除后需要进行垃圾回收来释放内存。然而上面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cut&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delete&lt;/code&gt;方法有可能引起内存泄漏：被删除的数据依然被 a 所引用（底层数据中引用）导致无法进行垃圾回收。可以用下面的代码解决这个问题：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Cut&lt;/strong&gt;&lt;/p&gt;
  &lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// or the zero value of T&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;&lt;em&gt;相比于之前多了一步操作，将被删除的位置置为 nil。这样指针就没有被引用的地方了，可以被垃圾回收。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Delete&lt;/strong&gt;&lt;/p&gt;
  &lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// or the zero value of T&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Delete without preserving order&lt;/strong&gt;&lt;/p&gt;
  &lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;expand&quot;&gt;Expand&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;在中间位置 i 扩展长度为 j 的 slice。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;extend&quot;&gt;Extend&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;在最后延伸长度是 j 的 slice。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;insert&quot;&gt;Insert&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;在位置 i 插入元素 x。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;通过第二个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;把 a[i:] 追加到 x 后面（&lt;em&gt;这个操作会引起&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]T{x}&lt;/code&gt;发生多次扩容&lt;/em&gt;），然后通过第一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;把这个新的 slice 追加到 a[:i]后面（&lt;em&gt;这个操作会引起 a 发生一次扩容&lt;/em&gt;）。这两个操作创建了一个新的 slice（这样相当于创建了内存垃圾），第二个复制也可以被避免：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Insert&lt;/strong&gt;&lt;/p&gt;
  &lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;&lt;em&gt;首先通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;将 slice 扩容，然后把 i 后面的元素后移，最后复制。整个操作一次扩容。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;insertvector&quot;&gt;InsertVector&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;在位置 i 插入 slice b。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;popshift&quot;&gt;Pop/Shift&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;一行实现 pop 出队列头。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;pop-back&quot;&gt;Pop Back&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;一行实现 pop 出队列尾。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;push&quot;&gt;Push&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;push x 到队列尾。&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;push-frontunshift&quot;&gt;Push Front/Unshift&lt;/h4&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;push x 到队列头。&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;additional-tricks-附加技巧&quot;&gt;Additional Tricks 附加技巧&lt;/h2&gt;
&lt;h3 id=&quot;filtering-without-allocating-不申请内存过滤数据&quot;&gt;Filtering without allocating 不申请内存过滤数据&lt;/h3&gt;

&lt;p&gt;多个切片引用的底层数组是有可能是同一个，利用这个原理可以实现复用底层数组实现数据过滤。当然，过滤之后底层数组内容会被修改。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;就地过滤。首先申明切片 b，和 a 共享底层数组。遍历 a 进行过滤，过滤后到加入 b 中。这样 a 和 b 同时被修改了。b 是过滤后正确的 slice，而 a 的数据会错乱。&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;reversing-反转&quot;&gt;Reversing 反转&lt;/h3&gt;

&lt;p&gt;将 slice 的数据顺序反转：&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;opp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;代码再简化一下，还可以将反转用到的索引省略：&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;shuffling-随机&quot;&gt;Shuffling 随机&lt;/h3&gt;

&lt;p&gt;Fisher–Yates 算法:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;需要Go 1.10 以上 &lt;a href=&quot;https://godoc.org/math/rand#Shuffle&quot;&gt;math/rand.Shuffle&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Intn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;每个数据随机一个新位置出来。&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>用 Go 给 Redis 写组件</title>
   <link href="https://blog.cyeam.com/redis/2018/05/30/redis-module-go"/>
   <updated>2018-05-30T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/redis/2018/05/30/redis-module-go</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#一个-c-语言的例子&quot; id=&quot;markdown-toc-一个-c-语言的例子&quot;&gt;一个 C 语言的例子&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#redis-的组件&quot; id=&quot;markdown-toc-redis-的组件&quot;&gt;Redis 的组件&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#用-go-实现一个-redis-组件&quot; id=&quot;markdown-toc-用-go-实现一个-redis-组件&quot;&gt;用 Go 实现一个 Redis 组件&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#开发一个-json-格式的组件&quot; id=&quot;markdown-toc-开发一个-json-格式的组件&quot;&gt;开发一个 JSON 格式的组件&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#echo-命令&quot; id=&quot;markdown-toc-echo-命令&quot;&gt;echo 命令&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#写入-redis&quot; id=&quot;markdown-toc-写入-redis&quot;&gt;写入 Redis&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#读取-json&quot; id=&quot;markdown-toc-读取-json&quot;&gt;读取 JSON&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#总结&quot; id=&quot;markdown-toc-总结&quot;&gt;总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;
&lt;p&gt;Redis 从4.0开始支持组件开发，用户编译生成一个动态链接库，启动时加载就可以启用。Redis 时 C语言开发的，写插件还得用 C 语言。还好 Go 语言也能开发组件，我也能尝试一把开发一个插件了。&lt;/p&gt;

&lt;h3 id=&quot;一个-c-语言的例子&quot;&gt;一个 C 语言的例子&lt;/h3&gt;

&lt;p&gt;可以参考这个例子&lt;a href=&quot;https://redislabs.com/blog/writing-redis-modules/&quot;&gt;Writing Redis Modules&lt;/a&gt;来大概了解下开发流程。&lt;/p&gt;

&lt;p&gt;文章里描述了一下如何自己写一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HGETSET&lt;/code&gt;命令，他组合了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HGET&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HSET&lt;/code&gt;这两个原生的命令。&lt;/p&gt;

&lt;p&gt;从这里可以下载到源代码&lt;a href=&quot;https://github.com/RedisLabs/RedisModulesSDK&quot;&gt;RedisModulesSDK&lt;/a&gt;。需要编译出来一个动态链接库文件。例子提供的很完善，编译脚本都写在了 Makefile 里面。会生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.so&lt;/code&gt;文件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/RedisLabs/RedisModulesSDK.git
cd RedisModulesSDK/
cd example/
make all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;我们需要用到最新版的 Redis，并且编译。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget http://download.redis.io/releases/redis-4.0.9.tar.gz
tar -zxvf redis-4.0.9.tar.gz
cd redis-4.0.9
make
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动 Redis，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loadmodule&lt;/code&gt;参数指出动态链接库的地址。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port&lt;/code&gt;参数指定启动端口，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loglevel&lt;/code&gt;设置日志打印级别。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./redis-server --loadmodule ~/code/ccode/RedisModulesSDK/example/module.so --loglevel debug --port 6340
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后启动 Redis 客户端，试着输入下面的命令，很有成就感有没有。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;redis-cli -p 6340

127.0.0.1:6340&amp;gt; EXAMPLE.HGETSET foo bar baz
(nil)
127.0.0.1:6340&amp;gt; EXAMPLE.HGETSET foo bar vaz
&quot;baz&quot;
127.0.0.1:6340&amp;gt; EXAMPLE.PARSE SUM 5 2
(integer) 7
127.0.0.1:6340&amp;gt; EXAMPLE.PARSE PROD 5 2
(integer) 10
127.0.0.1:6340&amp;gt; EXAMPLE.TEST
PASS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;redis-的组件&quot;&gt;Redis 的组件&lt;/h4&gt;

&lt;p&gt;Redis 的热门组件都放在&lt;a href=&quot;https://redis.io/modules&quot;&gt;这里&lt;/a&gt;。有人用他做了搜索引擎，还有支持 SQL，还有一些扩展了数据结构，比如 JSON 类型、布隆过滤器、bitmap 等等。&lt;/p&gt;

&lt;p&gt;完整的开发&lt;a href=&quot;https://redis.io/topics/modules-api-ref&quot;&gt;文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;大概说下流程：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;程序入口是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int RedisModule_OnLoad(RedisModuleCtx *ctx)&lt;/code&gt;，里面完成相关初始化操作；&lt;/li&gt;
  &lt;li&gt;初始化组件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RedisModule_Init&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;如果扩展了数据类型，也需要注册。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleType *RedisModule_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver, void *typemethods_ptr);&lt;/code&gt;。
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;：这是一个 9 字符的字符串，必须是9个字符。可以使用 A-Z、a-z、0-9、-、_这些字符。一般命名规则是&lt;strong&gt;&lt;em&gt;类型-vendor&lt;/em&gt;&lt;/strong&gt;；&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;encver&lt;/strong&gt;：编码版本号，用于备份到磁盘时用；&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;typemethods_ptr&lt;/strong&gt;，这个结构体保存了数据类型相关的操作函数， 比如rdb格式的加载和备份等等。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;创建命令，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;是命令名字，这是实际调用命令时要输入的名字；&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;cmdfunc&lt;/strong&gt;是注册的函数，函数的定义格式是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);&lt;/code&gt;;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;获取一个 key 的句柄，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void *RedisModule_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode);&lt;/code&gt;，有了这个句柄，可以查看当前 key 的类型，并且操作这个 key，写入值、写入过期时间、删除等操作；&lt;/li&gt;
  &lt;li&gt;从 Redis 获取值。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void *RedisModule_ModuleTypeGetValue(RedisModuleKey *key);&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;把值写入 Redis，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int RedisModule_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value);&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;命令的返回需要注意，返回值只能是成功&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REDISMODULE_OK&lt;/code&gt;和失败&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REDISMODULE_ERR&lt;/code&gt;，但是只返回这个是不行的，还需要返回内容。
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RedisModule_ReplyWithError&lt;/code&gt;，返回错误；&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RedisModule_ReplyWithSimpleString&lt;/code&gt;，返回字符串；&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RedisModule_ReplyWithNull&lt;/code&gt;，返回空，一般查询的 key 没有对应值的时候用。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;用-go-实现一个-redis-组件&quot;&gt;用 Go 实现一个 Redis 组件&lt;/h3&gt;

&lt;p&gt;不太会用 C，自己想写个组件不是很好搞，还好有一个 Go 版本的库来辅助用 Go 开发。&lt;a href=&quot;https://github.com/wenerme/go-rm&quot;&gt;go-rm&lt;/a&gt;，看头像是个国人，很不错。README 里有用 Go 实现了最开始举的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HGETSET&lt;/code&gt;的例子，可以跑起来看看。&lt;/p&gt;

&lt;p&gt;这个 Go 语言的包函数命名和 C 的差不太多，上手不是很费劲。&lt;/p&gt;

&lt;h4 id=&quot;开发一个-json-格式的组件&quot;&gt;开发一个 JSON 格式的组件&lt;/h4&gt;

&lt;p&gt;我准备着手开发支持 JSON 格式的组件，它支持保存 JSON，根据用户输入的参数返回 JSON 内符合条件的数据。命令大概是这个样子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;127.0.0.1:6340&amp;gt; json.set doc &apos;{&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:43}&apos;
&quot;{\&quot;foo\&quot;:\&quot;bar\&quot;,\&quot;baz\&quot;:43}&quot;

127.0.0.1:6340&amp;gt; json.get doc baz a
{&quot;baz&quot;:43}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;echo-命令&quot;&gt;echo 命令&lt;/h4&gt;

&lt;p&gt;先写一个简单的练练手。初始化当前组件：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func init() {
    rm.Mod = CreateMyMod()
}

func CreateMyMod() *rm.Module {
    mod := rm.NewMod()
    mod.Name = &quot;json&quot;
    mod.Version = 1
    mod.Commands = []rm.Command{CreateCommand_ECHO()}
    return mod
}

func main() {
    // In case someone try to run this
    rm.Run()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编写 echo 的命令实现。里面有参数校验，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reply&lt;/code&gt;很重要，否则客户的会卡死。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func CreateCommand_ECHO() rm.Command {
    return rm.Command{
        Usage:    &quot;print message&quot;,
        Desc:     `like echo.`,
        Name:     &quot;print&quot;,
        Flags:    &quot;&quot;,
        FirstKey: 1, LastKey: 1, KeyStep: 1,
        Action: func(cmd rm.CmdContext) int {
            ctx, args := cmd.Ctx, cmd.Args
            if len(args) != 2 {
                return ctx.WrongArity()
            }
            ctx.ReplyWithString(args[1])
            return rm.OK
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;写入-redis&quot;&gt;写入 Redis&lt;/h4&gt;

&lt;p&gt;把数据解析到一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]interface{}&lt;/code&gt;里面保存。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func CreateCommand_JSONSET() rm.Command {
    return rm.Command{
        Usage:    `json.set a {&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:42}`,
        Desc:     `store a json object.`,
        Name:     &quot;json.set&quot;,
        Flags:    &quot;&quot;,
        FirstKey: 1, LastKey: 1, KeyStep: 1,
        Action: func(cmd rm.CmdContext) int {
            ctx, args := cmd.Ctx, cmd.Args
            if len(args) != 3 {
                return ctx.WrongArity()
            }

            ctx.AutoMemory()
            key, ok := openHashKey(ctx, args[1])
            if !ok {
                return rm.ERR
            }

            // var val *JsonData
            raw := args[2].String()
            rm.LogDebug(&quot;raw: %s&quot;, raw)

            val := new(JsonData)
            val.Name = args[1]
            val.data = make(map[string]interface{}, 1)
            err := json.Unmarshal([]byte(raw), &amp;amp;val.data)
            if err != nil {
                ctx.ReplyWithError(fmt.Sprintf(&quot;ERR %v&quot;, err))
                return rm.ERR
            }

            if key.IsEmpty() {
                if key.ModuleTypeSetValue(ModuleType, unsafe.Pointer(val)) == rm.ERR {
                    ctx.ReplyWithError(&quot;ERR Failed to set module type value&quot;)
                    return rm.ERR
                }
            } else {
                valOld := (*JsonData)(key.ModuleTypeGetValue())
                valOld.data = val.data
            }

            ctx.ReplyWithString(args[2])
            return rm.OK
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;读取-json&quot;&gt;读取 JSON&lt;/h4&gt;

&lt;p&gt;如果没有找到 key，需要返回空。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func CreateCommand_JSONGET() rm.Command {
    return rm.Command{
        Usage:    `json.get a foo`,
        Desc:     `get a json object.`,
        Name:     &quot;json.get&quot;,
        Flags:    &quot;&quot;,
        FirstKey: 1, LastKey: 1, KeyStep: 1,
        Action: func(cmd rm.CmdContext) int {
            ctx, args := cmd.Ctx, cmd.Args
            if len(args) &amp;lt; 2 {
                return ctx.WrongArity()
            }

            key, ok := openHashKey(ctx, args[1])
            if !ok {
                return rm.ERR
            }
            val := (*JsonData)(key.ModuleTypeGetValue())
            rm.LogDebug(&quot;raw: %v&quot;, val)

            if val == nil || val.data == nil {
                ctx.ReplyWithNull()
                return rm.OK
            }

            resLen := len(args[2:])

            var resMap map[string]interface{}
            if resLen == 0 {
                resMap = make(map[string]interface{}, len(val.data))
                for k, v := range val.data {
                    resMap[k] = v
                }
            } else {
                resMap = make(map[string]interface{}, resLen)
                for _, arg := range args[2:] {
                    a := arg.String()
                    rm.LogDebug(a)
                    if v, exists := val.data[a]; exists {
                        resMap[a] = v
                    }
                }
            }

            res, err := json.Marshal(resMap)
            if err != nil {
                ctx.ReplyWithError(err.Error())
                return rm.ERR
            }

            ctx.ReplyWithSimpleString(string(res))
            return rm.OK
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;Redis 这个组件功能真的是很厉害，用Redis的分布式、主从同步、自动备份到磁盘这些特性，分分钟搭起一个高可用分布式应用。&lt;/p&gt;

&lt;p&gt;关于数据备份和恢复的部分我还没有研究，等下次搞明白了再说吧。完整的代码放在&lt;a href=&quot;https://github.com/mnhkahn/RedisJsonGo&quot;&gt;GitHub&lt;/a&gt;上面，有需要可以去看。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>常见的哈希算法和用途</title>
   <link href="https://blog.cyeam.com/hash/2018/05/28/hash-method"/>
   <updated>2018-05-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/hash/2018/05/28/hash-method</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#djb&quot; id=&quot;markdown-toc-djb&quot;&gt;DJB&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#djb-在-redis中的应用&quot; id=&quot;markdown-toc-djb-在-redis中的应用&quot;&gt;DJB 在 Redis中的应用&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#java-字符串哈希&quot; id=&quot;markdown-toc-java-字符串哈希&quot;&gt;Java 字符串哈希&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fnv&quot; id=&quot;markdown-toc-fnv&quot;&gt;FNV&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#thomas-wangs-32-bit-mix-function&quot; id=&quot;markdown-toc-thomas-wangs-32-bit-mix-function&quot;&gt;Thomas Wang’s 32 bit Mix Function&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#murmur&quot; id=&quot;markdown-toc-murmur&quot;&gt;Murmur&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#crc32&quot; id=&quot;markdown-toc-crc32&quot;&gt;CRC32&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#memhash&quot; id=&quot;markdown-toc-memhash&quot;&gt;memhash&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#性能比较&quot; id=&quot;markdown-toc-性能比较&quot;&gt;性能比较&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;
&lt;p&gt;哈希算法经常会被用到，比如我们Go里面的map，Java的HashMap，目前最流行的缓存Redis都大量用到了哈希算法。它们支持把很多类型的数据进行哈希计算，我们实际使用的时候并不用考虑哈希算法的实现。而其实不同的数据类型，所使用到的哈希算法并不一样。&lt;/p&gt;

&lt;h3 id=&quot;djb&quot;&gt;DJB&lt;/h3&gt;

&lt;p&gt;下面是C语言实现。初始值是5381，遍历整个串，按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash * 33 +c&lt;/code&gt;的算法计算。得到的结果就是哈希值。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;unsigned long
    hash(unsigned char *str)
    {
        unsigned long hash = 5381;
        int c;

        while (c = *str++)
            hash = ((hash &amp;lt;&amp;lt; 5) + hash) + c; /* hash * 33 + c */

        return hash;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;里面涉及到两个神奇的数字，5381和33。为什么是这两个数？我还特意去查了查，说是经过大量实验，这两个的结果碰撞小，哈希结果分散。&lt;/p&gt;

&lt;p&gt;还有一个事情很有意思，乘以33是用左移和加法实现的。底层库对性能要求高啊。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;h4 id=&quot;djb-在-redis中的应用&quot;&gt;DJB 在 Redis中的应用&lt;/h4&gt;

&lt;p&gt;在Redis中，它被用来计算大小写不敏感的字符串哈希。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;static uint32_t dict_hash_function_seed = 5381;
/* And a case insensitive hash function (based on djb hash) */
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) {
    unsigned int hash = (unsigned int)dict_hash_function_seed;

    while (len--)
        hash = ((hash &amp;lt;&amp;lt; 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
    return hash;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;算法和之前的一样，只是多了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tolower&lt;/code&gt;函数把字符转成小写。&lt;/p&gt;

&lt;h3 id=&quot;java-字符串哈希&quot;&gt;Java 字符串哈希&lt;/h3&gt;

&lt;p&gt;看了上面的再看Java内置字符串哈希就很有意思了。Java对象有个内置对象&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt;，它缓存了哈希结果，如果当前对象有缓存，直接返回。如果没有缓存，遍历整个字符串，按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash * 31 + c&lt;/code&gt;的算法计算。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public int hashCode() {
    int h = hash;
    if (h == 0 &amp;amp;&amp;amp; value.length &amp;gt; 0) {
        char val[] = value;

        for (int i = 0; i &amp;lt; value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;和DJB相比，初始值从5381变成了0，乘的系数从33变成了31。&lt;/p&gt;

&lt;h3 id=&quot;fnv&quot;&gt;FNV&lt;/h3&gt;

&lt;p&gt;这个算法之前写过&lt;a href=&quot;https://blog.cyeam.com/golang/2015/01/15/go_index&quot;&gt;《字符串查找算法（二）》&lt;/a&gt;，字符串每一位都看成是一个数字，32位的话看成是16777169进制的数字，计算当前串的哈希值就是在把当前串转成10进制。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const primeRK = 16777619

// hashstr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func hashstr(sep string) (uint32, uint32) {
    hash := uint32(0)
    for i := 0; i &amp;lt; len(sep); i++ {
        hash = hash*primeRK + uint32(sep[i])
    }
    var pow, sq uint32 = 1, primeRK
    for i := len(sep); i &amp;gt; 0; i &amp;gt;&amp;gt;= 1 {
        if i&amp;amp;1 != 0 {
            pow *= sq
        }
        // 只有32位，超出范围的会被丢掉
        sq *= sq
    }
    return hash, pow
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个算法的厉害之处在于他可以保存状态。比如有个字符串&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ab&lt;/code&gt;，它的哈希值是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a*E+b=HashAB&lt;/code&gt;，如果计算&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bc&lt;/code&gt;的哈希值，可以利用第一次计算的结果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(HashAB-a*E)*E+c=HashBC&lt;/code&gt;。这么一个转换例子里是两个字符效果不明显，如果当前串是100个字符，后移一位的哈希算法性能就会快很多。&lt;/p&gt;

&lt;p&gt;在Golang里面字符串匹配算法查找用到了这个。&lt;/p&gt;

&lt;h3 id=&quot;thomas-wangs-32-bit-mix-function&quot;&gt;Thomas Wang’s 32 bit Mix Function&lt;/h3&gt;

&lt;p&gt;前面说的都是字符串的哈希算法，这次说整数的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public
int hash32shift(int key)
{
    key = ~key + (key &amp;lt;&amp;lt; 15); // key = (key &amp;lt;&amp;lt; 15) - key - 1;
    key = key ^ (key &amp;gt;&amp;gt;&amp;gt; 12);
    key = key + (key &amp;lt;&amp;lt; 2);
    key = key ^ (key &amp;gt;&amp;gt;&amp;gt; 4);
    key = key * 2057; // key = (key + (key &amp;lt;&amp;lt; 3)) + (key &amp;lt;&amp;lt; 11);
    key = key ^ (key &amp;gt;&amp;gt;&amp;gt; 16);
    return key;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Redis对于Key是整数类型时用了这个算法。&lt;/p&gt;

&lt;h3 id=&quot;murmur&quot;&gt;Murmur&lt;/h3&gt;

&lt;p&gt;就纯哈希算法来说，这个算法算是综合能力不错的算法了。碰撞小、性能好。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hash           Lowercase      Random UUID  Numbers
=============  =============  ===========  ==============
Murmur            145 ns      259 ns          92 ns
                    6 collis    5 collis       0 collis
FNV-1a            152 ns      504 ns          86 ns
                    4 collis    4 collis       0 collis
FNV-1             184 ns      730 ns          92 ns
                    1 collis    5 collis       0 collis▪
DBJ2a             158 ns      443 ns          91 ns
                    5 collis    6 collis       0 collis▪▪▪
DJB2              156 ns      437 ns          93 ns
                    7 collis    6 collis       0 collis▪▪▪
SDBM              148 ns      484 ns          90 ns
                    4 collis    6 collis       0 collis**
SuperFastHash     164 ns      344 ns         118 ns
                   85 collis    4 collis   18742 collis
CRC32             250 ns      946 ns         130 ns
                    2 collis    0 collis       0 collis
LoseLose          338 ns        -             -
               215178 collis
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一般在分布式系统中用的比较多。对于一个Key做哈希，把不同的请求转发到不同的服务器上面。&lt;/p&gt;

&lt;p&gt;推荐一个Go的&lt;a href=&quot;https://github.com/huichen/murmur/blob/master/murmur.go&quot;&gt;实现&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;crc32&quot;&gt;CRC32&lt;/h3&gt;

&lt;p&gt;CRC32的哈希碰撞和murmur的差不多，但是CRC32可以使用CPU的硬件加速实现哈希提速。&lt;/p&gt;

&lt;p&gt;在Codis上就使用了这个哈希算法做哈希分片，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SlotId= crc32(key) % 1024&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;Codis使用Go语言实现，CRC32算法直接用了Go的原生包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash/crc32&lt;/code&gt;。这个包会提前判断当前CPU是否支持硬件加速：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func archAvailableIEEE() bool {
    return cpu.X86.HasPCLMULQDQ &amp;amp;&amp;amp; cpu.X86.HasSSE41
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;memhash&quot;&gt;memhash&lt;/h3&gt;

&lt;p&gt;Go语言内置的哈希表数据结构&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;，也是一个哈希结构，它内置的哈希算法更讲究。&lt;/p&gt;

&lt;p&gt;这里用到的哈希算法是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memhash&lt;/code&gt;，源代码在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime/hash32.go&lt;/code&gt;里面。它基于谷歌的两个哈希算法实现。大家有兴趣的可以去研究下具体实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Hashing algorithm inspired by
//   xxhash: https://code.google.com/p/xxhash/
// cityhash: https://code.google.com/p/cityhash/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;memhash在具体实现时也用到了硬件加速。如果硬件支持，会用AES哈希算法。如果不支持，才会去用memhash。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func memhash(p unsafe.Pointer, seed, s uintptr) uintptr {
    if GOARCH == &quot;386&quot; &amp;amp;&amp;amp; GOOS != &quot;nacl&quot; &amp;amp;&amp;amp; useAeshash {
        return aeshash(p, seed, s)
    }
    h := uint32(seed + s*hashkey[0])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;性能比较&quot;&gt;性能比较&lt;/h3&gt;

&lt;p&gt;memhash并不是可导出函数，我在runtime包里增加了一个memhash_test.go的测试文件，执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go test -benchmem -run=^$ -bench ^BenchmarkMemHash$&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package runtime_test

import (
    . &quot;runtime&quot;
    &quot;testing&quot;
)

func BenchmarkMemHash(b *testing.B) {
    for i := 0; i &amp;lt; b.N; i++ {
        for _, g := range goldenMurmur3 {
            StringHash(g.in, 0)
        }
    }
}

type _Golden struct {
    out uint32
    in  string
}

var goldenMurmur3 = []_Golden{
    {0x00000000, &quot;&quot;},
    {0x3c2569b2, &quot;a&quot;},
    {0x9bbfd75f, &quot;ab&quot;},
    {0xb3dd93fa, &quot;abc&quot;},
    {0x43ed676a, &quot;abcd&quot;},
    {0xe89b9af6, &quot;abcde&quot;},
    {0x6181c085, &quot;abcdef&quot;},
    {0x883c9b06, &quot;abcdefg&quot;},
    {0x49ddccc4, &quot;abcdefgh&quot;},
    {0x421406f0, &quot;abcdefghi&quot;},
    {0x88927791, &quot;abcdefghij&quot;},
    {0x91e056d3, &quot;Discard medicine more than two years old.&quot;},
    {0xc4d1cdf9, &quot;He who has a shady past knows that nice guys finish last.&quot;},
    {0x92a09da9, &quot;I wouldn&apos;t marry him with a ten foot pole.&quot;},
    {0xba22e6c4, &quot;Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave&quot;},
    {0xb3ba11cb, &quot;The days of the digital watch are numbered.  -Tom Stoppard&quot;},
    {0x941ada4d, &quot;Nepal premier won&apos;t resign.&quot;},
    {0x03f1f7b4, &quot;For every action there is an equal and opposite government program.&quot;},
    {0x03946117, &quot;His money is twice tainted: &apos;taint yours and &apos;taint mine.&quot;},
    {0x91e89ce1, &quot;There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977&quot;},
    {0xdc39bd00, &quot;It&apos;s a tiny change to the code and not completely disgusting. - Bob Manchek&quot;},
    {0xe898a1fa, &quot;size:  a.out:  bad magic&quot;},
    {0xcb5affb4, &quot;The major problem is with sendmail.  -Mark Horton&quot;},
    {0xc84510d4, &quot;Give me a rock, paper and scissors and I will move the world.  CCFestoon&quot;},
    {0xd4466554, &quot;If the enemy is within range, then so are you.&quot;},
    {0xe718d618, &quot;It&apos;s well we cannot hear the screams/That we create in others&apos; dreams.&quot;},
    {0xa6fb1684, &quot;You remind me of a TV show, but that&apos;s all right: I watch it anyway.&quot;},
    {0x65cb8d60, &quot;C is as portable as Stonehedge!!&quot;},
    {0x164935d1, &quot;Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley&quot;},
    {0x33e03966, &quot;The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction.  Lewis-Randall Rule&quot;},
    {0x04944630, &quot;How can you write a big system without C++?  -Paul Glick&quot;},
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BenchmarkMemHash-8   	 3000000	       475 ns/op	       0 B/op	       0 allocs/op
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dgryski/dgohash&quot;&gt;dgohash&lt;/a&gt;用Go实现了一些哈希算法，对比压测一下。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BenchmarkJava32-8          	  500000	      2548 ns/op	    1456 B/op	      30 allocs/op
BenchmarkDJB-8             	  500000	      2516 ns/op	    1456 B/op	      30 allocs/op
BenchmarkElf32-8           	  500000	      3204 ns/op	    1456 B/op	      30 allocs/op
BenchmarkJenkins32-8       	  500000	      3154 ns/op	    1456 B/op	      30 allocs/op
BenchmarkMarvin32-8        	  500000	      3375 ns/op	    1456 B/op	      30 allocs/op
BenchmarkMurmur-8          	 1000000	      2184 ns/op	    1456 B/op	      30 allocs/op
BenchmarkSDBM32-8          	  500000	      2789 ns/op	    1456 B/op	      30 allocs/op
BenchmarkSQLite32-8        	 1000000	      2419 ns/op	    1456 B/op	      30 allocs/op
BenchmarkSuperFastHash-8   	 1000000	      2003 ns/op	    1456 B/op	      30 allocs/op 硬件加速的和这些比确实可以碾压。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jianshu.com/u/c3e3ddbf4828&quot;&gt;《几种常见的hash函数》 - allanYan&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed&quot;&gt;《Which hashing algorithm is best for uniqueness and speed?》- Ian Boyd&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_37246875/article/details/54169625&quot;&gt;《CRC32 Hash PK Murmur Hash》- 老陈988&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://halfrost.com/go_map_chapter_one/&quot;&gt;《如何设计并实现一个线程安全的 Map ？(上篇)》- halfrost&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://halfrost.com/go_map_chapter_two/&quot;&gt;《如何设计并实现一个线程安全的 Map ？(下篇)》- halfrost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang HTTP 服务路由查询</title>
   <link href="https://blog.cyeam.com/http/2018/02/07/http-server-router"/>
   <updated>2018-02-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/http/2018/02/07/http-server-router</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#golang-http-原始包&quot; id=&quot;markdown-toc-golang-http-原始包&quot;&gt;Golang HTTP 原始包&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#路由&quot; id=&quot;markdown-toc-路由&quot;&gt;路由&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#黑科技获取变量内部变量&quot; id=&quot;markdown-toc-黑科技获取变量内部变量&quot;&gt;黑科技获取变量内部变量&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;golang-http-原始包&quot;&gt;Golang HTTP 原始包&lt;/h3&gt;

&lt;p&gt;Golang 的框架用过不少，越来越发现还是原生的好。我们一般只做接口，对于项目服务没有那么高的灵活性要求，原生的 HTTP 包已经够用。而且原生包通过接口的形式提供了扩展的方式，自己简单扩展一下就方便很多。Golang 的设计思想就是简单，还是用简单的方式比较好。&lt;/p&gt;

&lt;h3 id=&quot;路由&quot;&gt;路由&lt;/h3&gt;

&lt;p&gt;原生包的路由非常简单，就用了一个哈希表来报存路由。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool // whether any patterns contain hostnames
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;每次请求进来都要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt;里查询路由。但是这个路由有个问题，它是局部变量，而且没有对应的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getter&lt;/code&gt;函数，我们没法知道路由的内容。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&quot;黑科技获取变量内部变量&quot;&gt;黑科技获取变量内部变量&lt;/h3&gt;

&lt;p&gt;获取内部变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt;的方法也不难，通过反射的方式。路由变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServeMux&lt;/code&gt;可以拿到，通过反射是可以拿到它的局部变量的。反射也提供了操作哈希表的对于方法。直接上代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;v := reflect.ValueOf(mux)
m := v.Elem().FieldByName(&quot;m&quot;)

keys := m.MapKeys()

routers := make([]string, 0, len(keys))
for _, key := range keys {
	routers = append(routers, key.String())
}

sort.Strings(routers)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;题图：天津瓷房子。&lt;/p&gt;

&lt;p&gt;最近半年更新少了很多，太忙了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Go&lt;/code&gt;代码写得也少了很多，实在惭愧。又要到新年了，希望明年更好。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang int 和 uint 天天用，那么问题来了，它多大？</title>
   <link href="https://blog.cyeam.com/json/2017/12/18/go-int-size"/>
   <updated>2017-12-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/json/2017/12/18/go-int-size</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#int-和-uint-到底占多大空间&quot; id=&quot;markdown-toc-int-和-uint-到底占多大空间&quot;&gt;int 和 uint 到底占多大空间？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#more&quot; id=&quot;markdown-toc-more&quot;&gt;More&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#size-of-int-on-64-bit-platforms&quot; id=&quot;markdown-toc-size-of-int-on-64-bit-platforms&quot;&gt;Size of int on 64-bit platforms&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#ps&quot; id=&quot;markdown-toc-ps&quot;&gt;PS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;今天调试一个问题，发现一个我无法理解的情况：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;math&quot;
	&quot;runtime&quot;
)

func main() {
	var a uint = math.MaxUint64
	fmt.Println(&quot;Hello, playground&quot;, a, runtime.Version())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;把64位的数字赋值给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint&lt;/code&gt;，我理解&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint&lt;/code&gt;是32位的，为啥可以编译通过？但是我接着又在 playground 上试了一把，结果是编译不过了：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;constant 18446744073709551615 overflows uint&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;int-和-uint-到底占多大空间&quot;&gt;int 和 uint 到底占多大空间？&lt;/h3&gt;

&lt;p&gt;其实我一直理解是32位的。因为别的语言是这样，惯性思维了。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;直接看一下官方文档：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;int is a signed integer type that is at least 32 bits in size. It is a distinct type, however, and not an alias for, say, int32.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;情况差不多。翻译一下，就是说这个整形最少占32位，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int32&lt;/code&gt;是两码事。&lt;/p&gt;

&lt;p&gt;再看一下 davecheney 大神的回复（大神半夜回复 GitHub 真是敬业啊）：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;uint is a variable sized type, on your 64 bit computer uint is 64 bits wide.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我的理解&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint&lt;/code&gt;类型长度取决于 CPU，如果是32位CPU就是4个字节，如果是64位就是8个字节。我的电脑是64位的，而 playground 是32位的，问题就出在这里。&lt;/p&gt;

&lt;h3 id=&quot;more&quot;&gt;More&lt;/h3&gt;

&lt;p&gt;这里就会出现一个情况，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint&lt;/code&gt;是根据 CPU 变化的，如何知道当前系统的情况？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CPU 型号：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime.GOARCH&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;的长度：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv.IntSize&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;写了这么多年 Golang，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;天天用，一直被我当32位处理，说来惭愧。。。&lt;/p&gt;

&lt;h3 id=&quot;size-of-int-on-64-bit-platforms&quot;&gt;Size of int on 64-bit platforms&lt;/h3&gt;

&lt;p&gt;经热心网友提醒，从 &lt;a href=&quot;https://golang.org/doc/go1.1#int&quot;&gt;1.1&lt;/a&gt; 开始，Go 的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint&lt;/code&gt;长度发生了变化。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The language allows the implementation to choose whether the int type and uint types are 32 or 64 bits. Previous Go implementations made int and uint 32 bits on all systems. Both the gc and gccgo implementations now make int and uint 64 bits on 64-bit platforms such as AMD64/x86-64. Among other things, this enables the allocation of slices with more than 2 billion elements on 64-bit platforms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x := ^uint32(0) // x is 0xffffffff
i := int(x)     // i is -1 on 32-bit systems, 0xffffffff on 64-bit
fmt.Println(i)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ps&quot;&gt;PS&lt;/h3&gt;

&lt;p&gt;今天看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv.Itoa&lt;/code&gt;的源码，发现一个有意思的地方：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const host32bit = ^uint(0)&amp;gt;&amp;gt;32 == 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;它用到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;长度的特性来判断是不是32位的。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://play.golang.org/p/Or99SPVNbm&quot;&gt;https://play.golang.org/p/Or99SPVNbm&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;题图：孜然牛肉。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang map 如何进行删除操作？</title>
   <link href="https://blog.cyeam.com/json/2017/11/02/go-map-delete"/>
   <updated>2017-11-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/json/2017/11/02/go-map-delete</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#map-的删除操作&quot; id=&quot;markdown-toc-map-的删除操作&quot;&gt;map 的删除操作&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#map-的删除原理&quot; id=&quot;markdown-toc-map-的删除原理&quot;&gt;map 的删除原理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#如何清空整个-map&quot; id=&quot;markdown-toc-如何清空整个-map&quot;&gt;如何清空整个 map&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#如何真正释放内存&quot; id=&quot;markdown-toc-如何真正释放内存&quot;&gt;如何真正释放内存？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#验证&quot; id=&quot;markdown-toc-验证&quot;&gt;验证&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#为什么这么设计&quot; id=&quot;markdown-toc-为什么这么设计&quot;&gt;为什么这么设计？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#这样是内存泄漏么&quot; id=&quot;markdown-toc-这样是内存泄漏么&quot;&gt;这样是内存泄漏么？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;map-的删除操作&quot;&gt;map 的删除操作&lt;/h3&gt;

&lt;p&gt;Golang 内置了哈希表，总体上是使用哈希链表实现的，如果出现哈希冲突，就把冲突的内容都放到一个链表里面。&lt;/p&gt;

&lt;p&gt;Golang 还内置了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delete&lt;/code&gt;函数，如果作用于哈希表，就是把 map 里面的 key 删除。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;delete(intMap, 1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;map-的删除原理&quot;&gt;map 的删除原理&lt;/h3&gt;

&lt;p&gt;可以直接看&lt;a href=&quot;https://github.com/golang/go/blob/master/src/runtime/hashmap.go#L607&quot;&gt;源码&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;我简单摘几行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
	for ; b != nil; b = b.overflow(t) {
		for i := uintptr(0); i &amp;lt; bucketCnt; i++ {
			b.tophash[i] = empty
			h.count--
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;外层的循环就是在遍历整个 map，删除的核心就在那个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty&lt;/code&gt;。它修改了当前 key 的标记，而不是直接删除了内存里面的数据。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;empty          = 0 // cell is empty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;如何清空整个-map&quot;&gt;如何清空整个 map&lt;/h3&gt;

&lt;p&gt;看了我上面的分析，那么这段代码可以清空 map 么？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for k, _ := range m {
	delete(m, k)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;map 被清空。执行完之后调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;len&lt;/code&gt;函数，结果肯定是0；&lt;/li&gt;
  &lt;li&gt;内存没有释放。清空只是修改了一个标记，底层内存还是被占用了；&lt;/li&gt;
  &lt;li&gt;循环遍历了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;len(m)&lt;/code&gt;次。上面的代码每一次遍历都会删除一个元素，而遍历的次数并不会因为之前每次删一个元素导致减少。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;如何真正释放内存&quot;&gt;如何真正释放内存？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;map = nil
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这之后坐等垃圾回收器回收就好了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;如果你用 map 做缓存，而每次更新只是部分更新，更新的 key 如果偏差比较大，有可能会有内存逐渐增长而不释放的问题&lt;/em&gt;&lt;/strong&gt;。要注意。&lt;/p&gt;

&lt;h3 id=&quot;验证&quot;&gt;验证&lt;/h3&gt;

&lt;p&gt;下面来验证一下上面说的原理。我们申请一个全局&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;来保证内存被分配到堆上面。初始化这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;，分配比较大的空间，方便对比。每完成一次操作，进行一个垃圾回收，并且打印当前内存堆的情况。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var intMap map[int]int
var cnt = 8192

func main() {
	printMemStats()

	initMap()
	runtime.GC()
	printMemStats()

	log.Println(len(intMap))
	for i := 0; i &amp;lt; cnt; i++ {
		delete(intMap, i)
	}
	log.Println(len(intMap))

	runtime.GC()
	printMemStats()

	intMap = nil
	runtime.GC()
	printMemStats()
}

func initMap() {
	intMap = make(map[int]int, cnt)

	for i := 0; i &amp;lt; cnt; i++ {
		intMap[i] = i
	}
}

func printMemStats() {
	var m runtime.MemStats
	runtime.ReadMemStats(&amp;amp;m)
	log.Printf(&quot;Alloc = %v TotalAlloc = %v Sys = %v NumGC = %v\n&quot;, m.Alloc/1024, m.TotalAlloc/1024, m.Sys/1024, m.NumGC)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2018/05/31 10:54:25 Alloc = 100 TotalAlloc = 100 Sys = 1700 NumGC = 0
2018/05/31 10:54:25 Alloc = 422 TotalAlloc = 426 Sys = 3076 NumGC = 1
2018/05/31 10:54:25 8192
2018/05/31 10:54:25 0
2018/05/31 10:54:25 Alloc = 424 TotalAlloc = 429 Sys = 3140 NumGC = 2
2018/05/31 10:54:25 Alloc = 112 TotalAlloc = 431 Sys = 3140 NumGC = 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结论很明显：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;NumGC 是垃圾回收次数；Alloc 是对对象大小，单位是 KB；Sys 是从 OS 获取的内存大小，单位是 KB；&lt;/li&gt;
  &lt;li&gt;第一行，没有进行过 GC，默认真用了 100 KB 的内存；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;初始化完成之后进行一次 GC，此时内存占了 422 KB；&lt;/li&gt;
  &lt;li&gt;接下来就是执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delete&lt;/code&gt;操作，可以看到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;已经被清空了，也执行了一次 GC，但是内存没有被释放；&lt;/li&gt;
  &lt;li&gt;最后把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;置为空，内存才被释放。&lt;/li&gt;
  &lt;li&gt;我使用的版本&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go version go1.10.1 darwin/amd64&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;为什么这么设计&quot;&gt;为什么这么设计？&lt;/h3&gt;

&lt;p&gt;这么设计看起来不是那么完美，为什么要这么做呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;query := map[string]string{}

query[&quot;test0&quot;] = &quot;0&quot;
query[&quot;test1&quot;] = &quot;1&quot;
query[&quot;test2&quot;] = &quot;2&quot;

i := 0
for k, v := range query {
	delete(query, &quot;test2&quot;)
	fmt.Println(query, k, v)
	i++
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以在遍历&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;的时候删除里面的元素，而且可以删除没有遍历到的元素，为了保证删除了之后遍历不发生异常，才这么设计的吧。&lt;/p&gt;

&lt;h3 id=&quot;这样是内存泄漏么&quot;&gt;这样是内存泄漏么？&lt;/h3&gt;

&lt;p&gt;我觉得这样不算是内存泄漏。如果继续给这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;写入值，如果这个值命中了之前被删除的bucket，那么会覆盖之前的empty数据。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>说说 JSONP 和 XSS</title>
   <link href="https://blog.cyeam.com/json/2017/10/27/jsonp-xss"/>
   <updated>2017-10-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/json/2017/10/27/jsonp-xss</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#jsonp&quot; id=&quot;markdown-toc-jsonp&quot;&gt;JSONP&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#xss&quot; id=&quot;markdown-toc-xss&quot;&gt;XSS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#问题原因&quot; id=&quot;markdown-toc-问题原因&quot;&gt;问题原因&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#content-type-applicationjson&quot; id=&quot;markdown-toc-content-type-applicationjson&quot;&gt;Content-Type: application/json&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#content-type-texthtml&quot; id=&quot;markdown-toc-content-type-texthtml&quot;&gt;Content-Type: text/html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#解决方法&quot; id=&quot;markdown-toc-解决方法&quot;&gt;解决方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;jsonp&quot;&gt;JSONP&lt;/h3&gt;

&lt;p&gt;先说 JSONP。通过 JavaScript 调用，被调用域名和当前页面域名不一致，就需要用到 JSONP。不过我不太推荐这么跨域调用。&lt;/p&gt;

&lt;p&gt;如果真的要解决跨域问题，我觉得有几个不错的方法，一个是两组服务器配上相同的域名。还有就是自己的服务器 nginx 上做一个转发。&lt;/p&gt;

&lt;h3 id=&quot;xss&quot;&gt;XSS&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;跨站脚本（英语：Cross-site scripting，通常简称为：XSS）是一种网站应用程序的安全漏洞攻击，是代码注入的一种。它允许恶意用户将代码注入到网页上，其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上面是维基百科的解释。实际一点的例子可以看看我文本的头图。页面被注入了一张图片。恶意的注入可以注入一段脚本。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&quot;问题原因&quot;&gt;问题原因&lt;/h3&gt;

&lt;p&gt;浏览器为了保证跨域访问的安全性，会默认发一个 callback 参数到后台，接口拿到这个参数之后，需要将返回的 JSON 数据外面包上 callback 参数。&lt;/p&gt;

&lt;p&gt;具体的返回格式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CALLBACK(JSON)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果 ajax 请求是 JSONP 请求，返回的内容浏览器还会自动检测，如果不是按这个格式返回或者 callback 的内容不对，这次请求就算失败了。&lt;/p&gt;

&lt;p&gt;这里有一个机制，那就是请求的 callback 会被放入返回的内容当中，这也是可能出问题的地方。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;支持 JSONP 的链接如果直接放到浏览器里面访问，浏览器就不会做 callback 校验了。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;content-type-applicationjson&quot;&gt;Content-Type: application/json&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;浏览器渲染就是靠 Content-Type 来做的&lt;/em&gt;&lt;/strong&gt;。如果返回内容标记是 json，哪怕 body 里面都是 html 的标签，浏览器也不会渲染。所以，如果接口返回的不是 html，千万不要写成 html。&lt;/p&gt;

&lt;h3 id=&quot;content-type-texthtml&quot;&gt;Content-Type: text/html&lt;/h3&gt;

&lt;p&gt;如果返回的内容是页面，html类型。那么 callback 注入的 html 元素都可以直接放到页面上了。那么，html 页面必然不能支持 callback。&lt;/p&gt;

&lt;h3 id=&quot;解决方法&quot;&gt;解决方法&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;就是前面说到的，Content-Type 不要乱用，严格按照标准协议来做。目前的框架默认肯定会检测一下内容类型，如果不是很必要，不要手动设置。因为有可能多转发几次 Content-Type 就被改了。&lt;/li&gt;
  &lt;li&gt;callback 做长度限制，这个比较 low。&lt;/li&gt;
  &lt;li&gt;检测 callback 里面的字符。一般 callback 里面都是字母和数字，别的符号都不能有。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;callback 做一个编码，如果用 Go 语言做的话，如下。对所有 callback 都处理。&lt;/p&gt;

    &lt;p&gt;callback = template.JSEscapeString(callback)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;完整代码可以参考&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/jsonp/main.go&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>开发了一个rarbg插件</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2017/07/28/rarbg-douban"/>
   <updated>2017-07-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2017/07/28/rarbg-douban</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#效果图&quot; id=&quot;markdown-toc-效果图&quot;&gt;效果图&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#安装方法&quot; id=&quot;markdown-toc-安装方法&quot;&gt;安装方法&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#一些问题&quot; id=&quot;markdown-toc-一些问题&quot;&gt;一些问题&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;效果图&quot;&gt;效果图&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537938730/cyeam/9090jkikKD.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;安装方法&quot;&gt;安装方法&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;安装浏览器插件&lt;a href=&quot;http://tampermonkey.net/&quot;&gt;Tampermonkey&lt;/a&gt;。支持的浏览器很多，居然连 UC 浏览器都支持，真不错。&lt;/li&gt;
  &lt;li&gt;去&lt;a href=&quot;https://greasyfork.org/&quot;&gt;Greasy Fork&lt;/a&gt;下载我开发的rarbg插件，&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/27376-rarbg&quot;&gt;下载地址&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;一些问题&quot;&gt;一些问题&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;服务器在国外，读取豆瓣的接口会比较慢，如果数据没有命中缓存响应会慢。大家尝试多刷一下就好。&lt;/li&gt;
  &lt;li&gt;目前只支持电影，有兴趣的可以帮忙扩充一下电视剧。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 优化之路——自己造一个日志轮子</title>
   <link href="https://blog.cyeam.com/golang/2017/07/14/go-log"/>
   <updated>2017-07-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/07/14/go-log</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#按日志级别打印和控制日志&quot; id=&quot;markdown-toc-按日志级别打印和控制日志&quot;&gt;按日志级别打印和控制日志&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#日志文件自动分割&quot; id=&quot;markdown-toc-日志文件自动分割&quot;&gt;日志文件自动分割&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#日志的异步输出&quot; id=&quot;markdown-toc-日志的异步输出&quot;&gt;日志的异步输出&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#日志行号&quot; id=&quot;markdown-toc-日志行号&quot;&gt;日志行号&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#线程安全&quot; id=&quot;markdown-toc-线程安全&quot;&gt;线程安全&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#最后&quot; id=&quot;markdown-toc-最后&quot;&gt;最后&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#github&quot; id=&quot;markdown-toc-github&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;Golang 的&lt;a href=&quot;https://golang.org/pkg/log/&quot;&gt;log&lt;/a&gt;包内容不多，说实话，直接用来做日志开发有些简易。主要是缺少一些功能：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;按日志级别打印和控制日志；&lt;/li&gt;
  &lt;li&gt;日志文件自动分割；&lt;/li&gt;
  &lt;li&gt;异步打印日志。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;按日志级别打印和控制日志&quot;&gt;按日志级别打印和控制日志&lt;/h3&gt;

&lt;p&gt;我们实现的日志模块将会支持4个级别：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const (
	LevelError = iota
	LevelWarning
	LevelInformational
	LevelDebug
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;定义一个日志结构体：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Logger struct {
	level int
	l     *log.Logger
}

func (ll *Logger) Error(format string, v ...interface{}) {
	if LevelError &amp;gt; ll.level {
		return
	}
	msg := fmt.Sprintf(&quot;[E] &quot;+format, v...)
	ll.l.Printf(msg)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就能实现日志级别控制输出，并且打印的时候追加一个标记，比如上面的例子，&lt;strong&gt;&lt;em&gt;Error&lt;/em&gt;&lt;/strong&gt; 级别就会追加&lt;strong&gt;&lt;em&gt;[E]&lt;/em&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这个实现已经可以了，但是还是有优化的空间。比如打印追加标记&lt;strong&gt;&lt;em&gt;[E]&lt;/em&gt;&lt;/strong&gt;的时候，用的是字符串加法。字符串加法会申请新的内存，对性能不是很优化。我们需要通过字符数组来优化。&lt;/p&gt;

&lt;p&gt;但我不会这么去优化了。这个时候看一下 log 包的 API，可以发现原生包是支持设置前缀的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (l *Logger) SetPrefix(prefix string)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再去看一下具体的实现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
	*buf = append(*buf, l.prefix...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;原生包在写日志前缀的时候就用到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;来提升性能。既然人家已经提供了，我们还是不要自己造了。那么问题来了，设置前缀是初始化的时候就要设置，打印的时候自动输出出来。一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;log.Logger&lt;/code&gt;对象只能有一个前缀，而我们需要4种级别的前缀，这个如何打印？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Logger struct {
	level int
	err   *log.Logger
	warn  *log.Logger
	info  *log.Logger
	debug *log.Logger
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们直接申请4个日志对象就能解决。为了保证所有级别都打印到同一个文件里面，初始化的时候设置成同一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Writer&lt;/code&gt;即可。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;logger := new(LcLogger)

logger.err = log.New(w, &quot;[E] &quot;, flag)
logger.warn = log.New(w, &quot;[W] &quot;, flag)
logger.info = log.New(w, &quot;[I] &quot;, flag)
logger.debug = log.New(w, &quot;[D] &quot;, flag)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;设置日志级别：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (ll *Logger) SetLevel(l int) {
	ll.level = l
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打印的时候根据日志级别控制输出。（讲一个我遇到的坑。之前有一次打印日志打太多了，磁盘都打满了，就寻思着把日志级别调高减少打印内容。把级别调成 Error 后发现还是没有效果，最后看了看代码发现出问题的日志打印的是 Error 级别。。。Error级别的日志要尽量少打。）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (ll *Logger) Error(format string, v ...interface{}) {
	if LevelError &amp;gt; ll.level {
		return
	}
	ll.err.Printf(format, v...)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;日志文件自动分割&quot;&gt;日志文件自动分割&lt;/h3&gt;

&lt;p&gt;日志文件需要自动分割。否则一个文件过大，清理磁盘的时候这个文件因为还是打印日志没办法清理。&lt;/p&gt;

&lt;p&gt;日志分割我觉得简单的以大小分割就好。&lt;/p&gt;

&lt;p&gt;那么日志分割功能如何接入咱们上面实现的日志模块呢？关键就在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Writer&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Writer interface {
    Write(p []byte) (n int, err error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writer&lt;/code&gt;这个接口只有一个方法，如此简单。原生包默认打印日志会输出到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.Stderr&lt;/code&gt;里面，这是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.File&lt;/code&gt;类型的变量，它实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writer&lt;/code&gt;这个接口。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (f *File) Write(b []byte) (n int, err error)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;写日志的时候，log 包会自动调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;方法。我们可以自己实现一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writer&lt;/code&gt;，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;的时候计算一下写入此行日志之后当前日志文件大小，如果超过设定的值，执行一次分割。按日子分割日志也是这个时候操作。&lt;/p&gt;

&lt;p&gt;推荐用 gopkg.in/natefinch/lumberjack.v2 这个包来做日志分割，功能很强大。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jack := &amp;amp;lumberjack.Logger{
	Filename: lfn,
	MaxSize:  maxsize, // megabytes
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用也很简单，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jack&lt;/code&gt;对象就是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writer&lt;/code&gt;了，可以直接复制给&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Logger&lt;/code&gt;使用。&lt;/p&gt;

&lt;h3 id=&quot;日志的异步输出&quot;&gt;日志的异步输出&lt;/h3&gt;

&lt;p&gt;协程池也整个包：github.com/ivpusic/grpool。协程池就不展开说了，有兴趣的可以看看这个包的实现。&lt;/p&gt;

&lt;p&gt;日志的结构体再一次升级：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Logger struct {
	level int
	err   *log.Logger
	warn  *log.Logger
	info  *log.Logger
	debug *log.Logger
	p     *grpool.Pool
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;初始化：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;logger.p = grpool.NewPool(numWorkers, jobQueueLen)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;日志输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (ll *Logger) Error(format string, v ...interface{}) {
	if LevelError &amp;gt; ll.level {
		return
	}
	ll.p.JobQueue &amp;lt;- func() {
		ll.err.Printf(format, v...)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;日志行号&quot;&gt;日志行号&lt;/h3&gt;

&lt;p&gt;如果你一步一步按上面的做了，打印日志设置了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lshortfile&lt;/code&gt;，展示行号的花，你可能会发现这个时候打印出来的行号有问题。打印日志的时候用到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime&lt;/code&gt;里面的堆栈信息，因为我们封装了一层，所以打印的堆栈深度会发生变化。简单的说就是深了一层。&lt;/p&gt;

&lt;p&gt;原生的日志包提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func (l *Logger) Output(calldepth int, s string) error&lt;/code&gt;来控制日志堆栈深度输出，我们再次对代码进行调整。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Logger struct {
	level int
	err   *log.Logger
	warn  *log.Logger
	info  *log.Logger
	debug *log.Logger
	p     *grpool.Pool
	depth int
}

func (ll *Logger) Error(format string, v ...interface{}) {
	if LevelError &amp;gt; ll.level {
		return
	}
	ll.p.JobQueue &amp;lt;- func() {
		ll.err.Output(ll.depth, fmt.Sprintf(format, v...))
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们只封装了一层，所以深度设置成3就可以了。&lt;/p&gt;

&lt;h3 id=&quot;线程安全&quot;&gt;线程安全&lt;/h3&gt;

&lt;p&gt;原生包打印日志是线程安全的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (l *Logger) Output(calldepth int, s string) error {
	now := time.Now() // get this early.
	var file string
	var line int
	l.mu.Lock() // 看到这里了么？
	defer l.mu.Unlock()
	if l.flag&amp;amp;(Lshortfile|Llongfile) != 0 {
		// release lock while getting caller info - it&apos;s expensive.
		l.mu.Unlock()
		var ok bool
		_, file, line, ok = runtime.Caller(calldepth)
		if !ok {
			file = &quot;???&quot;
			line = 0
		}
		l.mu.Lock()
	}
	l.buf = l.buf[:0]
	l.formatHeader(&amp;amp;l.buf, now, file, line)
	l.buf = append(l.buf, s...)
	if len(s) == 0 || s[len(s)-1] != &apos;\n&apos; {
		l.buf = append(l.buf, &apos;\n&apos;)
	}
	_, err := l.out.Write(l.buf)
	return err
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有它的保证，我们也不需要考虑线程安全的问题了。&lt;/p&gt;

&lt;p&gt;那么问题来了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt&lt;/code&gt;包打印日志是线程安全的么？&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println&lt;/code&gt;安全么？&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println&lt;/code&gt;打印日志都打印到了哪里？有兴趣的可以留言一下一起讨论。&lt;/p&gt;

&lt;h3 id=&quot;最后&quot;&gt;最后&lt;/h3&gt;

&lt;p&gt;日志的打印会用到诸如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.Sprintf&lt;/code&gt;的东西，这个在实现的时候将会用到反射。反射会对性能有影响，但是不用反射的话代码过于恶心。&lt;/p&gt;

&lt;p&gt;上面介绍的日志只是在针对输出到文件。如果你想输出有邮件、ElasticSearch等其它地方，不要在初始化的时候通过各种复杂配置参数来实现。&lt;/p&gt;

&lt;p&gt;我说的是这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NewLogger(&quot;es&quot;, ...)
NewLogger(&quot;smtp&quot;, ...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样做的问题就是，我只能用你提供好的东西，如果想扩展只能修改日志包了。如果这个包是第三方的包，那让别人怎么扩展呢？而且这种实现也不是 Golang 的实现风格。&lt;/p&gt;

&lt;p&gt;其实大家看看原生的这些包，很多都是通过接口串联起来的。原生的 log 包，你可以认为他提供的服务主要是&lt;strong&gt;&lt;em&gt;流程方面的服务&lt;/em&gt;&lt;/strong&gt;，拼接好要打印的内容，包括行号、时间等等，保证线程安全，然后调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Writer&lt;/code&gt;来打印。如果我们要把日志打印到 ES 里面，就实现一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ESWriter&lt;/code&gt;。这才是 Golang 风格的代码。&lt;/p&gt;

&lt;h3 id=&quot;github&quot;&gt;GitHub&lt;/h3&gt;

&lt;p&gt;完整的代码放到了 GitHub 上面，&lt;a href=&quot;https://github.com/mnhkahn/gogogo/blob/master/logger/level_logger.go&quot;&gt;地址&lt;/a&gt;。欢迎大家 Star。&lt;/p&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=gogogo&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=gogogo&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;完整文档：&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/gogogo/logger&quot;&gt;godoc&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;【1】《Go 语言实战》&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 公共变量包——expvar</title>
   <link href="https://blog.cyeam.com/golang/2017/06/12/expvar"/>
   <updated>2017-06-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/06/12/expvar</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#功能&quot; id=&quot;markdown-toc-功能&quot;&gt;功能&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#调试接口&quot; id=&quot;markdown-toc-调试接口&quot;&gt;调试接口&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#源码&quot; id=&quot;markdown-toc-源码&quot;&gt;源码&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#不足&quot; id=&quot;markdown-toc-不足&quot;&gt;不足&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#总结&quot; id=&quot;markdown-toc-总结&quot;&gt;总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://golang.org/pkg/expvar/&quot;&gt;expvar&lt;/a&gt;包是 Golang 官方提供的公共变量包，它可以辅助调试全局变量。支持一些常见的类型：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;。如果我们的程序要用到上面提的四种类型（其中，Map 类型要求 Key 是字符串）。可以考虑使用这个包。&lt;/p&gt;

&lt;h3 id=&quot;功能&quot;&gt;功能&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;它支持对变量的基本操作，修改、查询这些；&lt;/li&gt;
  &lt;li&gt;整形类型，可以用来做计数器；&lt;/li&gt;
  &lt;li&gt;操作都是线程安全的。这点很不错。相信大家都自己整过全局变量，除了变量还得整的锁，自己写确实挺麻烦的；&lt;/li&gt;
  &lt;li&gt;此外还提供了调试接口，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/debug/vars&lt;/code&gt;。它能够展示所有通过这个包创建的变量；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;所有的变量都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Var&lt;/code&gt;类型，可以自己通过实现这个接口扩展其它的类型；&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; type Var interface {
     // String returns a valid JSON value for the variable.
     // Types with String methods that do not return valid JSON
     // (such as time.Time) must not be used as a Var.
     String() string
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handler()&lt;/code&gt;方法可以得到调试接口的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.Handler&lt;/code&gt;，和自己的路由对接。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这些基础的功能就不多说了，大家可以直接看官方的&lt;a href=&quot;https://golang.org/pkg/expvar/&quot;&gt;文档&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;调试接口&quot;&gt;调试接口&lt;/h3&gt;

&lt;p&gt;看源码的时候发现一个非常有意思的调试接口，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/debug/vars&lt;/code&gt;会把所有注册的变量打印到接口里面。这个接口很有情怀。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func init() {
	http.HandleFunc(&quot;/debug/vars&quot;, expvarHandler)
	Publish(&quot;cmdline&quot;, Func(cmdline))
	Publish(&quot;memstats&quot;, Func(memstats))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;源码&quot;&gt;源码&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var (
	mutex   sync.RWMutex
	vars    = make(map[string]Var)
	varKeys []string // sorted
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;varKeys&lt;/code&gt;是全局变量所有的变量名，而且是有序的；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vars&lt;/code&gt;根据变量名保存了对应的数据。当然&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutex&lt;/code&gt;就是这个 Map 的锁；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;这三个变量组合起来其实是一个有序线程安全哈希表的实现。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; type Var interface {
     // String returns a valid JSON value for the variable.
     // Types with String methods that do not return valid JSON
     // (such as time.Time) must not be used as a Var.
     String() string
 }
	
 type Int struct {
     i int64
 }

 func (v *Int) Value() int64 {
     return atomic.LoadInt64(&amp;amp;v.i)
 }

 func (v *Int) String() string {
     return strconv.FormatInt(atomic.LoadInt64(&amp;amp;v.i), 10)
 }

 func (v *Int) Add(delta int64) {
     atomic.AddInt64(&amp;amp;v.i, delta)
 }

 func (v *Int) Set(value int64) {
     atomic.StoreInt64(&amp;amp;v.i, value)
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;这个包里面的所有类型都实现了这个接口；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;以 Int 类型举例。实现非常的简单，注意&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set&lt;/code&gt;方法是线程安全的。别的类型实现也一样&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; func Publish(name string, v Var) {
     mutex.Lock()
     defer mutex.Unlock()
     if _, existing := vars[name]; existing {
         log.Panicln(&quot;Reuse of exported var name:&quot;, name)
     }
     vars[name] = v
     varKeys = append(varKeys, name)
     sort.Strings(varKeys)
 }
	
 func NewInt(name string) *Int {
     v := new(Int)
     Publish(name, v)
     return v
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;将变量注册到一开始介绍的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vars&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;varKeys&lt;/code&gt;里面；&lt;/li&gt;
  &lt;li&gt;注册时候也是线程安全的，所有的变量名在注册的最后排了个序；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;创建对象的时候会自动注册。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; func Do(f func(KeyValue)) {
     mutex.RLock()
     defer mutex.RUnlock()
     for _, k := range varKeys {
         f(KeyValue{k, vars[k]})
     }
 }

 func expvarHandler(w http.ResponseWriter, r *http.Request) {
     w.Header().Set(&quot;Content-Type&quot;, &quot;application/json; charset=utf-8&quot;)
     fmt.Fprintf(w, &quot;{\n&quot;)
     first := true
     Do(func(kv KeyValue) {
         if !first {
             fmt.Fprintf(w, &quot;,\n&quot;)
         }
         first = false
         fmt.Fprintf(w, &quot;%q: %s&quot;, kv.Key, kv.Value)
     })
     fmt.Fprintf(w, &quot;\n}\n&quot;)
 }

 func Handler() http.Handler {
     return http.HandlerFunc(expvarHandler)
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Do&lt;/code&gt;方法，利用一个闭包，按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;varKeys&lt;/code&gt;的顺序遍历所有全局变量；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expvarHandler&lt;/code&gt;方法是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.Handler&lt;/code&gt;类型，将所有变量通过接口输出，里面通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Do&lt;/code&gt;方法，把所有变量遍历了一遍。挺巧妙；&lt;/li&gt;
  &lt;li&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.HandleFunc&lt;/code&gt;方法把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expvarHandler&lt;/code&gt;这个外部不可访问的方法对外，这个方法用于对接自己的路由；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;输出数据的类型，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.Fprintf(w, &quot;%q: %s&quot;, kv.Key, kv.Value)&lt;/code&gt;，可以发现，值输出的字符串，所以输出的内容是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String()&lt;/code&gt;的结果。这里有一个技巧，虽然调用的字符串的方法，但是由于输出格式&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%s&lt;/code&gt;外面并没有引号，所有对于 JSON 来说，输出的内容是对象类型。相当于在 JSON 编码的时候做了一次类型转换。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; type Func func() interface{}

 func (f Func) Value() interface{} {
     return f()
 }

 func (f Func) String() string {
     v, _ := json.Marshal(f())
     return string(v)
 }
	
 func cmdline() interface{} {
     return os.Args
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;这是一个非常有意思的写法，它可以把任何类型转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Var&lt;/code&gt;类型；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Func&lt;/code&gt;定义的是函数，它的类型是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func() interface{}&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Func(cmdline)&lt;/code&gt;，使用的地方需要看清楚，参数是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmdline&lt;/code&gt;而不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmdline()&lt;/code&gt;，所以这个写法是类型转换。转换完之后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmdline&lt;/code&gt;方法就有了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String()&lt;/code&gt;方法，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String()&lt;/code&gt;方法里又调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f()&lt;/code&gt;，通过 JSON 编码输出。这个小技巧在前面提到的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.HandleFunc&lt;/code&gt;里面也有用到，Golang 的程序员对这个是真爱，咱们编码的时候也要多用用啊。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;不足&quot;&gt;不足&lt;/h3&gt;

&lt;p&gt;感觉这个包还是针对简单变量，比如整形、字符串这种比较好用。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;前面已经说了，Map 类型只支持 Key 是字符串的变量。其它类型还得自己扩展，扩展的话锁的问题还是得自己搞。而且 JSON 编码低版本不支持 Key 是整形类型的编码，也是个问题；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Var&lt;/code&gt;接口太简单，只有一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String()&lt;/code&gt;方法，基本上只能输出变量所有内容，别的东西都没办法控制，如果你的变量有10000个键值对，那么这个接口基本上属于不能用。多说一句，这是 Golang 设计的常见问题，比如日志包，输出的类型是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Writer&lt;/code&gt;，而这个接口只支持一个方法&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write([]byte)&lt;/code&gt;，想扩展日志包的功能很难，这也失去了抽象出来一个接口的意义。&lt;/li&gt;
  &lt;li&gt;路由里面还默认追加了启动参数和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemStats&lt;/code&gt;内存相关参数。我个人觉得后面这个不应该加，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime.ReadMemStats(stats)&lt;/code&gt;会引起 Stop The World，总感觉不值当。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;总结&quot;&gt;总结&lt;/h3&gt;

&lt;p&gt;看到就写了，并没有什么沉淀，写得挺乱的。这个包很简单，但是里面还是有些可以借鉴的编码和设计。新版本的 Golang 已经能解析整形为 Key 的哈希表了，这个包啥时候能跟上支持一下？&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 优化之路——HTTP长连接</title>
   <link href="https://blog.cyeam.com/golang/2017/05/31/go-http-keepalive"/>
   <updated>2017-05-31T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/05/31/go-http-keepalive</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tcp-相关&quot; id=&quot;markdown-toc-tcp-相关&quot;&gt;TCP 相关&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#http-包如何使用-tcp-长连接&quot; id=&quot;markdown-toc-http-包如何使用-tcp-长连接&quot;&gt;HTTP 包如何使用 TCP 长连接？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#我们的程序为啥长连接失效&quot; id=&quot;markdown-toc-我们的程序为啥长连接失效&quot;&gt;我们的程序为啥长连接失效？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#如何解决问题&quot; id=&quot;markdown-toc-如何解决问题&quot;&gt;如何解决问题？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#后续&quot; id=&quot;markdown-toc-后续&quot;&gt;后续&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;压测的是否发现服务端&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIME_WAIT&lt;/code&gt;状态的连接很多。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;netstat -nat | grep :8080 | grep TIME_WAIT | wc -l   
17731
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIME_WAIT&lt;/code&gt;状态多，简单的说就是服务端主动关闭了TCP连接。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/268981-20151123221518233-1312440646.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;TCP频繁的建立连接，会有一些问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;三次握手建立连接、四次握手断开连接都会对性能有损耗；&lt;/li&gt;
  &lt;li&gt;断开的连接断开不会立刻释放，会等待2MSL的时间，据我观察是1分钟；&lt;/li&gt;
  &lt;li&gt;大量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIME_WAIT&lt;/code&gt;会占用内存，一个连接实测是3.155KB。而且占用太多，有可能会占满端口，一台服务器最多只能有6万多个端口；&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;tcp-相关&quot;&gt;TCP 相关&lt;/h3&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;长连接的概念包括TCP长连接和HTTP长连接。首先得保证TCP是长连接。我们就从它说起。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (c *TCPConn) SetKeepAlive(keepalive bool) error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SetKeepAlive sets whether the operating system should send keepalive messages on the connection. 这个方法比较简单，设置是否开启长连接。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (c *TCPConn) SetReadDeadline(t time.Time) error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SetReadDeadline sets the deadline for future Read calls and any currently-blocked Read call. A zero value for t means Read will not time out.这个函数就很讲究了。我之前的理解是设置读取超时时间，这个方法也有这个意思，但是还有别的内容。它设置的是读取超时的绝对时间。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (c *TCPConn) SetWriteDeadline(t time.Time) error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SetWriteDeadline sets the deadline for future Write calls and any currently-blocked Write call. Even if write times out, it may return n &amp;gt; 0, indicating that some of the data was successfully written. A zero value for t means Write will not time out. 这个方法是设置写超时，同样是绝对时间。&lt;/p&gt;

&lt;h3 id=&quot;http-包如何使用-tcp-长连接&quot;&gt;HTTP 包如何使用 TCP 长连接？&lt;/h3&gt;

&lt;p&gt;http 服务器启动之后，会循环接受新请求，为每一个请求（连接）创建一个协程。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// net/http/server.go L1892
for {
	rw, e := l.Accept()
	go c.serve()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;下面是每个协程的执行的代码，我只摘录了一部分关键的逻辑。可以发现，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serve&lt;/code&gt;方法里面还有一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// net/http/server.go L1320
func (c *conn) serve() {
	defer func() {
		if !c.hijacked() {
			c.close()
		}
	}()

	for {
		w, err := c.readRequest()
		
		if err != nil {
		}
		
		serverHandler{c.server}.ServeHTTP(w, w.req)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个循环是用来做什么的？其实也容易理解，如果是长连接，一个协程可以执行多次响应。如果只执行了一次，那就是短连接。长连接会在超时或者出错后退出循环，也就是关闭长连接。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;函数可以让协程结束之后关闭 TCP 连接。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readRequest&lt;/code&gt;函数用来解析 HTTP 协议。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// net/http/server.go
func (c *conn) readRequest() (w *response, err error) {
	if d := c.server.ReadTimeout; d != 0 {
		c.rwc.SetReadDeadline(time.Now().Add(d))
	}
	if d := c.server.WriteTimeout; d != 0 {
		defer func() {
			c.rwc.SetWriteDeadline(time.Now().Add(d))
		}()
	}
	
	if req, err = ReadRequest(c.buf.Reader); err != nil {
		if c.lr.N == 0 {
			return nil, errTooLarge
		}
		return nil, err
	}
}

func ReadRequest(b *bufio.Reader) (req *Request, err error) {
	// First line: GET /index.html HTTP/1.0
	var s string
	if s, err = tp.ReadLine(); err != nil {
		return nil, err
	}
	
	req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
	
	mimeHeader, err := tp.ReadMIMEHeader()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;具体参与解析 HTTP 协议的部分是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadRequest&lt;/code&gt;方法，而调用它之前，设置了读写超时时间。根据前面的描述，超时时间设置的是绝对时间。所以这里都是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time.Now().Add(d)&lt;/code&gt;来设置的。不同的是写超时是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;执行，也就是函数返回后才执行。&lt;/p&gt;

&lt;h3 id=&quot;我们的程序为啥长连接失效&quot;&gt;我们的程序为啥长连接失效？&lt;/h3&gt;

&lt;p&gt;通过源码我们能大概知道程序流程了，按道理是支持长连接的。为啥我们的程序不行呢？&lt;/p&gt;

&lt;p&gt;我们的程序使用的是 beego 框架，它支持的超时是同时设置读写超时。而我们的设置是1秒。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;beego.HttpServerTimeOut = 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我对读写超时的理解，读超时是收到数据到读取完毕的时间；写超时是从一开始写到写完的时间。我对这两个超时的理解都不对。&lt;/p&gt;

&lt;p&gt;实际上，从上面的源码可以发现，&lt;strong&gt;&lt;em&gt;写超时是读取完毕之后设置的超时时间。也就是读取完毕之后的时间，加上逻辑执行时间，加上内容返回时间的总和&lt;/em&gt;&lt;/strong&gt;。按照我们的设置，超过1秒就算超时。&lt;/p&gt;

&lt;p&gt;下面详细说说读超时。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadRequest&lt;/code&gt;是堵塞执行的，如果没有用户请求，它会一直等待着。&lt;strong&gt;&lt;em&gt;而读超时是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadRequest&lt;/code&gt;之前设置的，它除了读取数据之外，还有一部分耗时，那就是等待时间&lt;/em&gt;&lt;/strong&gt;。假如一直没有用户请求，此时读超时已经被设置成1秒后了，超过1秒之后，这个连接还是会被断开。&lt;/p&gt;

&lt;h3 id=&quot;如何解决问题&quot;&gt;如何解决问题？&lt;/h3&gt;

&lt;p&gt;原因已经说明白了。大量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIME_WAIT&lt;/code&gt;是超时引起的，有可能是等待时间过长引起的读超时；也有可能是程序在压测情况下出现一部分执行超时，这样会导致写超时。&lt;/p&gt;

&lt;p&gt;我们目前使用的是 beego 框架，它并不支持单独设置读写超时，所以我目前的解决方式是将读写超时调整得大一些。&lt;/p&gt;

&lt;p&gt;从1.6版本开始，Golang 能够支持空闲超时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IdleTimeout&lt;/code&gt;，可以认为读超时就是读取数据的时间，空闲超时来控制等待时间。但是它有一个问题，如果空闲超时没有设置，而读超时设置了，那么读超时还是会作为空闲超时时间来使用。我估计这么做的原因是为了向前兼容。再一个问题就是 beego 并不支持这个时间的设置，所以我目前也没有别的太好的方法来控制超时时间。&lt;/p&gt;

&lt;h3 id=&quot;后续&quot;&gt;后续&lt;/h3&gt;

&lt;p&gt;其实服务端最合理的超时控制需要这几个方面：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;读超时。就是单纯的读超时，不要包括等待时间，否则无法区分超时是读数据引起的还是等待引起的。&lt;/li&gt;
  &lt;li&gt;写超时。最好也是单纯的写数据超时。如果网络良好，因为逻辑执行慢就把连接断开，这样也不是很合适。读写超时都应该和目前逻辑设置的一样，设置得短一些。&lt;/li&gt;
  &lt;li&gt;空闲超时。这个可以根据实际情况配置，可以适当大一些。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;逻辑超时。一般情况下是不会发生网络层面的读写超时的，压测情况下超时大部分都是由于逻辑超时引起的。Golang 原生包支持了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TimeoutHandler&lt;/code&gt;。它可以控制逻辑的超时。可惜 beego 目前不支持设置逻辑超时。而我也没有想到太好的方法把 beego 中接入它。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://zhuanlan.zhihu.com/p/25241630?hmsr=toutiao.io&amp;amp;utm_medium=toutiao.io&amp;amp;utm_source=toutiao.io&quot;&gt;Linux 中每个 TCP 连接最少占用多少内存？ - 陈硕&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/&quot;&gt;The complete guide to Go net/http timeouts - Filippo Valsorda&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Awesome Go</title>
   <link href="https://blog.cyeam.com/golang/2017/05/09/awesomego"/>
   <updated>2017-05-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/05/09/awesomego</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#go-包&quot; id=&quot;markdown-toc-go-包&quot;&gt;Go 包&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#go-工具&quot; id=&quot;markdown-toc-go-工具&quot;&gt;Go 工具&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;go-包&quot;&gt;Go 包&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/astaxie/beego&quot;&gt;beego&lt;/a&gt;。算起来用了三年beego了。当初选择用它的理由很简单，文档是中文的，开发者是中国的，交流方便。优点就是用得人较多，框架集成度高，工具比较多。不过从1.6开始兼容性大大降低开始对它无感。现在越来越觉得beego框架设计的太重，Golang的设计特点就是轻便，把各个功能包组装起来用。比如配置它的config包，不用又不行，因为框架启动就会调用。最近大家都在推荐&lt;a href=&quot;https://github.com/gin-gonic/gin&quot;&gt;gin&lt;/a&gt;，有兴趣可以试试。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/astaxie/beego/tree/master/logs&quot;&gt;beego/logs&lt;/a&gt;。日志包一直在用beego内置的logs包。它有一个特点就是支持日志自动分割，可以按行数分割或者按日期分割。目前还没有发现支持此功能别的日志包，有的话大家给我推荐一下。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/astaxie/beego/tree/master/orm&quot;&gt;beego/orm&lt;/a&gt;。我的服务对数据库操作很少，orm只是简单用用。之前还用过&lt;a href=&quot;https://github.com/go-gorp/gorp&quot;&gt;grop&lt;/a&gt;和&lt;a href=&quot;https://github.com/go-xorm/xorm&quot;&gt;xorm&lt;/a&gt;。Golang主要是用来做接口，对于数据库操作都比较简单，orm高级操作基本用不到。所以对我来说这些orm功能都差不多。。。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/garyburd/redigo&quot;&gt;redigo&lt;/a&gt;。连Redis必备。功能很完善。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/PuerkitoBio/goquery&quot;&gt;goquery&lt;/a&gt;。用来解析HTML。开发爬虫都会用到它。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/franela/goreq&quot;&gt;goreq&lt;/a&gt;。一个HTTP请求包。之前会用它是因为它支持Gzip压缩。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/spf13/viper&quot;&gt;viper&lt;/a&gt;。一个配置包。支持解析各种格式的配置文件，最让我惊喜的是它支持etcd。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/emirpasic/gods&quot;&gt;gods&lt;/a&gt;。各种数据结构的Golang实现。这些代码生产环境没有直接用到过，不过自己写的时候可以借鉴一下。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/pquerna/ffjson&quot;&gt;ffjson&lt;/a&gt;。根据Golang的结构体自动生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MarshalJSON&lt;/code&gt;方法从而避免原生包通过反射编码引起的垃圾回收的问题。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/tools/godep&quot;&gt;godep&lt;/a&gt;。说实话Golang对包管理不太友好。有一个项目用了godep来做版本管理，每次都要执行好多命令真是麻烦。Golang新版内置包管理了，这个可以放弃使用了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/willf/bitset&quot;&gt;bitset&lt;/a&gt;。这个包已经在生产环境使用了，它是Bitmap的Golang实现。底层用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint64&lt;/code&gt;切片保存数据。性能是内置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;的40倍。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/bamzi/jobrunner&quot;&gt;jobrunner&lt;/a&gt;。一个Crontab包。beego内置的Crontab包之前有bug，只能找个新的。我关注这个的包的时候才100个Star，不过好在好用。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;go-工具&quot;&gt;Go 工具&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://gsquire.github.io/static/post/cleaner-go/&quot;&gt;Cleaner Go&lt;/a&gt;。代码检测相关不错的文章。
    &lt;ul&gt;
      &lt;li&gt;静态代码检测 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;staticcheck.exe $(glide.exe nv)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;无用代码检测 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unused.exe $(glide.exe nv)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;代码简化建议 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gosimple.exe $(glide.exe nv)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;原生检测 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go vet&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ChimeraCoder/gojson&quot;&gt;gojson&lt;/a&gt;。一个工具，可以通过Json格式的文本生成Golang结构体代码。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;会持续更新。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 优化之路——空结构</title>
   <link href="https://blog.cyeam.com/golang/2017/04/11/go-empty-struct"/>
   <updated>2017-04-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/04/11/go-empty-struct</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#空对象&quot; id=&quot;markdown-toc-空对象&quot;&gt;空对象&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hashset&quot; id=&quot;markdown-toc-hashset&quot;&gt;hashset&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#性能比较&quot; id=&quot;markdown-toc-性能比较&quot;&gt;性能比较&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#结论&quot; id=&quot;markdown-toc-结论&quot;&gt;结论&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#后续&quot; id=&quot;markdown-toc-后续&quot;&gt;后续&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;开发 hashset 常用的套路：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;map[int]int8
map[int]bool
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们一般只用 map 的键来保存数据，值是没有用的。所以来缓存集合数据会造成内存浪费。&lt;/p&gt;

&lt;h3 id=&quot;空对象&quot;&gt;空对象&lt;/h3&gt;

&lt;p&gt;空对象是个神奇的东西。它指的是没有字段的结构类型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Q struct{}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;它牛逼的地方在于：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;可以和普通结构一样操作&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  var a = []struct{}{struct{}{}}
  fmt.Println(len(a)) // prints 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;不占用空间&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  var s struct{}
  fmt.Println(unsafe.Sizeof(s)) // prints 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;声明两个空对象，它们指向同一个地址&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  type A struct{}
  a := A{}
  b := A{}
  fmt.Println(&amp;amp;a == &amp;amp;b) // prints true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;造成这个结果的原因是 Golang 的编译器会把这种空对象都当成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime.zerobase&lt;/code&gt;处理。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var zerobase uintptr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;hashset&quot;&gt;hashset&lt;/h3&gt;

&lt;p&gt;有了上面的介绍，就可以利用空结构来优化 hashset 了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var itemExists = struct{}{}

type Set struct {
	items map[interface{}]struct{}
}

func New() *Set {
	return &amp;amp;Set{items: make(map[interface{}]struct{})}
}

func (set *Set) Add(item interface{}) {
	set.items[item] = itemExists
}

func (set *Set) Remove(item interface{}) {
	delete(set.items, item)
}

func (set *Set) Contains(item interface{}) bool {
	if _, contains := set.items[item]; !contains {
		return false
	}
	return true
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一个简易的 hashset 实现就完成了。&lt;/p&gt;

&lt;h3 id=&quot;性能比较&quot;&gt;性能比较&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func BenchmarkIntSet(b *testing.B) {
	var B = NewIntSet(3)
	B.Set(10).Set(11)
	for i := 0; i &amp;lt; b.N; i++ {
		if B.Exists(1) {

		}
		if B.Exists(11) {

		}
		if B.Exists(1000000) {

		}
	}
}

func BenchmarkMap(b *testing.B) {
	var B = make(map[int]int8, 3)
	B[10] = 1
	B[11] = 1
	for i := 0; i &amp;lt; b.N; i++ {
		if _, exists := B[1]; exists {

		}
		if _, exists := B[11]; exists {

		}
		if _, exists := B[1000000]; exists {

		}
	}
}

BenchmarkIntSet-2       50000000                35.3 ns/op             0 B/op          0 allocs/op
BenchmarkMap-2          30000000                41.2 ns/op             0 B/op          0 allocs/op
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;性能，有些提升，但不是特别明显。尤其是线上压力不大的情况性能应该不会有明显变化；&lt;/li&gt;
  &lt;li&gt;内存占用。我们的服务缓存较多、占用内存较大，通过这个优化实测可以减少 1.6 GB 的空间。不过这个优化的空间取决于数据量。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;后续&quot;&gt;后续&lt;/h3&gt;

&lt;p&gt;前面有个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var a, b struct{}
fmt.Println(&amp;amp;a == &amp;amp;b) // true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从 Go1.6 开始输出的结果有了变化，之前是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;，现在是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://golang.org/ref/spec#Comparison_operators&quot;&gt;官方&lt;/a&gt;的解释：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value nil. &lt;strong&gt;Pointers to distinct zero-size variables may or may not be equal&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;不过这个处理并不影响对于空结构体的使用。官方对于空指针相等的判断进行了修改，至于原因并没有给出详细的解释。&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://dave.cheney.net/2014/03/25/the-empty-struct&quot;&gt;The empty struct - Dave Cheney&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;https://github.com/emirpasic/gods/blob/master/sets/hashset/hashset.go&quot;&gt;gods - emirpasic&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】 《Go 语言学习笔记》 - 雨痕。5.5 结构。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>jsonfield——动态输出 Json 内容</title>
   <link href="https://blog.cyeam.com/golang/2017/04/05/jsonfield"/>
   <updated>2017-04-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/04/05/jsonfield</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#jsonfield&quot; id=&quot;markdown-toc-jsonfield&quot;&gt;jsonfield&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#安装&quot; id=&quot;markdown-toc-安装&quot;&gt;安装&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#使用&quot; id=&quot;markdown-toc-使用&quot;&gt;使用&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#反馈&quot; id=&quot;markdown-toc-反馈&quot;&gt;反馈&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=jsonfield&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=jsonfield&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;jsonfield&quot;&gt;jsonfield&lt;/h3&gt;

&lt;p&gt;它能够动态输出指定字段，开发简单。我将它用于通过接口调试数据时使用。&lt;/p&gt;

&lt;h3 id=&quot;安装&quot;&gt;安装&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get -v github.com/mnhkahn/jsonfield
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;使用&quot;&gt;使用&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type A struct {
	A int
	B int
}

jj := new(A)
jj.A, jj.B = 111, 222

byts, err := Marshal(jj) // {&quot;A&quot;:111,&quot;B&quot;:222}

byts, err := Marshal(jj, &quot;A&quot;) // {&quot;A&quot;:111}

byts, err := Marshal(jj, &quot;B&quot;) // {&quot;B&quot;:222}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;反馈&quot;&gt;反馈&lt;/h3&gt;

&lt;p&gt;如果有疑问或者建议，可以通过微信联系我或者在 Github 里面提 &lt;a href=&quot;https://github.com/mnhkahn/jsonfield/issues&quot;&gt;Issue&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>我常用的开发工具</title>
   <link href="https://blog.cyeam.com/golang/2017/03/27/tool"/>
   <updated>2017-03-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/03/27/tool</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#vagrant&quot; id=&quot;markdown-toc-vagrant&quot;&gt;vagrant&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#securecrt&quot; id=&quot;markdown-toc-securecrt&quot;&gt;SecureCRT&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#jsoncn&quot; id=&quot;markdown-toc-jsoncn&quot;&gt;json.cn&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#heroku&quot; id=&quot;markdown-toc-heroku&quot;&gt;heroku&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#everything&quot; id=&quot;markdown-toc-everything&quot;&gt;Everything&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#web前端助手fehelper&quot; id=&quot;markdown-toc-web前端助手fehelper&quot;&gt;WEB前端助手(FeHelper)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tampermonkey&quot; id=&quot;markdown-toc-tampermonkey&quot;&gt;tampermonkey&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#shadowsocks&quot; id=&quot;markdown-toc-shadowsocks&quot;&gt;ShadowSocks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#proxifier&quot; id=&quot;markdown-toc-proxifier&quot;&gt;Proxifier&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#wingy&quot; id=&quot;markdown-toc-wingy&quot;&gt;Wingy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;工具清单很多人都列过，我也来说说我的，看看对你有没有帮助。&lt;/p&gt;

&lt;h3 id=&quot;vagrant&quot;&gt;vagrant&lt;/h3&gt;

&lt;p&gt;你有没有在 Windows 下面开发程序手动编译 C 程序的苦恼？你有没有因为 Windows 缺少 Linux shell 脚本无法痛快得使用命令的烦恼？你有没有换台电脑重新搭建开发环境的烦恼？vagrant 通通帮你排忧解难。&lt;/p&gt;

&lt;p&gt;对我来说，在 Linux 环境下面开发是非常重要的。尝试过 N 种方法。安过虚拟机，太卡；用过 Powershell，太挫；后来用上了MobaXterm，这个用了很长时间，算是比较接近 Linux 环境的了，但是它还是基于封装 Powershell，差那么点意思。最后用上了 vagrant，一直用到了现在。&lt;/p&gt;

&lt;p&gt;vagrant 也是基于 Virtualbox 的虚拟机。但是算是简化版，运行很流畅。对于虚拟机还有一点非常重要，就是 Windows 和 虚拟机之间代码的同步，Windows 下面通过 IDE 编码，虚拟机里面编译、调试、运行程序。用它来搭建一整套开发环境要比自己动手方便很多。还有就是软件的安装，类似于 Ubuntu 的方式，安装命令可以通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get&lt;/code&gt; 搞定。&lt;/p&gt;

&lt;p&gt;安装教程&lt;a href=&quot;https://github.com/astaxie/go-best-practice/blob/master/ebook/zh/01.2.md&quot;&gt;《Go实战开发——Vagrant安装配置》&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;系统开机之后需要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt; 到 vagrant 的目录执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vagrant up&lt;/code&gt; 来启动虚拟机。它也不是那么完美，有时虚拟机会死掉，可以执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vagrant reload&lt;/code&gt; 重启一下。&lt;/p&gt;

&lt;p&gt;通过 SSH 的方式登录虚拟机，我一般都是用 SecureCRT。&lt;/p&gt;

&lt;h3 id=&quot;securecrt&quot;&gt;SecureCRT&lt;/h3&gt;

&lt;p&gt;这个不用多说，基本上都是标配。我就说一个大家可能不了解的小功能：文件的上传和下载。一般如果向 Linux 服务器传文件，都是通过 FileZilla 这种工具，通过 FTP 协议来传输。实际上 SecureCRT 内置了文件传输工具。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rz&lt;/code&gt; 可以帮助从本地上传文件到服务器；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sz&lt;/code&gt; 可以从服务器下载文件到本地。&lt;/p&gt;

&lt;h3 id=&quot;jsoncn&quot;&gt;json.cn&lt;/h3&gt;

&lt;p&gt;一般数据传输都免不了用到 JSON 格式。我一般都是用 json.cn 来帮我格式化。感觉用这个操作步骤能少一些。其实我也没有正经数过^ ^。&lt;/p&gt;

&lt;h3 id=&quot;heroku&quot;&gt;heroku&lt;/h3&gt;

&lt;p&gt;免费又支持 Golang ，并且国内能访问的 PaaS 服务，我只知道 heroku 了。&lt;a href=&quot;https://www.cyeam.com&quot;&gt;我的网站首页&lt;/a&gt;就放在这个上面。&lt;/p&gt;

&lt;p&gt;登录 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku login&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;它通过提交代码的方式来部署代码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push heroku master&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;需要安装 CLI 工具来辅助初始化和部署。安装可以参考其&lt;a href=&quot;https://devcenter.heroku.com/articles/heroku-cli&quot;&gt;官方文档&lt;/a&gt;。其实访问速度也一般啦，开发小工具玩玩就好。&lt;/p&gt;

&lt;h3 id=&quot;everything&quot;&gt;Everything&lt;/h3&gt;

&lt;p&gt;据说这是用 Windows 的唯一理由。用它来查找文件要比原生的快很多。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://everything.en.softonic.com/&quot;&gt;下载地址&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;web前端助手fehelper&quot;&gt;WEB前端助手(FeHelper)&lt;/h3&gt;

&lt;p&gt;JSON 格式化插件大家肯定都安装了，大部分都是用的JSONView。我推荐一下狼长的&lt;a href=&quot;https://chrome.google.com/webstore/detail/web%E5%89%8D%E7%AB%AF%E5%8A%A9%E6%89%8Bfehelper/pkgccpejnmalmdinmhkkfafefagiiiad?utm_source=plus&quot;&gt;FeHelper&lt;/a&gt;。在格式化 JSON 的时候这个工具可以帮助计算对象长度，比如它可以展示数组的长度。功能很简单是吧，但是我只知道这个工具有这个功能。&lt;/p&gt;

&lt;h3 id=&quot;tampermonkey&quot;&gt;tampermonkey&lt;/h3&gt;

&lt;p&gt;一直在用 Chrome，也一直想开发个插件工具类的东西。但是开发一个完整的插件对我来说成本还是很高的。&lt;a href=&quot;http://tampermonkey.net/&quot;&gt;Tampermonkey&lt;/a&gt;是个浏览器插件，支持运行自定义脚本。这使得我可以高效得开发一些趁手的工具。我在公司就开发了一个检测页面调试信息的工具。&lt;/p&gt;

&lt;p&gt;我还开发了一个 rarbg 页面翻工具，&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/27376-rarbg&quot;&gt;下载地址&lt;/a&gt;。不过接口那边缓存还有问题，大家多刷几次。。。&lt;/p&gt;

&lt;h3 id=&quot;shadowsocks&quot;&gt;ShadowSocks&lt;/h3&gt;

&lt;p&gt;现在科学上网的成本是越来越高了，还是花钱靠谱一些。这个我已经用了两年了，支持支付宝，还是挺不错的。支持这个协议的客户端也比较多。好了，说这么多无非就是想放一个我的&lt;a href=&quot;https://portal.shadowsocks.to/aff.php?aff=5842&quot;&gt;推荐链接&lt;/a&gt;，好像你用这个注册可以减免一个月的费用。&lt;/p&gt;

&lt;h3 id=&quot;proxifier&quot;&gt;Proxifier&lt;/h3&gt;

&lt;p&gt;用 ShadowSocks 只能解决网页的问题，如果要在命令行也能使用，Windows 得用这个工具。&lt;/p&gt;

&lt;h3 id=&quot;wingy&quot;&gt;Wingy&lt;/h3&gt;

&lt;p&gt;这是最近发现的神器，算是 Shadowrocket 的免费版。这个可以在 iOS 系统上把 SS 协议转成 VPN，实现全局代理。它还支持自动路由。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://itunes.apple.com/us/app/wingy-http-s-socks5-proxy-utility/id1178584911?mt=8&quot;&gt;下载地址&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 通过fmt包输出完整struct信息</title>
   <link href="https://blog.cyeam.com/golang/2017/03/06/go-fmt-v"/>
   <updated>2017-03-06T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/03/06/go-fmt-v</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#结论&quot; id=&quot;markdown-toc-结论&quot;&gt;结论&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#后续&quot; id=&quot;markdown-toc-后续&quot;&gt;后续&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt&lt;/code&gt;调试，是非常常见的做法，打印一个对象，我一直都是在用&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fmt.Println(a)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;或者&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fmt.Printf(&quot;%v&quot;, a)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打印复杂对象不能用内置的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;println&lt;/code&gt;，这个只会打印出来指针，看不到内容。而上面两种方式是可以打印结构体内容的，但是没有值对应的名字。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func main() {
	a := new(AAA)
	a.A = &quot;Hello&quot;
	fmt.Println(a)
}

type AAA struct {
	A string
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;amp;{Hello}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;如果结构很复杂，内部变量很多，多个值之间只会通过空格分隔，而且如果有的值是空字符串，这就是一件非常尴尬的事情了，肉眼根本就看不出来有一个空格。&lt;/p&gt;

&lt;p&gt;所以我一直的方法就是把结构体通过Json编码，再输出。现在想想真蠢。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt&lt;/code&gt;包内置的方法，本来就可以展示类似Json的形式，没必要自己瞎搞。把上面的输出代码改了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fmt.Printf(&quot;%+v\n&quot;, a)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;amp;{A:Hello}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;就是这么简单。&lt;/p&gt;

&lt;p&gt;查看源码，fmt/print.go的926行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if p.fmt.plusV || p.fmt.sharpV {
	if f := t.Field(i); f.Name != &quot;&quot; {
		p.buf.WriteString(f.Name)
		p.buf.WriteByte(&apos;:&apos;)
	}
}
p.printValue(getField(v, i), verb, depth+1)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过反射拿到变量的Name，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;printValue&lt;/code&gt;方法也是通过反射拿到值，最后打印。这里就不展开说了。&lt;/p&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;p&gt;其实这个东西没多高端，官方文档上来就介绍了。想起来每次调试都那么费尽，以后还是得多看看这些。温故而知新，可以为师矣。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;%v	the value in a default format
		when printing structs, the plus flag (%+v) adds field names&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;后续&quot;&gt;后续&lt;/h3&gt;

&lt;p&gt;Go 的官方问题有个小缺陷就是太精简了，很多东西说明不是那么细致。今天看到一篇文章，详细说明了格式化输出的写法，&lt;a href=&quot;https://yourbasic.org/golang/fmt-printf-reference-cheat-sheet/&quot;&gt;How to format anything (string, number or arbitrary data) with fmt&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;完整介绍 Golang 格式化输出：&lt;a href=&quot;https://blog.cyeam.com/golang/2018/09/10/fmt&quot;&gt;fmt 如何进行格式化？&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 优化之路——临时对象池</title>
   <link href="https://blog.cyeam.com/golang/2017/02/08/go-optimize-slice-pool"/>
   <updated>2017-02-08T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/02/08/go-optimize-slice-pool</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#堆还是栈&quot; id=&quot;markdown-toc-堆还是栈&quot;&gt;堆还是栈？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#内存碎片化&quot; id=&quot;markdown-toc-内存碎片化&quot;&gt;内存碎片化&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#临时对象池&quot; id=&quot;markdown-toc-临时对象池&quot;&gt;临时对象池&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#结论&quot; id=&quot;markdown-toc-结论&quot;&gt;结论&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;在高并发的情况下，如果每次请求都需要申请一块用于计算的内存，比如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make([]int64, 0, len(ids))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将会是一件成本很高的事情。为了定位项目中的慢语句，我曾经采用“二分法”的方式打印慢日志，定位程序变慢的代码位置。它并不是每次都慢，而是每过几秒钟就突然变得极其慢，TPS能从2000降到200。引起这个问题就是类似于上面这条语句。&lt;/p&gt;

&lt;p&gt;初始化一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;，初学者会用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make([]int64, 0)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;高级一些的程序员都会知道，这样第一次分配内存相当于没有分配，如果要后续&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;元素，会引起&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;以指数形式扩充，可以参考下面的代码，追加了3个元素，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;扩容了3次。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a := make([]int64, 0)
fmt.Println(cap(a), len(a))

for i := 0; i &amp;lt; 3; i++ {
	a = append(a, 1)
	fmt.Println(cap(a), len(a))
}

0 0
1 1
2 2
4 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;每一次扩容空间，都是会重新申请一块区域，把就空间里面的元素复制进来，把新的追加进来。那旧空间里面的元素怎么办？等着垃圾回收呗。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;简单的优化方式，就是给自己要用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;提前申请好空间，类似于最开头的那行代码。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make([]int64, 0, len(ids))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样做避免了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;多次扩容申请内存，但还是有问题的。&lt;/p&gt;

&lt;h3 id=&quot;堆还是栈&quot;&gt;堆还是栈？&lt;/h3&gt;

&lt;p&gt;程序会从操作系统申请一块内存，而这块内存也会被分成堆和栈。栈可以简单得理解成一次函数调用内部申请到的内存，它们会随着函数的返回把内存还给系统。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func F() {
	temp := make([]int, 0, 20)
	...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;类似于上面代码里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;temp&lt;/code&gt;变量，只是内函数内部申请的临时变量，并不会作为返回值返回，它就是被编译器申请到栈里面。申请到栈内存好处：&lt;strong&gt;&lt;em&gt;函数返回直接释放，不会引起垃圾回收，对性能没有影响&lt;/em&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func F() []int{
	a := make([]int, 0, 20)
	return a
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而上面这段代码，申请的代码一模一样，但是申请后作为返回值返回了，编译器会认为变量之后还会被使用，当函数返回之后并不会将其内存归还，那么它就会被申请到堆上面了。&lt;strong&gt;&lt;em&gt;申请到堆上面的内存才会引起垃圾回收&lt;/em&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;那么考考大家，下面这三种情况怎么解释？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func F() {
	a := make([]int, 0, 20)
	b := make([]int, 0, 20000)

	l := 20
	c := make([]int, 0, l)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;代码一样，就是申请的空间不一样大，但是它们两个的命运是截然相反的。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;前面已经介绍过，会申请到栈上面，而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;，由于申请的内存较大，编译器会把这种申请内存较大的变量转移到堆上面。&lt;strong&gt;&lt;em&gt;即使是临时变量，申请过大也会在堆上面申请&lt;/em&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;而&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt;，对我们而言其含义和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;是一致的，但是&lt;strong&gt;&lt;em&gt;编译器对于这种不定长度的申请方式，也会在堆上面申请，即使申请的长度很短&lt;/em&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;可以通过下面的命令查看变量申请的位置。详细内容可以参考我之前的文章&lt;a href=&quot;https://blog.cyeam.com/golang/2016/08/18/apatternforoptimizinggo&quot;&gt;《【译】优化Go的模式》&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go build -gcflags=&apos;-m&apos; . 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;内存碎片化&quot;&gt;内存碎片化&lt;/h3&gt;

&lt;p&gt;实际项目基本都是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c := make([]int, 0, l)&lt;/code&gt;来申请内存，长度都是不确定的。自然而然这些变量都会申请到堆上面了。Golang使用的垃圾回收算法是『标记——清除』。简单得说，就是程序要从操作系统申请一块比较大的内存，内存分成小块，通过链表链接。每次程序申请内存，就从链表上面遍历每一小块，找到符合的就返回其地址，没有合适的就从操作系统再申请。如果申请内存次数较多，而且申请的大小不固定，就会引起内存碎片化的问题。申请的堆内存并没有用完，但是用户申请的内存的时候却没有合适的空间提供。这样会遍历整个链表，还会继续向操作系统申请内存。这就能解释我一开始描述的问题，申请一块内存变成了慢语句。&lt;/p&gt;

&lt;h3 id=&quot;临时对象池&quot;&gt;临时对象池&lt;/h3&gt;

&lt;p&gt;如何解决这个问题，首先想到的就是对象池。Golang在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sync&lt;/code&gt;里面提供了对象池&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pool&lt;/code&gt;。一般大家都叫这个为对象池，而我喜欢叫它临时对象池。因为每次垃圾回收会把池子里面不被引用的对象回收掉。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func (p *Pool) Get() interface{}&lt;/code&gt;&lt;/p&gt;

  &lt;p&gt;Get selects an arbitrary item from the Pool, removes it from the Pool, and returns it to the caller. Get may choose to ignore the pool and treat it as empty. Callers should not assume any relation between values passed to Put and the values returned by Get.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;需要注意的是，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Get&lt;/code&gt;方法会把返回的对象从池子里面删除。所以用完了的对象，还是得重新放回池子。&lt;/p&gt;

&lt;p&gt;很快，我写出了第一版对象池优化方案：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var idsPool = sync.Pool{
	New: func() interface{} {
		ids := make([]int64, 0, 20000)
		return &amp;amp;ids
	},
}

func NewIds() []int64 {
	ids := idsPool.Get().(*[]int64)
	*ids = (*ids)[:0]
	idsPool.Put(ids)
	return *ids
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样的实现，是把所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;都放到同一个池子里面了。为了应对变长的问题，都是按照一个较大的值申请的变量。虽然是一种优化，但是使用超大的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;计算，性能并没有怎么提升。&lt;/p&gt;

&lt;p&gt;紧接着参考了达达大神的代码&lt;a href=&quot;https://github.com/funny/slab/blob/master/sync_pool.go&quot;&gt;sync_pool.go&lt;/a&gt;，又写了一版：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var DEFAULT_SYNC_POOL *SyncPool

func NewPool() *SyncPool {
	DEFAULT_SYNC_POOL = NewSyncPool(
		5,     
		30000, 
		2,     
	)
	return DEFAULT_SYNC_POOL
}

func Alloc(size int) []int64 {
	return DEFAULT_SYNC_POOL.Alloc(size)
}

func Free(mem []int64) {
	DEFAULT_SYNC_POOL.Free(mem)
}

// SyncPool is a sync.Pool base slab allocation memory pool
type SyncPool struct {
	classes     []sync.Pool
	classesSize []int
	minSize     int
	maxSize     int
}

func NewSyncPool(minSize, maxSize, factor int) *SyncPool {
	n := 0
	for chunkSize := minSize; chunkSize &amp;lt;= maxSize; chunkSize *= factor {
		n++
	}
	pool := &amp;amp;SyncPool{
		make([]sync.Pool, n),
		make([]int, n),
		minSize, maxSize,
	}
	n = 0
	for chunkSize := minSize; chunkSize &amp;lt;= maxSize; chunkSize *= factor {
		pool.classesSize[n] = chunkSize
		pool.classes[n].New = func(size int) func() interface{} {
			return func() interface{} {
				buf := make([]int64, size)
				return &amp;amp;buf
			}
		}(chunkSize)
		n++
	}
	return pool
}

func (pool *SyncPool) Alloc(size int) []int64 {
	if size &amp;lt;= pool.maxSize {
		for i := 0; i &amp;lt; len(pool.classesSize); i++ {
			if pool.classesSize[i] &amp;gt;= size {
				mem := pool.classes[i].Get().(*[]int64)
				// return (*mem)[:size]
				return (*mem)[:0]
			}
		}
	}
	return make([]int64, 0, size)
}

func (pool *SyncPool) Free(mem []int64) {
	if size := cap(mem); size &amp;lt;= pool.maxSize {
		for i := 0; i &amp;lt; len(pool.classesSize); i++ {
			if pool.classesSize[i] &amp;gt;= size {
				pool.classes[i].Put(&amp;amp;mem)
				return
			}
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;attrFilters := cache.Alloc(len(ids))
defer cache.Free(attrFilters)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;重点在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alloc&lt;/code&gt;方法。为了能支持变长的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;，这里申请了多个池子，其大小是从5开始，最大到30000，以2为倍数。也就是5、10、20……&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DEFAULT_SYNC_POOL = NewSyncPool(
	5,     
	30000, 
	2,     
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;分配内存的时候，从池子里面找满足容量切最小的池子。比如申请长度是2的，就分配大小为5的那个池子。如果是11，就分配大小是20的那个池子里面的对象；&lt;/li&gt;
  &lt;li&gt;如果申请的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;很大，超过了上限30000，这种情况就不使用池子了，直接从内存申请；&lt;/li&gt;
  &lt;li&gt;当然这些参数可以根据自己实际情况调整；&lt;/li&gt;
  &lt;li&gt;和之前的做法有所区别，把对象重新放回池子是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt;方法实现的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;p&gt;为了优化接口，前前后后搞了一年。结果还是不错的，TPS提升了最少30%，TP99也降低很多。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 优化之路——Cantor pair</title>
   <link href="https://blog.cyeam.com/golang/2017/02/07/go-optimize-pair"/>
   <updated>2017-02-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/02/07/go-optimize-pair</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#cantor-pairing-function-简介&quot; id=&quot;markdown-toc-cantor-pairing-function-简介&quot;&gt;Cantor pairing function 简介&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#example&quot; id=&quot;markdown-toc-example&quot;&gt;Example&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#实现&quot; id=&quot;markdown-toc-实现&quot;&gt;实现&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#与其它数据结构的对比&quot; id=&quot;markdown-toc-与其它数据结构的对比&quot;&gt;与其它数据结构的对比&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#结论&quot; id=&quot;markdown-toc-结论&quot;&gt;结论&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#github&quot; id=&quot;markdown-toc-github&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;某一种对象是通过两个ID唯一确定的，如何处理这种数据结构以便快速查找以及节约内存？先说一种笨方法——用字符串来处理。这是比较容易想到的（我觉得一般最容易想到的也是最简单粗暴的方法都是用字符串来搞搞搞）。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fmt.Sprintf(&quot;%d_%d&quot;, id1, id2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就成了。存储的时候用字符串来保存，查询比较的时候用字符串的方法来计算。当然，把数字当作字符串来保存和计算本身就是极其浪费内存和CPU的。&lt;/p&gt;

&lt;h3 id=&quot;cantor-pairing-function-简介&quot;&gt;Cantor pairing function 简介&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Pairing_function&quot;&gt;康托尔配对 - Cantor pairing function&lt;/a&gt;，是一种将两个自然数转成唯一一个自然数的方法。具体原理我就不说了，我也看不懂。。。简单地说：&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;只支持自然数。自然数是整数（自然数包括正整数和零）；&lt;/li&gt;
  &lt;li&gt;支持反解；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(k1, k2)&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(k2, k1)&lt;/code&gt;得到的结果是不同的。当然，如果你想得到相同的也是可以的，计算支持把两个数字排个序；&lt;/li&gt;
  &lt;li&gt;计算结果有可能比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k1&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k2&lt;/code&gt;都大很多，需要注意溢出的问题。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;

&lt;p&gt;这个算法只有一个公式，实现起来很容易。我索性自己造了一个轮子，&lt;a href=&quot;https://github.com/mnhkahn/pairing&quot;&gt;pairing&lt;/a&gt;。公式的推导请移步&lt;a href=&quot;https://en.wikipedia.org/wiki/Pairing_function&quot;&gt;维基百科&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import &quot;github.com/mnhkahn/pairing&quot;

pair := pairing.Encode(k1, k2)

k3, k4 := pairing.Decode(pair2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;支持正向编码以及反向解码。&lt;/p&gt;

&lt;h3 id=&quot;实现&quot;&gt;实现&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import &quot;math&quot;

func Encode(k1, k2 uint64) uint64 {
	pair := k1 + k2
	pair = pair * (pair + 1)
	pair = pair / 2
	pair = pair + k2

	return pair
}

func Decode(pair uint64) (uint64, uint64) {
	w := math.Floor((math.Sqrt(float64(8*pair+1)) - 1) / 2)
	t := (w*w + w) / 2

	k2 := pair - uint64(t)
	k1 := uint64(w) - k2
	return k1, k2
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;与其它数据结构的对比&quot;&gt;与其它数据结构的对比&lt;/h3&gt;

&lt;p&gt;其中一个要对比的就是和前面说的字符串的比较。还有一种代替方案：两个整数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int32&lt;/code&gt;，这64位数字拼接一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;里面，第一个数字占前32位，后一个数字占后32位，也是一个可行的方案。我们可以把这个方法叫做bit方法。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func EncodeBit(k1, k2 uint32) uint64 {
	pair := uint64(k1)&amp;lt;&amp;lt;32 | uint64(k2)
	return pair
}

func DecodeBit(pair uint64) (uint32, uint32) {
	k1 := uint32(pair &amp;gt;&amp;gt; 32)
	k2 := uint32(pair) &amp;amp; 0xFFFFFFFF
	return k1, k2
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;实现起来更简单。那和Cantor pair相比性能怎么样呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import (
	&quot;fmt&quot;
	&quot;testing&quot;
)

var TEST_PAIRS = [][]uint64{
	[]uint64{0, 0},
	[]uint64{0, 1},
	[]uint64{1, 0},
}

var TEST_RESs = []uint64{
	0,
	2,
	1,
}

var TEST_RESBits = []uint64{
	0,
	1,
	4294967296,
}

func TestPair(t *testing.T) {
	for i, p := range TEST_PAIRS {
		a, b := p[0], p[1]
		if pair := Encode(a, b); pair != TEST_RESs[i] {
			t.Error(a, b, pair)
		}
	}

	for i, p := range TEST_PAIRS {
		a, b := p[0], p[1]
		pair := TEST_RESs[i]
		if x, y := Decode(pair); x != a || y != b {
			t.Error(a, b, pair)
		}
	}

	fmt.Println(Encode(559, 83792))

	for i, p := range TEST_PAIRS {
		a, b := uint32(p[0]), uint32(p[1])
		if pair := EncodeBit(a, b); pair != TEST_RESBits[i] {
			t.Error(a, b, pair)
		}
	}

	for i, p := range TEST_PAIRS {
		a, b := uint32(p[0]), uint32(p[1])
		pair := TEST_RESBits[i]
		if x, y := DecodeBit(pair); x != a || y != b {
			t.Error(a, b, pair)
		}
	}

	fmt.Println(EncodeBit(559, 83792))
}

func BenchmarkEncode(b *testing.B) {
	for i := 0; i &amp;lt; b.N; i++ {
		Encode(559, 83792)
	}
}

func BenchmarkDecode(b *testing.B) {
	for i := 0; i &amp;lt; b.N; i++ {
		Decode(3557671568)
	}
}

func BenchmarkEncodeBit(b *testing.B) {
	for i := 0; i &amp;lt; b.N; i++ {
		EncodeBit(559, 83792)
	}
}

func BenchmarkDecodeBit(b *testing.B) {
	for i := 0; i &amp;lt; b.N; i++ {
		DecodeBit(2400886802256)
	}
}

func BenchmarkEncodeStr(b *testing.B) {
	for i := 0; i &amp;lt; b.N; i++ {
		_ = fmt.Sprintf(&quot;%d_%d&quot;, 559, 83792)
	}
}

/*
go test -bench=. -benchmem
BenchmarkEncode-2       2000000000               0.37 ns/op            0 B/op          0 allocs/op
BenchmarkDecode-2       50000000                26.9 ns/op             0 B/op          0 allocs/op
BenchmarkEncodeBit-2    2000000000               0.37 ns/op            0 B/op          0 allocs/op
BenchmarkDecodeBit-2    2000000000               0.37 ns/op            0 B/op          0 allocs/op
BenchmarkEncodeStr-2     5000000               271 ns/op              32 B/op          3 allocs/op
*/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;用字符串保存的性能最差，剩余两个是这个性能的400倍；&lt;/li&gt;
  &lt;li&gt;Cantor pair和bit方法的性能相当，反解的时候性能还查一些；&lt;/li&gt;
  &lt;li&gt;既然性能相当，虽然本文着重介绍的是Cantor pair算法，但我还是建议，如果bit方法能满足你的需求，尤其是数字范围较小的时候，还是用这个比较好。简单的方法在维护方面会让你更加得心应手，即使你懂那些复杂的公式是如何推导出来的^ ^。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;github&quot;&gt;GitHub&lt;/h3&gt;

&lt;p&gt;我把代码整理了一下，放到了 GitHub 上面，欢迎 Star。&lt;/p&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=pairing&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=pairing&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Swiftype的Golang API 发布！</title>
   <link href="https://blog.cyeam.com/golang/2017/01/19/swiftype-api"/>
   <updated>2017-01-19T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/01/19/swiftype-api</id>
   <content type="html">&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;我是一个Golang程序员，基本上我所有的东西都是用Go开发的。前不久想给我的&lt;a href=&quot;https://www.cyeam.com&quot;&gt;个人网站&lt;/a&gt;接入搜索功能，使用了Swiftype这个工具。然而我发现它并没有Golang的API工具包。在GitHub上面找了一个包，却发现有bug不能用，遂自己fork了代码搞一套。&lt;/p&gt;

&lt;p&gt;源码地址：&lt;a href=&quot;https://github.com/mnhkahn/swiftype&quot;&gt;https://github.com/mnhkahn/swiftype&lt;/a&gt;&lt;/p&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=swiftype&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=swiftype&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;h3 id=&quot;安装&quot;&gt;安装&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get -v gopkg.in/mnhkahn/swiftype.v1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;文档&quot;&gt;文档&lt;/h3&gt;

&lt;p&gt;文档可以在&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype&quot;&gt;godoc&lt;/a&gt;上面查看。目前只支持搜索方法，因为我这边也只用这个，如果未来我这边有别的API需求，会考虑更新。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#Client&quot;&gt;type Client&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#NewClientWithApiKey&quot;&gt;func NewClientWithApiKey(api_key string, host string) *Client&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#NewClientWithUsernamePassword&quot;&gt;func NewClientWithUsernamePassword(username string, password string, host string) *Client&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#Client.Engine&quot;&gt;func (c *Client) Engine(engine string) ([]byte, error)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#Client.Engines&quot;&gt;func (c *Client) Engines() ([]byte, error)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#Client.Search&quot;&gt;func (c &lt;em&gt;Client) Search(engine string, query string) (&lt;/em&gt;SwiftypeResult, error)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://godoc.org/github.com/mnhkahn/swiftype#SwiftypeResult&quot;&gt;type SwiftypeResult&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;例子&quot;&gt;例子&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SWIFTYPE := *swiftype.Client
SWIFTYPE_APIKEY := &quot;YOUR OWN API KEY&quot;
SWIFTYPE_HOST := &quot;api.swiftype.com&quot;
SWIFTYPE_ENGINE := &quot;YOUR OWN ENGINE&quot;

SWIFTYPE := swiftype.NewClientWithApiKey(SWIFTYPE_APIKEY, SWIFTYPE_HOST)

data, err := SWIFTYPE.Search(SWIFTYPE_ENGINE, q)
if err != nil {
    panic(err)
}
_ = data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 优化之路——bitset</title>
   <link href="https://blog.cyeam.com/golang/2017/01/18/go-optimize-bitset"/>
   <updated>2017-01-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/01/18/go-optimize-bitset</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#写在前面&quot; id=&quot;markdown-toc-写在前面&quot;&gt;写在前面&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bitset-简介&quot; id=&quot;markdown-toc-bitset-简介&quot;&gt;bitset 简介&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#example&quot; id=&quot;markdown-toc-example&quot;&gt;Example&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#实现原理&quot; id=&quot;markdown-toc-实现原理&quot;&gt;实现原理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#与其它数据结构的对比&quot; id=&quot;markdown-toc-与其它数据结构的对比&quot;&gt;与其它数据结构的对比&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#结论&quot; id=&quot;markdown-toc-结论&quot;&gt;结论&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;写在前面&quot;&gt;写在前面&lt;/h3&gt;

&lt;p&gt;开发过程中会经常处理集合这种数据结构，简单点的处理方法都是使用内置的map实现。但是如果要应对大量数据，例如，存放大量电话号码，使用map占用内存大的问题就会凸显出来。内存占用高又会带来一些列的问题，这里就不展开说了。还有就是，大量数据存放于map，查找的哈希算法消耗也会很高。这时就该考虑对数据结构进行优化。之前浏览&lt;a href=&quot;http://awesome-go.com/&quot;&gt;awesome-go&lt;/a&gt;时发现了一种叫bitset的数据结构，今天就介绍一下它。&lt;/p&gt;

&lt;h3 id=&quot;bitset-简介&quot;&gt;bitset 简介&lt;/h3&gt;

&lt;p&gt;首先这是一个数据结构。从名字set不难发现，这是一个集合的数据结构。bit的含义也比较好懂，通过set是通过bit实现的。如果你需要一个集合，正好集合内的元素都是正整数，那么用这个就没错了。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注：biset 有时也会被叫做 Bitmap。&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import &quot;github.com/willf/bitset&quot;

var b bitset.BitSet // 定义一个BitSet对象
b.Set(10).Set(11) // 给这个set新增两个值10和11
if b.Test(1000) { // 查看set中是否有1000这个值（我觉得Test这个名字起得是真差劲，为啥不叫Exist）
	b.Clear(1000) // 情况set
}
for i,e := v.NextSet(0); e; i,e = v.NextSet(i + 1) { // 遍历整个Set
   fmt.Println(&quot;The following bit is set:&quot;,i);
}
if B.Intersection(bitset.New(100).Set(10)).Count() &amp;gt; 1 { // set求交集
	fmt.Println(&quot;Intersection works.&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个包功能已经非常完善了，完整的文档可以参考它的&lt;a href=&quot;https://godoc.org/github.com/willf/bitset&quot;&gt;godoc&lt;/a&gt;。我使用这些包，除了看重基础功能（对于集合，就是增删改查这些），还有就是得方便调试。bitset内部保存数字都是按位存的，如果调试的时候是把bitset的内部数据给我看，我也是看不懂的，还好这个包提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String()&lt;/code&gt;方法，可以把我设置的数据已字符串的形式返回，棒棒哒。&lt;/p&gt;

&lt;h3 id=&quot;实现原理&quot;&gt;实现原理&lt;/h3&gt;

&lt;p&gt;研究一下实现原理才是我的Style。大概说一下原理。正整数集合可以都放到一个大的整数里面，用位来表示数字。比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1001&lt;/code&gt;就可以表示0和2这两个数字。用一个bit代替了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;，可以大大降低内存的占用。但是一个整数最大也就64位，也就是说最大表示的数字就是64了，所以可以通过多个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;拼接的形式来表示大整数。&lt;/p&gt;

&lt;p&gt;bitset的内部数据结构，很亲切有木有：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type BitSet struct {
	length uint // set的大小
	set    []uint64 // 这个就会被用来表示一个大整数
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过下面的测试代码对于内部实现一探究竟：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var b bitset.BitSet // 定义一个BitSet对象
fmt.Println(b.Bytes())
b.Set(0)
fmt.Println(b.Bytes(),0)
b.Set(10) // 给这个set新增两个值10
fmt.Println(b.Bytes(),0,10)
b.Set(64)
fmt.Println(b.Bytes(),0,10,64)
if b.Test(1000) { // 查看set中是否有1000这个值（我觉得Test这个名字起得是真差劲，为啥不叫Exist）
	b.Clear(1000) // 情况set
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[]
[1] 0
[1025] 0 10
[1025 1] 0 10 64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;新建的bitset，set是空&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;放入了一个0，用第一位表示，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00000001&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;放入了10，内部结构&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00000041&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;放入了64，这个时候一个整数已经存不下了，内部结构是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00000041&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00000001&lt;/code&gt;。set这个数组里面，从前往后表示的数据依次增加，但是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint64&lt;/code&gt;内部，是从低位开始，低位表示小的数。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;与其它数据结构的对比&quot;&gt;与其它数据结构的对比&lt;/h3&gt;

&lt;p&gt;表示正整数的集合，Golang有很多种方式，自带的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;就可以，当然这是最差的一种选择，首先就是内存的浪费，其次是每次查找还涉及到hash计算，虽然理论上hashmap的复杂度是O(1)，实际上跟bitset比完全就是渣渣。此外，bitset都得升级版&lt;a href=&quot;//github.com/RoaringBitmap/roaring&quot;&gt;roaring&lt;/a&gt;也是不错的选择。如果你要保存的数据是10000000000这种级别的，那么用bitset就会存在低位浪费内存的情况，roaring可以用来压缩空间。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import (
	&quot;testing&quot;

	&quot;github.com/RoaringBitmap/roaring&quot;
	&quot;github.com/willf/bitset&quot;
)

func BenchmarkMap(b *testing.B) {
	var B = make(map[int]int8, 3)
	B[10] = 1
	B[11] = 1
	for i := 0; i &amp;lt; b.N; i++ {
		if _, exists := B[1]; exists {

		}
		if _, exists := B[11]; exists {

		}
		if _, exists := B[1000000]; exists {

		}
	}
}

func BenchmarkBitset(b *testing.B) {
	var B bitset.BitSet
	B.Set(10).Set(11)
	for i := 0; i &amp;lt; b.N; i++ {
		if B.Test(1) {

		}
		if B.Test(11) {

		}
		if B.Test(1000000) {

		}
	}
}

func BenchmarkRoaring(b *testing.B) {
	for i := 0; i &amp;lt; b.N; i++ {
		B := roaring.BitmapOf(10, 11)
		if B.ContainsInt(1) {

		}
		if B.ContainsInt(11) {
		}
		if B.ContainsInt(1000000) {

		}

	}
}

$ go test -bench=.* -benchmem 

BenchmarkMap-2          50000000                28.4 ns/op             0 B/op          0 allocs/op
BenchmarkBitset-2       2000000000               1.86 ns/op            0 B/op          0 allocs/op
BenchmarkRoaring-2       3000000               492 ns/op             152 B/op          6 allocs/op
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;结论&quot;&gt;结论&lt;/h3&gt;

&lt;p&gt;如果是比较连续的非负整数，推荐用bitset解决集合的问题。当然具体问题具体分析。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/tree/master/bitset&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>【译】GOPATH的默认值</title>
   <link href="https://blog.cyeam.com/golang/2017/01/09/the-default-gopath"/>
   <updated>2017-01-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2017/01/09/the-default-gopath</id>
   <content type="html">&lt;p&gt;从Go 1.8开始，如果GOPATH的环境变量为空，Go将会设置一个默认的GOPATH环境变量。&lt;/p&gt;

&lt;p&gt;Go初学者第一次安装完Go之后，他们往往会因为忘记设置GOPATH环境变量而得到&lt;em&gt;you have to set a GOPATH&lt;/em&gt;这样的错误。这个需求的优先级逐渐变高。对于Go的新用户来说，解释GOPATH的作用、指导他们如何设置GOPATH将会使它们不能专注使用Go。尤其是有些时候，这些人并不是要去使用Go语言去开发，而是使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;去下载一些必要的命令。&lt;/p&gt;

&lt;p&gt;Go 1.8将会设置&lt;a href=&quot;https://github.com/golang/go/issues/17262&quot;&gt;默认的GOPATH&lt;/a&gt;。如果你自己没有设置GOPATH，Go将会使用默认值。默认GOPATH是：&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在Unix-like系统上是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HOME/go&lt;/code&gt;目录下&lt;/li&gt;
  &lt;li&gt;在Windows系统下是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%USERPROFILE%\go&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然已经有了默认的GOPATH，但是它并不能解决所有问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;我们还是得自己把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GOPATH/bin&lt;/code&gt;添加到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt;里面，这样通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;和go install`安装的二进制程序才能够被直接运行。（译者注：当然，通过绝对路径运行这些程序也是可以的，只不过比较麻烦）。&lt;/li&gt;
  &lt;li&gt;Go语言的开发者依然需要了解GOPATH的作用和它的目录结构。&lt;/li&gt;
  &lt;li&gt;如果你的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOROOT&lt;/code&gt;路径（就是你让Go源码的位置）和默认的GOPATH是一样的，并且你并没有设置一个默认的GOPATH，Go也并不为为你设置默认GOPATH，因为这样会把GOROOT里面的内容搞乱。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当你有疑问的时候，可以运行命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go env GOPATH&lt;/code&gt;来检查GOPATH的路径。如果有问题，比如上面说的情况，Go并没有自动生成GOPATH，这个命令将会打印空。&lt;/p&gt;

&lt;p&gt;阅读原文&lt;a href=&quot;https://rakyll.org/default-gopath/&quot;&gt;The default GOPATH&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>【译】Golang中使用『弃用(Deprecate)』</title>
   <link href="https://blog.cyeam.com/golang/2016/12/12/deprecating-tings-in-go"/>
   <updated>2016-12-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2016/12/12/deprecating-tings-in-go</id>
   <content type="html">&lt;p&gt;Go语言很长时间都没有一套标记弃用API的定义规范。这几年，出现了个规范可以在文档当中添加弃用注释。&lt;/p&gt;

&lt;p&gt;现在，标准库开始使用这个格式了。&lt;/p&gt;

&lt;p&gt;举个例子，Go 1.8的包中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sql/driver.Execer&lt;/code&gt;被弃用，这里增加了一套注释，它可以被&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;godoc&lt;/code&gt;识别。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Execer is an optional interface that may be implemented by a Conn.
//
// If a Conn does not implement Execer, the sql package&apos;s DB.Exec will
// first prepare a query, execute the statement, and then close the
// statement.
//
// Exec may return ErrSkip.
//
// Deprecated: Drivers should implement ExecerContext instead (or additionally).
type Execer interface {
	Exec(query string, args []Value) (Result, error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;弃用的通知会出现在godoc里面，注释需要以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deprecated&lt;/code&gt;开头，后面再写上代替此API的提示。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Deprecated: Use strings.HasPrefix instead.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用API的用户就可以根据这个提示使用新的API了。&lt;/p&gt;

&lt;p&gt;Additional to the notices, there is an effort going on to discourage users to keep depending on the deprecated APIs.
除了注释通知之外，还可以有一些方法来阻止用户一直使用被弃用的API。&lt;/p&gt;

&lt;p&gt;可以参考下面的文章:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/golang/go/issues/17056&quot;&gt;《默认隐藏弃用API的方法》&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/golang/gddo/issues/456&quot;&gt;《在godoc.org上面隐藏弃用的API》&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/golang/lint/issues/238&quot;&gt;《通过golint通知弃用的API》&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;总之，可以使用这个注释格式来支持弃用通知。不要用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEPRECATED&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;This type is deprecated&lt;/code&gt;。将来，你可以用一些工具来通知用户停止使用弃用的API。&lt;/p&gt;

&lt;p&gt;阅读原文&lt;a href=&quot;http://golang.rakyll.org/deprecated/&quot;&gt;Deprecating things in Go&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>【译】Go工具要点</title>
   <link href="https://blog.cyeam.com/golang/2016/09/27/go-tool-flags"/>
   <updated>2016-09-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2016/09/27/go-tool-flags</id>
   <content type="html">&lt;p&gt;你是刚开始使用Go工具么？或者你想扩展知识？这篇文章将会描述每个人都需要知道的Go工具参数。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;免责声明：这篇文章可能会有些偏见。这篇文章描述了我个人会用到的Go工具参数，还有一些是我周围的人遇到的问题。如果你还有别的想法，在&lt;a href=&quot;https://twitter.com/rakyll&quot;&gt;Twitter&lt;/a&gt;联系我。&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;-go-build--x&quot;&gt;$ go build -x&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-x&lt;/code&gt;会列出来&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go build&lt;/code&gt;调用到的所有命令。&lt;/p&gt;

&lt;p&gt;如果你对Go的工具链好奇，或者使用了一个跨C编译器，并且想知道调用外部编译器用到的具体参数，或者怀疑链接器有bug；使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-x&lt;/code&gt;来查看所有调用。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go build -x
WORK=/var/folders/00/1b8h8000h01000cxqpysvccm005d21/T/go-build600909754
mkdir -p $WORK/hello/perf/_obj/
mkdir -p $WORK/hello/perf/_obj/exe/
cd /Users/jbd/src/hello/perf
/Users/jbd/go/pkg/tool/darwin_amd64/compile -o $WORK/hello/perf.a -trimpath $WORK -p main -complete -buildid bbf8e880e7dd4114f42a7f57717f9ea5cc1dd18d -D _/Users/jbd/src/hello/perf -I $WORK -pack ./perf.go
cd .
/Users/jbd/go/pkg/tool/darwin_amd64/link -o $WORK/hello/perf/_obj/exe/a.out -L $WORK -extld=clang -buildmode=exe -buildid=bbf8e880e7dd4114f42a7f57717f9ea5cc1dd18d $WORK/hello/perf.a
mv $WORK/hello/perf/_obj/exe/a.out perf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-build--gcflags&quot;&gt;$ go build -gcflags&lt;/h3&gt;

&lt;p&gt;这个参数将会传递给编译器。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go tool compile -help&lt;/code&gt;列出来了所有我们可以传递给编译器的参数。&lt;/p&gt;

&lt;p&gt;例如，禁用编译器优化和内联优化，你可以使用下面的参数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go build -gcflags=&quot;-N -I&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-test--v&quot;&gt;$ go test -v&lt;/h3&gt;

&lt;p&gt;这个命令可以为测试提供完整的输出。它会打印测试名称、状态（成功或者失败）、测试所耗费的时间，还有测试的日志等等。&lt;/p&gt;

&lt;p&gt;如果不使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v&lt;/code&gt;参数来测试，输出很少很多，我经常使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v&lt;/code&gt;参数来打开详细测试日志。例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go test -v context
=== RUN   TestBackground
--- PASS: TestBackground (0.00s)
=== RUN   TestTODO
--- PASS: TestTODO (0.00s)
=== RUN   TestWithCancel
--- PASS: TestWithCancel (0.10s)
=== RUN   TestParentFinishesChild
--- PASS: TestParentFinishesChild (0.00s)
=== RUN   TestChildFinishesFirst
--- PASS: TestChildFinishesFirst (0.00s)
=== RUN   TestDeadline
--- PASS: TestDeadline (0.16s)
=== RUN   TestTimeout
--- PASS: TestTimeout (0.16s)
=== RUN   TestCanceledTimeout
--- PASS: TestCanceledTimeout (0.10s)
...
PASS
ok  	context	2.426s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-test--race&quot;&gt;$ go test -race&lt;/h3&gt;

&lt;p&gt;现在可以使用Go工具提供的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-race&lt;/code&gt;参数进行竞争检测。它会检测并报告竞争。开发的过程中用这个命令来检测一下。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注：完整的命令是：&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go test -race mypkg    // to test the package
$ go run -race mysrc.go  // to run the source file
$ go build -race mycmd   // to build the command
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-test--run&quot;&gt;$ go test -run&lt;/h3&gt;

&lt;p&gt;你可以在测试的时候通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-run&lt;/code&gt;参数来正则匹配过滤需要测试的代码。下面的命令只会运行&lt;a href=&quot;https://blog.golang.org/examples&quot;&gt;test examples&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go test -run=Example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-test--coverprofile&quot;&gt;$ go test -coverprofile&lt;/h3&gt;

&lt;p&gt;当测试一个包的时候，可以输出一个测试覆盖率，然后使用命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go tool&lt;/code&gt;来在浏览器里面可视化。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go test -coverprofile=c.out &amp;amp;&amp;amp; go tool cover -html=c.out
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面的命令将会创建一个测试覆盖率文件在浏览器打开结果。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注：测试fmt包&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go test -coverprofile=c.out fmt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-test--exec&quot;&gt;$ go test -exec&lt;/h3&gt;

&lt;p&gt;一般很少有人知道Go的这个功能，你可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-exec&lt;/code&gt;插入另一个程序。这个参数允许通过Go工具完成一些外部工作。&lt;/p&gt;

&lt;p&gt;一个常见的需求场景是你需要在一些宿主机上面执行一些测试。我们可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-exec&lt;/code&gt;命令调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adb&lt;/code&gt;命令来把二进制文件导入安卓设备并且可以收集到结果信息。参考&lt;a href=&quot;https://github.com/golang/go/blob/master/misc/android/go_android_exec.go&quot;&gt;这个&lt;/a&gt;来在安卓设备上面执行。&lt;/p&gt;

&lt;h3 id=&quot;-go-get--u&quot;&gt;$ go get -u&lt;/h3&gt;

&lt;p&gt;如果你通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;命令获取Go包，而这个包已经存在于本地的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOPATH&lt;/code&gt;，那么这个命令并不会帮你更新包。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-u&lt;/code&gt;可以强制更新到最新版。&lt;/p&gt;

&lt;p&gt;如果你是一个库作者，你最好在你的安装说明上加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-u&lt;/code&gt;参数，例如，&lt;a href=&quot;https://github.com/golang/lint#installation&quot;&gt;golint&lt;/a&gt;是这么做的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go get -u github.com/golang/lint/golint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-go-get--d&quot;&gt;$ go get -d&lt;/h3&gt;

&lt;p&gt;如果你想clone一个代码仓库到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOPATH&lt;/code&gt;里面，跳过编译和安装环节，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt;参数。这样它只会下载包并且在编译和安装之前停止。&lt;/p&gt;

&lt;p&gt;当需要clone虚拟网址代码仓库的时候，我经常使用这个命令来代替&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt;，因为这样可以把Go代码自动放入合适的目录下面。例如：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go get -d golang.org/x/oauth2/...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样可以克隆到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GOPATH/src/golang.org/x/ouath2&lt;/code&gt;目录下面。假设&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;golang.org/x/oauth2&lt;/code&gt;是一个虚拟网址，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;获取这个代码仓库要比找出仓库的真实地址(go.googlesource.com/oauth2)更简单。&lt;/p&gt;

&lt;h3 id=&quot;-go-get--t&quot;&gt;$ go get -t&lt;/h3&gt;

&lt;p&gt;如果你的测试包的有附加的依赖包，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-t&lt;/code&gt;可以一并下载测试包的依赖包。如果没有加这个参数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt;只会下载非测试包的依赖包。&lt;/p&gt;

&lt;h3 id=&quot;-go-list--f&quot;&gt;$ go list -f&lt;/h3&gt;

&lt;p&gt;这个命令可以列出来Go的所有包，并且可以指定格式。这个写脚本的时候很有用。&lt;/p&gt;

&lt;p&gt;下面这个命令将会打印所有依赖的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime&lt;/code&gt;包&lt;/p&gt;

&lt;p&gt;go list -f ‘’ runtime
[runtime/internal/atomic runtime/internal/sys unsafe]&lt;/p&gt;

&lt;p&gt;更多的格式化方法可以进一步阅读&lt;a href=&quot;http://dave.cheney.net/2014/09/14/go-list-your-swiss-army-knife&quot;&gt;Dave Cheney的文章&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>【译】优化Go的模式</title>
   <link href="https://blog.cyeam.com/golang/2016/08/18/apatternforoptimizinggo"/>
   <updated>2016-08-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2016/08/18/apatternforoptimizinggo</id>
   <content type="html">&lt;p&gt;之前写过一篇文章《为什么SignalFx metric proxy通过Go语言开发》，这篇文章将会关注以我们的ingest服务为例，来讲述我们是如何优化Go代码的。&lt;/p&gt;

&lt;p&gt;SingalFx基于流分析和时间报警序列，例如应用程序指标，可以为时间序列数据的现代应用开发的高级监控平台（“我的应用程序收到了多少请求？”），还有系统级指标（“我的Linux服务器使用了多少网络流量？”）。我们用户流量很大并且粒度很高，每次用户的流量都要先通过我们的ingest服务才能访问其它的SignalFx服务。&lt;/p&gt;

&lt;h3 id=&quot;第一步启用pprof&quot;&gt;第一步：启用pprof&lt;/h3&gt;

&lt;h4 id=&quot;啥是pprof&quot;&gt;啥是pprof？&lt;/h4&gt;
&lt;p&gt;pprof是Go语言内置的标准方法用来调试Go程序性能。可以通过HTTP的方式调用pprof包，它能提取出来应用程序的CPU和内存数据，此外还有运行的代码行数和内容信息。&lt;/p&gt;

&lt;h4 id=&quot;如何启用pprof&quot;&gt;如何启用pprof？&lt;/h4&gt;
&lt;p&gt;你可以通过在你的应用增加一行代码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import _ &quot;net/http/pprof&quot;&lt;/code&gt;，然后启动你的应用服务器，pprof就算是启动了。还有一种方式，就是我们在做SignalFx的时候，为了在外部控制pprof，我们附加了一些处理程序，可以用过路由设置暴露出去，代码如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import &quot;github.com/gorilla/mux&quot;
import &quot;net/http/pprof&quot;
var handler *mux.Router
// ...
handler.PathPrefix(&quot;/debug/pprof/profile&quot;).HandlerFunc(pprof.Profile)
handler.PathPrefix(&quot;/debug/pprof/heap&quot;).HandlerFunc(pprof.Heap)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;第二步找到可以优化的代码&quot;&gt;第二步：找到可以优化的代码&lt;/h3&gt;

&lt;h4 id=&quot;要执行什么&quot;&gt;要执行什么？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl http://ingest58:6060/debug/pprof/profile &amp;gt; /tmp/ingest.profile
go tool pprof ingest /tmp/ingest.profile
(pprof) top7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;这是干嘛的&quot;&gt;这是干嘛的？&lt;/h4&gt;
&lt;p&gt;Go语言包含了一个本地的pprof工具来可视化输出pprof的结果。我们配置的路由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/debug/pprof/profile&lt;/code&gt;可以收集30秒数据。我上面的操作，第一步是保存输出到本地文件，然后运行保存后的文件。值得一提的是，最后一个参数可以直接输入一个URL来取代文件（译者注：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go  tool pprof ingest http://ingest58:6060/debug/pprof/profile&lt;/code&gt;）。 命令top7可以展示消耗CPU最好的7个函数。&lt;/p&gt;

&lt;h4 id=&quot;结果&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;12910ms of 24020ms total (53.75%)
Dropped 481 nodes (cum &amp;lt;= 120.10ms)
Showing top 30 nodes out of 275 (cum &amp;gt;= 160ms)
     flat  flat%   sum%        cum   cum%
   1110ms  4.62%  4.62%     2360ms  9.83%  runtime.mallocgc
    940ms  3.91%  8.53%     1450ms  6.04%  runtime.scanobject
    830ms  3.46% 11.99%      830ms  3.46%  runtime.futex
    800ms  3.33% 15.32%      800ms  3.33%  runtime.mSpan_Sweep.func1
    750ms  3.12% 18.44%      750ms  3.12%  runtime.cmpbody
    720ms  3.00% 21.44%      720ms  3.00%  runtime.xchg
    580ms  2.41% 23.86%      580ms  2.41%  runtime._ExternalCode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;为啥是这个结果&quot;&gt;为啥是这个结果&lt;/h4&gt;
&lt;p&gt;我们可以发现，这些函数我们都没有直接调用过。然而，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mallocgc&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sacnobject&lt;/code&gt;还有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mSpan_Sweep&lt;/code&gt;全部都会导致是垃圾回收的时候CPU占用高。我们可以深入了解这些函数，而不是去优化Go语言的垃圾回收器本身，更好的优化办法是我们来优化我们代码里面使用Go语言的垃圾回收器的方法。在这个例子中，我们可以优化的是减少在堆上面创建对象。&lt;/p&gt;

&lt;h3 id=&quot;第三步探究gc的原因&quot;&gt;第三步：探究GC的原因&lt;/h3&gt;

&lt;h4 id=&quot;执行啥&quot;&gt;执行啥？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl http://ingest58:6060/debug/pprof/heap &amp;gt; /tmp/heap.profile
go tool pprof -alloc_objects /tmp/ingest /tmp/heap.profile
(pprof) top3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;做了啥&quot;&gt;做了啥？&lt;/h4&gt;
&lt;p&gt;可以注意到这次下载的URL和之前的有点像，但是是以/heap结尾的。这个将会给我们提供机器上面堆的使用总结的数据。我再一次保存成文件用户后面的比较。参数-alloc_objects将会可视化应用程序在执行过程中分配的对象数量。&lt;/p&gt;

&lt;h4 id=&quot;结果-1&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;4964437929 of 7534904879 total (65.89%)
Dropped 541 nodes (cum &amp;lt;= 37674524)
Showing top 10 nodes out of 133 (cum &amp;gt;= 321426216)
     flat  flat%   sum%        cum   cum%
853721355 11.33% 11.33%  859078341 11.40%  github.com/signalfuse/sfxgo/ingest/tsidcache/tsiddiskcache.(*DiskKey).EncodeOld
702927011  9.33% 20.66%  702927011  9.33%  reflect.unsafe_New
624715067  8.29% 28.95%  624715067  8.29%  github.com/signalfuse/sfxgo/ingest/bus/rawbus.(*Partitioner).Partition
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;啥意思&quot;&gt;啥意思？&lt;/h4&gt;
&lt;p&gt;可以看出，11.33%的对象分配都发生在对象&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiskKey&lt;/code&gt;的函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EncodeOld&lt;/code&gt;里面，我们预期也是这个结果。然而，没有料到的是Partition函数占用了全部内存分配的8.29%，因为这个函数只是一些基本的计算，我得着重研究一下这个问题。&lt;/p&gt;

&lt;h3 id=&quot;第四步找到为什么partitioner使用如此多内存的原因&quot;&gt;第四步：找到为什么partitioner使用如此多内存的原因&lt;/h3&gt;

&lt;h4 id=&quot;执行啥-1&quot;&gt;执行啥？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(pprof) list Partitioner.*Partition
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;做了啥-1&quot;&gt;做了啥？&lt;/h4&gt;
&lt;p&gt;这个命令可以打印出来我关注的源代码行，还有就是函数内部哪些代码引起了堆的内存申请。这是pprof里面许多命令的其中一个。另一个非常有用的是查看调用方和被调用方。可以通过help命令查看完整的帮助并且都试一试。&lt;/p&gt;

&lt;h4 id=&quot;结果-2&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Total: 11323262665
ROUTINE ======================== github.com/signalfuse/sfxgo/ingest/bus/rawbus.(*Partitioner).Partition in /opt/jenkins/workspace/ingest/gopath/src/github.com/signalfuse/sfxgo/ingest/bus/rawbus/partitioner.go
927405893  927405893 (flat, cum)  8.19% of Total
        .          .     64: if ringSize == 0 {
        .          .     65: return 0, ErrUnsetRingSize
        .          .     66: }
        .          .     67: var b [8]byte
        .          .     68: binary.LittleEndian.PutUint64(b[:], uint64(message.Key.(*partitionPickingKey).tsid))
239971917  239971917     69: logherd.Debug2(log, &quot;key&quot;, message.Key, &quot;numP&quot;, numPartitions, &quot;Partitioning&quot;)
        .          .     70: murmHash := murmur3.Sum32(b[:])
        .          .     71:
        .          .     72: // 34026 =&amp;gt; 66
        .          .     73: setBits := uint(16)
        .          .     74: setSize := uint32(1 &amp;lt;&amp;lt; setBits)
        .          .     75: shortHash := murmHash &amp;amp; (setSize - 1)
        .          .     76: smallIndex := int32(shortHash) * int32(k.ringSize) / int32(setSize)
687433976  687433976     77: logherd.Debug3(log, &quot;smallIndex&quot;, smallIndex, &quot;murmHash&quot;, murmHash, &quot;shortHash&quot;, shortHash, &quot;Sending to partition&quot;)
        .          .     78: return smallIndex, nil
        .          .     79:}
        .          .     80:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;啥意思-1&quot;&gt;啥意思？&lt;/h4&gt;
&lt;p&gt;这个可以表示debug日志是引起变量从栈逃逸到堆的原因。因为调试日志并不是直接需要的，我能够直接删掉这些行。但是首先，还是让我们来确认这个假设。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logherd.Debug2&lt;/code&gt;函数看起来封装了如下所示，如果日志级别debug没有符合条件，WithField对象并不会调用。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Debug2 to logger 2 key/value pairs and message.  Intended to save the mem alloc that WithField creates
func Debug2(l *logrus.Logger, key string, val interface{}, key2 string, val2 interface{}, msg string) {
     if l.Level &amp;gt;= logrus.DebugLevel {
          l.WithField(key, val).WithField(key2, val2).Debug(msg)
     }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从pprof检测看起来是传递整数到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug2&lt;/code&gt;函数引起的内存分配，让我们进一步确认。&lt;/p&gt;

&lt;h3 id=&quot;第五步找到日志语句引起内存分配的原因&quot;&gt;第五步：找到日志语句引起内存分配的原因&lt;/h3&gt;

&lt;h4 id=&quot;执行什么&quot;&gt;执行什么：&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go build -gcflags=&apos;-m&apos; . 2&amp;gt;&amp;amp;1 | grep partitioner.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;这个用来干啥&quot;&gt;这个用来干啥？&lt;/h4&gt;
&lt;p&gt;通过-m参数编译可以让编译器打印内容到stderr。这包括编译器是否能够在栈上面分配内存还是一定得将变量放到堆上面申请。如果编译器不能决定一个变量是否在外部继续被调用，他会被Go语言放到堆上面。&lt;/p&gt;

&lt;h4 id=&quot;结果-3&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./partitioner.go:63: &amp;amp;k.ringSize escapes to heap
./partitioner.go:62: leaking param: k
./partitioner.go:70: message.Key escapes to heap
./partitioner.go:62: leaking param content: message
./partitioner.go:70: numPartitions escapes to heap
./partitioner.go:77: smallIndex escapes to heap
./partitioner.go:77: murmHash escapes to heap
./partitioner.go:77: shortHash escapes to heap
./partitioner.go:68: (*Partitioner).Partition b does not escape
./partitioner.go:71: (*Partitioner).Partition b does not escape
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意第77行，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;smallIndex&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;murmHash&lt;/code&gt;还有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shortHash&lt;/code&gt;全部逃逸到了堆上面。编译器为短生命周期的变量在堆上面申请了空间，导致我们在对上创建了很多我们并不需要的对象。&lt;/p&gt;

&lt;h3 id=&quot;第六步对partition函数压测&quot;&gt;第六步：对partition函数压测&lt;/h3&gt;

&lt;h4 id=&quot;写什么&quot;&gt;写什么？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func BenchmarkPartition(b *testing.B) {

     r := rand.New(rand.NewSource(0))

     k := partitionPickingKey{}

     msg := sarama.ProducerMessage {

          Key: &amp;amp;k,

     }

     p := Partitioner{

          ringSize: 1024,

          ringName: &quot;quantizer.ring&quot;,

     }

     num_partitions := int32(1024)

     for i := 0; i &amp;lt; b.N; i++ {

          k.tsid = r.Int63()

          part, err := p.Partition(&amp;amp;msg, num_partitions)

          if err != nil {

               panic(&quot;Error benchmarking&quot;)

          }

          if part &amp;lt; 0 || part &amp;gt;= num_partitions {

               panic(&quot;Bench failure&quot;)

          }

     }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;压测只是简单的创建了B.N个对象，并且在返回的时候做了一个基本的检查来确认对象不会被简单的优化掉。我们推荐当程序员在优化代码之前编写压测代码来确保你在朝着正确的方向进行。&lt;/p&gt;

&lt;h3 id=&quot;第七步对partition函数压测内存分配&quot;&gt;第七步：对partition函数压测内存分配&lt;/h3&gt;

&lt;h4 id=&quot;执行啥-2&quot;&gt;执行啥？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go test -v -bench . -run=_NONE_ -benchmem BenchmarkPartition
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;做了啥-2&quot;&gt;做了啥？&lt;/h4&gt;
&lt;p&gt;压测会按照正则匹配符合“.”条件的函数，-benchmen将会追踪每次循环的堆使用平均情况。通过传递参数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-run=_NONE_&lt;/code&gt;，我可以节约一些时间，这样测试只会运行有“&lt;em&gt;NONE&lt;/em&gt;”字符串的单元测试。换句话说，不下运行任何一个单元测试，只运行全部的压力测试。&lt;/p&gt;

&lt;h4 id=&quot;结果-4&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PASS

BenchmarkPartition-8 10000000       202 ns/op      64 B/op       4 allocs/op
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;意味着啥&quot;&gt;意味着啥？&lt;/h4&gt;
&lt;p&gt;每一次循环消耗平均202ns，最重要的是，每个操作有4次对象分配。&lt;/p&gt;

&lt;h3 id=&quot;第八步删掉日志语句&quot;&gt;第八步：删掉日志语句&lt;/h3&gt;

&lt;h4 id=&quot;咋写&quot;&gt;咋写？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@@ -66,7 +65,6 @@ func (k *Partitioner) Partition(message *sarama.ProducerMessage, numPartitions i

       }

       var b [8]byte

       binary.LittleEndian.PutUint64(b[:], uint64(message.Key.(*partitionPickingKey).tsid))

-       logherd.Debug2(log, &quot;key&quot;, message.Key, &quot;numP&quot;, numPartitions, &quot;Partitioning&quot;)

       murmHash := murmur3.Sum32(b[:])

       // 34026 =&amp;gt; 66

@@ -74,7 +72,6 @@ func (k *Partitioner) Partition(message *sarama.ProducerMessage, numPartitions i

       setSize := uint32(1 &amp;lt;&amp;lt; setBits)

       shortHash := murmHash &amp;amp; (setSize - 1)

       smallIndex := int32(shortHash) * int32(k.ringSize) / int32(setSize)

-       logherd.Debug3(log, &quot;smallIndex&quot;, smallIndex, &quot;murmHash&quot;, murmHash, &quot;shortHash&quot;, shortHash, &quot;Sending to partition&quot;)

       return smallIndex, nil

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;干了什么&quot;&gt;干了什么？&lt;/h4&gt;
&lt;p&gt;我的修复方式是删除日志代码。测试期间/调试期间，我增加了这些调试代码，但是一直没有删掉它们。这种情况下，删掉这些代码最简单。&lt;/p&gt;

&lt;h3 id=&quot;第九步重新编译评估是否变量逃逸到了堆&quot;&gt;第九步：重新编译评估是否变量逃逸到了堆&lt;/h3&gt;

&lt;h4 id=&quot;如何执行&quot;&gt;如何执行？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go build -gcflags=&apos;-m&apos; . 2&amp;gt;&amp;amp;1 | grep partitioner.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;结果-5&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./partitioner.go:62: &amp;amp;k.ringSize escapes to heap

./partitioner.go:61: leaking param: k

./partitioner.go:61: (*Partitioner).Partition message does not escape

./partitioner.go:67: (*Partitioner).Partition b does not escape

./partitioner.go:68: (*Partitioner).Partition b does not escape
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;意味着什么&quot;&gt;意味着什么？&lt;/h4&gt;
&lt;p&gt;可以发现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;smallIndex&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;murmHash&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shortHash&lt;/code&gt;变量不在有逃逸到堆的消息。&lt;/p&gt;

&lt;h3 id=&quot;第十步重新压测评估每个操作的内存分配情况&quot;&gt;第十步：重新压测评估每个操作的内存分配情况&lt;/h3&gt;

&lt;h4 id=&quot;如何执行-1&quot;&gt;如何执行？&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go test -v -bench . -run=_NONE_ -benchmem BenchmarkPartition
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;结果-6&quot;&gt;结果&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PASS

BenchmarkPartition-8 30000000        40.5 ns/op       0 B/op       0 allocs/op

ok   github.com/signalfuse/sfxgo/ingest/bus/rawbus 1.267s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;啥意思-2&quot;&gt;啥意思？&lt;/h4&gt;
&lt;p&gt;注意到每个操作只消耗40ns，更重要的是，每个操作不再有内存分配。因为我是准备来优化堆，这对我来说很重要。&lt;/p&gt;

&lt;h3 id=&quot;结束语&quot;&gt;结束语&lt;/h3&gt;
&lt;p&gt;pprof是非常有用的工具来剖析Go代码的性能问题。通过结合Go语言内置的压测工具，你能够得到关于代码改变引起的变化的真正的数字。不幸的是，性能衰退会随着时间而攀升。下一步，读者可以练习，保存benchmark的结果到数据库，这样你可以在每一次代码提交之后查看代码的性能。&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>介绍一下Json的Number</title>
   <link href="https://blog.cyeam.com/golang/2016/05/02/jsonnumber"/>
   <updated>2016-05-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2016/05/02/jsonnumber</id>
   <content type="html">&lt;p&gt;Json的使用基本没有什么难度，就拿Golang来说，直接来个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/json&lt;/code&gt;包里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func Marshal(v interface{}) ([]byte, error)&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func Unmarshal(data []byte, v interface{}) error&lt;/code&gt;就能对Json进行编解码了。具体的文件就是采用反射的方法，可以参考我之前的文章&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/11/go_json&quot;&gt;『Golang通过反射实现结构体转成JSON数据』&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;现在问题来了，如下的map需要大家是如何解析的？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;10000000000&quot;:10000000000,&quot;111&quot;:1}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果直接定义一个map来解析，定义成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]int64&lt;/code&gt;，我们是肯定可以解析成功的，解析的时候会将数据转换为我们需要的数据类型。那么问题来了：如果把类型定义成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]interface{}&lt;/code&gt;会是如何解析的呢？&lt;/p&gt;

&lt;p&gt;我一直是用显示定义的来解析，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]int64&lt;/code&gt;，当我用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]interface{}&lt;/code&gt;解析的时候，我就想当然的认为，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interface{}&lt;/code&gt;里面存的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;的数据。后来调试了一通，最后才发现是本末倒置了。&lt;/p&gt;

&lt;p&gt;Json数据其实就是一个字符串，里面按照一定的格式保存我们的数据。Json支持的数据类型与Golang语言的关系如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们可以注意到，Json格式的数字和Golang语言里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;是相关联的。也就是说，&lt;strong&gt;&lt;em&gt;默认情况下数字类型将会转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;类型&lt;/em&gt;&lt;/strong&gt;。如果我们显示的指出了数字类型，比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;，他会将数字再转成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;我们看一下源码，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/json/decode.go func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool)&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) {
	...
	switch c := item[0]; c {
		case &apos;n&apos;: // null
		...
		case &apos;t&apos;, &apos;f&apos;: // true, false
		...
		case &apos;&quot;&apos;: // string
		...
		default: // number
		if c != &apos;-&apos; &amp;amp;&amp;amp; (c &amp;lt; &apos;0&apos; || c &amp;gt; &apos;9&apos;) {
			if fromQuoted {
				d.error(fmt.Errorf(&quot;json: invalid use of ,string struct tag, trying to unmarshal %q into %v&quot;, item, v.Type()))
			} else {
				d.error(errPhase)
			}
		}
		s := string(item)
		switch v.Kind() {
		default:
			if v.Kind() == reflect.String &amp;amp;&amp;amp; v.Type() == numberType {
				v.SetString(s)
				break
			}
			if fromQuoted {
				d.error(fmt.Errorf(&quot;json: invalid use of ,string struct tag, trying to unmarshal %q into %v&quot;, item, v.Type()))
			} else {
				d.error(&amp;amp;UnmarshalTypeError{&quot;number&quot;, v.Type(), int64(d.off)})
			}
		case reflect.Interface:
			n, err := d.convertNumber(s)
			if err != nil {
				d.saveError(err)
				break
			}
			if v.NumMethod() != 0 {
				d.saveError(&amp;amp;UnmarshalTypeError{&quot;number&quot;, v.Type(), int64(d.off)})
				break
			}
			v.Set(reflect.ValueOf(n))

		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			n, err := strconv.ParseInt(s, 10, 64)
			if err != nil || v.OverflowInt(n) {
				d.saveError(&amp;amp;UnmarshalTypeError{&quot;number &quot; + s, v.Type(), int64(d.off)})
				break
			}
			v.SetInt(n)

		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			n, err := strconv.ParseUint(s, 10, 64)
			if err != nil || v.OverflowUint(n) {
				d.saveError(&amp;amp;UnmarshalTypeError{&quot;number &quot; + s, v.Type(), int64(d.off)})
				break
			}
			v.SetUint(n)

		case reflect.Float32, reflect.Float64:
			n, err := strconv.ParseFloat(s, v.Type().Bits())
			if err != nil || v.OverflowFloat(n) {
				d.saveError(&amp;amp;UnmarshalTypeError{&quot;number &quot; + s, v.Type(), int64(d.off)})
				break
			}
			v.SetFloat(n)
		}
	}
}

func (d *decodeState) convertNumber(s string) (interface{}, error) {
	if d.useNumber {
		return Number(s), nil
	}
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return nil, &amp;amp;UnmarshalTypeError{&quot;number &quot; + s, reflect.TypeOf(0.0), int64(d.off)}
	}
	return f, nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看出来，Json解析实现的时候通过反射来判断要生成的具体的类型。如果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interface{}&lt;/code&gt;类型，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;converNumber&lt;/code&gt;方法转成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;float64&lt;/code&gt;（里面是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv.ParseFloat&lt;/code&gt;实现），如果类型是整形相关，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv.ParseInt&lt;/code&gt;方法转换。无符号整形是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv.ParseUint&lt;/code&gt;实现。&lt;/p&gt;

&lt;p&gt;Golang 也提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Number&lt;/code&gt;类型来解决这种情况，详细说明请看下一篇文章&lt;a href=&quot;https://blog.cyeam.com/golang/2018/08/22/json-number&quot;&gt;《介绍一下Json的Number（二）》&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>狗日的slice</title>
   <link href="https://blog.cyeam.com/golang/2016/01/18/fuckslice"/>
   <updated>2016-01-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2016/01/18/fuckslice</id>
   <content type="html">&lt;p&gt;首先推荐一下雨痕大神的新书：&lt;a href=&quot;https://github.com/qyuhen/book&quot;&gt;《Golang源码剖析（第五版）》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;进入正题。Golang和其它语言不通的是，他增加了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;，这不同于传统的数组，但是我们使用它又要按照数组的用法来，容易混淆。&lt;/p&gt;

&lt;p&gt;先说一下我理解的传统数组，数组的名称就是数组在内存里面的首地址，访问每个子元素就是首地址加子元素长度访问。所以这应该是一个引用对象类型。&lt;/p&gt;

&lt;p&gt;而Golang的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;，引用一下雨痕大神的文章说明：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;runtime.h&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct Slice
{
	byte* array;
	uintgo len;
	uintgo cap;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;底层是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;实现的，所以传递的时候是值拷贝传递，但是，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;的内容是通过数组指针实现的。就会发生这种现象：***通过值传递，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;len&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cap&lt;/code&gt;都不会变，所以使用的时候不会感觉到内容有变化，但是实际上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt;数组是发生变化的。&lt;/p&gt;

&lt;p&gt;上代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func main() {
	testMap := make(map[int64]int64)
	fmt.Println(testMap)
	FuckMap(testMap)
	fmt.Println(testMap)
	FuckMap2(testMap)
	fmt.Println(testMap)

	testSlice := []int64{}
	fmt.Println(testSlice)
	FuckSlice(testSlice)
	fmt.Println(testSlice)
	
	FuckSlice2(&amp;amp;testSlice)
	fmt.Println(testSlice)
}

func FuckMap(t map[int64]int64) map[int64]int64 {
	t[1] = 1
	return t
}

func FuckMap2(t map[int64]int64) map[int64]int64 {
	t[1] = 2
	return t
}

func FuckSlice(a []int64) []int64 {
	a = append(a, 1)
	return a
}

func FuckSlice2(a *[]int64) *[]int64 {
	*a = append(*a, 1)
	return a
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;直接看后半部分。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FuckSlice&lt;/code&gt;函数对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;的修改是不起作用的，因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;是按值传递的；然后我们强行改成按引用传递，就是传递&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;的地址作为参数，这时的修改就是起作用的。所以，&lt;strong&gt;&lt;em&gt;对于修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;的操作要注意，有可能会修改失败。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>最简单的HTTP SERVER</title>
   <link href="https://blog.cyeam.com/network/2015/12/11/simplesthttpserver"/>
   <updated>2015-12-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2015/12/11/simplesthttpserver</id>
   <content type="html">&lt;p&gt;到了新单位总算有点闲了，接着倒腾HTTP。之前的文章可以参考：&lt;a href=&quot;https://blog.cyeam.com/network/2014/09/28/go_http&quot;&gt;《基于TCP套接字，通过Golang模拟HTTP请求》&lt;/a&gt;和&lt;a href=&quot;https://blog.cyeam.com/network/2014/09/29/go_http2&quot;&gt;《基于TCP套接字，通过Golang模拟HTTP请求（续）》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;之前都是研究的客户端，现在来研究一下服务端。《HTTP权威指南》上面有一个非常简单的用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perl&lt;/code&gt;开发的一个服务器，我就用大&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Golang&lt;/code&gt;照着写一个。HTTP协议是基于传输层的TCP协议，监听80端口。简单来写（复杂的我也不会），就是最简单的TCP监听，接收消息，处理并返回。我是参考的&lt;a href=&quot;https://colobu.com/2014/12/02/go-socket-programming-TCP/&quot;&gt;《Go socket编程实践: TCP服务器和客户端实现》&lt;/a&gt;。主逻辑就是这样：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ln, err := net.Listen(&quot;tcp&quot;, fmt.Sprintf(&quot;%s:%d&quot;, &quot;&quot;, *portFlag))
defer ln.Close()
if err != nil {
	panic(err)
}

log.Printf(&quot;&amp;lt;&amp;lt;&amp;lt;Server Accepting on Port %d&amp;gt;&amp;gt;&amp;gt;\n\n&quot;, *portFlag)
for {
	conn, err := ln.Accept()
	if err != nil {
		log.Panicln(err)
	}
	go handleConnection(conn)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我做东西喜欢从最简单开始，一个是因为我会的不多，一个是简单的来自己能理明白，以后维护其实更省事。&lt;/p&gt;

&lt;p&gt;回归正题，这块其实从念书的时候就老写，分分钟搞定。然后就是HTTP协议的东西。当然，还是按最简单的来。HTTP分为三部分，起始行（start line）、首部（header）和主体（body）。&lt;/p&gt;

&lt;p&gt;起始行只有一行，就是协议版本和状态码这些，以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt;结尾，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r\n&lt;/code&gt;；接着就是头，头里面的内容就是键值对，每组键值对以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt;结尾；头和主体中间，还需要多一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt;，我猜是为了解析内容方便。主体之后不需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;下图就是详细的请求格式。其中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SP&lt;/code&gt;是空格。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933529/cyeam/General_format_of_an_HTTP_request_message.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933529/cyeam/General_format_of_an_HTTP_response_message.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一些必要的头（因为我发现如果没有，请求会失败），就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Type&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-length&lt;/code&gt;。Content-length就是Body的长度。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func handleConnection(conn net.Conn) {
	defer conn.Close()
	log.Printf(&quot;[%s]&amp;lt;&amp;lt;&amp;lt;Request From %s&amp;gt;&amp;gt;&amp;gt;\n&quot;, time.Now().String(), conn.RemoteAddr())

	serve_time := time.Now()
	buffers := bytes.Buffer{}
	buffers.WriteString(&quot;HTTP/1.1 200 OK\r\n&quot;)
	buffers.WriteString(&quot;Server: Cyeam\r\n&quot;)
	buffers.WriteString(&quot;Date: &quot; + serve_time.Format(time.RFC1123) + &quot;\r\n&quot;)
	buffers.WriteString(&quot;Content-Type: text/html; charset=utf-8\r\n&quot;)
	buffers.WriteString(&quot;Content-length:&quot; + fmt.Sprintf(&quot;%d&quot;, len(DEFAULT_HTML)) + &quot;\r\n&quot;)
	buffers.WriteString(&quot;\r\n&quot;)
	buffers.WriteString(DEFAULT_HTML)
	conn.Write(buffers.Bytes())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后补充一点，连接记得关闭&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】《HTTP权威指南》&lt;/li&gt;
  &lt;li&gt;【2】James F.Kurose, Keith W.Ross.COMPUTER NETWORKING. A Top-Down Approach Featuring the Internet(Third Edition).&lt;/li&gt;
  &lt;li&gt;【3】NETWORKING INFO BLOG. HTTP Message Format.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>京东工作记录</title>
   <link href="https://blog.cyeam.com/notebook/2015/11/23/jdcareer"/>
   <updated>2015-11-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2015/11/23/jdcareer</id>
   <content type="html">&lt;p&gt;9月份入职后，主要就是在熟悉业务和流程、熟悉代码，还有就是数据加载和集群切换的工作。我们的服务有两套集群，每个集群又包含三套机房，分别是黄村、永丰和廊坊机房。集群分为线上集群和线下集群。多个集群是为了保证数据加载不影响线上用户的正常使用。三级列表页有大约1000个分类，每个分类需要加载2万个商品，如果在线上集群进行加载数据的操作，哪怕是只加载一个分类，都会引起CPU和数据库的报警。所以，线上机群只是正常展示数据，线下集群进行全量数据加载。&lt;/p&gt;

&lt;p&gt;接口部分使用Golang语言+MySQL+JimDB+twmeproxy(JimDB可以简单的认为是Redis)开发。由我们组其他同事将异构好的商品计算好之后持久化到MySQL里面。我负责将MySQL里面的数据加载到JimDB和内存里面。Twmeproxy用来当作JimDB的前台分片代理。&lt;/p&gt;

&lt;p&gt;10月份开始，开始着手开始更重要的双11凤凰项目开发。我们组负责的是4免1的pop商品的活动页。凤凰项目是列表页项目的一个简化项目，逻辑相似且更为简单。这对我了解项目有很大帮助。开发倒是很顺利，大概用了不到1周就开发完成了。后来就是在解决坑的踩。&lt;/p&gt;

&lt;p&gt;坑1，丢商品。数据加载完之后，有的专题会丢商品，而且很规律。每个专题固定丢的就是这些。但是排查起来很难。我们的服务都是分布式的集群，尤其是JimDB用了twmeproxy做分片，丢的这个商品是在哪个JimDB上面丢的，我都无从判断。而且是线上有问题，而测试环境没问题。当时只能是申请了一台机器转为预发，通过给代码加日志来跟踪问题。后来终于发现在保存商品的时候出错了。之前访问JimDB的代码封装的时候，保存到JimDB的函数返回值是error错误类型，但是在实际排查的时候它从来都没有返回过错误，后来深入看代码才发现所有错误都被过滤掉了，这里影响我的排查方向。最后，看到错误输出之后，发现是没有写入JimDB的权限，再比对twmeproxy的配置，发现有一台twmeproxy的配置项填错了，填成了从JimDB，故而写入失败。总结：错误不可避免，但是错误日志很重要。如果当时的逻辑能直接看到错误日志，排查的速度能从半天时间缩减到半小时。&lt;/p&gt;

&lt;p&gt;坑2，twmeproxy分片问题。Twmeproxy分片是根据存入键值对的键进行处理的，所以分配到每个JimDB分片上面的键是均匀的。而实际使用的过程中发现，即使键是均匀的，我们保存的值大小是不可控的。这样，有可能某一个分片分配过来的值都很大，最后导致整个分片内存满了。而JimDB对于此种情况的处理并不是LRU，而且一旦内存满了，写入就会失败。我们对应的解决方案是发现哪个分片满了，就给这个分片单独申请增加内存。&lt;/p&gt;

&lt;p&gt;坑3，JimDB连接数问题。最后压测的时候，发现集群压测性能不是很理想。排查了很多方面，磁盘IO、MySQL读写、Redis读写等。后来发现是程序与Twmeproxy连接数过低，与压测的同事沟通改为1000之后，压测效果理想，单台机器能够支持1800的TPS。由于不同的商品被打散到了不同的服务器上面，获取这些商品虽然是通过MGet的方式获取，其实底层还是串行从每台机器上面取的数据。这里还需要优化。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>字符串横向对比：C、Golang、Redis</title>
   <link href="https://blog.cyeam.com/golang/2015/09/15/string"/>
   <updated>2015-09-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/09/15/string</id>
   <content type="html">&lt;h3 id=&quot;c语言中的字符串&quot;&gt;C语言中的字符串&lt;/h3&gt;
&lt;p&gt;C语言的字符串是通过字符数组实现的，每个字符串以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;\0&apos;&lt;/code&gt;结束。C语言字符串的三大操作函数也是常见笔试题。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int strlen(char *s) {
	char *p = s;
	while (*p != &apos;\0&apos;)
		p++
	return p -s;
}

void strcpy(char *s, char *t) {
	int i;
	i = 0;
	while ((s[i] = t[i]) != &apos;\0&apos;)
		i++;
}

void strcmp(char *s, char *t) {
	for ( ; *s == *t; s++, t++)
		if (*s == &apos;\0&apos;)
			return 0;
	return *s - *t;

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;具体实现的方式为就不说了，主要说说这里的问题。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在计算字符串长度的时候，每次都是需要遍历整个数组，直到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;\0&apos;&lt;/code&gt;为止，时间复杂度是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n)&lt;/code&gt;，如果字符串较长，性能还是会受到影响；&lt;/li&gt;
  &lt;li&gt;在字符串复制的时候，有可能会导致越界。如果此时的内存结构是：&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;a&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;\0&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;b&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;\0&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;要给前面的一个字符串&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;赋值&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;abc&lt;/code&gt;，那么结果就是后面紧跟着的字符串&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;也会被修改；&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;字符串相加的时候，也有可能会引起越界的问题。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;golang的字符串&quot;&gt;Golang的字符串&lt;/h3&gt;
&lt;p&gt;在Golang1.5之前，底层代码是用C语言写的。Golang1.5实现了自举，这个我还不懂是怎么具体实现的，就说1.4的。字符串不再像C语言那样用比较简易的字符数组实现，而是封装了字符串类型&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct String
{
	byte* str;
	intgo len;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;这里定义了一个结构体，包含str和len字段。新增的len字段用于保存字符串长度。这样计算字符串长度的时间复杂度就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(1)&lt;/code&gt;，用空间换取了时间性能。&lt;/li&gt;
  &lt;li&gt;字符串数据依然是用数组存储，但是由于有了len字段存储长度，所以不再需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;\0&apos;&lt;/code&gt;来表示结束了。&lt;/li&gt;
  &lt;li&gt;字符串的赋值就是指针指向对象的变化，不会有赋值操作，这样就不会导致越界问题了。&lt;/li&gt;
  &lt;li&gt;如果两个字符串相加，操作方案是重新申请内存，保存相加的两个字符串相加的结果，这样也不会导致越界。&lt;/li&gt;
  &lt;li&gt;但是还不是很完美，字符串是常量，不能通过数据下标的方式访问和修改。每次修改字符串，需要将字符串转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;数组。这样修改完之后，再转成字符串，这个字符串和之前的字符串已经不是同一个变量了。&lt;/li&gt;
  &lt;li&gt;字符串相加操作也是，任何修改都会导致重新申请内存，时间复杂度是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(m+n)&lt;/code&gt;。Golang字符串修改一般是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;或者&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bytes&lt;/code&gt;包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bytes.WriteString&lt;/code&gt;实现。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;redis的字符串&quot;&gt;Redis的字符串&lt;/h3&gt;
&lt;p&gt;Redis是用C语言开发的，Redis支持字符串保存数据。它肯定不会用字符数组来做字符串实现。。。它开发了一个叫做简单动态字符串（simpledynamic string，SDS）的类型来实现字符串。定义的结构体如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct sdshdr {    
	// 记录buf数组中已使用字节的数量    
	// 等于SDS所保存字符串的长度，用空间解决时间问题
	int len;    
	// 记录buf数组中未使用字节的数量。用于避免缓冲区溢出，如果当前空间不够，则再分配一个符合大小的空间  
	int free;   
	// 字节数组，用于保存字符串。用这个是为了和C语言字符串兼容
	char buf[];
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;和Golang的实现相比，除了长度字段len和保存内容的数组buf。计算字符串长度的时间复杂度是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(1)&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;Redis是用C语言开发的，为了和C语言兼容，buf是C语言的字符串，以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;\0&apos;&lt;/code&gt;结束。&lt;/li&gt;
  &lt;li&gt;它还增加了free字段表示空闲空间（别的类似实现可能是用容量字段，都差不多）。为了解决像Golang实现那样对于写操作不友好的问题，它会初始化一个较大的内存空间，字符串的增加会写入这些提前准备好的空间里面。一旦用完free的内容，就给buf追加空间。当追加空间的时候，性能会降到和Golang差不多的性能，一般情况还是很好的。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/1760757/how-to-efficiently-concatenate-strings-in-go&quot;&gt;How to efficiently concatenate strings in Go? - stackoverflow&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>离线版的Golang官方文档</title>
   <link href="https://blog.cyeam.com/golang/2015/09/11/fuckgolang"/>
   <updated>2015-09-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/09/11/fuckgolang</id>
   <content type="html">&lt;p&gt;由于众所周知的原因，Golang不支持国内使用。当查文档或者升级版本的时候就会比较苦恼。有的时候实在是没辙，就用必应的缓存页看文档，下载升级包也是各种想辙。每次都很麻烦。&lt;/p&gt;

&lt;p&gt;为了我自己能方便点用，我把Golang官方文档的页面都下载下来放在我的VPS上面。VPS也不是每天都能连得上，趁昨天有机会赶紧放上去，然后用CDN加速。链接：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;https://cyeam.com/go/pkg/index.html&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这让我想起前几天看到的一条微博。说古巴的一个网站，架设在了万恶的美帝，万恶的美帝不让他们用这个网站。古巴的屌丝们都是把这个网站整站下载到U盘里面偷出美帝进行浏览。异曲同工，哈哈哈。&lt;/p&gt;

&lt;p&gt;下载整站用了命令：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget -r -p -np -k http://URL
-r 递归下载
-p 下载html里面所有的图片
-np 不要追溯到父目录（应该是为了避免重复下载吧）
-k 相对路径转换为绝对路径
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/peida/archive/2013/03/18/2965369.html&quot;&gt;每天一个linux命令（61）：wget命令 - 竹子&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>too many open files错误</title>
   <link href="https://blog.cyeam.com/golang/2015/08/27/toomanyopenfiles"/>
   <updated>2015-08-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/08/27/toomanyopenfiles</id>
   <content type="html">&lt;p&gt;大家都知道，最近我模仿binux大婶的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyspider&lt;/code&gt;的害羞组在线上跑了一段时间了。后来加入了一些新的东西，比如代理池等。看瞅着代码越来越靠谱了，结果突然有一天，发现抓取停止了，紧接着去看日志：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2015/08/12 23:18:22 Post http://api.duoshuo.com/posts/import.json: dial tcp: lookup api.duoshuo.com: too many open files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;作为一个菜鸟，我哪知道这是啥啊。后来用Google去搜，发现这是Linux套接字占满了。在目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc//fd/&lt;/code&gt;下，里面有该进程所有打开的文件标识符相关文件，套接字也属于文件的一种。默认Linux下规定每个进程的最大socket并发数是1024，就是对打开的文件有所限制。我最早是通过进入该目录，使用命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ll | wc -l&lt;/code&gt;查看当前进程的socket连接数。&lt;/p&gt;

&lt;p&gt;这一看就是有东西申请了没释放，果断重启了服务好了。但是到底是哪里没释放，我还是不得而知。只能是用Google继续查，我懂得少，查了很多东西，把结论说一下。&lt;/p&gt;

&lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc/net/tcp&lt;/code&gt;文件里，保存的是当前计算机的TCP协议连接，我这边服务器的结果如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; sl  local_address rem_address   st tx_queue rx_queue tr tm-&amp;gt;when retrnsmt   uid  timeout inode
276: BE848368:80E5 2F80CFB7:0050 08 00000000:000019E4 00:00000000 00000000  1000        0 3748204 1 ffff8800182c1c00 116 4 30 10 -1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后就是换算IP，这里面本地和远程IP都是16进制的，并且是倒着排的，换算完的本地IP是104.131.132.190:32997(BE848368:80E5)，远程IP是183.207.128.47:80(2F80CFB7:0050)，说明我的计算机连接这个IP没有正常断开。查了一下这个IP，他是我用的西祠代理里面提供的一个，说明代理那部分新写的代码有问题。&lt;/p&gt;

&lt;p&gt;后来又得知，用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstat&lt;/code&gt;命令也成，而且比这个更牛逼。我一般还会用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt;参数，这个能看到连接相关的进程，别的参数我也不懂了，大家想知道就Google把。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstat -p | grep haixiuzu&lt;/code&gt;这个就能看到我的害羞组程序的连接。当时显示出的一个结果如下表。此外，还发现一次抓取结束后会有30个CLOSE_WAIT状态的连接，其中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;118.144.80.201&lt;/code&gt;这个IP有20个，量也不小，他是多说API的IP，说明保存到多说的时候HTTP也没有释放连接。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Proto Recv-Q Send-Q Local Address         Foreign Address     State      PID/Program name
tcp        1      0 104.131.132.190:60059 118.144.80.201:http CLOSE_WAIT 14397/haixiuzu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;网上查了查，大神们都说如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Recv-Q&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Send-Q&lt;/code&gt;有不为0的就说明不对，这是发送和接受队列，说明一直在等待，一般需要两个小时操作系统才会自动释放这个资源，但是据我观察，两个小时也不会正常，要不也不会增加到1024个。&lt;/p&gt;

&lt;p&gt;问题大体了定位好了，然后就是定位和修复。又去看了TCP连接的3次握手和断开的4次握手，大概猜测是握手握了一半没继续握。又去看了看Golang的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/http&lt;/code&gt;包，断开连接就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resp.Body.Close()&lt;/code&gt;，我没写这个，抱着试一试的态度改了一下，发现TCP连接恢复正常了。。。&lt;/p&gt;

&lt;p&gt;我自己造的这个轮子还是可以的，让我学到了TCP的一些皮毛，还是很有成就感的。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/maodou&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang strings包的TrimRight和TrimSuffix的区别</title>
   <link href="https://blog.cyeam.com/golang/2015/08/19/trim"/>
   <updated>2015-08-19T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/08/19/trim</id>
   <content type="html">&lt;p&gt;昨天遇到一个问题，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings.TrimRight(&quot;cyeamblog.go&quot;, &quot;.go&quot;)&lt;/code&gt;结果居然是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;cyeambl&quot;&lt;/code&gt;，这让我百思不得其解。当然，要看官方文档的解释：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;func TrimRight(s string, cutset string) string
TrimRight returns a slice of the string s, with all trailing Unicode code points contained in cutset removed.&lt;/p&gt;

  &lt;p&gt;func TrimSuffix(s, suffix string) string
TrimSuffix returns s without the provided trailing suffix string. If s doesn’t end with suffix, s is returned unchanged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrimSuffix&lt;/code&gt;能看明白，这也是我想要用的，符合我的要求。但是这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrimRight&lt;/code&gt;怎么看也不知道有啥区别。除了代码结果不一样以别的都不清楚了。今天去大Google上面去搜，看到这个标题&lt;em&gt;Is this a bug? strings.TrimRight seems to be cutting too much&lt;/em&gt;，乐死我了。我也是这样想的呀，只不过不好意思上去问。&lt;/p&gt;

&lt;p&gt;看了这个算是弄明白了。大概解释说明一下，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrimRight&lt;/code&gt;会把第二个参数字符串里面所有的字符拿出来处理，只要与其中任何一个字符相等，将其删除。也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cutset&lt;/code&gt;参数所有字符排列组合的形式进行删除。&lt;/p&gt;

&lt;p&gt;最否附上一个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;log&quot;
	&quot;strings&quot;
)

func main() {
	log.Println(strings.TrimRight(&quot;abba&quot;, &quot;ba&quot;))
	log.Println(strings.TrimRight(&quot;abcdaaaaa&quot;, &quot;abcd&quot;))
	log.Println(strings.TrimSuffix(&quot;abcddcba&quot;, &quot;dcba&quot;))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2015/08/13 15:56:30
2015/08/13 15:56:30
2015/08/13 15:56:30 abcd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_trim.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://groups.google.com/forum/#!topic/golang-nuts/WAItFEvrhmU&quot;&gt;Is this a bug? strings.TrimRight seems to be cutting too much.. - golang-nuts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>单件模式——Golang实现</title>
   <link href="https://blog.cyeam.com/designpattern/2015/08/12/singleton"/>
   <updated>2015-08-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/designpattern/2015/08/12/singleton</id>
   <content type="html">&lt;p&gt;单件模式比较常见，算是创建型的设计模式，和工厂模式不同，他只能创建一个实例。他的应用场景很多，比如MySQL只能有一个实例这种都算。&lt;/p&gt;

&lt;p&gt;单件模式能简单分成支持并发和不支持并发两种。不过并发这个很简单，满大街Golang实现的单件模式都是这样的。&lt;/p&gt;

&lt;h4 id=&quot;普通的单件模式&quot;&gt;普通的单件模式&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package singleton

import (
	&quot;fmt&quot;
)

var _self *Singleton

type Singleton struct {
	Name string
}

func Instance() *Singleton {
	if _self == nil {
		_self = new(Singleton)
		return _self
	}
	return _self
}

func (o *Singleton) SetName(s string) {
	_self.Name = s
}

func (o *Singleton) GetName() {
	fmt.Println(&quot;Name:&quot;, _self.Name)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;支持并发的单件模式也不算难，在这个基础上增加一个叫做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Double Check&lt;/code&gt;的处理。当年在去哪儿网面试被虐的时候被问到过这个，所以这个东西一直也都记着。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;单件模式在并发情况下，上面的代码就有问题了，有可能会被创建多次，上面的例子加个日志：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var _self *Singleton

type Singleton struct {
	Name string
}

func NewInstance(name string) *Singleton {
	fmt.Println(&quot;Create instance&quot;, name)
	time.Sleep(4 * time.Second)
	_self.Name = name
	return _self
}

func Instance(name string) *Singleton {
	if _self.Name == &quot;&quot; {
		return NewInstance(name)
	}
	return _self
}

func main() {
	_self = new(Singleton)
	go Instance(&quot;cyeam&quot;)
	go Instance(&quot;bryce&quot;)
	time.Sleep(10 * time.Second)
	fmt.Println(_self.Name)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果如下。例子里面给实例化函数加了参数，方便判断实例化的次数和结果。从下面的结果看来，无锁的单件模式创建过两次实例，第二次创建的实例覆盖了第一个创建的实例。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Create instance cyeam
Create instance bryce
bryce
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;支持并发的单件模式&quot;&gt;支持并发的单件模式&lt;/h4&gt;

&lt;p&gt;为了保证在并行调用的情况下只创建一个实例，就需要加锁来保证串行创建。简单粗暴的方法就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Instance()&lt;/code&gt;方法直接全部上锁。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var _self *Singleton

type Singleton struct {
	Name string
	sync.Mutex
}

func NewInstance(name string) *Singleton {
	fmt.Println(&quot;Create instance&quot;, name)
	time.Sleep(4 * time.Second)
	_self.Name = name
	return _self
}

func Instance(name string) *Singleton {
	_self.Mutex.Lock()
	defer _self.Mutex.Unlock()
	if _self.Name == &quot;&quot; {
		return NewInstance(name)
	}
	return _self
}

func main() {
	_self = new(Singleton)
	_self.Mutex = sync.Mutex{}
	go Instance(&quot;cyeam&quot;)
	go Instance(&quot;bryce&quot;)
	time.Sleep(10 * time.Second)
	fmt.Println(_self.Name)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;简单粗暴的加锁，可以发现能够解决并发多次创建的问题。但是如此一来，整个创建流程就变成串行调用了。比如有1000次创建请求，只要创建一个实例就好，剩下999次完全没有必要加锁，直接将之前第一个创建的实例返回就好。而这样的写法会导致每一次都是带锁访问，影响速度。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Create instance cyeam
cyeam
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着上面的思路，我们可以不为这个函数整体加锁，在创建的时候加锁即可。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Instance(name string) *Singleton {
	if _self.Name == &quot;&quot; {
		_self.Mutex.Lock()
		defer _self.Mutex.Unlock()
		return NewInstance(name)
	}
	return _self
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在判断确认对象为空后，开始创建对象，结果如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Create instance cyeam
Create instance bryce
bryce
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;虽然已经加上了锁，但是可以看到，依然创建了两次对象。如果两个并行的创建调用，此时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if _self.Name == &quot;&quot;&lt;/code&gt;这个调用也同时执行，自然也都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;，接着，虽然有锁，但是也就是串行得去创建对象，外面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;在无锁的情况是失效了。&lt;/p&gt;

&lt;p&gt;这时就需要牛逼的&lt;strong&gt;Double Check&lt;/strong&gt;了，在锁里面再加一次判空检查。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Instance(name string) *Singleton {
	if _self.Name == &quot;&quot; {
		_self.Mutex.Lock()
		defer _self.Mutex.Unlock()
		if _self.Name == &quot;&quot; {
			return NewInstance(name)
		}
	}
	return _self
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果如下。如此一来，既兼顾了效率，又能够很好的支持并发。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Create instance cyeam
cyeam
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面写的这些都是传统的写法，对于一般语言使用的，对于我大Golang，还有一个更简单的实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

var _self *Singleton

type Singleton struct {
	Name string
	sync.Once
}

func NewInstance(name string) *Singleton {
	fmt.Println(&quot;Create instance&quot;, name)
	time.Sleep(4 * time.Second)
	_self.Name = name
	return _self
}

func Instance(name string) *Singleton {
	if _self.Name == &quot;&quot; {
		_self.Once.Do(func() { NewInstance(name) })
	}
	return _self
}

func main() {
	_self = new(Singleton)
	_self.Once = sync.Once{}
	go Instance(&quot;cyeam&quot;)
	go Instance(&quot;bryce&quot;)
	time.Sleep(10 * time.Second)
	fmt.Println(_self.Name)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/tree/master/singleton&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/shltsh/article/details/17429363&quot;&gt;设计模式(2) - Singleton单件模式 - shltsh&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Thrift数据传输序列化</title>
   <link href="https://blog.cyeam.com/thrift/2015/07/31/thriftserialization"/>
   <updated>2015-07-31T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/thrift/2015/07/31/thriftserialization</id>
   <content type="html">&lt;p&gt;Thrift的序列化是比Json更好用的结构（具体哪里好了我还没研究）。但是它有一个非常严重的问题：兼容性差。常年使用Json的我们已经被惯坏了，潜意识里就觉得数据默认是能保持兼容的。&lt;/p&gt;

&lt;p&gt;先说一下那个线上问题。我与公司其它部门用Thrift对接，先上了一版，我是调用方。接着，二次升级的时候，合作的部门改了一下Idl，在函数返回结果结构体的最前面，插入了一个结构，是一个结构体，原本第一版第一个位置是整形。然后他们上线了，我还在排队上线。突然监控就报我接口返回了404(一般错误都转成404)。然后去排查，发现返回了一堆负数，负数导致后面的逻辑出问题了，哪怕返回了整数，也是非常不正常。&lt;/p&gt;

&lt;p&gt;这个关键的时刻，我们组长（永远是他）给解释了bug的原因，是Thrift不兼容导致的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct Pair {
	1: required string key
	2: required string value
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面这个结构体，如果解析成Json，是按照键值对的形式展示，字段变量的名称是有意义的。而在Thrift里面，依然是键值对的形式，但是没有用变量名的名字作为键，而是通过&lt;strong&gt;&lt;em&gt;序号和类型&lt;/em&gt;&lt;/strong&gt;作为键，序号决定位置，类型决定解析方式。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Versioning in Thrift is implemented via ﬁeld identiﬁers. The ﬁeld header for every member of a struct in Thrift is encoded with a unique ﬁeld identiﬁer. The combination of this ﬁeld identiﬁer and its type speciﬁer is used to uniquely identify the ﬁeld. The Thrift deﬁnition language supports automatic assignment of ﬁeld identiﬁers, but it is good programming practice to always explicitly specify ﬁeld identiﬁers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;之前写过一篇&lt;a href=&quot;https://blog.cyeam.com/golang/2014/07/22/go_thrift&quot;&gt;《Golang开发Thrift接口》&lt;/a&gt;，这次接着以这个为基础修改进行实验。新增一个测试函数，返回我们要实验的这个结构体。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Pair helloPair()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过Idl生成Golang包：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;thrift -v --gen go:package_prefix=go_code/test_thrift Hello1.thrift
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloimpl.go&lt;/code&gt;实现这个新增的方法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (h *HelloHandler) HelloPair() (*hello.Pair, error) {
	p := new(hello.Pair)
	p.Key = &quot;rowkey&quot;
	p.Value = &quot;column-family&quot;
	return p, nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动服务端：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go run helloserver.go helloimpl.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloclient.go&lt;/code&gt;增加调用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;res, err := client.HelloPair()
fmt.Println(res.String())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run helloclient.go
Pair({Key:rowkey Value:column-family})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果正确，下面来模拟错误的情况。这个模拟很简单，修改Idl顺序：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct Pair {
	2: required string key
	1: required string value
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;重新生成一遍Idl，为了对比，我把这个Idl命名为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hello1.thrift&lt;/code&gt;。然后复制原文件并重命名为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloimpl1.go&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloserver1.go&lt;/code&gt;。把引用的包名改成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello &quot;go_code/test_thrift/hello1&quot;&lt;/code&gt;。再启动服务端：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hello &quot;go_code/test_thrift/hello1&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;客户端不做任何修改：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run helloclient.go
Pair({Key:column-family Value:rowkey})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;结果正好相反，Amazing有没有。。。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/tree/master/test_thrift&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/mumuxinfei/p/3876075.html&quot;&gt;Thrift 个人实战–Thrift 的序列化机制 - mumuxinfei&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>【译】并发、协程和GOMAXPROCS</title>
   <link href="https://blog.cyeam.com/golang/2015/07/24/gomaxprocs"/>
   <updated>2015-07-24T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/07/24/gomaxprocs</id>
   <content type="html">&lt;h4 id=&quot;介绍&quot;&gt;介绍&lt;/h4&gt;

&lt;p&gt;当有新人加入Go-Miami组，他们经常希望去学习的并发模型。当我刚刚开始听说Go语言的时候，并发似乎看起来是这门语言的热门词汇。当我看了Rob Pike的&lt;a href=&quot;http://www.youtube.com/watch?v=f6kdp27TYZs&quot;&gt;Go并发模式&lt;/a&gt;的视频之后，我认为我应该去学习这门语言了。&lt;/p&gt;

&lt;p&gt;为了去理解是如何通过Go语言编写出更简单、难出错的并发程序，我们首先需要去理解什么是并发程序和并发程序的结果是什么这两个问题。我不会去讲述CSP（通信顺序进程、Communicating Sequential Processes），这是Go语言实现管道（channel）的基础。这篇文章将着重讲并发是什么，协程的角色是什么，环境变量GOMAXPROCS和运行时函数如何影响Go语言的程序的执行。&lt;/p&gt;

&lt;h4 id=&quot;进程和线程&quot;&gt;进程和线程&lt;/h4&gt;

&lt;p&gt;当我们运行一个应用，比如我写这篇文章用的浏览器，操作系统会为这个应用创建一个进程。进程的任务就像是一个容器，为这个应用运行的时候使用和管理资源。这些资源包括内存地址空间、指向文件的句柄、设备和线程。&lt;/p&gt;

&lt;p&gt;线程是执行的一部分，它会被操作系统调度去执行进程里面、被我们在函数里面实现的代码。一个进程开始于一个线程，也叫做主线程，当这个线程结束整个进程也会随之结束。这是因为主线程是应用程序的来源。主线程可以反过来创建更多的线程，而这些线程还能创建更多的线程。&lt;/p&gt;

&lt;p&gt;操作系统可以调度一个线程在一个可用的处理器上面执行，而不管处理这个线程的父线程在哪个处理器上面执行。每个操作系统都有自己的调度算法，并不是特定的一种，来决定最好的方式让我们开发并行程序。并且这些算法将会随着操作系统的每次升级发生改变，这个需要我们注意。&lt;/p&gt;

&lt;h4 id=&quot;协程和并行&quot;&gt;协程和并行&lt;/h4&gt;

&lt;p&gt;Go语言里面任何函数或者方法可以创建一个协程。我们可以认为主方法执行起来是一个协程，然而Go的运行时并没有启动这个协程。协程可以认为是轻量级的，因为它们占用很小的内存和资源，并且它初始化的栈空间也很小。早先的Go语言1.2版本栈空间会被初始化为4K，目前的1.4版本会被初始化为8K。这个栈可以随着需要自动增加空间。&lt;/p&gt;

&lt;p&gt;操作系统调度可用的处理器来执行线程，Go的运行时会通过一个操作系统线程来调度协程。默认情况下，Go的运行时会分配一个逻辑处理器来执行代码里面定义的全部协程。甚至通过这一个逻辑处理器和操作系统线程，成百上千个协程可以被并发的高效迅速地调用执行。不建议添加更多的逻辑处理器，但如果你想在并行运行协程，Go提供了通过GOMAXPROCS环境变量或运行时函数来增加逻辑处理器。&lt;/p&gt;

&lt;p&gt;并发并不是并行。并行是在多核处理器不能的核同时并行得执行两个或两个以上的线程。如果你配置运行时的逻辑处理器多于1个，调度器将会在这些逻辑处理器上面分配协程，这样做的结果就是在不同的操作系统线程上面执行多个协程。然而，想要真正的实现并行执行你的服务器必须是多核处理器。如果不是，即使Go的运行时被设置为需要多个逻辑处理器，协程仍会并发的在同一个处理处理器上面执行协程。&lt;/p&gt;

&lt;h4 id=&quot;并发的例子&quot;&gt;并发的例子&lt;/h4&gt;

&lt;p&gt;我们来创建一个小程序来展示Go并发执行协程s。这个例子我们是在默认设置下面执行，默认设置是要使用一个逻辑处理器。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    fmt.Println(&quot;Starting Go Routines&quot;)
    go func() {
	defer wg.Done()

	for char := &apos;a&apos;; char &amp;lt; &apos;a&apos;+26; char++ {
	    fmt.Printf(&quot;%c &quot;, char)
	}
    }()

    go func() {
	defer wg.Done()

	for number := 1; number &amp;lt; 27; number++ {
	    fmt.Printf(&quot;%d &quot;, number)
	}
    }()

    fmt.Println(&quot;Waiting To Finish&quot;)
    wg.Wait()

    fmt.Println(&quot;\nTerminating Program&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个程序通过关键字&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go&lt;/code&gt;启动了两个协程，声明了两个匿名函数。第一个协程打印了小写英语字母表，第二个打印了1到26这些数字。当我们运行这个程序，将会得到以下的结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Starting Go Routines
Waiting To Finish
a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Terminating Program
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们来看一下输出的结果，代码被并发的执行。一旦这两个协程被启动，主的协程将会等待这两个协程执行结束。我们需要用这个方法否则一旦主协程运行结束，整个程序就结束了。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WaitGroup&lt;/code&gt;是一个不错的方来用来在不同的协程之间通信是否完成。&lt;/p&gt;

&lt;p&gt;我们可以发现第一个协程完整地展示了26个字母之后，第二个协程才开始展示26个数字。因为这两个协程执行速度太快，毫秒时间内就能执行结束，我们并没有能够看到是否调度器在第一个协程执行完之前中断了它。我们可以在第一个协程里面增加一个等待时间来判断调度器的策略。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
    &quot;time&quot;
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    fmt.Println(&quot;Starting Go Routines&quot;)
    go func() {
	defer wg.Done()

	time.Sleep(1 * time.Microsecond)
	for char := &apos;a&apos;; char &amp;lt; &apos;a&apos;+26; char++ {
	    fmt.Printf(&quot;%c &quot;, char)
	}
    }()

    go func() {
	defer wg.Done()

	for number := 1; number &amp;lt; 27; number++ {
	    fmt.Printf(&quot;%d &quot;, number)
	}
    }()

    fmt.Println(&quot;Waiting To Finish&quot;)
    wg.Wait()

    fmt.Println(&quot;\nTerminating Program&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这次我们在第一个协程里面增加了1秒钟的等待时间，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sleep&lt;/code&gt;导致了调度器交换了两个协程的执行顺序：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Starting Go Routines
Waiting To Finish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 a
b c d e f g h i j k l m n o p q r s t u v w x y z
Terminating Program
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这次数字在字母前面。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sleep&lt;/code&gt;会影响调度器停止第一个协程，先执行第二个协程。&lt;/p&gt;

&lt;h4 id=&quot;并行的例子&quot;&gt;并行的例子&lt;/h4&gt;

&lt;p&gt;我们上面的两个例子协程都是在并发执行，并不是并行的。我们改变一下代码来允许代码并行执行。我们需要做的就是为调度器增加一个逻辑处理器让他能够使用两个线程：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;runtime&quot;
    &quot;sync&quot;
)

func main() {
    runtime.GOMAXPROCS(2)

    var wg sync.WaitGroup
    wg.Add(2)

    fmt.Println(&quot;Starting Go Routines&quot;)
    go func() {
	defer wg.Done()

	for char := &apos;a&apos;; char &amp;lt; &apos;a&apos;+26; char++ {
	    fmt.Printf(&quot;%c &quot;, char)
	}
    }()

    go func() {
	defer wg.Done()

	for number := 1; number &amp;lt; 27; number++ {
	    fmt.Printf(&quot;%d &quot;, number)
	}
    }()

    fmt.Println(&quot;Waiting To Finish&quot;)
    wg.Wait()

    fmt.Println(&quot;\nTerminating Program&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是程序的输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Starting Go Routines
Waiting To Finish
a b 1 2 3 4 c d e f 5 g h 6 i 7 j 8 k 9 10 11 12 l m n o p q 13 r s 14
t 15 u v 16 w 17 x y 18 z 19 20 21 22 23 24 25 26
Terminating Program
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;每一次我们运行这个程序将会得到不同的结果。调度器并不会每一次都执行得到相同的结果。我们可以看到协程这次真的是并行执行了。两个协程立刻开始运行，并且你可以看到两个都在竞争输出各自的结果。&lt;/p&gt;

&lt;h4 id=&quot;结论&quot;&gt;结论&lt;/h4&gt;

&lt;p&gt;我们可以为调度器增加多个逻辑处理器，但这不意味着我们必须要这么做。Go团队把运行时的并行数默认设为1是有原因的。随意添加逻辑处理器和并行的协程并不会一定为你的程序提供更好的性能。要做好性能压力测试，确保修改Go运行时GOMAXPROCS一定能优化性能时再这么做。&lt;/p&gt;

&lt;p&gt;在我们的应用里面创建并发的协程，这个问题最终将会导致我们的协程可以同时尝试访问同样的资源。读写共享资源一定要是原子性的。换句话说，读和写操作一定要在一个协程里面同一个时刻只有一个操作被执行，否则我们就需要在代码里面创建临界条件。学习更多的&lt;a href=&quot;http://www.goinggo.net/2013/09/detecting-race-conditions-with-go.html&quot;&gt;竞争条件&lt;/a&gt;可以读这篇文章。&lt;/p&gt;

&lt;p&gt;管道是Go语言里面安全和优雅的编写并发程序的方法，它消除了编写竞争条件，可以使得开发并行程序更加有趣。我们现在已经知道协程是如何工作、被调度和如果并行执行，接下来我们将会讲述管道。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.goinggo.net/2014/01/concurrency-协程s-and-gomaxprocs.html&quot;&gt;Concurrency, Goroutines and GOMAXPROCS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://developer.51cto.com/art/200908/141553.htm&quot;&gt;并发和并行的区别：吃馒头的比喻&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang的垃圾回收</title>
   <link href="https://blog.cyeam.com/golang/2015/07/03/gogc"/>
   <updated>2015-07-03T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/07/03/gogc</id>
   <content type="html">&lt;p&gt;我们公司服务端都是在用Golang，每天几百万的UV，过亿的PV一直没啥问题。后来改了一次逻辑，首页能展示一个列表，之前这个列表都没有做过缓存处理，一个是因为数据少，一共才一千多条；还有就是量不大。但是首页加了入口之后出了问题，内存疯涨，一般内存也就占200M+，这还是包含In Memory缓存的情况下，这下好家伙，最高我见过占用20G内存的情况，但是程序重启一下又能好。当然这是后知后觉了，当时也没把这两个东西联系到一起。&lt;/p&gt;

&lt;p&gt;完整的说一下事情的经过。&lt;/p&gt;

&lt;p&gt;上线之后内存疯涨，一开始会占10G左右，不知道为啥，只能&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kill&lt;/code&gt;掉。程序重启之后短时间内也会维持在200M左右，但是过个4、5天又会涨上来，而这个过程中内存大小都没有影响用户使用。最后只能跟运维说，看见内存大了就&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kill&lt;/code&gt;一次。。。&lt;/p&gt;

&lt;p&gt;当时觉得内存这么大，肯定是GC的问题，不晓得是哪里内存泄漏了。我们用的是beego，我就用它封装好的内存&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pprof&lt;/code&gt;和CPU检测工具各种查，实在是没发现啥可疑的。毫无头绪。&lt;/p&gt;

&lt;p&gt;后来机缘巧合去看了Gopher China 2015大会的视频，全程视频都放在了&lt;a href=&quot;http://www.imooc.com/view/407&quot;&gt;慕课网&lt;/a&gt;，大家也可以去看看。都是业内用Golang的大佬的演讲。推荐大家去看：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.imooc.com/video/7915&quot;&gt;Go 语言在游戏项目的应用情况汇报&lt;/a&gt;，达达在GC方面在国内算是先驱了，很多人研究GC都是参考的他的数据。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.imooc.com/video/7924&quot;&gt;Go 语言构建高并发分布式系统实践&lt;/a&gt;，Golang在360的应用。&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.imooc.com/video/7940&quot;&gt;Go 1.4 runtime&lt;/a&gt;，这个是真大神，雨痕，把整个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime&lt;/code&gt;讲得很明白，我这种菜鸟都能听的懂，想看内存分配原理就去看这个。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;再后来，又发生了一件事情，我们大促的时候，线上出现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Can&apos;t create more than max_prepared_stmt_count statements (current value: 16382)&lt;/code&gt;，数据库查询量太大，DBA把查询数调成了30000都不行。DBA怀疑是我们没有释放，但是这都是用现成的orm写的代码，并且去看了源码，确实释放了。&lt;/p&gt;

&lt;p&gt;最后没辙，加缓存。这下邪了，数据库好了，再也没有那个错误了，而且内存也好了，再也不乱涨了。今天又在开发者头条看到了一篇文章&lt;a href=&quot;http://blog.pandocloud.com/?p=616&quot;&gt;《golang gc 探究》&lt;/a&gt;，跟我们的情况很类似。这也就能说明，我们之前内存离奇疯涨，是和Golang的GC机制有关。&lt;/p&gt;

&lt;p&gt;下面说一下我对GC的理解。为了保证程序内内存的连续，Golang会申请一大块内存（有时候，只写一个hello, world可能监控内存可能都会发现占用内存比想象中的大）。当用户的程序申请的内存大于之前预申请的内存时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime&lt;/code&gt;会进行一次GC，并且将GC的阈值翻倍。也就是说，之前是超过10M时进行GC，那么下一次GC就是超过20M才进行。此外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime&lt;/code&gt;还支持定时GC。我们内存升高的原因，目前看来就是访问量过大，数据库访问的时候导致GC阈值变大，回收频率变低。而且在回收方面，Golang采用了一种&lt;em&gt;拖延症&lt;/em&gt;策略，即使是被释放的内存，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runtime&lt;/code&gt;也不会立刻把内存还给系统。这就导致了内存降不下来，一种内存泄漏的假象。&lt;/p&gt;

&lt;p&gt;Golang在GC的时候会发生&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stop the world&lt;/code&gt;，整个程序会暂停，然后去标记整个内存里面可以被回收的变量，标记完之后恢复程序执行，最后异步得去回收内存。一般这个过程会达到20ms。&lt;strong&gt;标记可回收变量的时间取决于临时变量的个数。&lt;/strong&gt;临时变量数量越多，扫描时间会越长。&lt;/p&gt;

&lt;p&gt;所以目前GC的优化方式原则就是尽可能少的声明临时变量：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;局部变量尽量复用；&lt;/li&gt;
  &lt;li&gt;如果局部变量过多，可以把这些变量放到一个大结构体里面，这样扫描的时候可以只扫描一个变量，回收掉它包含的很多内存；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Golang目前一直在优化GC，目前整体效果来看和大Java差不多，但是稳定性上面来看，还是不行。一般压力测试第一波上面效果极差。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.zhihu.com/question/21615032&quot;&gt;Go 的垃圾回收机制在实践中有哪些需要注意的地方？ - 达达&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang实现多线程并发下载</title>
   <link href="https://blog.cyeam.com/network/2015/07/02/goget"/>
   <updated>2015-07-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2015/07/02/goget</id>
   <content type="html">&lt;p&gt;大家都用过迅雷等下载工具，特点就是支持并发下载，断点续传。我们这里不介绍它，这个比较复杂了，逼人也不懂。本文只介绍狭义上的简易的断点续传和狭义上的多线程下载。跟之前一样，旨在研究原理，实际生活中基本没啥用，实测下来多线程下载比单线程下载还慢。。。太丢人了。&lt;/p&gt;

&lt;p&gt;主要讲三个方面，如何HTTP的并发下载、通过Golang进行多协程开发、如何断点续传。&lt;/p&gt;

&lt;h4 id=&quot;http的并发下载&quot;&gt;HTTP的并发下载&lt;/h4&gt;
&lt;p&gt;想要并发下载，就是把下载内容分块，然后并行下载这些块。这就要求服务器能够支持分块获取数据。大迅雷、电驴这种都有自己的协议，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thunder://&lt;/code&gt;这种，我们只研究原理，就说说HTTP协议对于并发的支持。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;em&gt;HTTP头&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;em&gt;对应值&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;em&gt;含义&lt;/em&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Content-Length&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;14247&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;HTTP响应的Body大小，下载的时候，Body就是文件，也可以认为是文件大小，单位是比特&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Content-Disposition&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;inline; filename=”bryce.jpg”&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;是MIME协议的扩展，MIME协议指示MIME用户代理如何显示附加的文件。当浏览器接收到头时，它会激活文件下载。这里还包含了文件名&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Accept-Ranges&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;bytes&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;允许客户端以bytes的形式获取文件&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Range&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;bytes=0-511&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;分块获取数据，这里表示获取第0到第511的数据，共512字节&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;如果要下载一个文件，想知道这些文件的信息，例如文件名、文件大小、是否支持并发下载、文件类型都可以从响应的头里面获取。如何在下载前获得到这些内容而不是下载中获取，可以用HTTP提供的HEAD方法。HEAD方法只响应HTTP的头部分，不包含Body部分。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;req, err := http.NewRequest(&quot;HEAD&quot;, get.Url, nil)
resp, err := get.GetClient.Do(req)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取文件类型、文件名等参数。HTTP从Url到Head在到Body，你都可以认为是字符串，也确实是字符串，但是解析的时候不要自己以字符串的方式处理，要不恶心死你。Url的解析大Golang有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/url&lt;/code&gt;包支持，MIME有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mime&lt;/code&gt;包支持，这都是原生包，别的语言必然也支持。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;get.ContentLength = int(resp.ContentLength)
get.MediaType, get.MediaParams, _ = mime.ParseMediaType(get.Header.Get(&quot;Content-Disposition&quot;))
log.Printf(&quot;Get %s MediaType:%s, Filename:%s, Length %d.\n&quot;, get.Url, get.MediaType, get.MediaParams[&quot;filename&quot;], get.ContentLength)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2015/07/02 09:56:47 Get https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/bryce.jpg MediaType:inline, Filename:bryce.jpg, Length 14247.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果响应头里面还包含了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Accept-Ranges&lt;/code&gt;，就说明服务器支持分块获取：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if get.Header.Get(&quot;Accept-Ranges&quot;) != &quot;&quot; {
	log.Printf(&quot;Server %s support Range by %s.\n&quot;, get.Header.Get(&quot;Server&quot;), get.Header.Get(&quot;Accept-Ranges&quot;))
} else {
	log.Printf(&quot;Server %s doesn&apos;t support Range.\n&quot;, get.Header.Get(&quot;Server&quot;))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;分模块下载，新建N个临时文件，我的命名规则是加个分块区间的后缀，例如bryce.jpg.0-512，这样可以省掉一个配置文件（主要是我懒的写）。将下载好的块存入临时文件里面，最后都下载完之后统一存入最终的文件里面。分块下载加个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Range&lt;/code&gt;头就可以了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;range_i := fmt.Sprintf(&quot;%d-%d&quot;, get.DownloadRange[i][0], get.DownloadRange[i][1])
log.Printf(&quot;Download #%d bytes %s.\n&quot;, i, range_i)

defer get.TempFiles[i].Close()

req, err := http.NewRequest(&quot;GET&quot;, get.Url, nil)
req.Header.Set(&quot;Range&quot;, &quot;bytes=&quot;+range_i)
resp, err := get.GetClient.Do(req)
defer resp.Body.Close()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后将下载好的保持到文件里。这里是等这个块都下载完之后再写入硬盘，下载完之后都是保持在内存里面。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cnt, err := io.Copy(get.TempFiles[i], resp.Body)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;多线程开发&quot;&gt;多线程开发&lt;/h4&gt;

&lt;p&gt;并发下载的时候，要启动N个协程，主线程这时需要阻塞，等待这N个协程下载完毕。先开始想用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel&lt;/code&gt;自己写，不是特别会。。。用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sync&lt;/code&gt;包辅助实现，它支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WaitGroup&lt;/code&gt;，正好可以解决我这里的问题。&lt;/p&gt;

&lt;p&gt;在主线程里面启动N个协程，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add&lt;/code&gt;方法可以理解成增加一个任务，任务计数器加一；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Wait&lt;/code&gt;方法用于阻塞，指导所有任务完成。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for i, _ := range get.DownloadRange {
	get.WG.Add(1)
	go get.Download(i)
}
get.WG.Wait()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在下载协程增加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Done&lt;/code&gt;函数，协程结束之后通知任务完成，任务计数器减一。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;defer get.WG.Done()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;断点续传&quot;&gt;断点续传&lt;/h4&gt;

&lt;p&gt;这块最简单，如果任务下载暂停了，就是传输的内容不足。第一步创建临时文件的文件名后缀有派上用场了，读取到块应有的大小之后，检查块实际大小。通过文件的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.FileInfo&lt;/code&gt;就能获取到文件相关属性信息。这样再下载的时候就增加一个偏移量，跳过已经下载好的内容。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for i := 0; i &amp;lt; len(get.DownloadRange); i++ {
	range_i := fmt.Sprintf(&quot;%d-%d&quot;, get.DownloadRange[i][0], get.DownloadRange[i][1])
	temp_file, err := os.OpenFile(get.FilePath+&quot;.&quot;+range_i, os.O_RDONLY|os.O_APPEND, 0)
	if err != nil {
		temp_file, _ = os.Create(get.FilePath + &quot;.&quot; + range_i)
	} else {
		fi, err := temp_file.Stat()
		if err == nil {
			get.DownloadRange[i][0] += int(fi.Size())
		}
	}
	get.TempFiles = append(get.TempFiles, temp_file)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大概简单的原理就是这些，前面说了，比项目无法用于实际用途，原因如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;需要有一个线程池来并发下载，目前的设计在下载大文件时会导致并发数过大。已经完成下载的线程还能继续下载未完成的块，这个又涉及到下任务的动态分配和动态拆分；&lt;/li&gt;
  &lt;li&gt;并发下载时的块大小，这个值也很讲究，一般4096和字节是一个内存块单位，以此数字的倍数下载比较节约内存，目前我这里的块大小是根据并发数计算的，比较水；&lt;/li&gt;
  &lt;li&gt;前面提到了，这是狭义的多线程下载，前提是服务器必须支持Range，否则还是无法并发获取数据，实际在做的时候最起码是会有几台下载完的服务器，这样自己的服务器就能开发支持分块取数据的协议来支持并发下载，市面上的也都是这么干；&lt;/li&gt;
  &lt;li&gt;还有下载进度条，就是类似于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wget&lt;/code&gt;命令在控制台展示进度条和下载速度的日志，这个不太会写，查了查，据说可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.Println(&quot;abc\rcde&quot;)&lt;/code&gt;实现，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r&lt;/code&gt;表示回车符，可以回到行首，我没试过，大家可以试试。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/goget/goget.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/zhuhuiby/article/details/6725951&quot;&gt;http协议 文件下载原理及多线程断点续传 - zhuhuiby&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://golang.org/pkg/sync/&quot;&gt;Package sync - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/15714126/how-to-update-command-line-output&quot;&gt;How to update command line output? - stackoverflow&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>bee源码分析</title>
   <link href="https://blog.cyeam.com/golang/2015/04/20/bee"/>
   <updated>2015-04-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/04/20/bee</id>
   <content type="html">&lt;p&gt;4 月 9 号那天，出了一个线上 bug，回家分析了半天，主观原因就不说了，客观原因就在于 beego 提供的编译打包工具 bee 不支持配置文件检查。。。研究了半天，关于配置文件的预编译检查，我也没啥想法，不过看了看 bee 的源码，还是懂了不少，记录一下。&lt;/p&gt;

&lt;p&gt;围绕&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bee run&lt;/code&gt;命令说一下。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用这个命令，要进入当前包，检查目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./conf/app.conf&lt;/code&gt;文件，得到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appname&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;编译。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go install controllers&lt;/code&gt;，编译生成静态链接库到 pkg 文件夹下面。此命令还可编译出执行文件到 bin 文件夹下面。&lt;/li&gt;
  &lt;li&gt;生成可执行文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go build -o Cyeam main.go&lt;/code&gt;，编译出可执行文件 Cyeam。可执行文件的名字就是第一步得到的 appnane。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;启动应用。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;appname = &quot;./&quot; + appname
cmd = exec.Command(appname)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Args = append([]string{appname}, conf.CmdArgs...)
cmd.Env = append(os.Environ(), conf.Envs...)

go cmd.Run()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;关闭应用。启动和关闭都是用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exec&lt;/code&gt;包，创建进程和关闭进程进行操作的。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;defer func() {
	if e := recover(); e != nil {
		fmt.Println(&quot;Kill.recover -&amp;gt; &quot;, e)
	}
}()
if cmd != nil &amp;amp;&amp;amp; cmd.Process != nil {
	err := cmd.Process.Kill()
	if err != nil {
		fmt.Println(&quot;Kill -&amp;gt; &quot;, err)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;文件修改监控。bee 命令还有个比较厉害的特性，就是每当文件发生了变化，都能自动编译。这里它使用了开源包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/howeyc/fsnotify&lt;/code&gt;，这是一个开源的文件通知系统。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;最后补充点其他的：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;bee 命令还可以有配置文件，名称是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bee.json&lt;/code&gt;。如果你要引用你自己的包，例如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/mnhkahn/maodou&lt;/code&gt;，就可以在 beego 目录下新建 bee.json 文件，内容如下。此外，bee 还支持很多命令，我这里只是抛砖引玉，大家可以自行研究。吐槽一下，bee 命令的文档实在是少，想详细了解还是得看代码。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
	&quot;version&quot;: 0,
	&quot;gopm&quot;: {
		&quot;enable&quot;: false,
		&quot;install&quot;: false
	},
	&quot;go_install&quot;: false,
	&quot;watch_ext&quot;: [],
	&quot;dir_structure&quot;: {
		&quot;watch_all&quot;: false,
		&quot;controllers&quot;: &quot;&quot;,
		&quot;models&quot;: &quot;&quot;,
		&quot;others&quot;: [&quot;$GOPATH/src/github.com/mnhkahn/maodou&quot;]
	},
	&quot;cmd_args&quot;: [],
	&quot;envs&quot;: [],
	&quot;database&quot;: {
		&quot;driver&quot;: &quot;mysql&quot;
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;还有一个问题，bee 命令老给人一个错觉。如果你把项目复制到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/&lt;/code&gt;目录下，执行 bee run，项目还是可以启动的，但实际上编译的是 GOPATH 下面的项目。编译 GOPATH 下面的代码也本来也无可厚非，因为这是 Golang 规定的，但是能编译过并且顺利执行，这就不是很好。经常我想在/tmp 目录下测试，然后测了半天发现改的东西没效果。我觉得应该检测出不再 GOPATH 目录下应该果断通知用户目录错了。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>垂直搜索爬虫——maodou</title>
   <link href="https://blog.cyeam.com/se/2015/04/05/maodou"/>
   <updated>2015-04-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/se/2015/04/05/maodou</id>
   <content type="html">&lt;p&gt;去年开始学习搜索引擎，目前主要研究的是爬虫这块。主要是因为爬虫相对简单一些，索引这块不是很了解，只能借助于Solr实现。&lt;/p&gt;

&lt;p&gt;之前文档&lt;a href=&quot;https://blog.cyeam.com/se/2014/12/26/search_engine&quot;&gt;《如何开发搜索引擎——爬虫（一）》&lt;/a&gt;介绍了一个比较传统的抓取方式。就是把互联网当作是一张有向图，通过遍历这张图来抓取网站。后来也实现了抓取的爬虫，但一直没有啥实际用途，因为这么抓只能做成Google那种传统搜索引擎。&lt;/p&gt;

&lt;p&gt;后来发现了大神binux的垂直搜索爬虫&lt;a href=&quot;https://github.com/binux/pyspider&quot;&gt;pyspider&lt;/a&gt;，用Python写的一个垂直抓取框架。试用了一翻，确实很好用，还有他的&lt;a href=&quot;https://demo.pyspider.org/&quot;&gt;demo&lt;/a&gt;。这是他抓取豆瓣害羞组的&lt;a href=&quot;https://f.binux.me/haixiuzu.html&quot;&gt;效果页&lt;/a&gt;。看到这个才发现，做这个挺有意思的。&lt;/p&gt;

&lt;p&gt;Python基本不会，索性就用Golang写了，看了看他的抓取效果，想着自己基于也有的知识也是能够实现的，就用Golang写了一个抓取框架&lt;a href=&quot;https://github.com/mnhkahn/maodou&quot;&gt;maodou&lt;/a&gt;（maodou是媳妇的名字）。抓取害羞组的代码如下，我也是将结果存在了&lt;a href=&quot;https://duoshuo.com/&quot;&gt;多说&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;github.com/PuerkitoBio/goquery&quot;
	&quot;github.com/mnhkahn/maodou&quot;
	&quot;github.com/mnhkahn/maodou/cygo&quot;
	&quot;github.com/mnhkahn/maodou/dao&quot;
	&quot;github.com/mnhkahn/maodou/models&quot;
	&quot;strings&quot;
)

type Haixiu struct {
	maodou.MaoDou
}

func (this *Haixiu) Start() {
	this.Index(this.Cawl(&quot;http://www.douban.com/group/haixiuzu/discussion&quot;))
}

func (this *Haixiu) Index(resp *maodou.Response) {
	resp.Doc(`#content &amp;gt; div &amp;gt; div.article &amp;gt; div:nth-child(2) &amp;gt; table &amp;gt; tbody &amp;gt; tr &amp;gt; td.title &amp;gt; a`).Each(func(i int, s *goquery.Selection) {
		href, has := s.Attr(&quot;href&quot;)
		if has {
			this.Detail(this.Cawl(href))
		}
	})
}

func (this *Haixiu) Detail(resp *maodou.Response) {
	res := new(models.Result)
	res.Id = strings.Split(resp.Url, &quot;/&quot;)[5]
	res.Title = resp.Doc(&quot;#content &amp;gt; h1&quot;).Text()
	res.Author = resp.Doc(&quot;#content &amp;gt; div &amp;gt; div.article &amp;gt; div.topic-content.clearfix &amp;gt; div.topic-doc &amp;gt; h3 &amp;gt; span.from &amp;gt; a&quot;).Text()
	res.Figure, _ = resp.Doc(&quot;#link-report &amp;gt; div.topic-content &amp;gt; div.topic-figure.cc &amp;gt; img&quot;).Attr(&quot;src&quot;)
	res.Link = resp.Url
	res.Source = &quot;www.douban.com/group/haixiuzu/discussion&quot;
	res.ParseDate = cygo.Now()
	res.Description = &quot;haixiuzu&quot;
	this.Result(res)
}

func (this *Haixiu) Result(result *models.Result) {
	if result.Figure != &quot;&quot; {
		Dao, err := dao.NewDao(&quot;duoshuo&quot;, `{&quot;short_name&quot;:&quot;cyeam&quot;,&quot;secret&quot;:&quot;df66f048bd56cba5bf219b51766dec0d&quot;}`)
		if err != nil {
			panic(err)
		}
		Dao.AddResult(result)
	}
}

func main() {
	maodou.Register(new(Haixiu), 30)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;介绍一下抓取害羞租时候遇到的问题。抓取的难度就在于模拟浏览。因为小组里的东西肯定是不让随便抓走的，所以就要防止机器抓取。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;第一个是请求头里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAgent&lt;/code&gt;，本来想写自己的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cyeambot&lt;/code&gt;，发现不行，人家会拦掉不认识的设备；&lt;/li&gt;
  &lt;li&gt;第二个是请求头里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Referer&lt;/code&gt;。这个代表了当前请求来源页面。如果是空，就认为是直接在浏览器打开的，或者是从书签打开的。。如果请求的来源一直是空，就有可能是爬虫。但是具体问题具体分析，比如害羞组的首页&lt;a href=&quot;http://www.douban.com/group/haixiuzu/discussion&quot;&gt;http://www.douban.com/group/haixiuzu/discussion&lt;/a&gt;，常用用户肯定会把这个地址保存在书签里面，所以前面的防爬虫机制尽量不起作用，而里面的帖子，&lt;a href=&quot;http://www.douban.com/group/topic/73646091/&quot;&gt;http://www.douban.com/group/topic/73646091/&lt;/a&gt;，帖子地址是不可预估的，理论上只能通过前面讨论组的地址进入，那么如果打开具体帖子的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Referer&lt;/code&gt;也还是空，这样就可以认为是爬虫。我抓取的时候就有遇到这样的问题，有时候会有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;302&lt;/code&gt;的情况出现，就是访问帖子，有一定的概率会跳转到登陆页，让你登陆来防范爬虫。这样应对也不难，就是抓取帖子的时候把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Referer&lt;/code&gt;加上害羞组的地址即可；&lt;/li&gt;
  &lt;li&gt;关于访问次数限制，害怕被封杀IP，我都是没30分钟抓取一次，每访问一次就等待5秒钟，尽量模拟得像人一些。这样没半个小时会访问21次，量不算大。程序运行了快一个月了，IP没有被拦截的迹象。之前抓取CSDN，没有休息5秒钟这一步，爬了一周就不能爬了。。。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后再说一些没用到的但是很牛逼的技术：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;IP代理抓取。如果你有21台代理机器，那么上面的抓取可以同时进行。这样能大大降低抓取时间。这一般是企业级爬虫要用到的，对抓取速度有要求，像我这种半小时抓21页的就不需要用这个。如果有代理服务器，抓取豆瓣所有帖子也可以很快。当然，这样也会有问题，就是你抓取这么多东西就会有被封杀IP的风险了，所以大规模抓取还需要定时更换代理IP。其实这招可以被认为是&lt;strong&gt;&lt;em&gt;黑科技&lt;/em&gt;&lt;/strong&gt;，就是去搞中了病毒的电脑（俗称肉鸡），用这个进行代理，一般网上都有卖的；&lt;/li&gt;
  &lt;li&gt;关于封杀IP。你只要别太恶意去抓，一般也是不会去封你IP的。现在都还是IPv4，IP地址是有限的，而设备越来越多，封杀一个IP有可能导致整个小区都没办法访问该网站了，所以也不用特别担心；&lt;/li&gt;
  &lt;li&gt;如何更像人去抓取。有一个模拟器，叫做selenium。我发现Golang也有相应的客户端&lt;a href=&quot;https://github.com/tebeka/selenium&quot;&gt;tebeka - selenium&lt;/a&gt;。用这个，你模拟登陆，自动记录你的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cookie&lt;/code&gt;并附带到抓取请求头里面，这样有了登陆信息，就更难进行封杀了。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后就是展示部分，&lt;a href=&quot;https://www.cyeam.com/haixiuzu&quot;&gt;点这里访问&lt;/a&gt;。关于前端图片瀑布流的东西我一窍不通，完全抄袭了binux大神写的前端代码，请大神原谅。。。此爬虫并没有去抓取豆瓣小组里面的图片，只是记录了地址，所以在展示的时候还是需要访问豆瓣的图片地址，这就属于盗链图片。豆瓣肯定会对此进行处理。&lt;/p&gt;

&lt;p&gt;你把豆瓣的图片放在你自己的网页上面展示，这就属于盗链了。浏览器在加载完HTML之后会去加载图片，这时图片请求会自动追加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Referer&lt;/code&gt;，也就是你自己网页的地址，豆瓣的图片服务器发现请求来源是其他站点的，就会取消响应。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;盗链图片有两种方式，第一种是在网页里面加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iframe&lt;/code&gt;，这个标签能新建一个嵌套的HTML，这样浏览器在请求图片的时候不会自动追加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Referer&lt;/code&gt;，图片就能打开了；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;还有一种方式，就是用HTTPS发送请求。你把自己站点做成HTTPS的请求，这样在请求图片的时候，浏览器会把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Referer&lt;/code&gt;去掉，保护用户隐私。搭建HTTPS服务器需要SSL证书，一般是一年$10。我去申请一个免费的，人家就是不同意。无奈之下，只能自己给自己颁发证书。坏处就是，用户第一次登陆我的站点的时候，浏览器会提示你不安全。铁道部不是也这样么。。。盗链图片的详细说明请参考最下面的两篇引用文章。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;最近GitHub被DDos了，我的博客访问量也有点降低了。本来量就不多。。。是不是需要把博客牵走了。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;最近有个同事离职了，跟我一起入职的，有点惋惜。可能互联网就是这样。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://bindog.github.io/blog/2014/11/18/http-referer-security-and-anti-anti-hotlink/&quot;&gt;《HTTP Referer安全与反反盗链》&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.zhujun.org/web/nginx-selfsign-ssl-cert/&quot;&gt;《Nginx下绑定自己签发的免费SSL数字安全证书》 - 朱俊&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>探测局域网里面的设备</title>
   <link href="https://blog.cyeam.com/network/2015/03/16/fing"/>
   <updated>2015-03-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2015/03/16/fing</id>
   <content type="html">&lt;p&gt;之前用了一个牛逼的App，叫做Fing，它可以获取到本地局域网里面所有设备的IP、MAC地址和设备厂商。一直觉得很牛逼，今天好好想了想，发现也没那么多神秘。&lt;/p&gt;

&lt;h3 id=&quot;穷举局域网里的ip&quot;&gt;穷举局域网里的IP&lt;/h3&gt;

&lt;p&gt;局域网IP一般有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;192&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10&lt;/code&gt;两种形式。一般来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10&lt;/code&gt;开始的局域网高端一些，能容纳的设备比较多。&lt;/p&gt;

&lt;p&gt;穷举所有IP，有一个方式是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trancert&lt;/code&gt;命令，记录访问某个网站经过的路径，那么第一条路径就是访问路由器，得到路由器IP之后，按最后一部分进行穷举。&lt;/p&gt;

&lt;p&gt;但是这个方法有点麻烦，简化一点的，就是获取当前设备在局域网里面的IP，以此IP进行穷举。通过Golang获取IP的方法可以参考[1]。获取当前设备IP的方式可以参考：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func ExternalIP() (string, string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return &quot;&quot;, &quot;&quot;, err
	}
	for _, iface := range ifaces {
		if iface.Flags&amp;amp;net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&amp;amp;net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return &quot;&quot;, &quot;&quot;, err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), iface.HardwareAddr.String(), nil
		}
	}
	return &quot;&quot;, &quot;&quot;, errors.New(&quot;are you connected to the network?&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ping穷举的ip获取mac地址&quot;&gt;Ping穷举的IP，获取MAC地址。&lt;/h3&gt;

&lt;p&gt;Ping穷举的IP地址，得到在线设备的IP，然后通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arp&lt;/code&gt;协议得到设备的MAC地址。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arp&lt;/code&gt;协议就是用来将IP转换成MAC的协议。这两步可以用在GitHub开源的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/j-keck/arping&lt;/code&gt;包辅助实现，这个包的文档可以参考[2]。简单的使用如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Mac(ip string) (net.HardwareAddr, time.Duration, error) {
	dstIP := net.ParseIP(ip)
	return arping.Ping(dstIP)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;获取设备的厂商也就是俗称的vendor&quot;&gt;获取设备的厂商，也就是俗称的Vendor&lt;/h3&gt;

&lt;p&gt;这步逼格最高。我一直以为设备的Vendor，就像HTTP协议里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAgent&lt;/code&gt;，可以通过这个东西来获取。所以有个协议可以用来得到这些东西。但是没找到。后来发现百度知道（没想到这个东西还是有点用的）里面有位大哥写了。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAC&lt;/code&gt;地址是国际上有个机构管理（好像是IEEE）的，它能保证每块网卡有不同的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAC&lt;/code&gt;地址。而它在分配这些地址的时候，也不是随机分配的。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAC&lt;/code&gt;地址有6个字节，简单的可以把前三个字节用来表示厂商编号，后3个字节用来区分厂商的网卡编号。那么，我们就能通过前3个字节来得到设备的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vendor&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在IEEE的&lt;a href=&quot;http://standards.ieee.org/develop/regauth/oui/public.html&quot;&gt;网站&lt;/a&gt;上，提供了查询方式，我把那个接口提取了出来，是向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://standards.ieee.org/cgi-bin/ouisearch&lt;/code&gt;发送一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FORM&lt;/code&gt;请求。还要将得到的HTML页面进行解析，就得到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vendor&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func Vendor(mac string) (string, error) {
	macs := strings.Split(mac, &quot;:&quot;)
	if len(macs) != 6 {
		return &quot;&quot;, fmt.Errorf(&quot;MAC Error: %s&quot;, mac)
	}
	mac = strings.Join(macs[0:3], &quot;-&quot;)
	form := url.Values{}
	form.Add(&quot;x&quot;, mac)
	form.Add(&quot;submit2&quot;, &quot;Search!&quot;)
	res, err := goreq.Request{
		Method:      &quot;POST&quot;,
		Uri:         &quot;http://standards.ieee.org/cgi-bin/ouisearch&quot;,
		ContentType: &quot;application/x-www-form-urlencoded&quot;,
		UserAgent:   &quot;Cyeam&quot;,
		ShowDebug:   true,
		Body:        form.Encode(),
	}.Do()
	if err != nil {
		return &quot;&quot;, err
	}
	body, err := res.Body.ToString()
	if err != nil {
		return &quot;&quot;, err
	}
	vendor := body[strings.Index(body, strings.ToUpper(mac))+len(mac):]
	vendor = strings.TrimLeft(vendor, &quot;&amp;lt;/b&amp;gt;   (hex)&quot;)
	vendor = strings.TrimSpace(vendor)
	return strings.Split(vendor, &quot;\n&quot;)[0], nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;HTTP请求通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;goreq&lt;/code&gt;发送，FORM请求&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/x-www-form-urlencoded&lt;/code&gt;格式，还需要用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/url&lt;/code&gt;包。&lt;/p&gt;

&lt;h3 id=&quot;结果&quot;&gt;结果&lt;/h3&gt;

&lt;p&gt;最后放上运行结果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/fing_result.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一个是我当前运行的设备，网卡是Intel的，剩下的是路由器和手机。&lt;/p&gt;

&lt;p&gt;我将上述完整的功能进行封装，本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/fing.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go&quot;&gt;How do I get the local IP address in Go? - Stackoverflow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://godoc.org/github.com/j-keck/arping&quot;&gt;package arping - GoDoc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://golangtc.com/t/52d26aa7320b5237d1000044&quot;&gt;go如何读取MAC地址或硬盘ID - Golang中国&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://zhidao.baidu.com/question/37072459.html&quot;&gt;如何通过MAC地址查询网络设备的厂家和型号 - 百度知道&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>网络协议的端口号</title>
   <link href="https://blog.cyeam.com/network/2015/03/14/port"/>
   <updated>2015-03-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2015/03/14/port</id>
   <content type="html">&lt;p&gt;今天同事问起一个问题，他建了一个thrift的服务器，用的是99999端口监听，启动不了，让我们帮着解决。&lt;/p&gt;

&lt;p&gt;之前学习计算机网络的时候也学过，比如HTTP是80端口，DNS是53端口等等，直接网上查了一下，TCP和UDP端口支持都是16比特，也就是最大是0~65535。&lt;/p&gt;

&lt;p&gt;扩展说一下，所有高层协议都是基于底层协议实现，在传输层的TCP和UDP协议决定了端口号占用2个字节，那么，高层协议所占用的端口范围不会超过16位。&lt;/p&gt;

&lt;p&gt;UDP数据报文结构如下，从96位开始，来源连接端口和目的连接端口一共占用了32位。&lt;/p&gt;

&lt;table class=&quot;wikitable&quot; style=&quot;margin: 0 auto; text-align: center;&quot;&gt;
&lt;tbody&gt;&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;&lt;span style=&quot;color: grey;&quot;&gt;位&lt;/span&gt;&lt;/th&gt;
&lt;th colspan=&quot;8&quot; style=&quot;width:75px;&quot;&gt;0 – 7&lt;/th&gt;
&lt;th colspan=&quot;8&quot; style=&quot;width:75px;&quot;&gt;8 – 15&lt;/th&gt;
&lt;th colspan=&quot;8&quot; style=&quot;width:75px;&quot;&gt;16 – 23&lt;/th&gt;
&lt;th colspan=&quot;8&quot; style=&quot;width:75px;&quot;&gt;24 – 31&lt;/th&gt;
&lt;/tr&gt;
&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td colspan=&quot;32&quot; style=&quot;background:#fdd;&quot;&gt;来源地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;32&lt;/th&gt;
&lt;td colspan=&quot;32&quot; style=&quot;background:#fdd;&quot;&gt;目的地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;64&lt;/th&gt;
&lt;td colspan=&quot;8&quot; style=&quot;background:#fdd;&quot;&gt;全零&lt;/td&gt;
&lt;td colspan=&quot;8&quot; style=&quot;background:#fdd;&quot;&gt;协议名&lt;/td&gt;
&lt;td colspan=&quot;16&quot; style=&quot;background:#fdd;&quot;&gt;UDP报文长度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;96&lt;/th&gt;
&lt;td colspan=&quot;16&quot;&gt;来源连接端口&lt;/td&gt;
&lt;td colspan=&quot;16&quot;&gt;目的连接端口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;128&lt;/th&gt;
&lt;td colspan=&quot;16&quot;&gt;报文长度&lt;/td&gt;
&lt;td colspan=&quot;16&quot;&gt;检验和&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;text-align:center;&quot;&gt;
&lt;th&gt;160+&lt;/th&gt;
&lt;td colspan=&quot;32&quot;&gt;&amp;nbsp;&lt;br /&gt;
&amp;nbsp;%%%&lt;br /&gt;
&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;TCP也一样，从一开始，从0位到第31位，4个字节用来表示来源连接端口和目的连接端口。&lt;/p&gt;

&lt;table class=&quot;wikitable&quot; style=&quot;text-align: center;&quot;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;&lt;font color=&quot;grey&quot;&gt;偏移&lt;/font&gt;&lt;/th&gt;
&lt;th colspan=&quot;4&quot; width=&quot;12%&quot;&gt;位 0–3&lt;/th&gt;
&lt;th colspan=&quot;3&quot; width=&quot;8%&quot;&gt;4–7&lt;/th&gt;
&lt;th colspan=&quot;9&quot; width=&quot;24%&quot;&gt;8–15&lt;/th&gt;
&lt;th colspan=&quot;16&quot; width=&quot;44%&quot;&gt;16–31&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td colspan=&quot;16&quot;&gt;来源连接端口&lt;/td&gt;
&lt;td colspan=&quot;16&quot;&gt;目的连接端口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;32&lt;/th&gt;
&lt;td colspan=&quot;32&quot;&gt;序列号码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;64&lt;/th&gt;
&lt;td colspan=&quot;32&quot;&gt;确认号码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;96&lt;/th&gt;
&lt;td colspan=&quot;4&quot;&gt;报头长度&lt;/td&gt;
&lt;td colspan=&quot;3&quot;&gt;保留&lt;/td&gt;
&lt;td colspan=&quot;9&quot;&gt;标志符&lt;/td&gt;
&lt;td colspan=&quot;16&quot;&gt;窗口大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;128&lt;/th&gt;
&lt;td colspan=&quot;16&quot;&gt;检查码&lt;/td&gt;
&lt;td colspan=&quot;16&quot;&gt;紧急指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;160&lt;/th&gt;
&lt;td colspan=&quot;32&quot; bgcolor=&quot;#FFDDDD&quot;&gt;选用项&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;160/192+&lt;/th&gt;
&lt;td colspan=&quot;32&quot;&gt;&amp;nbsp;&lt;br /&gt;
数据&lt;br /&gt;
&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;Thrift这种应用层协议必须是给予传输层实现的，那么端口号也必须不能超过65535。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE&quot;&gt;传输控制协议 - Wikipedia&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://zh.wikipedia.org/wiki/%E7%94%A8%E6%88%B7%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%8D%8F%E8%AE%AE&quot;&gt;用户数据报协议 - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>DNS协议Golang实现</title>
   <link href="https://blog.cyeam.com/network/2015/02/03/dns"/>
   <updated>2015-02-03T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2015/02/03/dns</id>
   <content type="html">&lt;p&gt;DNS客户端的实现还是用了Go语言，毕竟这个最熟悉。我的实现只是简单的实现了发送DNS请求，解析响应内容这些功能，对于多线程并发等机制都没有考虑。&lt;/p&gt;

&lt;p&gt;我最先参考了《计算机网络》，上面提了一句，DNS请求就是发送一个UDP数据包，然后我就天真了，直接把域名的字符串用UDP发送了。结果就是没有响应。&lt;/p&gt;

&lt;p&gt;后来就老老实实的分析协议，协议的分析请参考上一篇文章&lt;a href=&quot;https://blog.cyeam.com/network/2015/01/29/dns&quot;&gt;《DNS协议分析》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;域名服务器选用了阿里提供的233.5.5.5，53端口。详情可以查看阿里DNS&lt;a href=&quot;https://alidns.com/&quot;&gt;官网&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;先说一下Golang发送UDP数据报的方法。所有语言这块儿好像都长差不多，我就不多介绍了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service := &quot;223.5.5.5:53&quot;
udpAddr, err := net.ResolveUDPAddr(&quot;udp&quot;, service)
checkError(err)
conn, err := net.DialUDP(&quot;udp&quot;, nil, udpAddr)
checkError(err)

_, err = conn.Write(out.Pack())
checkError(err)

buf := []byte{}
buf = make([]byte, 512)
n, err := conn.Read(buf[0:])
checkError(err)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着就是发送和接收要用到的消息体，它由一个TransationID、Flags、查询数Questions、Answer是响应数和查询Question组成。查询Question由域名、类别、分类组成。查询结果ANswer由查询域名、记录类型、分类、数据长度和Primaryname组成。这些结构可以定义为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type DnsMsg struct {
	Id                                 uint16
	Bits                               uint16
	Qdcount, Ancount, Nscount, Arcount uint16
	Questions                          []dnsQuestion
	Answers                            []dnsAnswer
}

type dnsQuestion struct {
	Name   string `net:&quot;domain-name&quot;`
	Qtype  uint16
	Qclass uint16
}

type dnsAnswer struct {
	Name   uint16
	Qtype  uint16
	Qclass uint16
	QLive  uint32
	QLen   uint16
	CName  string `net:&quot;domain-name&quot;`
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;DNS协议规定是两个字节的，都用uint16进行处理，Question里面的Name和Answer里面定义的CNAME应该定义成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;，但是为了方便查看，先定义成了字符串，最后编码的时候再处理就行了。&lt;/p&gt;

&lt;p&gt;有了结构体，就可以为这些结构体赋值，然后将其编码成字节码进行发送。编码方法如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *DnsMsg) Pack() []byte {
	bs := make([]byte, 12)
	binary.BigEndian.PutUint16(bs[0:2], this.Id)
	binary.BigEndian.PutUint16(bs[2:4], this.Bits)
	binary.BigEndian.PutUint16(bs[4:6], uint16(len(this.Questions)))
	binary.BigEndian.PutUint16(bs[6:8], this.Ancount)
	binary.BigEndian.PutUint16(bs[8:10], this.Nscount)
	binary.BigEndian.PutUint16(bs[10:12], this.Arcount)

	ds := strings.Split(this.Questions[0].Name, &quot;.&quot;)
	for _, d := range ds {
		bs = append(bs, byte(len(d)))
		bs = append(bs, []byte(d)...)
	}
	bs = append(bs, 0)

	temp := make([]byte, 2)
	binary.BigEndian.PutUint16(temp, this.Questions[0].Qtype)
	bs = append(bs, temp...)
	binary.BigEndian.PutUint16(temp, this.Questions[0].Qclass)
	bs = append(bs, temp...)
	return bs
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;DNS头是固定的12个字节，所以先&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bs := make([]byte, 12)&lt;/code&gt;申请12个字节空间。接着，填充这些字节。就是将结构体里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint16&lt;/code&gt;转成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;。这要用到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binary&lt;/code&gt;包进行转换。最后就是填充Question，要将域名进行转换。转换剔除了域名里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;，改成其后面部分长度的信息，也就是把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.cyeam.com&lt;/code&gt;换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3www5cyeam3com0&lt;/code&gt;。转换完成之后，还要添加一位0来表示结束。最后，再加上请求的类型和分类即可。&lt;/p&gt;

&lt;p&gt;这些都请求成功之后，打印结果，终于能看到点东西了：一堆乱码里面有一些字母，能看得出来是我的域名的CDN地址。&lt;/p&gt;

&lt;p&gt;最后，就是解析响应内容。响应和请求格式是一样的，区别就是最后比请求多了一些响应内容。&lt;/p&gt;

&lt;p&gt;DNS头的12个字节和请求里面是完全一样的，返过来解析就可以了。需要注意两点，响应的头两个字节和请求的头两个字节是完全一样的，如果不一样，忽略这个请求。还有，请求的时候&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ancount&lt;/code&gt;是0，这个代表响应数量，在响应里面就会是找到的结果数。&lt;/p&gt;

&lt;p&gt;查询请求的Question也会在响应里面，所以也需要进行解析。请求查询时Question有可能是多个，这里都处理了一下。从第13个字节开始处理：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;i := 13
for ; j &amp;lt; int(res.Qdcount); j++ {
	domain_count := int(buf[i-1])
	question := dnsQuestion{}
	for buf[i] != 0 {
		if domain_count &amp;gt; 0 {
			question.Name += string(buf[i:i+domain_count]) + &quot;.&quot;
			i += domain_count
			domain_count = 0
		} else {
			domain_count = int(buf[i])
			i++
		}
	}
	i++
	question.Name = strings.TrimRight(question.Name, &quot;.&quot;)
	question.Qtype = binary.BigEndian.Uint16(buf[i : i+2])
	question.Qclass = binary.BigEndian.Uint16(buf[i+2 : i+4])
	i += 4
	res.Questions[j] = question
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;剩下的东西都是Answer。前5个部分都是固定长度，分别是2、2、2、4、2。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;answer := dnsAnswer{}
answer.Name = binary.BigEndian.Uint16(buf[i : i+2])
i += 2
answer.Qtype = binary.BigEndian.Uint16(buf[i : i+2])
i += 2
answer.Qclass = binary.BigEndian.Uint16(buf[i : i+2])
i += 2
answer.QLive = binary.BigEndian.Uint32(buf[i : i+4])
i += 4
answer.QLen = binary.BigEndian.Uint16(buf[i : i+2])
i += 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;后面的CName是关键，解析也要按照类型进行。一种类型是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt;类型，也就是别名，我的域名&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.cyeam.com&lt;/code&gt;的别名就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vm68h.x.incapdns.net&lt;/code&gt;，这是我的CDN。还有一种就是真实IP，返回别名也没多大用，IP才能解决问题，所以Answer里面的第二个就是返回这个别名的真实IP&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;149.126.77.152&lt;/code&gt;。所以一个按字符串处理，一个按数字处理。Answer里面的分类说明了解析类型，如果值是1，说明是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;记录，也就是IP，如果值是5，是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt;记录。解析Answer代码如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if answer.Qtype == dnsTypeCNAME {
	domain_count := int(buf[i])
	i++
	for buf[i] != 0 {
		if domain_count &amp;gt; 0 {
			answer.CName += string(buf[i:i+domain_count]) + &quot;.&quot;
			i += domain_count
			domain_count = 0
		} else {
			domain_count = int(buf[i])
			i++
		}
	}
	i++
	answer.CName = strings.TrimRight(answer.CName, &quot;.&quot;)
} else if answer.Qtype == dnsTypeA {
	for m := 0; m &amp;lt; int(answer.QLen); m++ {
		answer.CName += fmt.Sprintf(&quot;%d.&quot;, buf[i+m])
	}
	answer.CName = strings.TrimRight(answer.CName, &quot;.&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Answer里面还会包含结果的长度信息，用这个来遍历得到结果内容。&lt;/p&gt;

&lt;p&gt;完整请求如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service := &quot;223.5.5.5:53&quot;
udpAddr, err := net.ResolveUDPAddr(&quot;udp&quot;, service)
checkError(err)
conn, err := net.DialUDP(&quot;udp&quot;, nil, udpAddr)
checkError(err)

question := dnsQuestion{&quot;www.cyeam.com&quot;, dnsTypeA, dnsClassINET}
out := DnsMsg{}
out.Id = 2015
out.Bits |= _RD
out.Questions = append(out.Questions, question)
fmt.Println(out.Pack())
_, err = conn.Write(out.Pack())
checkError(err)

buf := []byte{}
buf = make([]byte, 512)
n, err := conn.Read(buf[0:])
checkError(err)
fmt.Println(buf[0:n])
fmt.Println(out.Unpack(buf[0:n]))
os.Exit(0)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解析完成后的结构体打印结果如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;amp;{2015 33152 1 2 0 0 [{www.cyeam.com 1 1}] [{49164 5 1 30 22 vm68h.x.incapdns.net} {49195 1 1 30 4 149.126.77.152}]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;涉及到的的数据包，可以从&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/dns.cap&quot;&gt;这里&lt;/a&gt;访问到。完整代码可以参考&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/dns.go&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;《计算机网络（第五版）》谢希仁&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://serverfault.com/questions/173187/what-does-a-dns-request-look-like&quot;&gt;What does a DNS request look like? - serverfault&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>DNS协议分析</title>
   <link href="https://blog.cyeam.com/network/2015/01/29/dns"/>
   <updated>2015-01-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2015/01/29/dns</id>
   <content type="html">&lt;p&gt;一直有一个愿望，能把知道的东西的原理搞明白：计算机网络、操作系统等等等等。今天好好研究了研究DNS协议。&lt;/p&gt;

&lt;p&gt;DNS协议是应用层协议，一般是基于UDP协议，不过我看了Golang&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net&lt;/code&gt; 包里相关源码用的是TCP协议传输。端口是53,这次写代码实现了一边DNS客户端，估计短时间内忘不了这个端口号了。&lt;/p&gt;

&lt;p&gt;了解DNS这个协议，从书本上我基本没看懂过，书里我基本就记着DNS是递归查询的，如果查询服务器上没有找到相应的记录，则递归得去查询上一级服务器。还有就是根域名服务器在美国这些，反正我就是从来没搞懂过。&lt;/p&gt;

&lt;p&gt;昨天在网上找到一篇文章[2]，上面提到说学习协议还是借助WireShark比较好，Linux之下就是通过tcpdump和WireShark结合进行抓取数据包。具体的方法可以参考我之前的&lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_prepare&quot;&gt;文章&lt;/a&gt;。而模拟DNS请求，Linux下是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host www.cyeam.com&lt;/code&gt;，Windows下是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nslookup www.cyeam.com&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1	0.000000	10.0.1.23	10.0.1.1	DNS	73	Standard query 0x11ac  A www.cyeam.com
2	0.061714	10.0.1.1	10.0.1.23	DNS	123	Standard query response 0x11ac  CNAME vm68h.x.incapdns.net A 149.126.77.152
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我的电脑IP是10.0.1.23,路由器IP是10.0.1.1,查询网站&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.cyeam.com&lt;/code&gt;的DNS记录。&lt;/p&gt;

&lt;p&gt;抓到的请求内容如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Domain Name System (query)
    [Response In: 2]
    Transaction ID: 0x11ac
    Flags: 0x0100 Standard query
	0... .... .... .... = Response: Message is a query
	.000 0... .... .... = Opcode: Standard query (0)
	.... ..0. .... .... = Truncated: Message is not truncated
	.... ...1 .... .... = Recursion desired: Do query recursively
	.... .... .0.. .... = Z: reserved (0)
	.... .... ...0 .... = Non-authenticated data: Unacceptable
    Questions: 1
    Answer RRs: 0
    Authority RRs: 0
    Additional RRs: 0
    Queries
	www.cyeam.com: type A, class IN
	    Name: www.cyeam.com
	    Type: A (Host address)
	    Class: IN (0x0001)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;DNS协议头是12个字节，共两项内容，每个都是两个字节。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Transaction ID。是客户端随机生成的一个无符号整数，范围是0~2^16。在响应头里面会返回这个值用于校验。如果值不相等，丢弃响应内容。&lt;/li&gt;
  &lt;li&gt;Flags。具体每个位的含义这里就不复述了，上面抓到请来看，是标记了递归调用这一项。&lt;/li&gt;
  &lt;li&gt;查询数Questions。一次DNS请求能查询多个域名，这里只查了一个域名，所以是1。&lt;/li&gt;
  &lt;li&gt;Answer是响应数，请求里面响应数肯定是0。剩下两个目前没有用到，还没有研究具体含义。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;协议头之后，就是消息体了。在请求里面，消息体是请求查询的内容，而在响应里面，则是查询到的内容。消息体由三方面组成：域名、类别、分类：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Name。我们要查询&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www.cyeam.com&lt;/code&gt;这个域名，分别用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;将域名进行了切割，而DNS协议设计的时候，用了一个巧妙的方法，将点去掉，而是在每一部分之前加上这个部分占的位数。还有，就是在最后补上一个字节的0,表示Name的结束。那么域名就会变成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3www5cyeam3com0&lt;/code&gt;这样。&lt;/li&gt;
  &lt;li&gt;Type。查询类型，有A、CNAME等。&lt;/li&gt;
  &lt;li&gt;查询分类，好像见到的都是IN。这两个值都是两个字节。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;头是12个字节，消息体用了15+2+2=19个字节，一个是31字节。下面的表格是16进制计数的，一行是16个，一共也是15个。大家也可以根据协议头和消息体的内容计算看看。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0000   11 ac 01 00 00 01 00 00 00 00 00 00 03 77 77 77
0010   05 63 79 65 61 6d 03 63 6f 6d 00 00 01 00 01
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;响应的头如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Domain Name System (response)
    [Request In: 1]
    [Time: 0.061714000 seconds]
    Transaction ID: 0x11ac
    Flags: 0x8180 Standard query response, No error
	1... .... .... .... = Response: Message is a response
	.000 0... .... .... = Opcode: Standard query (0)
	.... .0.. .... .... = Authoritative: Server is not an authority for domain
	.... ..0. .... .... = Truncated: Message is not truncated
	.... ...1 .... .... = Recursion desired: Do query recursively
	.... .... 1... .... = Recursion available: Server can do recursive queries
	.... .... .0.. .... = Z: reserved (0)
	.... .... ..0. .... = Answer authenticated: Answer/authority portion was not authenticated by the server
	.... .... ...0 .... = Non-authenticated data: Unacceptable
	.... .... .... 0000 = Reply code: No error (0)
    Questions: 1
    Answer RRs: 2
    Authority RRs: 0
    Additional RRs: 0
    Queries
	www.cyeam.com: type A, class IN
	    Name: www.cyeam.com
	    Type: A (Host address)
	    Class: IN (0x0001)
    Answers
	www.cyeam.com: type CNAME, class IN, cname vm68h.x.incapdns.net
	    Name: www.cyeam.com
	    Type: CNAME (Canonical name for an alias)
	    Class: IN (0x0001)
	    Time to live: 10 seconds
	    Data length: 22
	    Primaryname: vm68h.x.incapdns.net
	vm68h.x.incapdns.net: type A, class IN, addr 149.126.77.152
	    Name: vm68h.x.incapdns.net
	    Type: A (Host address)
	    Class: IN (0x0001)
	    Time to live: 3 minutes, 47 seconds
	    Data length: 4
	    Addr: 149.126.77.152 (149.126.77.152)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;协议头的含义和请求的一致。需要注意的是，Answer变成了2,因为找到了两条记录。消息体内也包含了请求的Queries的内容，含义和请求里的是一样的。多出来的就是响应的内容。&lt;/p&gt;

&lt;p&gt;查询的结果由5部分组成：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Name，也就是查询的域名，两个字节。&lt;/li&gt;
  &lt;li&gt;Type，记录类型，第一条是CNAME类型。对应的值是5。两个字节。&lt;/li&gt;
  &lt;li&gt;Class，分类，两个字节，值是1(IN）。&lt;/li&gt;
  &lt;li&gt;Data length，数据长度。两个字节。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Primaryname，这是重点，主要就是在查这个东西。如果上面Type是CNAME类型，那么解析的方式要按照域名的方式解析，编码方式和上面请求里发送域名的方式一样。如果是A，那么解要按照IP解析，Data length也对应的是4(IPv4)。一个字节是一个IP里的块，然后用点连起来。16进制分别是95、7e、4d、98，那么换成能看懂的就是149.126.77.152。&lt;/p&gt;

    &lt;p&gt;0000   11 ac 81 80 00 01 00 02 00 00 00 00 03 77 77 77
  0010   05 63 79 65 61 6d 03 63 6f 6d 00 00 01 00 01 c0
  0020   0c 00 05 00 01 00 00 00 0a 00 16 05 76 6d 36 38
  0030   68 01 78 08 69 6e 63 61 70 64 6e 73 03 6e 65 74
  0040   00 c0 2b 00 01 00 01 00 00 00 e3 00 04 95 7e 4d
  0050   98&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上面抓到的数据包，可以从&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/dns.cap&quot;&gt;这里&lt;/a&gt;访问到。本来是想写Golang语言实现的，太晚了，明天再写。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;《计算机网络（第五版）》谢希仁&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://serverfault.com/questions/173187/what-does-a-dns-request-look-like&quot;&gt;What does a DNS request look like? - serverfault&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>安全URL的Base64编码</title>
   <link href="https://blog.cyeam.com/web/2015/01/20/urlsafebase64"/>
   <updated>2015-01-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/web/2015/01/20/urlsafebase64</id>
   <content type="html">&lt;p&gt;这是一个例子，可以先感受下：&lt;a href=&quot;https://www.cyeam.com/tool/base64decode?utm_source=blog&quot;&gt;Base64编解码&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;之前在&lt;a href=&quot;https://blog.cyeam.com/web/2014/07/25/short_url2&quot;&gt;《网址压缩的调研分析（续）》&lt;/a&gt;介绍过Base62算法，他是一种类似于Base64的哈希算法。今天发现了另一种优化的Base64算法，又参考了下Golang的源码，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/base64/base64.go&lt;/code&gt;里面。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const encodeStd = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;传统的Base64用的是A-Z、a-z、0-9，还有+和/，一个64个编码串。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;然而，标准的Base64并不适合直接放在URL里传输，因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式，而这些“%”号在存入数据库时还需要再进行转换，因为ANSI SQL中已将“%”号用作通配符。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;为解决此问题，可采用一种用于URL的改进Base64编码，它不在末尾填充’=’号，并将标准Base64中的“+”和“/”分别改成了“-”和“_”，这样就免去了在URL编解码和数据库存储时所要作的转换，避免了编码信息长度在此过程中的增加，并统一了数据库、表单等处对象标识符的格式。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;base64.go文件里还定义了专用于URL里传输的URL安全的Base64算法。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const encodeURL = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;把之前的加号换成了减号，斜线换成了下划线。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;就是因为这个原因，我发现我的一个编码结果和应该得到的只相差一位，一个是加号，一个是减号。因为如果是编码串不同的原因，哪怕就是差一位，结果也是完全不同的，差一位肯定不是因为这个。原来是这样，懂得还是太少了。。。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://zh.wikipedia.org/wiki/Base64&quot;&gt;Base64 - 维基百科&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>使用Git工具统计代码</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2015/01/17/gitstats"/>
   <updated>2015-01-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2015/01/17/gitstats</id>
   <content type="html">&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-git-shortlog--s-since2013-12-01-before2015-12-10-head-no-merges&quot; id=&quot;markdown-toc-1-git-shortlog--s-since2013-12-01-before2015-12-10-head-no-merges&quot;&gt;1. git shortlog -s –since=2013-12-01 –before=2015-12-10 HEAD –no-merges&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-git-show-ref-tags&quot; id=&quot;markdown-toc-2-git-show-ref-tags&quot;&gt;2. git show-ref –tags&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-git-rev-list-prettyformatat-ai-an-ae-since2013-12-01-before2015-12-10-head--grep--v-commit&quot; id=&quot;markdown-toc-3-git-rev-list-prettyformatat-ai-an-ae-since2013-12-01-before2015-12-10-head--grep--v-commit&quot;&gt;3. git rev-list –pretty=format:”%at %ai %aN (%aE)” –since=2013-12-01 –before=2015-12-10 HEAD | grep -v ^commit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-git-rev-list-prettyformatat-t-since2013-12-01-before2015-12-10-head--grep--v-commit&quot; id=&quot;markdown-toc-4-git-rev-list-prettyformatat-t-since2013-12-01-before2015-12-10-head--grep--v-commit&quot;&gt;4. git rev-list –pretty=format:”%at %T” –since=2013-12-01 –before=2015-12-10 HEAD | grep -v ^commit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#5-git-ls-tree--r--l--z-head&quot; id=&quot;markdown-toc-5-git-ls-tree--r--l--z-head&quot;&gt;5. git ls-tree -r -l -z HEAD&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#6-git-log-shortstat-first-parent--m-prettyformatat-an-ae-since2013-12-01-before2015-12-10-head&quot; id=&quot;markdown-toc-6-git-log-shortstat-first-parent--m-prettyformatat-an-ae-since2013-12-01-before2015-12-10-head&quot;&gt;6. git log –shortstat –first-parent -m –pretty=format:”%at %aN (%aE)” –since=2013-12-01 –before=2015-12-10 HEAD&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#7-git-log-shortstat-date-order-prettyformatat-an-ae-since2013-12-01-before2015-12-10-head&quot; id=&quot;markdown-toc-7-git-log-shortstat-date-order-prettyformatat-an-ae-since2013-12-01-before2015-12-10-head&quot;&gt;7. git log –shortstat –date-order –pretty=format:”%at %aN (%aE)” –since=2013-12-01 –before=2015-12-10 HEAD&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#8-git-git-dirgit-work-tree-rev-parse-short-head&quot; id=&quot;markdown-toc-8-git-git-dirgit-work-tree-rev-parse-short-head&quot;&gt;8. git –git-dir=.git –work-tree=./ rev-parse –short HEAD&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#参考文献&quot; id=&quot;markdown-toc-参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;之前一直想写一个统计代码行数的工具，就是遍历一下文件目录，把里面文件的行数读一下。一直就是想这么干。但是这样干比较low，这是一锤子买卖，只能干一次，第二次统计就还是从头开始统计，没办法统计一个区间下修改行数。想要统计每天修改的行数，就更难，难到需要每天都运行一次并且把数字存下来么？还有，如果要统计每个人修改的情况改怎么办？这个办法就不行了。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/hoxu/gitstats&quot;&gt;GitStats项目&lt;/a&gt;，用Python开发的一个工具，通过封装Git命令来实现统计出来代码情况并且生成可浏览的网页。官方文档可以参考&lt;a href=&quot;https://github.com/hoxu/gitstats/blob/master/doc/gitstats.pod&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;其实这个项目的核心就是Git命令，如果项目是在Git下托管的，那么围绕&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt;命令都能够实现。虽然Git是我常用的代码仓库，但是只会提代码、合并代码这些操作，还是得学。我就把阅读GitStats源码相关的Git笔记放到这里，和大家分享。&lt;/p&gt;

&lt;p&gt;将会用到8个命令：&lt;/p&gt;

&lt;h4 id=&quot;1-git-shortlog--s-since2013-12-01-before2015-12-10-head-no-merges&quot;&gt;1. git shortlog -s –since=2013-12-01 –before=2015-12-10 HEAD –no-merges&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git shortlog -s --since=2013-12-01 --before=2015-12-10 HEAD --no-merges
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-s&lt;/code&gt; 显示提交次数和提交描述
这个命令用来&lt;strong&gt;得到提交过代码的用户数量，用来计算用户平均提交次数&lt;/strong&gt;。git log将日志按用户进行聚合。Suppress commit description and provide a commit count summary only，只显示用户及提交次数。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wc&lt;/code&gt;是shell命令，用来统计单词数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-l&lt;/code&gt;参数用来统计行数。Git命令返回结果如下：&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     6  Bryce
    34  git
    11  lichao
    12  mnhkahn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2-git-show-ref-tags&quot;&gt;2. git show-ref –tags&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git show-ref --tags
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取tag相关内容。显示本地commit ID和远程分支的关系。Git里面的tag我没有用过，暂且先不分析他。&lt;/p&gt;

&lt;h4 id=&quot;3-git-rev-list-prettyformatat-ai-an-ae-since2013-12-01-before2015-12-10-head--grep--v-commit&quot;&gt;3. git rev-list –pretty=format:”%at %ai %aN (%aE)” –since=2013-12-01 –before=2015-12-10 HEAD | grep -v ^commit&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git rev-list --pretty=format:&quot;%at %ai %aN (%aE)&quot; --since=2013-12-01 --before=2015-12-10 HEAD | grep -v ^commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;倒序显示全部的时间片、日期、用户名和邮箱日志。计算出来最后一次提交时间、第一次提交时间。时间用来计算每小时、每天、每周、每月、每年的activity。为每一个用户计算第一次和最后一次提交的时间。根据时间片得到时间，记录当月、当年用户的提交次数和当月提交次数。记录作者的活动天数和最后活动日期&lt;/strong&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rev-list&lt;/code&gt;按时间倒序。返回结果和参数解释如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;420382132 2015-01-04 22:35:32 +0800 mnhkahn (lichao0407@gmail.com)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;%at: 日期, UNIX timestamp&lt;/li&gt;
  &lt;li&gt;%ai: 日期, ISO 8601 格式&lt;/li&gt;
  &lt;li&gt;%aN: mailmap的作者名字&lt;/li&gt;
  &lt;li&gt;%aE: 作者邮箱&lt;/li&gt;
  &lt;li&gt;%T: tree hash&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;4-git-rev-list-prettyformatat-t-since2013-12-01-before2015-12-10-head--grep--v-commit&quot;&gt;4. git rev-list –pretty=format:”%at %T” –since=2013-12-01 –before=2015-12-10 HEAD | grep -v ^commit&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git rev-list --pretty=format:&quot;%at %T&quot; --since=2013-12-01 --before=2015-12-10 HEAD | grep -v ^commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;倒序显示时间片和哈希值。把这两个值保存到cache里面。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1420382132 dd29c4a84acc48c2d30583db0ac47235818142c3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;5-git-ls-tree--r--l--z-head&quot;&gt;5. git ls-tree -r -l -z HEAD&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git ls-tree -r -l -z HEAD

100644 blob 6982c3544235f7c141bfe4e509eb5ed55bbfd0e0     675 wallpaper/win32api/kernel.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;-r 递归调用&lt;/li&gt;
  &lt;li&gt;-l 显示文件大小（和ls -a命令返回的大小数一样）。&lt;/li&gt;
  &lt;li&gt;-z 以\0作为行连接符
显示所有文件的commit hash、大小、文件名。6982c3544235f7c141bfe4e509eb5ed55bbfd0e0是blob_id，675是文件大小，最后一个参数是文件路径。根据此计算后缀名提交次数。可以根据文件路径过滤指定路径和指定后缀的文件。像ls -a一样展示所有文件。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;6-git-log-shortstat-first-parent--m-prettyformatat-an-ae-since2013-12-01-before2015-12-10-head&quot;&gt;6. git log –shortstat –first-parent -m –pretty=format:”%at %aN (%aE)” –since=2013-12-01 –before=2015-12-10 HEAD&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git log --shortstat --first-parent -m --pretty=format:&quot;%at %aN (%aE)&quot; --since=2013-12-01 --before=2015-12-10 HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;统计主分支代码行数，根据日期计算代码行数比直接遍历日志好。得到的是两行结果，分别得到时间片、用户名，增加和删除的代码行数。最后得到得到每天、每月、每年、一共增加、删除的代码行数。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1420382132 mnhkahn (lichao0407@gmail.com)
1 file changed, 20 insertions(+)﻿
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;–stat&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;commit 782226c323df77dfaf99c5e8cb188a04d1539ec4
Author: mnhkahn &amp;lt;lichao0407@gmail.com&amp;gt;
Date: Sun Jan 4 22:35:32 2015 +0800

sego

test_sego.go | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;–shortstat 显示stat的最后一行，修改文件的总数&lt;/li&gt;
  &lt;li&gt;–first-parent 根据第一次父提交来查看合并提交。&lt;/li&gt;
  &lt;li&gt;-m 只关心主分支，不考虑合并代码&lt;/li&gt;
  &lt;li&gt;–date-order&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;7-git-log-shortstat-date-order-prettyformatat-an-ae-since2013-12-01-before2015-12-10-head&quot;&gt;7. git log –shortstat –date-order –pretty=format:”%at %aN (%aE)” –since=2013-12-01 –before=2015-12-10 HEAD&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git log --shortstat --date-order --pretty=format:&quot;%at %aN (%aE)&quot; --since=2013-12-01 --before=2015-12-10 HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再遍历一遍所有日志（不只是主分支），查询提交的人和内容。这样计算出每个人每天增加、删除、提交的行数和次数。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1420382132 mnhkahn (lichao0407@gmail.com)
1 file changed, 20 insertions(+)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;8-git-git-dirgit-work-tree-rev-parse-short-head&quot;&gt;8. git –git-dir=.git –work-tree=./ rev-parse –short HEAD&lt;/h4&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git --git-dir=.git --work-tree=./ rev-parse --short HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取git项目版本号。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;782226c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后说明一下，图片和二进制这些非文本文件不会被统计行数。上面所有的例子取自我的go_code项目下，如果大家想照着做一下，欢迎clone我的代码，地址点&lt;a href=&quot;https://github.com/mnhkahn/go_code&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html&quot;&gt;git-shortlog(1) Manual Page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ruby-china.org/topics/939&quot;&gt;个性化你的Git Log的输出格式 - hisea&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.kernel.org/pub/software/scm/git/docs/git-ls-tree.html&quot;&gt;git-ls-tree(1) Manual Page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.kernel.org/pub/software/scm/git/docs/git-log.html&quot;&gt;git-log(1) Manual Page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://git-scm.com/docs/git-rev-parse&quot;&gt;Git - git-rev-parse Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html&quot;&gt;git-rev-parse(1) Manual Page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>字符串查找算法（二）</title>
   <link href="https://blog.cyeam.com/golang/2015/01/15/go_index"/>
   <updated>2015-01-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2015/01/15/go_index</id>
   <content type="html">&lt;p&gt;接前面的&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/08/go_index&quot;&gt;《字符串查找算法》&lt;/a&gt;继续写。上一篇文章说过，神奇的数字&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16777619&lt;/code&gt;，当时不知道这个是干嘛用的，现在差不多知道了。&lt;/p&gt;

&lt;p&gt;字符串哈希，会经常用到FNV哈希算法。FNV哈希算法如下：将字符串看作是字符串长度的整数，这个数的进制是一个质数。计算出来结果之后，按照哈希的范围求余数，结果就是哈希结果。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define TRUE_HASH_SIZE ((u_int32_t)50000) /* range top plus 1 */
#define FNV_32_PRIME ((u_int32_t)16777619)
#define FNV1_32_INIT ((u_int32_t)2166136261)
#define MAX_32BIT ((u_int32_t)0xffffffff) /* largest 32 bit unsigned value */
#define RETRY_LEVEL ((MAX_32BIT / TRUE_HASH_SIZE) * TRUE_HASH_SIZE)
u_int32_t hash;
void *data;
size_t data_len;

hash = fnv_32_buf(data, data_len, FNV1_32_INIT);
while (hash &amp;gt;= RETRY_LEVEL) {
		hash = (hash * FNV_32_PRIME) + FNV1_32_INIT;
}
hash %= TRUE_HASH_SIZE;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;下面给出了三个质数，分别是求范围是32位、64位、128位和256位哈希值时使用。当然，这三个质数是怎么得到的，我肯定不知道。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;32 bit FNV_prime = 224 + 28 + 0x93 = 16777619
64 bit FNV_prime = 240 + 28 + 0xb3 = 1099511628211
128 bit FNV_prime = 288 + 28 + 0x3b = 309485009821345068724781371
256 bit FNV_prime = 2168 + 28 + 0x63 = 374144419156711147060143317175368453031918731002211&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;继续看Golang的代码，字符串字串匹配用的是无符号32位整数，那就是32位长度，自然，质数就需要选16777619了。结果会按照32位最大的整数求余，在这里，因为是将结果存在uint32里面的，所以超出范围的会被丢弃，也可以认为是求余操作。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const primeRK = 16777619

// hashstr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func hashstr(sep string) (uint32, uint32) {
	hash := uint32(0)
	for i := 0; i &amp;lt; len(sep); i++ {
		hash = hash*primeRK + uint32(sep[i])
	}
	var pow, sq uint32 = 1, primeRK
	for i := len(sep); i &amp;gt; 0; i &amp;gt;&amp;gt;= 1 {
		if i&amp;amp;1 != 0 {
			pow *= sq
		}
		// 只有32位，超出范围的会被丢掉
		sq *= sq
	}
	return hash, pow
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;剩下的就是上一篇文章提到的，RK算法，根据FNV哈希得到的值进行推算，能够在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;o(1)&lt;/code&gt;时间范围内计算得到下一条字串的哈希值。然而，FNV哈希算法能够保证大部分情况下哈希的结果都在指定范围内均匀分布，但不是全部。所以在最后判断字串是否相等的时候，还会再加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s[:n] == sep&lt;/code&gt;来保证完全一致，所以，RK算法的复杂度准确的说是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;o(m+n)&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if h == hashsep &amp;amp;&amp;amp; s[:n] == sep {
	return 0
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为啥判断相等的时候不直接用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s[:n] == sep&lt;/code&gt;来判断每个字串是否相等？因为这样的话，复杂度又变回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;o(m*n)&lt;/code&gt;了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/baiyan/archive/2011/04/23/2025701.html&quot;&gt;《FNV哈希算法【学习】》 - bai yan&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/yasi_xi/article/details/9311837&quot;&gt;《关于FNV Hash结果的分布情况》-yasi_xi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>北京地铁视频播放原理猜想</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2015/01/14/bjsubway"/>
   <updated>2015-01-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2015/01/14/bjsubway</id>
   <content type="html">&lt;p&gt;今天晚上乘坐8号线回家，一路都在地铁里面那个电视，当时正在播放那个《非常静距离》。看着看着，发现出bug了，屏幕最下面露出来了任务栏，赫然写着vlc播放器。嗯，用开源的，不错。此外，还能发现这东西用的是Windows系统，看着像Windows XP或者之前的系统。&lt;/p&gt;

&lt;p&gt;VLC播放器处于任务栏选中状态，说明视频的内容最起码车厢内的客户端是VLC。如果是用VLC作为客户端，那说明它就是在播放视频。VLC播放的视频不应该是本地视频，这样的话太low了，并且很难管理报站。VLC支持RTSP、RTP等协议，能够播放远程流媒体信息，具体操作可以参考我之前的&lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_prepare#vlc&quot;&gt;文章&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/5535219cjw1e4r3skxc9uj20qo0zk417.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么这个流媒体的服务器端在哪里呢？视频一般包含了到站信息、日期和时间等。知道当前是哪一站的是列车本身和控制台。我个人认为数据应该是控制台发送的，因为有时候会出现丢帧、卡顿的现象，并且是视频内容和到站信息都卡（据我观察是这样）。那么，说明数据应该都是远程传输过来的。列车内部传输的话应该都是有线传输，不应该出现那么严重卡顿。&lt;/p&gt;

&lt;p&gt;所以大体流程是这样：控制台能监控到列车运行情况，并结合一些视频资源，动态拼接出当前列车的视频内容。通过一定的流媒体技术进行传输。这样，能够解耦合，列车长开车的时候不需要管这些有的没的，视频内容也不需要在修改的时候每辆车都去更新。&lt;/p&gt;

&lt;p&gt;意淫结束了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;

</content>
 </entry>
 
 <entry>
   <title>如何开发搜索引擎——爬虫（一）</title>
   <link href="https://blog.cyeam.com/se/2014/12/26/search_engine"/>
   <updated>2014-12-26T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/se/2014/12/26/search_engine</id>
   <content type="html">&lt;p&gt;爬虫抓取网页的过程可以抽象为数据结构里面的有向图，超链接就是图的边，网页内容是节点。那么抓取的遍历方式，也就是广度优先遍历或者是深度优先遍历。图当中比较复杂的是防止回路，实际抓取当中会由于网站设计的多样导致循环抓取，需要有一套完善的机制来防止回路。&lt;/p&gt;

&lt;p&gt;网页内容的抓取也是一个复杂的问题，网页内部除了正文，还可能会有一些用户的联系信息、广告等其它数据。网页内容的抓取，微软最早设计了视觉抓取算法，就是模拟人通常观察一个网页会看的地方进行抓取。这个东西比较复杂，没有研究过。&lt;/p&gt;

&lt;p&gt;简单点的解析方法，根据文字和HTML标签的比例进行抓取：通常，网页的正文都是连续的，并且其HTML标签不会很复杂，占比例较小，而其它部分，例如导航、广告等內容，通常HTML标签的数量会多余文字数量。可以对网页进行实验，拟合出一个近似的比例来进行抓取。&lt;/p&gt;

&lt;p&gt;上面说的是通用的方法，还有一些情况，比如要去抓取今天NBA比赛的比分，这个通过上面的方式就很难了。一般去抓取这些内容的网页固定，比如都是去新浪体育去抓取，每个队的名字、比分都是在固定的HTML标签内，短时间内不会变化。这个时候就可以通过模板的方式进行抓取。&lt;/p&gt;

&lt;p&gt;下面是一些当时学习的笔记，供大家参考：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;为什么是图？
在图论中，连通图基于连通的概念。在一个无向图G中，若从顶点v_i到顶点v_j有路径相连（当然从v_j到v_i也一定有路径），则称v_i和v_j是连通的。&lt;/li&gt;
  &lt;li&gt;用哪种遍历方式，深度还是广度遍历？
网页的抓取策略可以分为深度优先、广度优先和最佳优先三种。深度优先在很多情况下会导致爬虫的陷入(trapped)问题，目前常见的是广度优先和最佳优先方法。广度优先搜索策略是指在抓取过程中，在完成当前层次的搜索后，才进行下一层次的搜索。最佳优先搜索策略按照一定的网页分析算法，预测候选URL与目标网页的相似度，或与主题的相关性，并选取评价最好的一个或几个URL进行抓取。它只访问经过网页分析算法预测为“有用”的网页。深度优先搜索策略从起始网页开始，选择一个URL进入，分析这个网页中的URL，选择一个再进入。重要网页通常距离种子较近，而过度深入抓取到的网页却价值很低。&lt;/li&gt;
  &lt;li&gt;遍历要防止回环，如果操作？
欧拉回路：从图中一个节点出发，经过图中所有边一次且仅一次，最后回到出发点，当且仅当图中所有节点的度都是偶数。
用哈希表防止回环，一般网址较长，用MD5或16位的MD5作为作为key用于验证重复。为了防止爬虫意外中断的情况，访问记录应该保存在硬盘当中而不是内存里面。可以用redis解决。
布隆过滤器也可以用来防止回路。
用最多抓取次数防止回路（节流）。&lt;/li&gt;
  &lt;li&gt;根据url判定是相同页面还是根据内容判断是相同页面？
url别名。
内容指纹，通过MD5计算。&lt;/li&gt;
  &lt;li&gt;html解析
提取超链接、提取缩略图、提取内容。
单行匹配s从头到尾匹配，.可以匹配到任意字符，包括换行符；多行匹配m以换行符为单位进行匹配，所以此时.无法匹配到换行符。
    &lt;ul&gt;
      &lt;li&gt;匹配html：(?is)&amp;lt;.*?&amp;gt;。(?is)是模式修改符。意思是将后面的匹配模式修改成IGNORECASE和SINGLELINE，IGNORECASE就是忽略大小写，SINGLELINE就是规定特殊字符“.” 匹配任意的字符，包括换行符。默认情况下，特殊字符“.”不匹配换行符；&lt;/li&gt;
      &lt;li&gt;匹配超链接：(?is)&amp;lt;a .*?&amp;lt;/a&amp;gt;；&lt;/li&gt;
      &lt;li&gt;匹配图片：(?is)&amp;lt;img .*?&amp;gt;；&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang通过邻接表实现有向图</title>
   <link href="https://blog.cyeam.com/golang/2014/12/20/golang_dg"/>
   <updated>2014-12-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/12/20/golang_dg</id>
   <content type="html">&lt;p&gt;本科学习数据结构的时候就听前辈说过，学好数据结构、计算机组成、操作系统和计算机网络后就会成为大神，我也使劲学过，但是一直没发现数据结构的用处。实际编程用过比较多的就是哈希表了，一般语言也都会通过一些扩展包支持。&lt;/p&gt;

&lt;p&gt;《数学之美》第九章——《图论和网络爬虫》，就浅显易懂的介绍了图的实际用途。搜索引擎里面的网络爬虫抓取网络数据，就是把互联网抽象成有向图这种数据结构，通过遍历这张图实现的互联网抓取。&lt;/p&gt;

&lt;p&gt;图一般分为有向图和无向图，一般用来开发网络爬虫和地图（我就知道这两个）。图可以认为是节点和连接边的集合，有两种实现方式：邻接表和邻接矩阵。稀疏图用邻接表实现，稠密图用邻接矩阵实现。图的重点在于遍历，有深度优先遍历和广度优先遍历。深度优先遍历可以通过递归实现，而广度优先遍历要转换成类似于树的层序遍历来实现。还要注意一点，图的遍历要防止回路，否则无法结束遍历。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Graph struct {
	edgnum int
	vexnum int
	adj    []Vertex
}

type Vertex struct {
	Data string
	e    *Edge
}

type Edge struct {
	ivex int
	next *Edge
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;定义了三个结构体， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Graph&lt;/code&gt;是图的数据结构，它包含边数、顶点数和顶点的集合；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vertex&lt;/code&gt;是顶点数据结构，包含顶点的数据内容和边的头指针，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Edge&lt;/code&gt;边的结构体定义了与此顶点相连的另一个顶点在图的顶点集合中的位置和下一个边的地址。&lt;/p&gt;

&lt;p&gt;图是顶点和边的集合，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adj&lt;/code&gt;对象就是顶点的集合，每一个顶点内部的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e&lt;/code&gt;就是通过链表实现的边的集合。这也就实现了图。&lt;/p&gt;

&lt;p&gt;在图中插入顶点，这个比较简单，加入顶点的集合即可：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *Graph) InsertVertex(v Vertex) {
	this.adj = append(this.adj, v)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在图中插入边，需要指出相连的两个顶点的信息，然后把边的信息插入到链表的最后：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *Graph) InsertEdge(v1, v2 Vertex) {
	var p *Edge = this.adj[this.get_position(v1.Data)].e
	if p == nil {
		// fmt.Println(&quot;nil...&quot;, v1.Data, v2.Data)
		this.adj[this.get_position(v1.Data)].e = NewEdge(this.get_position(v2.Data))
	} else {
		for ; p.next != nil; p = p.next {
		}
		p.next = NewEdge(this.get_position(v2.Data))
		// fmt.Println(&quot;append...&quot;, v1.Data, v2.Data)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取某个顶点的邻接顶点，找到顶点的边集合，就可以通过边集合找到与它相连的顶点了。也可以通过这个方法计算得到顶点的出度（Out Degree）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *Graph) Adjacent(v Vertex) []Vertex {
	res := make([]Vertex, 0)
	p := this.adj[this.get_position(v.Data)].e
	for ; p != nil; p = p.next {
		res = append(res, this.adj[p.ivex])
	}
	return res
}

func (this *Graph) OutDegree(v Vertex) int {
	res := 0
	p := this.adj[this.get_position(v.Data)].e
	for p != nil {
		res++
		p = p.next
	}
	return res
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;计算顶点的入度相比出度有点麻烦，因为图本身保存的边的集合是边的起始位置，结束位置，也就是入度，只能遍历整个边集合来计算：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *Graph) InDegree(v Vertex) int {
	res := 0
	pos := this.get_position(v.Data)
	for _, a := range this.adj {
		p := a.e
		for p != nil {
			if pos == p.ivex {
				res++
			}
			p = p.next
		}
	}
	return res
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;广度优先遍历，这个遍历类似于层序遍历，每遍历一层，需要记住当前层的节点，然后与遍历当前层相连的节点，如此实现遍历。需要一个队列来记住当前层，先进先出。我是通过Golang的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slice&lt;/code&gt;实现的简单队列。还有一个问题，就是需要防止回路，也就是说，一个节点不能遍历两次。这里用了Golang内置的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;实现，随机存储、随机查找。遍历还要防止非完全图的情况，如果只有结点没有边还需要处理，所以这里用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环来处理：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *Graph) Bfs() {
	res := map[int]Vertex{}
	for _, a := range this.adj {
		Q := []Vertex{a}
		for len(Q) != 0 {
			u := Q[0]
			Q = Q[1:]
			if _, ok := res[this.get_position(u.Data)]; !ok {
				Q = append(Q, this.Adjacent(u)...)
				res[this.get_position(u.Data)] = u
				fmt.Printf(&quot;%s &quot;, u.Data)
			}
		}
	}
	fmt.Printf(&quot;\n&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;深度优先遍历，遍历完一个节点，接着遍历这个节点的这个节点相连的节点，直到结束。用递归非常简单：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (this *Graph) Dfs() {
	res := map[int]Vertex{}
	for _, a := range this.adj {
		this.dfs(a, res)
	}
	fmt.Printf(&quot;\n&quot;)
}

func (this *Graph) dfs(u Vertex, res map[int]Vertex) {
	if _, ok := res[this.get_position(u.Data)]; !ok {
		res[this.get_position(u.Data)] = u
		fmt.Printf(&quot;%s &quot;, u.Data)
		p := u.e
		for p != nil {
			if _, ok := res[p.ivex]; !ok {
				this.dfs(this.adj[p.ivex], res)
			}
			p = p.next
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整的测试代码，大Golang的单元测试工具，直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go test&lt;/code&gt;即可：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func TestMain(t *testing.T) {
	g := create_example_lgraph()
	g.Print()
	println(&quot;Bfs.............&quot;)
	g.Bfs()
	println(&quot;Dfs.............&quot;)
	g.Dfs()
	if g.InDegree(Vertex{Data: &quot;E&quot;}) != 2 {
		t.Error(&quot;indegree of E wanted 2 bug got&quot;, g.InDegree(Vertex{Data: &quot;E&quot;}))
	}
	if g.OutDegree(Vertex{Data: &quot;E&quot;}) != 2 {
		t.Error(&quot;outdegree of E wanted 2 bug got&quot;, g.OutDegree(Vertex{Data: &quot;E&quot;}))
	}
}

func create_example_lgraph() *Graph {
	vexs := []string{&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;, &quot;F&quot;, &quot;G&quot;}
	edges := [][2]string{
		{&quot;A&quot;, &quot;B&quot;},
		{&quot;B&quot;, &quot;C&quot;},
		{&quot;B&quot;, &quot;E&quot;},
		{&quot;B&quot;, &quot;F&quot;},
		{&quot;C&quot;, &quot;E&quot;},
		{&quot;D&quot;, &quot;C&quot;},
		{&quot;E&quot;, &quot;B&quot;},
		{&quot;E&quot;, &quot;D&quot;},
		{&quot;F&quot;, &quot;G&quot;}}
	vlen := len(vexs)
	elen := len(edges)

	pG := Create()
	// 初始化&quot;顶点数&quot;和&quot;边数&quot;
	pG.vexnum = vlen
	pG.edgnum = elen
	// 初始化&quot;邻接表&quot;的顶点
	for i := 0; i &amp;lt; pG.vexnum; i++ {
		pG.InsertVertex(*NewVertex(vexs[i]))
	}
	// 初始化&quot;邻接表&quot;的边
	for i := 0; i &amp;lt; pG.edgnum; i++ {
		// 读取边的起始顶点和结束顶点
		pG.InsertEdge(*NewVertex(edges[i][0]), *NewVertex(edges[i][1]))
	}
	return pG
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/tree/master/graph&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://github.com/wangkuiwu/datastructs_and_algorithm/blob/master/source/graph/iterator/dg/c/list_dg.c&quot;&gt;wangkuiwu/datastructs_and_algorithm - GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】《数据结构与算法——C语言描述》，Mark Allen Weiss著&lt;/li&gt;
  &lt;li&gt;【3】《算法导论》Thomas H. Cormen等著&lt;/li&gt;
  &lt;li&gt;【4】《数学之美》吴军著&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang通过pup实现HTML解析</title>
   <link href="https://blog.cyeam.com/web/2014/12/01/golang_pup"/>
   <updated>2014-12-01T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/web/2014/12/01/golang_pup</id>
   <content type="html">&lt;p&gt;上一周给我的网站加了一个搜索功能，能自动抓取我的博客和别人的CSDN博客。通过RSS抓取。这样数据格式规范，容易解析。问题是信息较少。后来发现在HTML源代码里面，会有为了方便搜索引擎索引的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta&lt;/code&gt;字段，能指出作者和详情。以我的博客&lt;a href=&quot;https://blog.cyeam.com/golang/2014/11/29/golang_gzip&quot;&gt;《Golang实现HTTP发送gzip请求》&lt;/a&gt;为例。里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta&lt;/code&gt;信息如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
&amp;lt;meta name=&quot;description&quot; content=&quot;beego的httplib不支持发送gzip请求，自己研究了一下。&quot;&amp;gt;
&amp;lt;meta name=&quot;author&quot; content=&quot;Bryce&quot;&amp;gt;
&amp;lt;meta name=&quot;google-translate-customization&quot; content=&quot;a4136e955b3e09f2-45a74b56dc13e741-gf616ffda6e6360e0-11&quot;&amp;gt;
&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查了查，一般大家通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xpath&lt;/code&gt;进行解析。有一个现成的包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://github.com/go-xmlpath/xmlpath&lt;/code&gt;，按照说明做了一下，不行。看了一下源码，这个包内部是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/xml&lt;/code&gt;实现的，如果HTML的代码有问题，标签不是严格按照规范编写的，就会有解析问题。同理，如果把HTML当作XHTML处理，也是不行的。&lt;/p&gt;

&lt;p&gt;后来发现一个神奇的工具&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://github.com/EricChiang/pup&lt;/code&gt;，通过命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get github.com/ericchiang/pup&lt;/code&gt;安装。它可以通过管道调用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -s http://blog.cyeam.com | pup &apos;div div div div h2 a&apos; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;直接抓取作者和简介可以用如下命令：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -s https://blog.cyeam.com/golang/2014/11/29/golang_gzip | pup &apos;head meta[name=&quot;author&quot;] attr{content}&apos;
curl -s https://blog.cyeam.com/golang/2014/11/29/golang_gzip | pup &apos;head meta[name=&quot;description&quot;] attr{content}&apos; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个包能完美解决我的问题，进去看了一下源码，发现包名是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;，再一个是因为它用来解析HTML不是那么方便，想了想，我囧的还是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmd&lt;/code&gt;的方式通过管道执行。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;req := httplib.Get(&quot;https://blog.cyeam.com/golang/2014/11/29/golang_gzip&quot;)
res, err := req.Bytes()
if err != nil {
	panic(err)
}

cmd := exec.Command(&quot;pup&quot;, `head meta`)
stdin, err := cmd.StdinPipe()
if err != nil {
	panic(err)
}
// defer stdin.Close()
var output bytes.Buffer
cmd.Stdout = &amp;amp;output

if err = cmd.Start(); err != nil { //Use start, not run
	fmt.Println(&quot;An error occured: &quot;, err) //replace with logger, or anything you want
}
stdin.Write(res)
stdin.Close()

if err := cmd.Wait(); err != nil {
	panic(err)
}
fmt.Println(string(output.Bytes())) //for debug
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shell&lt;/code&gt;命令行管道是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|&lt;/code&gt;实现，而通过Golang代码，需要通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exec&lt;/code&gt;包提供的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stdin&lt;/code&gt;实现。把内容写入标准输入流，就相当于管道输入了。写完了要关闭输入流&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdin.Close()&lt;/code&gt;，如果不关闭，输入流不会被写入。。。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_pup.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/pkg/os/exec/&quot;&gt;Package exec - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://www.widuu.com/archives/01/927.html&quot;&gt;golang讲解（go语言）标准库分析之os/exec - widuu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang实现HTTP发送gzip请求</title>
   <link href="https://blog.cyeam.com/golang/2014/11/29/golang_gzip"/>
   <updated>2014-11-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/11/29/golang_gzip</id>
   <content type="html">&lt;p&gt;互联网是基于HTTP协议的，但是数据量大的情况下，HTTP就显得有点慢了，接口API一般使用Facebook开源的Thrift。但是Thrfit使用场景有限。这个时候就只能考虑减少数据量来优化HTTP请求了。&lt;/p&gt;

&lt;p&gt;压缩的原理就是服务器端将本来要返回的数据进行压缩，传输给客户端之后，客户端用双方约定好的方式再解压缩。通过Fiddler和Debug测试，一般用Gzip相对于原数据能压缩80%~90%。而响应时间并没有因此而增加。&lt;/p&gt;

&lt;p&gt;客户端和服务器之间是通过请求时的HTTP头&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Encoding&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Accept-Encoding&lt;/code&gt;来通知服务器进行何种方式压缩数据。&lt;/p&gt;

&lt;p&gt;Golang提供了”compress/gzip”包进行压缩和解压缩处理。这个包里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gunzip.go&lt;/code&gt;文件实现了解压方法。结构体&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reader&lt;/code&gt;实现了”io”包里的接口&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reader&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Reader interface {
	Read(p []byte) (n int, err error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;通过包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io/ioutil&lt;/code&gt;包里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadAll&lt;/code&gt;函数，可以自动调用实现类里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt;函数。下面是基于Golang原生包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/http&lt;/code&gt;发送Gzip请求的完整代码。打印出来了解压前和解压后的数据量。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;client := http.Client{}
req1, err := http.NewRequest(&quot;GET&quot;, &quot;http://blog.cyeam.com&quot;, nil)
req1.Header.Add(&quot;Content-Encoding&quot;, `gzip`)
req1.Header.Add(&quot;Accept-Encoding&quot;, `gzip`)
resp1, err := client.Do(req1)
if err != nil {	
	panic(err)
}
defer resp1.Body.Close()
body, err := ioutil.ReadAll(resp1.Body)
fmt.Println(&quot;Length of uncompress: &quot;, len(body))
compressedReader, err := gzip.NewReader(resp1.Body)
body1, err := ioutil.ReadAll(compressedReader)
if err != nil {
	panic(err)
}
fmt.Println(&quot;Length of gzip: &quot;, len(body1))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;虽然接口定义了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt;函数，能够通过此函数将数据读取出来，但是提取数据还是比较麻烦的。Golang提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io/ioutil&lt;/code&gt;包辅助实现数据的读取，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ioutil.ReadAll&lt;/code&gt;方法进行自动读取，它里面自动调用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io&lt;/code&gt;包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Read&lt;/code&gt;方法。我把源码里面调用的函数都贴了过来。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func ReadAll(r io.Reader) ([]byte, error) {
	return readAll(r, bytes.MinRead) // 调用realAll
}

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
	buf := bytes.NewBuffer(make([]byte, 0, capacity))
	// If the buffer overflows, we will get bytes.ErrTooLarge.
	// Return that as an error. Any other panic remains.
	defer func() {
		e := recover()
		if e == nil {
			return
		}
		if panicErr, ok := e.(error); ok &amp;amp;&amp;amp; panicErr == bytes.ErrTooLarge {
			err = panicErr
		} else {
			panic(e)
		}
	}()
	_, err = buf.ReadFrom(r) // 调用ReadFrom
	return buf.Bytes(), err
}

func (devNull) ReadFrom(r io.Reader) (n int64, err error) {
bufp := blackHolePool.Get().(*[]byte)
readSize := 0
for {
	readSize, err = r.Read(*bufp) // 在这里自动调用了实现类的Read函数进行读取操作。
	n += int64(readSize)
	if err != nil {
		blackHolePool.Put(bufp)
		if err == io.EOF {
			return n, nil
		}
		return
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_gzip2.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang字符串切割函数Split</title>
   <link href="https://blog.cyeam.com/golang/2014/11/22/golang_split"/>
   <updated>2014-11-22T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/11/22/golang_split</id>
   <content type="html">&lt;p&gt;先说结论：大Golang里面，如果一个空字符串通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt;包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Split&lt;/code&gt;函数进行切割，那么结果是一个长度为1的数组，里面的内容是一个空字符串。&lt;/p&gt;

&lt;p&gt;为了验证，分别在1.0.1、1.1、1.2.2、1.3.3、1.4rc上面进行了测试，验证了上面的结论是正确的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func main() {
	a := strings.Split(&quot;&quot;, &quot;;&quot;)
	fmt.Printf(&quot;%d****%s****\n&quot;, len(a), a[0])
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个也很好理解。切割一个空字符串，肯定是没办法切的，那么结果就是没切开，把原字符串直接加入结果数组里面而已。只不过一开始有点难理解，因为我们都会认为如果是空字符串去切，结果数组里应该是空的。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;最近升级了beego1.4.2，谢大把beego控制器获取请求参数函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Controller.GetInt&lt;/code&gt;函数的返回值，由之前的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;改成了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;，本来最早用的时候就别扭，如果想取到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int64&lt;/code&gt;，那直接再加个函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Controller.GetInt64&lt;/code&gt;就可以了，结果这么一搞，还得自己惦记着。结果这次谢大果断把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Controller.GetInt&lt;/code&gt;返回值改了，项目直接编译不过了。&lt;/p&gt;

&lt;p&gt;当然，这不算什么，编译不过最起码知道问题出在哪里了。还有一个坑，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego.AppConfig.Strings&lt;/code&gt;函数，之前是可以将配置里的字符串通过分号自动切割成数组。结果更新之后，之前的代码返回是空的了（也就是里面是长度是1的空字符串）。这个可害苦了我。看了下源码，发现config包解析配置文件的方法进行了大幅度更新，但是我经过分析，认为并不是这里的错误，反而是另一个地方&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego/config.go&lt;/code&gt;第111行&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (b *beegoAppConfig) Strings(key string) []string {
	v := b.innerConfig.Strings(RunMode + &quot;::&quot; + key)
	if len(v) == 0 {
		return b.innerConfig.Strings(key)
	}
	return v
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runmode&lt;/code&gt;里获取配置的值，如果没有取到，去取默认值。这里说一下，beego的配置文件支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;三种模式。如果配置了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runmode&lt;/code&gt;，根据其在配置文件内查找相应的配置，否则去取默认值。上面的代码大体是这个含义。但是如果没有找到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runmode&lt;/code&gt;下的配置，此时&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;的判断是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;len(v) == 0&lt;/code&gt;，而之前从配置文件的实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ini.go&lt;/code&gt;里取值的函数如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (c *IniConfigContainer) Strings(key string) []string {
	return strings.Split(c.String(key), &quot;;&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;它会把取到的字符串进行切割，结合我们上面提到的，无论怎么切割，长度最少是1,所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;判断永远是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;，取默认值的逻辑永远不会被执行。修改的方法也很简单，判断语言改成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if v[0] == &quot;&quot;&lt;/code&gt;即可。完成的beego/config包介绍可以看我之前的&lt;a href=&quot;https://blog.cyeam.com/beego/2014/11/12/beego_config&quot;&gt;文章&lt;/a&gt;。我已经给谢大提了Merge Request，但是他一直没有理我。。。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;24号谢大合并了我的代码，开心开心开心。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_split.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>beego/config包源码分析</title>
   <link href="https://blog.cyeam.com/beego/2014/11/12/beego_config"/>
   <updated>2014-11-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/beego/2014/11/12/beego_config</id>
   <content type="html">&lt;p&gt;从去年12月底开始接触golang，用过revel和beego框架。最后选择了beego，用beego的原因也很简单，因为beego是中国人开发的，有中文文档。&lt;/p&gt;

&lt;p&gt;golang起源于C语言，虽然不支持面向对象编程，但还是提供了接口interface、匿名字段等方式。虽然进行面向对象编程还是有点别扭，但是好歹都能实现。这里借花献佛，介绍一下beego的config包，顺便提一下golang的面向对象编程。&lt;/p&gt;

&lt;p&gt;config包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.go&lt;/code&gt;文件定义了两个接口，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConfigContainer&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Config&lt;/code&gt;，ConfigContainer定义了如何从原始数据里面获取配置信息的方法。Config接口是一个适配器，定义了把原始数据解析到ConfigContainer的方法。&lt;/p&gt;

&lt;p&gt;config包里的其它文件是这两个接口的实现。xml文件夹实现了解析XML数据格式的方法，yaml文件夹实现了解析YAML格式的方法，ini和json分别解析INI格式和JSON格式。这里主要说一下INI格式的实现。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ini.go&lt;/code&gt;文件定义了两个结构体&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IniConfigContainer&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IniConfig&lt;/code&gt;，分别实现了上面的两个接口。golang接口的实现语法可以参考之前的文章&lt;a href=&quot;https://blog.cyeam.com/golang/2014/07/20/go_inte&quot;&gt;《Golang 接口实现》&lt;/a&gt;。其中，IniConfigContainer不光实现了接口，还定义了内部变量来辅助实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;filename       string
data           map[string]map[string]string // section=&amp;gt; key:val
sectionComment map[string]string            // section : comment
keyComment     map[string]string            // id: []{comment, key...}; id 1 is for main comment.
sync.RWMutex
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;是用map实现的，用来保存配置文件的键值对。IniConfigContainer还有一个匿名对象&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sync.RWMutex&lt;/code&gt;，这是golang继承的语法，说明IniConfigContainer继承了同步锁，在这里用于互斥修改配置文件的值。config包和INI格式的实现可以参考下图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/beego_config.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>Apache Bench 压力测试</title>
   <link href="https://blog.cyeam.com/test/2014/11/11/ab"/>
   <updated>2014-11-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/test/2014/11/11/ab</id>
   <content type="html">&lt;p&gt;项目开发，尤其是接口开发完之后，都需要进行压力测试。这样做可以帮助我们检验接口的平均响应时间。比如我们接口是查询数据库，我们可以将压力测试的结果和一般常见的相比，如果明显变慢，那么可能就是逻辑有问题或者是SQL查询有问题。这样可以在宏观上避免失误。在压力测试的时候，还可以顺便用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top&lt;/code&gt;命令监控一下内存情况，看一下是否存在内存泄漏。&lt;/p&gt;

&lt;p&gt;Apache提供了ab工具，直接安一个就成。也可以使用wrk进行测试。ab工具有个坑，测试的url后面需要加个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;，要不会提示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid Url&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ab -c 10 -n 100 http://www.baidu.com/
Requests per second:    47.46 [#/sec] (mean)
Time per request:       210.682 [ms] (mean)
Time per request:       21.068 [ms] (mean, across all concurrent requests)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-c&lt;/code&gt;是并发数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt;是压力次数。并发10的情况下，处理响应时间是210ms，那么响应一次就是21ms，每秒的响应次数就是1000/21=47次。一般还会预估系统响应能力，如果要求每秒能够响应1000次，再假设前面压的是一台机子，那么就最少需要21台服务器。&lt;/p&gt;

&lt;p&gt;再补充一些。压力测试最好直接连到服务器上去压本机IP和端口，而不是去呀域名，因为CDN或者Nginx，还当前网络情况都会影响到响应时间。这样做可以简化流程，便于分析结果。并且我们本身只负责开发，其它的交给运维去处理就好了。&lt;/p&gt;

&lt;p&gt;如果一套接口是相关的，比如用户增加购物车，购物车列表接口。我们不光要单独去压这些接口，最好再进行一次集合测试。比如，批量去压添加购物车的接口，然后去压购物车列表接口。观察一下列表接口能够正常返回之前批量压添加购物车接口的时间。但是具体怎么做，我目前也不是很明白，希望大神指教。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>MySQL的find_in_set函数</title>
   <link href="https://blog.cyeam.com/database/2014/10/13/field_in_set"/>
   <updated>2014-10-13T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/database/2014/10/13/field_in_set</id>
   <content type="html">&lt;p&gt;之前学校念书的时候，学过各种范式。范式里一般要求，就是只要遇到一对多或者多对多的关系，就把多这边拆出去。这样做一来是为了节约空间，如果关联的是字符串或者占用比较大的东西，拆出去用一个id来关联；还有一个就是可以防止多对多关系的变化，比如关联性别，已经有男和女了，如果加一个不男不女还行，但是要删一个女，那么就需要这个了，一般数据库也支持关联的修改。&lt;/p&gt;

&lt;p&gt;但是我们都是比较懒的，还死活拿性别来说。性别就俩，男和女。为来也不会增加和减少，那么就不必要创建性别表，因为就是建了，也没有必要；一般我们也会省略关联表。而是只用一列进行关联，关联男或女就用1或0，如果关联两个，是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0,1&lt;/code&gt;表示。这也可能是大辉哥说的“反模式”。&lt;/p&gt;

&lt;p&gt;这样数据库设计比较省事，但是查的时候不太好查。如果列里面是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1,2,3&lt;/code&gt;，查里面有没有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;，一般我也是笨笨得遍历一下。今天同事告诉我一个新牛逼的MySQL函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FIND_IN_SET()&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;FIND_IN_SET(str,strlist)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Returns a value in the range of 1 to N if the string str is in the string list strlist consisting of N substrings. A string list is a string composed of substrings separated by “,” characters. If the first argument is a constant string and the second is a column of type SET, the FIND_IN_SET() function is optimized to use bit arithmetic. Returns 0 if str is not in strlist or if strlist is the empty string. Returns NULL if either argument is NULL. This function does not work properly if the first argument contains a comma (“,”) character.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这个是专门针对用逗号分隔的串的查找。如果没有找到，返回0；找到了，返回位置，位置从1开始。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_find-in-set&quot;&gt;12.5 String Functions - FIND_IN_SET()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>用Golang写了一个更新壁纸的小程序</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/10/09/go_wallpaper"/>
   <updated>2014-10-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/10/09/go_wallpaper</id>
   <content type="html">&lt;p&gt;之前安装了安了Ubuntu 14.04，还写了个小程序，能开机更新桌面壁纸——&lt;a href=&quot;https://blog.cyeam.com/kaleidoscope/2014/09/17/ubuntu_bing_bg&quot;&gt;《Ubuntu通过Bing壁纸自动更新》&lt;/a&gt;。结果谁知道Ubuntu这个版本是怎么回事，一把开发环境配置好，就开不了机了。。。我这种学渣实在是处理不了，只能换Windows 8了。&lt;/p&gt;

&lt;p&gt;换了Windows 8还是惦记着我的更新壁纸程序。Windows系统和Linux不同，并没有提供修改壁纸的命令，只能提供Windows API编程进行修改。本科学游戏开发的我对这个还是比较熟悉的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &quot;stdafx.h&quot;
#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int main(int argc, char** argv)
{
	LPWSTR file_path = L&quot;C:\\wallpaper.jpg&quot;;

	int result;
	result = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, file_path, SPIF_UPDATEINIFILE);

	if (result == TRUE)
	{
		cout &amp;lt;&amp;lt; &quot;Wallpaper set&quot; &amp;lt;&amp;lt; result;
	}
	else
	{
		cout &amp;lt;&amp;lt; &quot;Wallpaper not set&quot;;
		cout &amp;lt;&amp;lt; &quot;SPI returned&quot; &amp;lt;&amp;lt; result;
	}

	return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;修改也很好做，然后就是获取Bing每日更新的壁纸。需要发送HTTP请求获取列表和解析RSS的XML文件。学渣我只用过Qt自带的库处理过，做这个东西不需要做界面，用这样的库意思也不大。果断想到了我大Golang和cgo。我大Golang是C语言之父设计的，cgo支持调用C语言，但是不支持C++。&lt;/p&gt;

&lt;p&gt;cgo需要mingw提供gcc编译器，如果是64位系统还需要mingw64，这个可以去下载编译好的，默认官网下载的是32位的。&lt;/p&gt;

&lt;p&gt;接着就是将上面的代码改写成调用C语言包的Golang语法。调用的难度在于传入壁纸文件地址。大Golang语言级别实现了字符串，而C语言只有用字符数组表示字符串。先开始都是用字符串来存的，然后传入字符串的指针，一直不成功。&lt;/p&gt;

&lt;p&gt;这个函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SystemParametersInfo&lt;/code&gt;只有四个参数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPI_SETDESKWALLPAPER&lt;/code&gt;表示用来设置壁纸，第二个参数表示壁纸位置（好像是这样。。。），&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPIF_UPDATEINIFILE&lt;/code&gt;表示修改之后直接展示。这三个参数应该都没错，都是整形往里面传。有错误的可能就是文件路径了，他是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LPWSTR&lt;/code&gt;类型。查了一下，它的定义&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typedef _Null_terminated_ WCHAR *NWPSTR, *LPWSTR, *PWSTR;&lt;/code&gt;，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typedef wchar_t WCHAR;&lt;/code&gt;。它是2个字节的Unicode字符类型。也就是说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LPWSTR&lt;/code&gt;是一个字符数组，函数调用的时候就是传入字符数组的指针进去。后来我就把字符串转换成数组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;类型（文件路径不会有中文，所以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;还是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]rune&lt;/code&gt;都没有影响）。然后像C语言一样，把数组名称传进去（C语言里面，字符数组名称就是这个数组的指针）。结果还是不行，后来发现大Golang的数组名称不是地址。。。大Golang获取数组地址应该是这样&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;path[0]&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;大Golang做json解析非常简单，我起初也是按照json的方式来解析XML，结果不行。后来参考了谢大的《Go Web 编程》。具体原因我没有去了解。关于XML的后面再补。&lt;/p&gt;

&lt;p&gt;编译程序：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go build go_code/wallpaper/win32api
go install go_code/wallpaper/win32api

go build go_code/wallpaper/app
go install go_code/wallpaper/app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后，将编译好的程序放到目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup&lt;/code&gt;下面，就可以咯。&lt;/p&gt;

&lt;p&gt;本文所涉及到的代码可以参考&lt;a href=&quot;https://github.com/mnhkahn/go_code/tree/master/wallpaper&quot;&gt;这里&lt;/a&gt;。如果是64位系统，可以直接用里面编译好的文件。主要还是在讲我做过程中的思路，具体的实现，比如cgo这些，可以直接看我的代码，我这里就不详细写了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.tairan.net/blog/2012/04/15/the-go-with-win32-api/&quot;&gt;《使用 Go 调用 Windows API》&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;https://github.com/astaxie/build-web-application-with-golang/blob/master/ebook/07.1.md&quot;&gt;《Go Web 编程》 - XML处理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://blog.csdn.net/cashey1991/article/details/6776349&quot;&gt;Windows手动添加开机启动项&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【4】&lt;a href=&quot;http://stackoverflow.com/questions/22811138/print-the-address-of-slice-in-golang&quot;&gt;print the Address of slice in golang&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>基于TCP套接字，通过Golang模拟HTTP请求（续）</title>
   <link href="https://blog.cyeam.com/network/2014/09/29/go_http2"/>
   <updated>2014-09-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2014/09/29/go_http2</id>
   <content type="html">&lt;h3 id=&quot;http报文的格式问题&quot;&gt;HTTP报文的格式问题&lt;/h3&gt;

&lt;p&gt;接着昨天的写。昨天的文章有个不确定的地方，就是建立TCP连接之后，向服务器发送的数据，包括命令、头和主体的格式。这三个部分是如何分割的，我是参考了POSTMAN预览的格式和telnet发送的格式猜测的，原认为行直接是通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;进行区分的，而头和主题是两个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;进行区分。这样发送也是能够正常解析的，今天去读了一下《HTTP权威指南》和Golang&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/http&lt;/code&gt;包，具体了解了下到底是如何区分的。&lt;/p&gt;

&lt;p&gt;《HTTP权威指南》第三章3.2节，报文的组成部分当中提到：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;每行都以一个由两个字符组成的行终止序列作为结束，其中包括一个回车符\r和一个换行符\n，这个终止序列可以写作CRLF。尽管HTTP规范中说明应该用CRLF来表示终止，但稳健的应用程序也应该接受单个换行符\n作为行的终止。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这也就解释了我昨天留下的疑问。简单的说，我那样猜测是能够发送成功，原因是人家服务器牛逼。而标准的写法是两个CRLF，而不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;。HTTP的写入是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/http/request.go&lt;/code&gt;第365行的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;函数中。可以看到，每一次写入都是以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r\n&lt;/code&gt;结束。这里是写入命令和写入头中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User-Agent&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Header lines
fmt.Fprintf(w, &quot;Host: %s\r\n&quot;, host)

// Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header.
userAgent := defaultUserAgent
if req.Header != nil {
	if ua := req.Header[&quot;User-Agent&quot;]; len(ua) &amp;gt; 0 {
		userAgent = ua[0]
	}
}
if userAgent != &quot;&quot; {
	fmt.Fprintf(w, &quot;User-Agent: %s\r\n&quot;, userAgent)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;http的超时问题&quot;&gt;HTTP的超时问题&lt;/h3&gt;

&lt;p&gt;通过阅读Golang源码，再结合昨天的理解，明白了谢大的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/astaxie/beego/httplib&lt;/code&gt;包，设置请求超时为啥是两个参数。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/http_timeout.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;HTTP协议首先要和服务器建立TCP连接，接着再向服务器发送HTTP报文。这里涉及到两个和服务器端的步骤，也就需要两次超时判断。一次是判断TCP连接建立是否超时，另一次是需要判断HTTP报文发送和响应是否超时。&lt;/p&gt;

&lt;p&gt;发送超时在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/http/client.go&lt;/code&gt;第278行的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doFollowingRedirects&lt;/code&gt;函数中进行。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if c.Timeout &amp;gt; 0 {
	type canceler interface {
		CancelRequest(*Request)
	}
	tr, ok := c.transport().(canceler)
	if !ok {
		return nil, fmt.Errorf(&quot;net/http: Client Transport of type %T doesn&apos;t support CancelRequest; Timeout not supported&quot;, c.transport())
	}
	timer = time.AfterFunc(c.Timeout, func() {
		reqmu.Lock()
		defer reqmu.Unlock()
		tr.CancelRequest(req)
	})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TCP建立连接超时判断是在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net/http/transport.go&lt;/code&gt;的495行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dialConn&lt;/code&gt;函数中处理。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if d := t.TLSHandshakeTimeout; d != 0 {
	timer = time.AfterFunc(d, func() {
		errc &amp;lt;- tlsHandshakeTimeoutError{}
	})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于超时的判断都是基于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AfterFunc&lt;/code&gt;函数，它会在指定时间后调用函数。如果超过指定时间还没有收到响应或者建立连接，就取消这次请求。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】《HTTP权威指南》&lt;/li&gt;
  &lt;li&gt;【2】《计算机网络 - 谢希仁》&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>基于TCP套接字，通过Golang模拟HTTP请求</title>
   <link href="https://blog.cyeam.com/network/2014/09/28/go_http"/>
   <updated>2014-09-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2014/09/28/go_http</id>
   <content type="html">&lt;p&gt;最近买了本书《HTTP权威指南》，看了第一章，看到书上说通过telnet模拟HTTP请求，我这个学渣就发现自己没学过计算机网络。&lt;/p&gt;

&lt;p&gt;今天翻了一下考研指定教材《计算机网络》，里面也以访问清华大学主页为例详细讲了通信过程：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;浏览器分析链接指向页面的URL。&lt;/li&gt;
  &lt;li&gt;浏览器向DNS请求解析www.tsinghua.edu.cn的IP地址。&lt;/li&gt;
  &lt;li&gt;域名系统DNS解析出清华大学服务器的IP地址为166.111.4.100。&lt;/li&gt;
  &lt;li&gt;浏览器与服务器建立TCP连接（在服务器端IP地址是166.111.4.100，端口是80）。&lt;/li&gt;
  &lt;li&gt;浏览器发出取文件命令：GET /chn/yxsz/index.htma&lt;/li&gt;
  &lt;li&gt;服务器www.tsinghua.edu.cn给出响应，把文件index.htm发送给浏览器。&lt;/li&gt;
  &lt;li&gt;释放TCP连接。&lt;/li&gt;
  &lt;li&gt;浏览器显示“清华大学院系设置”文件index.htm中的所有文本。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;HTTP协议就是：与指定IP和端口建立TCP连接，然后通过连接发送请求命令、头和数据。服务器响应的数据也会通过这个请求得到。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;考过研的连这个都不懂的伤不起啊。&lt;/p&gt;

&lt;p&gt;在这里我主要想自己基于TCP连接来模拟一次HTTP请求。测试是否能发送HTTP的头信息，通过User-Agent字段来模拟。还要发送Body信息测试Body的发送。&lt;/p&gt;

&lt;h3 id=&quot;1-telnet模拟请求&quot;&gt;1. telnet模拟请求&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ telnet blog.cyeam.com 80
GET / HTTP/1.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此命令可以建立连接，建立之后，输入请求命令，方法为GET，路径是根路径&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\&lt;/code&gt;，端口是80。然后再输入两个回车，就可以发送请求了。这里会返回响应状态码、响应头和Body。响应返回后，这次的telnet请求也会随之关闭。POST的发送可以参考&lt;a href=&quot;http://burakkahraman.wordpress.com/2010/01/11/http-post-via-telnet/&quot;&gt;HTTP POST via telnet - Burak’s logs&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-postman模拟请求&quot;&gt;2. POSTMAN模拟请求&lt;/h3&gt;

&lt;p&gt;POSTMAN是一个Chrome插件，很强大，能够模拟HTTP请求，查看请求和响应的全部内容。我一般还会用它的Preview功能，这样能看到所有的请求数据。&lt;/p&gt;

&lt;h3 id=&quot;3-beego模拟服务器&quot;&gt;3. beego模拟服务器&lt;/h3&gt;

&lt;p&gt;也可以用公网上的服务器进行测试，但是没有办法查看服务器端收到的请求详细内容，用beego搭了服务器来测试（服务器还是beego最熟。。。）。&lt;/p&gt;

&lt;p&gt;beego当中获取User-Agent通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this.Ctx.Request.UserAgent()&lt;/code&gt;实现。获取Body内容通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string(this.Ctx.Input.RequestBody)&lt;/code&gt;实现，还需要在配置文件加入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copyrequestbody = true&lt;/code&gt;，默认取到的Body是空的。beego服务器功能的验证可以通过POSTMAN模拟。&lt;/p&gt;

&lt;h3 id=&quot;4-基于tcp套接字实现http的post请求&quot;&gt;4. 基于TCP套接字实现HTTP的POST请求&lt;/h3&gt;

&lt;p&gt;通过POSTMAN的Preview查看完整请求和telnet使用，发现HTTP请求的命令、头和Body是通过换行符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;来区分的。POSTMAN抓到的如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST  HTTP/1.1
Host: localhost:8080
Cache-Control: no-cache
Postman-Token: 649dbec6-4f13-7064-67f0-11461281ceb2

This is body
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以得到结论：HTTP命令、头和Body通过换行符来区分。第一行是请求命令，跟着是HTTP头，他们之间通过一个换行符区分，头直接也是通过一个换行符区分。两个换行符之后，是Body信息。&lt;/p&gt;

&lt;p&gt;通过大Golang发送请求：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;conn.Write([]byte(&quot;POST / HTTP/1.0\nUser-Agent: Cyeam\nContent-Length: 4\n\nBody\r\n\r\n&quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意一点，如果要传入Body内容，必须在头当中加入长度信息&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Content-Length: 4&lt;/code&gt;，否则服务器依然无法获取，反正beego是这样，可能是获取Body前会先判断一下长度，如果是0就不读取Body信息了。完整代码借鉴了《Go 语言编程》第五章的内容。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/gobook-src/chapter5/simplehttp.go&quot;&gt;测试客户端&lt;/a&gt;和&lt;a href=&quot;https://github.com/mnhkahn/go_code/tree/master/beego&quot;&gt;测试服务器&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】《HTTP权威指南》&lt;/li&gt;
  &lt;li&gt;【2】《计算机网络 - 谢希仁》&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://burakkahraman.wordpress.com/2010/01/11/http-post-via-telnet/&quot;&gt;HTTP POST via telnet - Burak’s logs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Go语言的测试</title>
   <link href="https://blog.cyeam.com/golang/2014/09/25/go_test"/>
   <updated>2014-09-25T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/09/25/go_test</id>
   <content type="html">&lt;p&gt;之前看过一本书，说：“凡大神都是先写好单元测试用例，才去写代码的”。我一直都记在心里。今天终于有空，就看了看Golang的测试包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testing&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;谢大的书和Golang官方的文档讲的差不多，Golang提供了两个测试方式：用例测试和压力测试。&lt;/p&gt;

&lt;h3 id=&quot;1-用例测试&quot;&gt;1. 用例测试&lt;/h3&gt;

&lt;p&gt;用例测试的规则我是复制谢大的：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;文件名必须是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_test.go&lt;/code&gt;结尾的，这样在执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go test&lt;/code&gt;的时候才会执行到相应的代码&lt;/li&gt;
  &lt;li&gt;你必须import &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testing&lt;/code&gt;这个包&lt;/li&gt;
  &lt;li&gt;所有的测试用例函数必须是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test&lt;/code&gt;开头&lt;/li&gt;
  &lt;li&gt;测试用例会按照源代码中写的顺序依次执行&lt;/li&gt;
  &lt;li&gt;测试函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestXxx()&lt;/code&gt;的参数是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testing.T&lt;/code&gt;，我们可以使用该类型来记录错误或者是测试状态&lt;/li&gt;
  &lt;li&gt;测试格式：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func TestXxx (t *testing.T)&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Xxx&lt;/code&gt;部分可以为任意的字母数字的组合，但是首字母不能是小写字母&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[a-z]&lt;/code&gt;，例如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Testintdiv&lt;/code&gt;是错误的函数名。&lt;/li&gt;
  &lt;li&gt;函数中通过调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testing.T&lt;/code&gt;的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Errorf&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FailNow&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fatal&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FatalIf&lt;/code&gt;方法，说明测试不通过，调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Log&lt;/code&gt;方法用来记录测试的信息。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以我之前写好的堆排序为例进行测试，测试需要以包为单位。对于Golang，就是以文件夹为单位。需要将堆排序文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quicksort.go&lt;/code&gt;和测试文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quicksort_test.go&lt;/code&gt;放到一起。&lt;/p&gt;

&lt;p&gt;验证堆排序测试用例：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func TestHeapSort(t *testing.T) {
	test0 := []int{49, 38, 65, 97, 76, 13, 27, 49}
	test1 := []int{13, 27, 38, 49, 49, 65, 76, 97}
	heapSort(BySortIndex(test0), 0, len(test0))
	for i := 0; i &amp;lt; len(test0); i++ {
		if test0[i] != test1[i] {
			t.Fatal(&quot;error&quot;)
		}
	}
}

$ go test -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-压力测试&quot;&gt;2. 压力测试&lt;/h3&gt;

&lt;p&gt;用例测试要用到的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testing.T&lt;/code&gt;，而压力测试需要&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testing.B&lt;/code&gt;。它的原型如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type B struct {
	common
	N                int
	previousN        int           // number of iterations in the previous run
	previousDuration time.Duration // total duration of the previous run
	benchmark        InternalBenchmark
	bytes            int64
	timerOn          bool
	showAllocResult  bool
	result           BenchmarkResult
	parallelism      int // RunParallel creates parallelism*GOMAXPROCS goroutines
	// The initial states of memStats.Mallocs and memStats.TotalAlloc.
	startAllocs uint64
	startBytes  uint64
	// The net total of this test after being run.
	netAllocs uint64
	netBytes  uint64
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此结构里面有一个属性&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt;，它表示的是进行压力测试的次数。可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b.N = 1234&lt;/code&gt;来设置压力次数。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func BenchmarkHeapSort(b *testing.B) {
	b.StopTimer()  //调用该函数停止压力测试的时间计数
	b.StartTimer() //重新开始时间

	b.N = 1234

	for i := 0; i &amp;lt; b.N; i++ {
		test0 := []int{49, 38, 65, 97, 76, 13, 27, 49}
		test1 := []int{13, 27, 38, 49, 49, 65, 76, 97}
		heapSort(BySortIndex(test0), 0, len(test0))
		for i := 0; i &amp;lt; len(test0); i++ {
			if test0[i] != test1[i] {
				b.Fatal(&quot;error&quot;)
			}
		}
	}
}

$ go test -v -test.bench=&quot;.*&quot;
=== RUN TestHeapSort
--- PASS: TestHeapSort (0.00 seconds)
PASS
BenchmarkHeapSort           1234              1620 ns/op
ok      go_code/test    0.051s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里我只是抛砖引玉，简单写了一点。今后写代码，我也得像大神一下写用例了。大家共勉，哈哈哈。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test/quicksort_test.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://github.com/astaxie/build-web-application-with-golang/blob/master/ebook/11.3.md&quot;&gt;Go怎么写测试用例 - astaxie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Ubuntu通过Bing壁纸自动更新</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/09/17/ubuntu_bing_bg"/>
   <updated>2014-09-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/09/17/ubuntu_bing_bg</id>
   <content type="html">&lt;p&gt;在我自己的Nexus 7上面自己写了一个小程序，每天可以定时获取Bing当日的最新壁纸。每天早上起来看到都是不一样的画面，还是很开心的。&lt;/p&gt;

&lt;p&gt;后来，也想自己写一个桌面的。本来想说用Qt去写，这样就能同时在我的Ubuntu和公司的Windows运行了。写了一半，就是读取图片这部分，设置壁纸这块不同的系统需要根据系统自身提供的接口才能修改，Windows有点麻烦，当时也有点事情，暂且作罢。&lt;/p&gt;

&lt;p&gt;之前我的想法比较复杂，希望通过HTTP得到图片之后，将其转换成二进制编码，通过设置壁纸接口直接修改。这样做的好处是不需要保存临时文件，就是编码比较复杂。后来找了一个用Python写的Qt代码。他的思路是将文件保存到本地临时目录里面，然后通过Linux命令来修改壁纸。如此一来，省去了Linux系统接口编码的繁琐。&lt;/p&gt;

&lt;p&gt;他的代码直接下载下来是有问题的，如果没有记错的话，是最后的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;madWindow&lt;/code&gt;函数对齐错了，加个缩进就可以了。后来，我还改了一下壁纸文件目录，我放到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;home&lt;/code&gt;下，现在觉得放在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/&lt;/code&gt;更好一点。最后应该是一个Linux修改壁纸命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gsettings set org.gnome.desktop.background picture-uri &quot;file:///home/%s/%s.jpg&quot;&lt;/code&gt;，并没有去研究这个，大家有兴趣可以看一下。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/bing.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/python_code/blob/master/bing.py&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.linuxidc.com/Linux/2014-06/103854.htm&quot;&gt;Ubuntu下实现用Python开机自动更新壁纸为bing壁纸&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Go语言的插入排序实现</title>
   <link href="https://blog.cyeam.com/golang/2014/09/01/go_insertsort"/>
   <updated>2014-09-01T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/09/01/go_insertsort</id>
   <content type="html">&lt;p&gt;这个算法还是我考研的时候看懂的。插入排序大体有两种，头插法和尾插法。区别就是插入的位置是头部还是尾部。&lt;/p&gt;

&lt;p&gt;简单说一下插入排序的思路：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;从第二个元素开始遍历，第一个元素认为是有序的；&lt;/li&gt;
  &lt;li&gt;将要插入的元素依次与已有序队列比较，插入到合适的位置；&lt;/li&gt;
  &lt;li&gt;循环执行，直到遍历结束。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Golang包里的实现和我上面说的严奶奶的有点区别。将遍历得到的元素倒着与有序队列依次比较。如果比有序队列的小，交换这两个元素。&lt;/p&gt;

&lt;p&gt;这样的方法和传统的相比，插入步骤同样都是通过从后向前依次移动实现的插入。而这个方法更加简单一点，不需要声明临时变量。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func insertionSort(data Interface, a, b int) {
	for i := a + 1; i &amp;lt; b; i++ {
		for j := i; j &amp;gt; a &amp;amp;&amp;amp; data.Less(j, j-1); j-- {
			data.Swap(j, j-1)
		}
	}
}

type BySortIndex []int

func (a BySortIndex) Len() int      { return len(a) }
func (a BySortIndex) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BySortIndex) Less(i, j int) bool {
	return a[i] &amp;lt; a[j]
}

func main() {
	test0 := []int{49, 38, 65, 97, 76, 13, 27, 49}
	insertionSort(BySortIndex(test0), 0, len(test0))
	fmt.Println(test0)
}

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/insertsort.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;
&lt;ul&gt;
  &lt;li&gt;【1】数据结构 - 严蔚敏&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Go语言的堆排序实现</title>
   <link href="https://blog.cyeam.com/golang/2014/08/29/go_heapsort"/>
   <updated>2014-08-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/29/go_heapsort</id>
   <content type="html">&lt;p&gt;关于堆排序的算法，可以参考我去年的文章&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/06/heapsort&quot;&gt;《堆排序(HEAP SORT)》&lt;/a&gt;。那篇文章讲的是建立小顶堆进行的排序，这里说的是建立大顶堆建立的排序，差不多。&lt;/p&gt;

&lt;p&gt;在Golang源码的sort包里，自带了排序函数。该函数可以对各种类型进行排序，只不过该类型需要实现三个函数，使得该类能够实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Interface&lt;/code&gt;接口。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这三个函数分别是，获取排序队列长度、队列任意两个元素比较大小和交换任意两个元素。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (a BySortIndex) Len() int      { return len(a) }
func (a BySortIndex) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BySortIndex) Less(i, j int) bool {
	return a[i] &amp;lt; a[j]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果是整形数组，可以像上面这样实现。Golang支持多值赋值，所以交换值很简单。自带的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;len&lt;/code&gt;也使得长度遍历很简单。比较大小，可以根据实际情况自己定义。&lt;/p&gt;

&lt;p&gt;堆排序的核心就是建立大顶堆和交换值，它是本地排序，不需要新分配空间。Golang的源码我已经加了注释，也不难，大家直接阅读即可。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func heapSort(data Interface, a, b int) {
	first := a
	lo := 0
	hi := b - a

	// Build heap with greatest element at top.
	for i := (hi - 1) / 2; i &amp;gt;= 0; i-- {
		siftDown(data, i, hi, first)
	}

	// Pop elements, largest first, into end of data.
	// 二叉树结构当中最后一个有子结点的结点
	for i := hi - 1; i &amp;gt;= 0; i-- {
		data.Swap(first, first+i)
		siftDown(data, lo, i, first)
	}
}

// 建立树函数
// 父节点root的左孩子2*root + 1
func siftDown(data Interface, lo, hi, first int) {
	root := lo
	for {
		child := 2*root + 1
		if child &amp;gt;= hi { // child 超出了数组长度，也就是该结点无孩子结点，返回
			break
		}
		if child+1 &amp;lt; hi &amp;amp;&amp;amp; data.Less(first+child, first+child+1) { // 右孩子结点存在
			child++
		}
		// 以上是为了在孩子结点当中找到较大的结点，用child表示
		if !data.Less(first+root, first+child) {
			return
		}
		// 如果根结点小于较大的孩子结点，则进行交换；该孩子结点的堆结构可能会被影响，继续去处理孩子结点
		data.Swap(first+root, first+child)
		root = child
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/heapsort.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/06/heapsort&quot;&gt;堆排序(HEAP SORT) - Cyeam&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>龙哥与刘翔的约战</title>
   <link href="https://blog.cyeam.com/ctalk/2014/08/28/zealer_t"/>
   <updated>2014-08-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ctalk/2014/08/28/zealer_t</id>
   <content type="html">&lt;p&gt;自从刘翔前几天放出锤子手机测评视频，我就预感，那句“认真可以，但别任性”会激怒龙哥。没有想到的是，龙哥居然忍了这么久。可能是要先去找证据吧。&lt;/p&gt;

&lt;p&gt;从我对他们的称呼也能看出来，这两个人我都不喜欢。先说刘翔。我从他第一个视频，翻译谷歌收购一个手势界面系统的视频开始注意他，到后来他自己买机器测，再到现在的Zealer，虽然他的视频我依然都会看，但是对于他来讲，我是越来越不喜欢。&lt;/p&gt;

&lt;p&gt;自从他说他要开始创业，我就觉得不对劲。之前的东西，说白了，就是一个脱口秀节目。看完之后出去吹吹牛，既能吹出些花开，又能帮刘翔宣传。而且以这种形式测评，完全可以和手机厂商和平共处，测评基本不会影响消费者，那也就不会影响友商了。这是一个比较好的发展模式。但问题就出在这里，刘翔并不甘于此，他想一呼百应。这就是他创业的原因，而且当时在接受某个节目采访的时候，他还专门提到，有个公司因为他的测评开掉了整个照相机团队，这是他想要的。再到后面他拿了雷布斯200万，到最近又爆出李侃拿了oppo一千多万，这应该都是所谓创业的妥协。&lt;/p&gt;

&lt;p&gt;提到钱，做脱口秀是可以发财的，高晓松从优酷转会爱奇艺，据说转会费上亿元。以刘翔的视频访问量，从优酷拿钱又不是不可能。可惜从他开始创业开始，他就选择往所有视频网站上传视频，所以当时优酷就直接取消在首页推荐他的视频了。&lt;/p&gt;

&lt;p&gt;还有一种可能，刘翔想借测评作为一个跳板。最近他们刚刚推出了zeal fix，可能就是借视频所做的另一件事情，可能刘翔最终的目的是做手机，这也不是不可能。&lt;/p&gt;

&lt;p&gt;虽然说视频还是公正的，但是总感觉多了点不该有的东西。&lt;/p&gt;

&lt;p&gt;再说说老罗。说实话，我是通过刘翔知道他的。也看过他的《我的奋斗》。口才不错，书一般。他去做手机我还是挺喜欢的，毕竟他不走寻常路。但是我也见证了自己一步一步从粉转黑。。。他总想通过自己的嘴巴解决各种问题，产能不行，就发长微博道歉，还暗指是富士康得问题，当你去问他为啥冤枉富士康，人家长微博又没明确说。他就是爱耍这种文字游戏，但说实话，耍嘴皮子解决不了任何问题。斗了这么长时间的嘴，虽然没输过，但是不少人已从粉转黑，这也包括我。&lt;/p&gt;

&lt;p&gt;关于手机，有一个问题，锤子到底为啥要比国内其他牌子手机贵。华为技术过硬，有自己的soc，而且那个超薄的也做的很好。小米和魅族，最起码价格便宜。&lt;/p&gt;

&lt;p&gt;昨天晚上，优酷直播了二人的当面对质。龙哥不太要脸，不过他一直是这样，最后都人格攻击了，而刘翔还是嫩了点。最要命的那个测评里面的屏幕拍摄问题，果断当场验啊，可能被龙哥的攻势搞得招架不住了。我觉得当时当场验，老罗觉得没那么嚣张了，并且可以继续说手机问题。可惜老罗习惯性的切话题，刘翔也跟着跑了。&lt;/p&gt;

&lt;p&gt;这架吵的，老罗赢得了不少粉丝，不过这件事会为销量增加多少呢？反正我是不会买的。而对于王，虽然公信力受到了影响，但是毕竟是测评界的一哥，如果他垮了，测评界就垮了，所以彭林站出来想帮帮他。如果他接受了彭林，并且放弃那些拿到的投资，那么，其实还是老罗输了。但如果他从此一蹶不振，或者继续这么做下去，可能也就这样了。&lt;/p&gt;

&lt;p&gt;这架的核心，不在于锤子手机，而在于王自如人品。这也怨不得别人，谁叫他那么膨胀，给测评这个单纯美好的东西加上了他自己那些梦想。梦想都是虚的，有梦想也没办法创业，有模式才行。他也就是做了一些错误的选择，视频还是教了我很多东西呢，不能因为有一些错而否定全盘。&lt;/p&gt;

&lt;p&gt;本文介绍摘自网络。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Solr里的整形字段</title>
   <link href="https://blog.cyeam.com/solr/2014/08/27/solr_trieint"/>
   <updated>2014-08-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/solr/2014/08/27/solr_trieint</id>
   <content type="html">&lt;p&gt;今天一个项目，要在后台配置好id信息，用逗号分隔，然后我读取配置好的id信息，从Solr里查询，将结果返回。结果出了错误，接口并没有按照配置返回id信息，并且一条都没有返回。&lt;/p&gt;

&lt;p&gt;看到错误，果断去抓Solr查询请求，请求很长，不太好看，不过运行一下就知道问题了。运行结果是返回了错误：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Invalid Number: &lt;/code&gt;。报整数错误，第一反应了超出了范围，因为id之间是通过逗号分隔的，看来是因为少打了逗号导致的。&lt;/p&gt;

&lt;p&gt;知道问题了，就去看看Solr的配置。id字段是int类型，而schema.xml里面配置的是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;fieldType name=&quot;int&quot; class=&quot;solr.TrieIntField&quot; precisionStep=&quot;0&quot; positionIncrementGap=&quot;0&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;再顺着去查&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TrieIntField&lt;/code&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A numeric field that can contain 32-bit signed two’s complement integer values.
Min Value Allowed: -2147483648
Max Value Allowed: 2147483647&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;验证了我的猜想。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://lucene.apache.org/solr/4_3_0/solr-core/org/apache/solr/schema/TrieIntField.html&quot;&gt;Class TrieIntField&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang的map迭代</title>
   <link href="https://blog.cyeam.com/golang/2014/08/25/go_map"/>
   <updated>2014-08-25T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/25/go_map</id>
   <content type="html">&lt;p&gt;Golang不同于Java等常见的语言，字节在语言层面支持了map。map类似于Java当中的Set，是数学中集合的概念。集合当中不会出现重复元素，并且是无序的。与此相关的就是数组和队列，它们是有序的。&lt;/p&gt;

&lt;p&gt;前几天要做一个接口调用，需要用到安全机制，将请求参数编码出一个签名，一并用来发送请求。相关的可以参考&lt;a href=&quot;https://blog.cyeam.com/framework/2014/08/18/go_hmacsha1&quot;&gt;《接口安全机制》&lt;/a&gt;。这里会涉及到两次取请求URL的步骤，一次是用来拼请求，一次是用来计算签名。当时机制的我，为了方便写代码，就将参数放在了map当中，遍历两次map就能够实现了。一般情况下都能够正常访问。极少数情况下是会提示调用失败的，其实就是签名或者参数错误。因为概率极低，所以没有引起注意。&lt;/p&gt;

&lt;p&gt;但是一直觉得代码有点bug，大概位置自己也知道，肯定是发送HTTP请求的模块。后来，就去review了一下自己的代码，又测了一下，发现了问题。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import (
	&quot;fmt&quot;
)

func main() {
	query := map[string]string{}
	// 需要按照字典排序
	query[&quot;test0&quot;] = &quot;0&quot;
	query[&quot;test1&quot;] = &quot;1&quot;
	query[&quot;test2&quot;] = &quot;2&quot;

	for i := 0; i &amp;lt; 100; i++ {
		for _, v := range query {
			fmt.Print(v)
		}
		fmt.Println()
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;map遍历100次，把结果grep了一下，里面有82个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;012&lt;/code&gt;，9个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;120&lt;/code&gt;，9个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;201&lt;/code&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;首先，map并不是完全随机，我执行了几次结果都一样；而之前也试过一次，同样也都是使用Go playground，结果全是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;210&lt;/code&gt;（结果不是很确定），可能随机还和时间有关；&lt;/li&gt;
  &lt;li&gt;其次，结果也不是很平均，按理说，这三个数，打散应该是6种结果，而这里只有3种。而且数量也相差很大。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以，由于遍历map随机得不是很彻底，我始终没有发现这个bug的问题。安装上面测试的结果，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;012&lt;/code&gt;出现概率82%，其它概率是18%，那么，接口调用出错的概率就是82%×82%=67.24%。
出问题的概率还是很高，看来测试也有问题。。。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_map.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/doc/go1.3#map&quot;&gt;Map iteration - Go 1.3 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>接口安全机制</title>
   <link href="https://blog.cyeam.com/framework/2014/08/18/go_hmacsha1"/>
   <updated>2014-08-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/framework/2014/08/18/go_hmacsha1</id>
   <content type="html">&lt;p&gt;HMAC-SHA1（基于SHA1哈希算法的消息验证代码） 是一种键控哈希算法。 此 HMAC 进程将密钥与消息数据混合，使用哈希函数对混合结果进行哈希计算，将所得哈希值与该密钥混合，然后再次应用哈希函数。 输出的哈希值长度为 160 位。&lt;/p&gt;

&lt;p&gt;虽然安全哈希算法（Secure Hash Algorithm，简称SHA1）是一种不可逆的加密算法，但是由于同样的内容计算之后结果是相同的，且不会重复，所以通过暴力计算，也是能够还原数据。所以HMAC-SHA1就在SHA1的基础上，增加一个密钥。如此一来，只要密钥不被泄漏，就无法破解信息内容。&lt;/p&gt;

&lt;p&gt;在接口验证过程中，将要发送的数据参数，再加上要发送时刻的时间片和密钥，通过HMAC-SHA1一并进行计算，请求的时候将计算得到的签名作为参数发送给服务器。服务器也要根据本地保存的密钥再计算一次，与客户端提供的签名进行对比。相同则为授权成功。&lt;/p&gt;

&lt;p&gt;通过HMAC-SHA1做加密处理，可以防止请求参数被恶意修改。如果参数被修改，签名也会随之变化，所以单纯的修改参数将会导致服务器授权验证失败。再传入的参数之中再加入时间片信息，如果和服务器时间相差过大，该请求也会被抛弃。&lt;/p&gt;

&lt;p&gt;基于beego框架实现接口验证，使用它提供的过滤器就能实现。过滤器函数如下所示：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;beego.InsertFilter(pattern string, postion int, filter FilterFunc)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;三个参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;路由规则，可以根据一定的规则进行路由，如果你全匹配可以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;postion 执行 Filter 的地方，四个固定参数如下，分别表示不同的执行过程
    &lt;ul&gt;
      &lt;li&gt;BeforeRouter 寻找路由之前&lt;/li&gt;
      &lt;li&gt;BeforeExec 找到路由之后，开始执行相应的 Controller 之前&lt;/li&gt;
      &lt;li&gt;AfterExec 执行完 Controller 逻辑之后执行的过滤器&lt;/li&gt;
      &lt;li&gt;FinishRouter 执行完逻辑之后执行的过滤器&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;filter filter 函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type FilterFunc func(*context.Context)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;路由规则用来匹配过滤器。过滤的位置有4个。我们这里是要校验请求参数，那么调用得靠前一些，能够减少后面不必要的计算。所以用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BeforeRouter&lt;/code&gt;较好。校验函数如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func FilterOauth(ctx *context.Context) {
	timestamp, err := strconv.ParseInt(ctx.Input.Query(&quot;timestamp&quot;), 10, 64)
	if err != nil {
		ctx.Abort(http.StatusUnauthorized, &quot;&quot;)
	}
	timestamp_t := time.Unix(timestamp, 0)
	now_t := time.Now()
	if now_t.Sub(timestamp_t).Minutes() &amp;gt; 10 {
		fmt.Println(timestamp_t, &quot;Duration is too long&quot;)
		ctx.Abort(http.StatusUnauthorized, &quot;&quot;)
	}

	size := ctx.Input.Query(&quot;size&quot;)
	if size == &quot;&quot; {
		ctx.Abort(http.StatusUnauthorized, &quot;&quot;)
	}

	sign := ctx.Input.Query(&quot;sign&quot;)
	if sign == &quot;&quot; {
		ctx.Abort(http.StatusUnauthorized, &quot;&quot;)
	}

	if CheckMAC(size+timestamp_t.String(), sign, &quot;seckey&quot;) {
		fmt.Println(&quot;Verify error&quot;)
		ctx.Abort(http.StatusUnauthorized, &quot;&quot;)
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;校验是根据传入的参数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestape&lt;/code&gt;进行，还需要传入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sign&lt;/code&gt;签名用于比对。过滤器函数传入了上下文对象&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*context.Context&lt;/code&gt;。如果传入的时间片和系统时间相差超过10分钟，会被认为是过期请求。&lt;/p&gt;

&lt;p&gt;校验函数用了Golang的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crypto/hmac&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crypto/sha1&lt;/code&gt;包：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func CheckMAC(message, sign, key string) bool {
	mac := hmac.New(sha1.New, []byte(key))

	mac.Write([]byte(message))
	return hex.EncodeToString(mac.Sum(nil)) == sign
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;加密的结果是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;类型，还需要转换成可读的16进制编码，通过包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/hex&lt;/code&gt;即可实现。&lt;/p&gt;

&lt;p&gt;接口加密还可以使用HTTPS加密的传输方式，这个我还不懂，以后明白了再写。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://beego.me/docs/mvc/controller/filter.md&quot;&gt;过滤器 - beego开发文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang实现大数乘法</title>
   <link href="https://blog.cyeam.com/golang/2014/08/15/go_largenumberx"/>
   <updated>2014-08-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/15/go_largenumberx</id>
   <content type="html">&lt;p&gt;大数乘法，简单的说，就是把小学学的列竖式计算的方法进行了实现。这其实也就是个乘法分配率的变形。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;5 * 12 = 5 * (2 + 10) = 5 * 2 + 5 * 10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以第二行竖式，12的十位1与5相乘的时候，需要再最后空一位，其实是在最后省略了一个0。十位就是省略一个0，也就是左移一位，那么百位就是左移两位。以此类推。&lt;/p&gt;

&lt;p&gt;通过代码实现，相乘的两个数就不能用整形表示了，因为存不了很大的整数。需要用字符串表示。按位相乘，最后把结果错位相加就行。乘法的结果等于乘数的位数，所以可以申请一个和乘数位数相同的数组，然后错位相加即可。但是这样太麻烦了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/bignum.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;乘法是从个位开始，但是遍历字符串是从最高为开始的，所以要首先将输入字符串反转。用i表示被乘数的遍历索引，j表示乘数的索引。前面说了左移的位数和乘数当前遍历的位相等，也就是j。相乘的时候，如果结果大于9，需要进位，现在暂不考虑进位问题。那么，结果的位数和乘数的长度将会是相等的，例子当中，都是6。如果不考虑偏移，那么乘法的结果将会和遍历的索引i相等。所以，可以得出，任意两位相乘，结果的位置是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i + j&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func LargeNumberMultiplication(a string, b string) (reuslt string) {
	a = strings.Reverse(a)
	b = strings.Reverse(b)
	c := make([]byte, len(a)+len(b))

	for i := 0; i &amp;lt; len(a); i++ {
		for j := 0; j &amp;lt; len(b); j++ {
			c[i+j] += (a[i] - &apos;0&apos;) * (b[j] - &apos;0&apos;)
		}
	}

	var plus byte = 0
	for i := 0; i &amp;lt; len(c); i++ {
		if c[i] == 0 {
			break
		}
		temp := c[i] + plus
		plus = 0
		if temp &amp;gt; 9 {
			plus = temp / 10
			reuslt += string(temp - plus*10 + &apos;0&apos;)
		} else {
			reuslt += string(temp + &apos;0&apos;)
		}

	}
	return strings.Reverse(reuslt)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;字符串的反转可以参考&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/14/go_reverse&quot;&gt;《字符串反转》&lt;/a&gt;。相乘的过程中，需要将字符和整数进行转换，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a[i] - &apos;0&apos;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;temp + &apos;0&apos;&lt;/code&gt;就能实现。进位在最后一并进行。通过变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plus&lt;/code&gt;保存上一次的进位数目。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/largenumberx.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www2.lssh.tp.edu.tw/~hlf/class-1/lang-c/big_num3.htm&quot;&gt;大数乘法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>字符串反转</title>
   <link href="https://blog.cyeam.com/golang/2014/08/14/go_reverse"/>
   <updated>2014-08-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/14/go_reverse</id>
   <content type="html">&lt;p&gt;字符串反转的Golang实现，应该是最简单的了。废话不多说，代码如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

func Reverse(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i &amp;lt; j; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}
func main() {
	a := &quot;Hello, 世界&quot;
	println(a)
	println(Reverse(a))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Golang本身支持计算字符串长度，并且由于支持多值赋值，交换值也是格外的简单。其他语言无非要单独处理一下这两个部分，通过遍历计算出字符串长度，再通过异或或者加法交换值。为了能够处理字符串里面包含中文的情况，这里使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rune&lt;/code&gt;进行处理。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/reverse.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>把一个字符串中的字符从小写转为大写</title>
   <link href="https://blog.cyeam.com/golang/2014/08/12/go_upper"/>
   <updated>2014-08-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/12/go_upper</id>
   <content type="html">&lt;p&gt;将字符串里面的英文小写转成大写，是一个很简单的操作。每个字符的编码可以认为是整数，Golang里面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rune&lt;/code&gt;是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint8&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int32&lt;/code&gt;。其它语言大同小异。在编码表当中，位置是是从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Z&lt;/code&gt;，接着是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;对应的整数是65，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;对应的整数是97，中间差了26个英文字母和6个其它字符的长度共32。所以转换的方法就是，将小写字母的值，剪掉32即可。&lt;/p&gt;

&lt;p&gt;还是编码的问题。最早的出现的编码是ASCII，从128个增加到256个字符。再到后面的Unicode、GBK等，这些新出的编码和最早的ASCII都是兼容的，也就是说，不同的编码里面，前256位都是一样的。所以，在这里的英文字符转大写问题，不存在编码问题。&lt;/p&gt;

&lt;p&gt;这个题目在Golang的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings&lt;/code&gt;包里有专门的实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToUpper&lt;/code&gt;，此函数内部封装了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unicode&lt;/code&gt;包，在此包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;letter.go&lt;/code&gt;文件内，是具体的实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const MaxASCII = &apos;\u007F&apos;

func toUpper(r rune) rune {
	if r &amp;lt;= MaxASCII {
		if &apos;a&apos; &amp;lt;= r &amp;amp;&amp;amp; r &amp;lt;= &apos;z&apos; {
			r -= &apos;a&apos; - &apos;A&apos;
		}
		return r
	}
	return r
}

func ToUpper(s []rune) (res []rune) {
	for i := 0; i &amp;lt; len(s); i++ {
		res = append(res, toUpper(s[i]))
	}
	return res
}

func main() {
	a := &quot;Hello, 世界&quot;
	fmt.Println(string(ToUpper([]rune(a))))

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大Golang支持UTF8和Unicode两种编码方式。分别对应的数据类型是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rune&lt;/code&gt;。通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte(str)&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]rune(str)&lt;/code&gt;可以将字符串转成UTF8和UTF16两种解析方式。解析的字符串可能是中文，所以要按照&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rune&lt;/code&gt;处理。对于中文等其他字符，不做处理，所以增加了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if r &amp;lt;= MaxASCII&lt;/code&gt;过滤。&lt;/p&gt;

&lt;p&gt;还有要注意的一点，Golang语言层面实现了字符串&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;。它不同于C语言里面的字符数组，所以不能够通过下标修改字符串内容。只能通过下标读取。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/upper.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/src/pkg/strings/strings.go&quot;&gt;Source file src/pkg/strings/strings.go - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://zh.wikipedia.org/wiki/ASCII&quot;&gt;ASCII - Wikipedia&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://golang.org/src/pkg/unicode/letter.go&quot;&gt;Source file src/pkg/unicode/letter.go - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang通过反射实现结构体转成JSON数据</title>
   <link href="https://blog.cyeam.com/golang/2014/08/11/go_json"/>
   <updated>2014-08-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/11/go_json</id>
   <content type="html">&lt;p&gt;Golang的结构体可以增加类似于Java里面&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@JsonProperty(&quot;id&quot;)&lt;/code&gt;注释。在结构体里面通过反引号包含的字符串被称为Tag。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Cyeam struct {
	Url   string `json:&quot;url&quot;`
	Other string `json:&quot;-&quot;`
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在Tag里加入对json的Tag的定义，就可以实现对输出的格式控制。而且，如果json字段的Tag定义为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;的话，不会被解析。&lt;/p&gt;

&lt;p&gt;这么强大的功能，借助&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reflect&lt;/code&gt;包，实现起来也不难。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;c := Cyeam{Url: &quot;blog.cyeam.com&quot;, Other: &quot;...&quot;}
var t reflect.Type
t = reflect.TypeOf(c)
var v reflect.Value
v = reflect.ValueOf(c)
json := &quot;{&quot;
for i := 0; i &amp;lt; t.NumField(); i++ {
	if t.Field(i).Tag.Get(&quot;json&quot;) != &quot;-&quot; {
		json += &quot;\&quot;&quot; + t.Field(i).Tag.Get(&quot;json&quot;) + &quot;\&quot;:\&quot;&quot; + v.FieldByName(t.Field(i).Name).String() + &quot;\&quot;&quot;
	}
}
json += &quot;}&quot;
fmt.Println(json)

{&quot;url&quot;:&quot;blog.cyeam.com&quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于每一个对象，都能够得到它的类型Type以及值Value。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.NumField()&lt;/code&gt;方法能够得到结构体内包含值的数目，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.Field(i)&lt;/code&gt;能够得到索引值处变量的值Value。通过这两个方法，就能够对结构体变量进行遍历。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.Field(i).Tag.Get(&quot;json&quot;)&lt;/code&gt;可以获取当前字段的Tag，并且从中获取json的Tag值。如此一来，就能够完成结构体的遍历和最后JSON流的拼接生成。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/json.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/pkg/reflect/&quot;&gt;http://golang.org/pkg/reflect/ - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://golang.org/src/pkg/encoding/json/encode.go&quot;&gt;Source file src/pkg/encoding/json/encode.go - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang源码剖析——字符串查找算法</title>
   <link href="https://blog.cyeam.com/golang/2014/08/08/go_index"/>
   <updated>2014-08-08T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/08/go_index</id>
   <content type="html">&lt;p&gt;发现好多笔试题，都问的是库函数。往简单的做，有效率不太高的算法，往复杂的做，就得看源码了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;写一个在一个字符串（n）中寻找一个子串（m）第一个位置的函数&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;暴力字符串匹配方法（Brute forceing matching）。这个写法不难，复杂度O(n*m)。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func IndexFuck(s, sep string) int {
	for i := 0; i &amp;lt; len(s); i++ {
		if s[i] == sep[0] {
			is := true
			j := 0
			for ; j &amp;lt; len(sep); j++ {
				if sep[j] != s[i] {
					is = false
					break
				}
			}
			if is {
				return i
			}
		}
	}
	return -1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Rabin-Karp算法，能把复杂度最小减到O(n)。设要匹配的子串为sep，查找的字符串为s。通过一个哈希算法，把sep哈希成一个数字，以sep的长度，依次遍历s中长度和sep相同的串，直到计算出的值是相等的。&lt;/p&gt;

&lt;p&gt;计算哈希的方法是，把原本的字符串看作是一种E进制的编码，然后将该编码转成10进制用数字表示。Golang用16777619作为该进制数。（这是我的理解，至于为什么选择这个整数，我也不是很清楚，只不过发现很多哈希算法都用过这个神奇的数字16777619 = (2^24 + 403)）。&lt;/p&gt;

&lt;p&gt;举个例子，原始串s是12345，匹配串sep是45。第一次用12和45比较计算出串的哈希值，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1*E+2 != 4*E+5&lt;/code&gt;。然后计算23和45的值。&lt;/p&gt;

&lt;p&gt;这里有一个问题，如果要匹配的字串长度比较长，是1000位，那么，每一次匹配都得对这1000位进行计算。这时的复杂度就是O(n*m)了，而且还需要计算，这比暴力判断算法还要复杂。仔细观察，发现每次计算的长度的都一样，而且是依次进行的，前后两个串12和23有共同部分2，如果子串sep长度是1000，那么前后两次子串的共同部分就是999的长度了。所以，只需要保留共同部分，把前面的头去掉，后面的尾补上，就能够完成新串的计算。&lt;/p&gt;

&lt;p&gt;上面的例子，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1*E+2)*E+3-1*E*E = 2*E+3&lt;/code&gt;。如此一来，一个简单的运算，就能够得到新串的哈希值。Golang里面strings包的实现：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const primeRK = 16777619

// hashstr returns the hash and the appropriate multiplicative
// factor for use in Rabin-Karp algorithm.
func hashstr(sep string) (uint32, uint32) {
	hash := uint32(0)
	for i := 0; i &amp;lt; len(sep); i++ {
		hash = hash*primeRK + uint32(sep[i])
	}
	var pow, sq uint32 = 1, primeRK
	for i := len(sep); i &amp;gt; 0; i &amp;gt;&amp;gt;= 1 {
		if i&amp;amp;1 != 0 {
			pow *= sq
		}
		// 只有32位，超出范围的会被丢掉
		sq *= sq
	}
	return hash, pow
}

func Index(s, sep string) int {
	n := len(sep)
	// Hash sep.
	hashsep, pow := hashstr(sep)
	var h uint32
	for i := 0; i &amp;lt; n; i++ {
		h = h*primeRK + uint32(s[i])
	}
	if h == hashsep &amp;amp;&amp;amp; s[:n] == sep {
		return 0
	}
	for i := n; i &amp;lt; len(s); {
		h *= primeRK
		h += uint32(s[i])
		h -= pow * uint32(s[i-n])
		i++
		if h == hashsep &amp;amp;&amp;amp; s[i-n:i] == sep {
			return i - n
		}
	}
	return -1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环，i表示计算新串新加的字符，也就是例子里面的3。所以，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i-n+1&lt;/code&gt;就是新串的头地址。&lt;/p&gt;

&lt;p&gt;一般，按照这种算法算出的值都会超过整形的范围。上面的16777619，计算一下平方就超&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unit32&lt;/code&gt;的范围了。常用的做法，是再取一个比较大的质数，求余，用余数作为哈希值。这样就能保证高位不会被截取丢弃了。而Golang包里的代码是不操作，直接丢弃，太霸气了。&lt;/p&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/index.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.ituring.com.cn/article/1759&quot;&gt;图说Rabin-Karp字符串查找算法 - 图灵社区&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://blog.csdn.net/lalor/article/details/7318401&quot;&gt;字符串匹配之Rabin-Karp算法 - lmx077&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://www.cnblogs.com/China-Dragon/archive/2010/04/15/1712792.html&quot;&gt;Rabin-Karp 字符串搜索算法 - 貔貅&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【4】&lt;a href=&quot;http://golang.org/src/pkg/strings/strings.go&quot;&gt;Source file src/pkg/strings/strings.go - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang——append的可变参数</title>
   <link href="https://blog.cyeam.com/golang/2014/08/07/go_append"/>
   <updated>2014-08-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/07/go_append</id>
   <content type="html">&lt;p&gt;合并slice，跟合并数组一样，是比较常见的操作。映像中C++运算符重载以后，直接用加号就可以了。Golang果断不行。先开始用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;解决。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func copy(dst, src []Type) int
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;从来没用过这个函数，返回值和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;还不一样，返回的是复制的数目。而且只能针对slice操作，不支持其他类型。能参数表也能看出，两个参数必须是同一种类型，不可以是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]Type&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]interface{}&lt;/code&gt;这种形式。此函数也不是追加到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dst&lt;/code&gt;后面，而是从头开始复制。如果要追加到后面，还需要指出位置信息。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;实现的合并数组完整代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a := []int{1, 2, 3, 4}
b := []int{5, 6, 7}
c := make([]int, len(a)+len(b))
copy(c, a)
copy(c[len(a):], b)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy&lt;/code&gt;的过程中，如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dst&lt;/code&gt;空间不足，是不会自动追加空间的。所以，合并之前要申请一个足够的空间。接着就是复制两次。第二次复制还需要指出slice位置，防止被覆盖。&lt;/p&gt;

&lt;p&gt;这个代码虽然功能上没问题，但是总觉得比较脏。后来无意中发现，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;append&lt;/code&gt;函数的参数是支持变长参数类型的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func append(slice []Type, elems ...Type) []Type
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，三行复杂的代码变成了一行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a := []int{1, 2, 3, 4}
b := []int{5, 6, 7}
d := append(a, b...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;参考结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0xfeee1f74
0xfeee1f38
[1 2 3 4 5 6 7]
0xfeee1f2c
[1 2 3 4 5 6 7]
0xfeee1f20
[5]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;本文所涉及到的完整源码请&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_append.go&quot;&gt;参考&lt;/a&gt;。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;最近在看&lt;a href=&quot;https://golang.org/doc/effective_go.html&quot;&gt;Effective Go&lt;/a&gt;，觉得不错，推荐给大家。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://bbs.studygolang.com/thread-9-1-1.html&quot;&gt;slice 删除一个或多个项 - Go中文社区&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;https://1234n.com/?post/ihu5vz&quot;&gt;Go语言中合并slice - 达达的主页&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;https://golang.org/pkg/builtin/#append&quot;&gt;Package builtin - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【4】&lt;a href=&quot;https://my.oschina.net/lxpan/blog/87432&quot;&gt;数组(Array)和切片(Slice) - 烈冰&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【5】&lt;a href=&quot;https://blog.golang.org/go-slices-usage-and-internals&quot;&gt;Go Slices: usage and internals - The Go Blog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【6】&lt;a href=&quot;https://ieqi.net/2013/03/11/%E7%9C%8B%E8%8C%83%E4%BE%8B%E5%AD%A6-golang-%EF%BC%88%E5%8D%81%E5%9B%9B%EF%BC%89-%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0/&quot;&gt;看范例学 Golang （十四）- 可变参数函数 - G_will’s Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang——for循环的两种用法</title>
   <link href="https://blog.cyeam.com/golang/2014/08/06/go_for"/>
   <updated>2014-08-06T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/08/06/go_for</id>
   <content type="html">&lt;p&gt;从大一开始学C++，所接触过的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环只有一种方式，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for (int i = 0; i &amp;lt; count; i++) {...}&lt;/code&gt;。后来接触了Java，知道Java 5 引入了一种新特性，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for (a : aa)&lt;/code&gt;遍历整个数组。虽然知道，但是也没用过，因为之前那种方法足够了。&lt;/p&gt;

&lt;p&gt;现在主力语言是Go。Go也支持传统的写法&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for i := 0; i &amp;lt; count; i++ {...}&lt;/code&gt;。同样，还有一种&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for i, a := range aa {...}&lt;/code&gt;。这个跟Java相比，除了能够遍历，还能得到当前遍历的索引值，所以在Go中，更倾向于后面这种方法，灵活度更高。&lt;/p&gt;

&lt;p&gt;今天要对一个数据进行遍历，对每个元素进行稍加修改，才发现，原来这两种方式是有很大区别的。当时使用第二种方式修改之后，一直发现没有修改成功。后来进过调试，才发现是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环的问题。&lt;/p&gt;

&lt;p&gt;测试代码在&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_for.go&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a := []string{}
a = append(a, &quot;hello&quot;)
a = append(a, &quot;, &quot;)
a = append(a, &quot;world&quot;)
fmt.Println(a)

for i := 0; i &amp;lt; len(a); i++ {
	fmt.Println(&amp;amp;a[i])
}

for _, aa := range a {
	aa += &quot;@&quot;
	fmt.Println(&amp;amp;aa)
}
for i := 0; i &amp;lt; len(a); i++ {
	a[i] += &quot;@&quot;
	fmt.Println(&amp;amp;a[i])
}
fmt.Println(a)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;range&lt;/code&gt;方式获得的变量，打印出它的地址，和之前的数组&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt;的地址相比，是不同的地址。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[hello ,  world]
0x10322280
0x10322288
0x10322290
0x1030e100
0x1030e100
0x1030e100
0x10322280
0x10322288
0x10322290
[hello@ , @ world@]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里就可以证明，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;range&lt;/code&gt;方式获得的变量，和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a[i]&lt;/code&gt;变量是不同的，只是里面存的值一样而已。所以，如果做出了修改操作，也不会修改到原来的数组。&lt;/p&gt;

&lt;p&gt;果断再去看一下大Java的&lt;a href=&quot;https://github.com/mnhkahn/java_code/blob/master/TestFor.java&quot;&gt;语法&lt;/a&gt;。结果也是一样的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class TestFor {

	public static void main(String[] args) {
		String[] a = {&quot;hello&quot;, &quot;, &quot;, &quot;world&quot;};
		System.out.println(a[0] + a[1] + a[2]);

		for (String aa : a) {
			aa += &quot;@&quot;;
		}
		System.out.println(a[0] + a[1] + a[2]);
	}

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果遍历数组，还需要对数组元素进行操作，建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取，可以简化成高级for循环，它的出现为了简化书写。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;传统的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;，写起来不太方便，但是功能强大；如果只是单纯得遍历，可以使用新的写法，简化代码。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;推荐阅读：&lt;a href=&quot;https://blog.cyeam.com/golang/2018/10/30/for-interals&quot;&gt;「通过两个例子介绍一下 Golang For Range 循环原理
」&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://blog.sina.com.cn/s/blog_8be86ad30101azoe.html&quot;&gt;JAVA5.0新特性 - 子默&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang——json数据处理</title>
   <link href="https://blog.cyeam.com/json/2014/08/04/go_json"/>
   <updated>2014-08-04T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/json/2014/08/04/go_json</id>
   <content type="html">&lt;p&gt;今天让官方文档虐了几条街。&lt;/p&gt;

&lt;p&gt;需要能够对JSON数据进行编码，将内部的中文字符串转成Unicode编码。编码这种东西接触也不少了，随便搜一下就能解决。果断去搜了一下。本文所有&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_unicode.go&quot;&gt;编码&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rs := []rune(&quot;golang中文unicode编码&quot;)
j := &quot;&quot;
html := &quot;&quot;
for _, r := range rs {
	rint := int(r)
	if rint &amp;lt; 128 {
		j += string(r)
		html += string(r)
	} else {
		j += &quot;\\u&quot; + strconv.FormatInt(int64(rint), 16) // json
		html += &quot;&amp;amp;#&quot; + strconv.Itoa(int(r)) + &quot;;&quot;       // 网页
	}
}
fmt.Printf(&quot;JSON: %s\n&quot;, j)
fmt.Printf(&quot;HTML: %s\n&quot;, html)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Golang里面，所有UTF8字符串里的单个字符，可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt;类型表示，经过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte(str)&lt;/code&gt;转换成数组之后，就能进行遍历获取。而对于Unicode，Golang提供了另一种数据类型&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rune&lt;/code&gt;，经过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]rune(str)&lt;/code&gt;转换之后，就能获取单个Unicode字符。对于英文字符以及英文标点，Unicode编码不变，而中文编码，转成16进制即可。&lt;/p&gt;

&lt;p&gt;我们一般认为：&lt;strong&gt;&lt;em&gt;UTF-16就是Unicode&lt;/em&gt;&lt;/strong&gt;，以16位两字节为单位，最少为两字节，最多4个字节。&lt;/p&gt;

&lt;p&gt;所以，一般情况下，转出的Unicode是4个16进制数字（2字节）组成。最后，为其追加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\u&lt;/code&gt;头即可完成。&lt;/p&gt;

&lt;p&gt;写完之后，就准备这么转了。结构体数据拼接完之后，把有中文的项单独编码一下。现在想想，硬编码太渣了。&lt;/p&gt;

&lt;p&gt;看看官方文档的说法：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Marshal traverses the value v recursively. If an encountered value implements the Marshaler interface and is not a nil pointer, Marshal calls its MarshalJSON method to produce JSON. The nil pointer exception is not strictly necessary but mimics a similar, necessary exception in the behavior of UnmarshalJSON.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json.Marshal&lt;/code&gt;方法之后，会递归的访问传入的结构体的每个项，如果遍历到的项实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Marshaler&lt;/code&gt;的方法，并且该项不是空指针，将会自动调用实现的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MarshalJSON&lt;/code&gt;方法。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type QuoteString struct {
	QString string
}

func (q QuoteString) MarshalJSON() ([]byte, error) {
	return []byte(strconv.QuoteToASCII(q.QString)), nil
}

type ColorGroup struct {
	ID     int         `json:&quot;id,string&quot;`
	Name   QuoteString `json:&quot;name&quot;`
	Colors []string
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面定义了一个结构体&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QuoteString&lt;/code&gt;，它实现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Marshaler&lt;/code&gt;，所以会自动进行Unicode编码。转码，用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strconv&lt;/code&gt;包里的函数&lt;a href=&quot;http://golang.org/src/pkg/strconv/quote.go&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QuoteToASCII&lt;/code&gt;&lt;/a&gt;。这个方法应该是我上面提供的升级版。&lt;/p&gt;

&lt;p&gt;源码很重要。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/pkg/encoding/json/&quot;&gt;Package json - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://www.dotcoo.com/golang-unicode-encode&quot;&gt;golang中文unicode编码 - 豆蔻&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://golang.org/pkg/strconv/#QuoteToASCII&quot;&gt;Package strconv - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>布隆过滤器</title>
   <link href="https://blog.cyeam.com/hash/2014/07/30/bloomfilter"/>
   <updated>2014-07-30T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/hash/2014/07/30/bloomfilter</id>
   <content type="html">&lt;p&gt;这篇文章可能涉及到一些拓扑知识，可以参考之前的文章：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/hash/2014/07/28/fnv_md5&quot;&gt;常见哈希函数FNV和MD5&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;httsp://blog.cyeam.com/hash/2014/07/29/go_bytearraytoint&quot;&gt;Golang binary包——byte数组如何转int？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;布隆过滤器的原理不算复杂。对数据进行查找，简单点的可以直接遍历；对于拍好顺序的数据，可以使用二分查找等。但这些方法的时间复杂度都较高，分别是O(n)和O(logn)。无法对大量乱序的数据进行快速的查找。&lt;/p&gt;

&lt;p&gt;哈希，将给定的数据通过哈希函数得到一个唯一的值，此值可以作为数据的唯一标识，只要通过该标识，再通过哈希函数逆向计算，就能还原出来原始的数据。在前面的&lt;a href=&quot;https://blog.cyeam.com/web/2014/07/24/short_url&quot;&gt;网址压缩的调研分析&lt;/a&gt;里面介绍的压缩网址的方法，其实也就是一种哈希，只不过借助了数据库的支持。&lt;/p&gt;

&lt;p&gt;布隆过滤器就是借助了哈希实现的过滤算法。通过将黑名单的数据哈希之后，可以得到一个数据。申请一个数组空间，长度是黑名单的长度。将前面的数据转换成数组中唯一的一个单元，并且标记为1，标识此位置对应的数据是黑名单。如此一来，过滤的时候，将数据哈希之后，去前面的数组当中查找，如果对应的位置值为1，表明需要被过滤，如果是0，则不需要。如果黑名单有一亿个黑名单数据，每个数据需要1bit来记录，最后也就会占用1G空间，放在内存里面妥妥的。而哈希函数计算时间可以认为是O(1)，所以过滤算法效率也很高。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/bloomfilter.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然而，哈希算法不可能保证不发生碰撞。尤其是黑名单这种字符串，可能会包含各种字符，也不能单纯的当作数字来处理。布隆过滤器的做法就是通过调用多个哈希函数，降低碰撞的概率。比如有3个哈希函数，碰撞概率都是10%，并且哈希方式不同，那么一个数据通过三次哈希得到的碰撞概率是单独哈希一次的 10%×10%×10%/10%=1%，也就是说，原本哈希一次碰撞概率为10%，现在三次是0.1%。黑名单误过滤的概率大大降低，而存在于黑名单当中的肯定会被过滤掉。而三次哈希的结果也直接放进之前的数组里即可。判断是否在黑名单当中，三次结果计算结果都匹配才需要过滤。&lt;/p&gt;

&lt;p&gt;上面提到的需要用三个哈希函数只是举例子，数目没有限制。而哈希函数的选取，也会是一个问题。这些明天再说吧。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang binary包——byte数组如何转int？</title>
   <link href="https://blog.cyeam.com/hash/2014/07/29/go_bytearraytoint"/>
   <updated>2014-07-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/hash/2014/07/29/go_bytearraytoint</id>
   <content type="html">&lt;p&gt;在C语言笔试的时候，比较喜欢考这个东西，如何将一个char数组转成int类型。当年看过，不过早就忘记了。后来看到这种东西&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binary.BigEndian.Uint32(a)&lt;/code&gt;，直接瞎了。后来去看&lt;a href=&quot;http://golang.org/pkg/encoding/binary/&quot;&gt;文档&lt;/a&gt;，看了半天也没搞明白。&lt;/p&gt;

&lt;p&gt;在这里直接说一下，&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_uint8arraytouint32.go&quot;&gt;源码&lt;/a&gt;。下面这个是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint8&lt;/code&gt;，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt;数组，大小为4，转换成int32的代码。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;
import &quot;encoding/binary&quot;

func main() {
	var a []byte = []byte{0, 1, 2, 3}
	fmt.Println(a)
	fmt.Println(binary.BigEndian.Uint32(a))
	fmt.Println(binary.LittleEndian.Uint32(a))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;执行结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[0 1 2 3]
66051
50462976
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;转换有两种不同的方式，也就是大端和小端。大端就是内存中低地址对应着整数的高位。按照上面的例子说，就是按照0123的顺序拼成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int32&lt;/code&gt;，整数的最高8位是0，接着是1，以此类推，所以是66051。小端就是反过来，最高8位是3，也就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00000101&lt;/code&gt;，这样最后得到50462976。&lt;/p&gt;

&lt;p&gt;在Golang源码目录&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/binary.go&lt;/code&gt;下，有函数的实现。不过里面没有对byte数组长度的检查，如果传入的数组长度小于4，自然会报错：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;panic: runtime error: index out of range&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func (bigEndian) Uint32(b []byte) uint32 {
	return uint32(b[3]) | uint32(b[2])&amp;lt;&amp;lt;8 | uint32(b[1])&amp;lt;&amp;lt;16 | uint32(b[0])&amp;lt;&amp;lt;24
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面讲的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigEndian&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LittleEndian&lt;/code&gt;都是实现类，都是实现的类&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteOrder&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type ByteOrder interface {
    Uint16([]byte) uint16
    Uint32([]byte) uint32
    Uint64([]byte) uint64
    PutUint16([]byte, uint16)
    PutUint32([]byte, uint32)
    PutUint64([]byte, uint64)
    String() string
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.cppblog.com/tx7do/archive/2009/01/06/71276.html&quot;&gt;字节序（Endian），大端（Big-Endian），小端（Little-Endian） - 牵着老婆满街逛&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://golang.org/pkg/encoding/binary/&quot;&gt;Package binary - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>常见哈希函数FNV和MD5</title>
   <link href="https://blog.cyeam.com/hash/2014/07/28/fnv_md5"/>
   <updated>2014-07-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/hash/2014/07/28/fnv_md5</id>
   <content type="html">&lt;p&gt;介绍哈希函数之前，先说一下Golang的哈希结果。在包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/hash/&lt;/code&gt;下的hash.go文件，定义了哈希函数的接口。所有哈希函数都要实现此接口。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Hash is the common interface implemented by all hash functions.
type Hash interface {
	// Write (via the embedded io.Writer interface) adds more data to the running hash.
	// It never returns an error.
	io.Writer

	// Sum appends the current hash to b and returns the resulting slice.
	// It does not change the underlying hash state.
	Sum(b []byte) []byte

	// Reset resets the Hash to its initial state.
	Reset()

	// Size returns the number of bytes Sum will return.
	Size() int

	// BlockSize returns the hash&apos;s underlying block size.
	// The Write method must be able to accept any amount
	// of data, but it may operate more efficiently if all writes
	// are a multiple of the block size.
	BlockSize() int
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此接口提供了常用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sum&lt;/code&gt;等函数，此外，它还继承了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io.Writer&lt;/code&gt;接口。该接口定义在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io&lt;/code&gt;包里的io.go文件里。该接口定义了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;写入函数。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 &amp;lt;= n &amp;lt;= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n &amp;lt; len(p).
type Writer interface {
	Write(p []byte) (n int, err error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;常见的哈希函数调用过程大体如此，初始化hash.Hash之后，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Write&lt;/code&gt;写入数据，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sum&lt;/code&gt;函数得到哈希结果。其中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sum(b []byte) []byte&lt;/code&gt;函数的参数b，当传入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt;的时候，将直接返回哈希结果，而b不为空的时候，就会将哈希结果追加到b后面。&lt;/p&gt;

&lt;p&gt;MD5的加密算法调用如下，&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_md5.go&quot;&gt;源码&lt;/a&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;crypto/md5&quot;
	&quot;encoding/hex&quot;
)

func main() {
	m := md5.New()
	m.Write([]byte(&quot;hello, world&quot;))
	s := hex.EncodeToString(m.Sum(nil))
	println(s)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;FNV的加密调用也很简单，&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_fnv.go&quot;&gt;源码&lt;/a&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fnv.New32()&lt;/code&gt;使用的是32位的FNV-1算法进行的哈希。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;
import &quot;hash/fnv&quot;
import &quot;encoding/hex&quot;

func main() {
	a := fnv.New32()
	a.Write([]byte(&quot;hello&quot;))
	fmt.Println(hex.EncodeToString(a.Sum(nil)))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;FNV是一种非加密的哈希算法，支持32位、64位、128位、256位、512位和1024位的哈希。碰撞还算比较低，具体的碰撞的对比可以网上查一下。&lt;/p&gt;

&lt;p&gt;直接哈希得到的都是整数类型，直接输出的话就会是乱码了，如果想查看正常的哈希结果，需要将结果转成对应表示的字符即可。使用包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encoding/hex&lt;/code&gt;可以帮助实现十六进制编码。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;补充一下。。。基础不好的坑自己。。。&lt;/p&gt;

&lt;p&gt;MD5加密结果是32个16进制数，而一个字节能表示的范围是0～255，256=16×16，也就是两个十六进制数代表一个字节。所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sum&lt;/code&gt;函数得到的byte数组长度是16，纠结了我半天。。。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/pkg/hash/fnv/&quot;&gt;Package fnv - The Go Programming Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function&quot;&gt;Fowler–Noll–Vo hash function - wikipedia&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://blog.csdn.net/taochenchang/article/details/7319739&quot;&gt;FNV哈希算法 - 思思入code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>网址压缩的调研分析（续）</title>
   <link href="https://blog.cyeam.com/web/2014/07/25/short_url2"/>
   <updated>2014-07-25T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/web/2014/07/25/short_url2</id>
   <content type="html">&lt;p&gt;上文提到在GitHub找到一个Python实现的打散ID压缩成62进制的&lt;a href=&quot;https://github.com/mozillazg/ShortURL/blob/master/shorturl/libs/short_url.py#L51&quot;&gt;算法&lt;/a&gt;，今天抽空翻译成了Golang。虽然说代码都看懂了，但是动手翻译的时候发现还是有一些细节的东西挺有意思的。代码放到了&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/short_url.go&quot;&gt;go_code项目&lt;/a&gt;下。源码也贴在这里。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import (
	&quot;bitbucket.org/tebeka/base62&quot;
	&quot;fmt&quot;
)

var (
	DEFAULT_BLOCK_SIZE = uint(24)
	DEFAULT_ENCODER    *UrlEncoder
	MIN_LENGTH         = 5
)

type UrlEncoder struct {
	BlockSize uint
	Mask      uint64
	Mapping   []uint
}

func NewUrlEncoder(blocksize uint) (u *UrlEncoder) {
	u = new(UrlEncoder)
	u.BlockSize = blocksize
	u.Mask = (1 &amp;lt;&amp;lt; blocksize) - 1
	for i := uint(0); i &amp;lt; blocksize; i++ {
		u.Mapping = append(u.Mapping, blocksize-i-1)
	}
	return u
}

func (this *UrlEncoder) Encode(n uint64) uint64 {
	return (n &amp;amp; ^this.Mask) | this.encode(n&amp;amp;this.Mask)
}

func (this *UrlEncoder) encode(n uint64) (result uint64) {
	for i, b := range this.Mapping {
		if n&amp;amp;(1&amp;lt;&amp;lt;uint(i)) &amp;gt; 0 {
			result |= (1 &amp;lt;&amp;lt; b)
		}
	}
	return result

}

func (this *UrlEncoder) Decode(n uint64) uint64 {
	return (n &amp;amp; ^this.Mask) | this.decode(n&amp;amp;this.Mask)
}

func (this *UrlEncoder) decode(n uint64) (result uint64) {
	for i, b := range this.Mapping {
		if n&amp;amp;(1&amp;lt;&amp;lt;uint(b)) &amp;gt; 0 {
			result |= (1 &amp;lt;&amp;lt; uint(i))
		}
	}
	return result
}

func (this *UrlEncoder) Enbase(n uint64) string {
	return base62.Encode(n)
}

func (this *UrlEncoder) Debase(n string) uint64 {
	return base62.Decode(n)
}

func (this *UrlEncoder) EncodeUrl(n uint64) (short_url string) {
	return DEFAULT_ENCODER.Enbase(DEFAULT_ENCODER.Encode(n))
}

func DecodeUrl(short_url string) (n uint64) {
	return DEFAULT_ENCODER.Decode(DEFAULT_ENCODER.Debase(short_url))
}

func init() {
	DEFAULT_ENCODER = NewUrlEncoder(DEFAULT_BLOCK_SIZE)
}

func Disperse(i uint64) (disperse_i uint64) {
	fmt.Println(DEFAULT_ENCODER)
	a := DEFAULT_ENCODER.Encode(i)
	b := DEFAULT_ENCODER.Enbase(a)
	c := DEFAULT_ENCODER.Debase(b)
	disperse_i = DEFAULT_ENCODER.Decode(c)
	fmt.Println(i, a, b, c, disperse_i)
	return disperse_i
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;里面用到了别人实现的62进制压缩包。实现原理在上一篇文章&lt;a href=&quot;https://blog.cyeam.com/web/2014/07/24/short_url&quot;&gt;《网址压缩的调研分析》&lt;/a&gt;的最后一篇文章里面已经说得很清楚了，可以参考那篇文章。&lt;/p&gt;

&lt;p&gt;下面接着说一下短网址的调研情况。这之前，都只是讲了算法的实现，而具体的存储数据库还没有介绍。GitHub上面与此相关的项目都是使用MySQL实现的。用数据库实现确实很方便，表很简单，两个字段ID和网址就可以了。但是，在数据结构是如此简单的情况下，一般都是使用Redis来实现。Redis是将Key缓存在内存当中，查询速度快。然而，MySQL有一个Redis无法比拟的优势：Redis只能通过Key查询Value的值，而数据库可以实现双向映射。在用户添加网址的时候，需要去库里查询是否已经存在于库中了，这时需要使用网址来作为查询字段；而在解析短网址的时候，还会需要通过ID查询原始网址。这里就用到了双向查询。&lt;/p&gt;

&lt;p&gt;看来使用ID进行编码短网址的方式，目前只能使用传统数据库了。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://github.com/mozillazg/ShortURL/blob/master/shorturl/libs/short_url.py#L51&quot;&gt;short_url.py - mozillazg/ShortURL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>网址压缩的调研分析</title>
   <link href="https://blog.cyeam.com/web/2014/07/24/short_url"/>
   <updated>2014-07-24T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/web/2014/07/24/short_url</id>
   <content type="html">&lt;p&gt;现在新浪、谷歌都提供了网址压缩服务，访问被压缩的网址，可以自动跳转到目标网址。对于访问者来说，除了网址节约了一些存储空间，别的也没啥好处吧，不过确实越来越火。&lt;/p&gt;

&lt;p&gt;要开发短网址，一般有两种思路。第一种，就是生成个码，把码和原始网址的对应关系放到数据库里面，压缩的时候存进去，读取的时候去库里找；还有一种方式是把原始网址按照一定方式压缩，压成比较短的。&lt;/p&gt;

&lt;p&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-1651120361108148&quot; data-ad-slot=&quot;4918476613&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;先开始肯定是想着用第二种方式，不用数据库，只用算法就能实现压缩，听着就很牛逼。有人说可以用gzip压缩，用Python试了一下，压出来的比原始的还长。。。要是用AES这种去压，估计也短不了。看来必须要借助数据库了。&lt;/p&gt;

&lt;p&gt;用数据库，肯定要存一个原始网址，问题就是解决短码（就是短网址后面跟着的那个乱码）的问题。比较传统的方法，就是用整数来存，从1开始，这样。先开始出来的就是00001、00002这种，而且短码都是递增的，随机不起来，看着也不好看。&lt;/p&gt;

&lt;p&gt;压缩文本还有一种思路，将文本视为数字，增加这些数字的底数。如果原本的底数是10进制，升到100进制，长度肯定缩短。进制越高越好。下面就是要看HTTP自身URL支持多少字符。HTTP的URL支持0到9十个数字，52个字母（包括大小写），还有一些其它字符，如下表所示。但是这些特殊字符在URL里面都是用用途的，所以不能用来编码。那么，显而易见，只能使用数字和大小写字母，也就是62个。将URL压缩成62进制，是最短的压缩方式了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;除了普通的字母，数字，中文，还有特殊字符，但是规范的使用应该是使用字符转义。&lt;br /&gt;
“+”	URL 中+号表示空格	%2B&lt;br /&gt;
“空格”	URL中的空格可以用+号或者编码	%20&lt;br /&gt;
“/”	分隔目录和子目录	%2F&lt;br /&gt;
“?”	分隔实际的 URL 和参数	%3F&lt;br /&gt;
“%”	指定特殊字符	%25&lt;br /&gt;
“#”	表示书签	%23&lt;br /&gt;
“&amp;amp;”	URL 中指定的参数间的分隔符	%26&lt;br /&gt;
“=”	URL 中指定参数的值	%3D&lt;br /&gt;
“&quot; 表示目录路径 %5C&lt;br /&gt;
“.” 句号 %2E&lt;br /&gt;
“:” 冒号 %3A&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;MD5加密，可以将任意文本生成一个32位的数字，每一位用16进制表示。然后转成上面提到的62进制数字，这样既不会发生碰撞，又能够起到压缩效果。试了一下，压缩完长度是12。去查了一下，谷歌提供的短网址提供5位压缩码，而新浪提供的是7位编码。看来这样还是不行。&lt;/p&gt;

&lt;p&gt;最后，短码就只能是用ID来表示了，从0算起，这样可以保证长度不会太长，并且不会碰撞。5位62进制数字，可以表示62^5个短网址，算下来是9亿多个。我觉得妥妥够用了。&lt;/p&gt;

&lt;p&gt;最后还有一个问题，就是上面提到的，ID是连续分配的，短网址也无法打散。去&lt;a href=&quot;https://github.com/mozillazg/ShortURL/blob/master/shorturl/libs/short_url.py#L51&quot;&gt;Github&lt;/a&gt;找了一个用Python写的打散算法，设定一个区间，将区间内的数字反转。比如，设定区间位8，要打散的ID是100000001，共9位，那么，反转之后就是110000000。这样，下一个数字100000010，打散之后是101000000。再对其进行62进制压缩之后，两个连续的URL也可以保证短网址完全打散，并且不会发生碰撞。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://github.com/mozillazg/ShortURL/blob/master/shorturl/libs/short_url.py#L51&quot;&gt;short_url.py - mozillazg/ShortURL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://segmentfault.com/q/1010000000094850&quot;&gt;url中可以包括哪些字符 - segmentfault问答&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang开发Thrift接口</title>
   <link href="https://blog.cyeam.com/golang/2014/07/22/go_thrift"/>
   <updated>2014-07-22T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/22/go_thrift</id>
   <content type="html">&lt;p&gt;三个月没在公司，回来后发现公司内部已经用上了facebook开源的Apache Thrift。大概看了一下介绍，传统接口编写是使用json或者xml作为信息格式进行传输。一般Web Service里面，SOAP这种，使用的就是xml（不过我从来没用过。。）；而轻量级网络服务REST，则用的是json作为传输媒介。json相较于xml，传输的内容变少了许多，传输更加便捷。这两种都是基于HTTP的传输方式。&lt;/p&gt;

&lt;p&gt;而Apache Thrift，是更加轻量级的Web Service。本科做网络游戏的时候也接触过，叫RPC（Remote Procudure Call，远程过程调用）。这应该也算是一种RPC吧。调用起来更加简单，根本不需要发送HTTP请求，解析返回结果这些操作。只需要预先定义好类和对应函数，调用的时候创建一个类，调用函数即可。返回内容也是直接映射到类当中的。&lt;/p&gt;

&lt;p&gt;完整地了解了一下Apache Thrift，这篇文章主要介绍hello, world的编写。如果你之前还没接触过Thrift，可以参考我之前写过的几篇文章所谓参考。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Apache Thrift安装——&lt;a href=&quot;https://blog.cyeam.com/kaleidoscope/2014/07/03/fuckgfwforgo&quot;&gt;如何离线完成go get——安装Apache Thrift有感&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;编译&lt;a href=&quot;https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=tree;f=tutorial/go/src;h=6773391364ad28cdf5cd359e363f66792907d684;hb=HEAD&quot;&gt;官方例子&lt;/a&gt;时会涉及到命令行参数的解析——&lt;a href=&quot;https://blog.cyeam.com/golang/2014/07/20/go_flag&quot;&gt;Golang 处理命令行启动参数&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;服务器端要实现定义好的接口，接口相关知识——&lt;a href=&quot;https://blog.cyeam.com/golang/2014/07/20/go_inte&quot;&gt;Golang 接口实现&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apache Thrift给了一个完整的例子，包含很多语言版本的，Golang的完整代码可以看&lt;a href=&quot;https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob;f=tutorial/go/src/client.go;h=a497d7f8b19a3e691786a7d8e634013de220c6bb;hb=HEAD&quot;&gt;这里&lt;/a&gt;。文档方面官网没对这个例子介绍多少，代码也没贴全，找到这个就好办多了，直接复制下来编译就行了。其他语言的实现，返回向上一级目录即可。&lt;/p&gt;

&lt;p&gt;这篇文章要讲的hello, world 主要根据developWorks的文章&lt;a href=&quot;https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/&quot;&gt;Apache Thrift - 可伸缩的跨语言服务开发框架&lt;/a&gt;进行介绍。&lt;/p&gt;

&lt;p&gt;Thrfit文件定义如下：&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_thrift/Hello.thrift&quot;&gt;Hello.thrift&lt;/a&gt;。接口Hello，包含五个函数，五个都必须实现，否则编译器会认为该接口并没有被实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;namespace java service.demo 
service Hello{ 
	string helloString(1:string para) 
	i32 helloInt(1:i32 para) 
	bool helloBoolean(1:bool para) 
	void helloVoid() 
	string helloNull() 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.thrfit&lt;/code&gt;文件就是定义了一套接口。通过命令可以生成对应语言的接口定义。会在当前目录下生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gen-go&lt;/code&gt;文件夹，把里面的内容放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gopath&lt;/code&gt;里即可。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ thrift --gen go Hello.thrift
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;自动生成的接口定义如下&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello.go&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type Hello interface {
	// Parameters:
	//  - Para
	HelloString(para string) (r string, err error)
	// Parameters:
	//  - Para
	HelloInt(para int32) (r int32, err error)
	// Parameters:
	//  - Para
	HelloBoolean(para bool) (r bool, err error)
	HelloVoid() (err error)
	HelloNull() (r string, err error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上述操作用来生成接口，有了接口之后，就是要实现接口、开发客户端和开发服务器端这三个部分。参考的例子是使用大Java实现的，果断用Go重写&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/feature/thrift/test_thrift/helloimpl.go&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloimpl.go&lt;/code&gt;&lt;/a&gt;。Go语言实现接口可以参考&lt;a href=&quot;https://blog.cyeam.com/golang/2014/07/20/go_inte&quot;&gt;Golang 接口实现&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type HelloHandler struct {
}

func NewHelloHandler() *HelloHandler {
	return &amp;amp;HelloHandler{}
}

func (h *HelloHandler) HelloString(para string) (string, error) {
	return &quot;hello, world&quot;, nil
}

func (h *HelloHandler) HelloBoolean(para bool) (r bool, err error) {
	return para, nil
}

func (h *HelloHandler) HelloInt(para int32) (r int32, err error) {
	return para, nil
}

func (h *HelloHandler) HelloVoid() (err error) {
	return nil
}

func (h *HelloHandler) HelloNull() (r string, err error) {
	return &quot;hello null&quot;, nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;服务器端的实现&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/feature/thrift/test_thrift/helloserver.go&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloserver.go&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func runServer(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string) error {
	var err error
	transport, err = thrift.NewTServerSocket(addr)

	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf(&quot;%T\n&quot;, transport)
	handler := NewHelloHandler()
	processor := hello.NewHelloProcessor(handler)
	server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)

	fmt.Println(&quot;Starting the simple server... on &quot;, addr)
	return server.Serve()
}

func main() {
	transportFactory := thrift.NewTTransportFactory()
	protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
	transportFactory = thrift.NewTBufferedTransportFactory(8192)
	runServer(transportFactory, protocolFactory, &quot;localhost:9090&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动服务器&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run helloserver.go helloimpl.go
Starting the simple server... on localhost:9090
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;客户端代码&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/feature/thrift/test_thrift/helloclient.go&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;helloclient.go&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func handleClient(client *hello.HelloClient) error {
	str, err := client.HelloString(&quot;&quot;)
	fmt.Println(str)
	fmt.Println(&quot;----------------&quot;)
	return err
}

func runClient(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string) error {
	var transport thrift.TTransport
	var err error

	transport, err = thrift.NewTSocket(addr)

	if err != nil {
		fmt.Println(&quot;Error opening socket:&quot;, err)
		return err
	}
	transport = transportFactory.GetTransport(transport)
	defer transport.Close()
	if err := transport.Open(); err != nil {
		return err
	}
	fmt.Println(transport, protocolFactory)
	return handleClient(hello.NewHelloClientFactory(transport, protocolFactory))
}

func main() {
	transportFactory := thrift.NewTTransportFactory()
	protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
	transportFactory = thrift.NewTBufferedTransportFactory(8192)
	runClient(transportFactory, protocolFactory, &quot;localhost:9090&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动客户端及运行结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run helloclient.go
hello, world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/&quot;&gt;Apache Thrift - 可伸缩的跨语言服务开发框架 - developWorks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 接口实现</title>
   <link href="https://blog.cyeam.com/golang/2014/07/20/go_inte"/>
   <updated>2014-07-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/20/go_inte</id>
   <content type="html">&lt;p&gt;面向对象程序语言已经是非常普及了。再过去的十几年内，面向对象的代表Java语句一直稳坐编程语言前三名的宝座。面向对象开发的学习，是每一个程序员的必修课。&lt;/p&gt;

&lt;p&gt;对于我来说，学完C++和Java之后，看了一堆各种特性，却发现花了不少时间，项目却依然没啥功能。。。由于面向对象的引入，使得编程语言一下子多了好多语法特性，致使开发效率低下。尤其是像我这种学渣，体现得尤为明显。&lt;/p&gt;

&lt;p&gt;后来，大家意识到一个问题：编程主要是用来解决一个问题，面向过程最符合人类正常的思维，也是最快的用来解决问题的方式；然而，在工程层面，面向对象的优势更加明显，虽然OO会使得开发效率降低，但是高度的解耦还是能够做到复用和稳定。所以一般大公司会采用Java作为主力开发语言，例如阿里巴巴、IBM，而一般创业小公司，喜欢用便捷的开发语言，例如Python，而我们则是Golang。&lt;/p&gt;

&lt;p&gt;习惯性跑题。。。Golang是C语言之父创建的面向过程语言，被誉为“互联网界的C语言”。当然，好的东西还是需要借鉴的，Golang的设计也添加了一些OO的东西，例如接口。&lt;/p&gt;

&lt;p&gt;Java中实现接口用interface和implements这两个关键字就能够实现了，而Golang不是面向对象语言，所以写出来的代码总感觉有点别扭，一直都记不住到底要怎么写。后来发现和Java对比一下，很容易理解。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;#&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Golang&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Java&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;接口定义&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;type Phone interface {…}&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;interface Phone {…}&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;定义实现类&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;type iPhone struct {…}&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;class iPhone implements Phone {…}&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;实现接口函数&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;func (p *Android) Company() string {…}&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;String Company() {…}&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其实这两个语法没差多少，而C++里面，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;也可以认为是类的一种，只是其内部变量和函数都是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public&lt;/code&gt;的。&lt;/p&gt;

&lt;p&gt;完整示例&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/test_inte.go&quot;&gt;test_inte.go&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

type Phone interface {
	Company() string
}

type Android struct {
}

func (p *Android) Company() string {
	return &quot;Google&quot;
}

type iPhone struct {
}

func (p *iPhone) Company() string {
	return &quot;Apple&quot;
}

func main() {
	android := Android{}
	iphone := iPhone{}
	println(android.Company())
	println(iphone.Company())
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run test_inte.go
Google
Apple
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://0x55aa.sinaapp.com/%E7%AE%97%E6%B3%95-%E7%BC%96%E7%A8%8B/659.html&quot;&gt;GOLANG学习笔记-接口 - 0X55AA’博客&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Golang 处理命令行启动参数</title>
   <link href="https://blog.cyeam.com/golang/2014/07/20/go_flag"/>
   <updated>2014-07-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/20/go_flag</id>
   <content type="html">&lt;p&gt;最近要做Apache Thrift相关的项目。大概看了一下，觉得不难。Thrift目前已经至此和Go语言了。照着官方提供的一个例子在学。周五搞了一上午，终于编译通过了。下午去读例子源码，发现一个从来没见过的包——flag。&lt;/p&gt;

&lt;p&gt;不管是C开发还是Java，都接触过命令行开发。像大Java的main函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public static void main(String []args)&lt;/code&gt;直接参数里面就能读取到命令行启动参数。而Go语言的主函数是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;func main()&lt;/code&gt;果断啥也没，需要用这个包。&lt;/p&gt;

&lt;p&gt;虽然Go不像大Java，在主函数里面直接就能获取到命令行参数，但是Go通过flag包获取到的参数类型，不像Java那样，只能是String类型。&lt;/p&gt;

&lt;p&gt;再获取之前，需要自定义要获取的参数名称、参数默认值、参数使用方法，还有参数类型。返回的是指针类型。也可以使用函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringVar&lt;/code&gt;，用处一样，只是参数表和返回值的变化。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func String(name string, value string, usage string) *string
func StringVar(p *string, name string, value string, usage string)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;定义好之后，通过调用flag包的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Parse&lt;/code&gt;函数，就会自动解析。完整例子如下&lt;a href=&quot;https://github.com/mnhkahn/go_code/blob/master/testflag.go&quot;&gt;testflag.go&lt;/a&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
    &quot;flag&quot;
)

var strFlag = flag.String(&quot;s&quot;, &quot;&quot;, &quot;Description&quot;)
var boolFlag = flag.Bool(&quot;bool&quot;, false, &quot;Description of flag&quot;)

func main() {
    flag.Parse()
    println(*strFlag, *boolFlag)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还有一点需要注意的是，Go的命令行参数和大Java对应方式是不同的，默认情况下不能只写值，还需要传入对于值的名称，也就是前面提到的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;函数的第一个参数。如果定义的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;，那么传的时候就需要加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-name {NAME}&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;运行命令及结果：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ go run testflag.go -s 123
123 false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;现在在GitHub上面新建了一个Go语言相关用例的库，里面都是平时的测试代码。从今天开始添加。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;https://github.com/mnhkahn/go_code&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://golang.org/pkg/flag/&quot;&gt;Package flag - http://golang.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Linux Mint 下ShadowSocks安装试用</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/07/18/shadowsocks"/>
   <updated>2014-07-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/07/18/shadowsocks</id>
   <content type="html">&lt;p&gt;随着墙越垒越高，梯子也得赶得上趟才行。发现现在各路神仙都是在用ShadowSocks（简称SS）。&lt;/p&gt;

&lt;p&gt;它与goagent的区别就在于，goagent是用过GAE所以代理服务器。在本地，通过Chrome浏览器的插件Proxy SwitchySharp，将请求代理到本地的goagent客户端，一般是8087端口。goagent客户端再将请求发送到GAE上面，从而达到梯子的作用。GAE的域名appspot.com一直是处于无法访问的状态，而解决访问GAE服务器的方法也很简单。强大的Google服务器集群，能够做到，访问Google的URL请求，发送到任意一个Google的服务器，都能够正常工作。如果我没有记错的话，GoAgent PAC就是用的这个方法。也就是说，只要墙有一道缝，就可以轻易化解。&lt;/p&gt;

&lt;p&gt;然而，自从方教授去和病魔抗争离开工作岗位之后，据说来了一位女士接替他的工作。女人发狠起来比男人礼拜多了，实在是堵的太严实了。&lt;/p&gt;

&lt;p&gt;下面是正题。其实，ShadowSocks与goagent最大的区别就是，ShadowSocks并不是通过GAE服务器访问网络，而是通过好心人共享出自己的网络实现上网的功能。这就需要在国外的热心人士安装上对应的客户端，共享出自己家的带宽了。这样的话，由于IP分布很广，所以通过IP拦截的方式就很难起作用了。然后，有人发现，如果GFW发现网络上在发送加密的包，那么，可能会自动做丢包处理，或者为该包选择最差路由路径传输。还有，使用VPN的用户，长时间连接的都是同样的地址，虽然内容无法侦测，但是会自动屏蔽掉改IP。&lt;/p&gt;

&lt;p&gt;有一个中文版的ShadowSocks公益组织&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://www.shadowsocks.net/&lt;/code&gt;，这里面提供了Windows下DOT NET开发的客户端程序，我的Mint果断不能用。如此高大上的东西，绝对不只是Windows平台的，所以继续找，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://shadowsocks.org/en/download/clients.html&lt;/code&gt;这个好像是官方网站，在Linux平台下依然写着shadowsocks-go，这应该是我大Golang开发的，但是去下载，发现是404。。。对node.js没有好感，果断选择了Python客户端。&lt;/p&gt;

&lt;p&gt;我不会用Python，电脑上只有简单的开发环境，要安装程序，还需要安装必要的依赖:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get install python-m2crypto
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着，安装ShadowSocks客户端。如果安不上，可以选择豆瓣提供的源。而Ruby，可以选择淘宝提供的源。为这两个公司点个赞：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip install shadowsocks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;安装教程2，要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/shadowsocks&lt;/code&gt;目录下创建文件config.json。去ShadowSocks中文网站，可以免费申请到帐号，按照此格式填入即可：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;server&quot;:&quot;remote-shadowsocks-server-ip-addr&quot;,
    &quot;server_port&quot;:8883,
    &quot;local_address&quot;:&quot;127.0.0.1&quot;,
    &quot;local_port&quot;:8883,
    &quot;password&quot;:&quot;whosyourdaddy&quot;,
    &quot;timeout&quot;:300,
    &quot;method&quot;:&quot;aes-256-cfb&quot;,
    &quot;fast_open&quot;:false,
    &quot;workers&quot;:1
}`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后，启动客户端。这个东西应该能直接加到开机启动里面，我不会，先这样吧。。。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sslocal -c /etc/shadowsocks/config.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后，配置Proxy SwitchySharp。它和goagent配置差不多，只是它用的是SOCK5传输，而不是HTTP，所以要在SOCKS Host里面填入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;和配置文件里写的端口号，并选择SOCKS v5。&lt;/p&gt;

&lt;p&gt;在Android和iOS端也都有相应的实现，iOS端由于权限的原因，只是一个浏览器，但是很好用。我的Nexus 7也安上了客户端，看着功能比较强，但是无法启动，可能需要root吧。ShadowSocks大家都知道是啥，但是为啥只提供Google Play一种安装方式，安起来好辛苦的说。。。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://www.shadowsocks.net/&quot;&gt;ShadowSocks公益组织&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;https://wiki.archlinux.org/index.php/Shadowsocks_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#.E5.AE.A2.E6.88.B7.E7.AB.AF&quot;&gt;Shadowsocks (简体中文)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;https://bbs.shadowsocks.net/discussion/6/&quot;&gt;Chrome谷歌浏览器代理插件Proxy SwitchySharp简要教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Android虚拟机模拟摄像头</title>
   <link href="https://blog.cyeam.com/android/2014/07/17/avd_camera"/>
   <updated>2014-07-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/android/2014/07/17/avd_camera</id>
   <content type="html">&lt;p&gt;一开始默认会提醒叫你插入SD卡，按照网上教程给模拟器加了一个。那个Front Camera也都试了一下，也不太行。后来发现有个CPU/ABI，果断选了Intel。可能跟这个有关。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/avd_camera.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.oschina.net/translate/enable-camera-in-android-emulator?cmp&quot;&gt;在 Android 模拟器中启用摄像头支持 - oschina&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Linux下定时执行——crontab</title>
   <link href="https://blog.cyeam.com/linux/2014/07/16/crontab"/>
   <updated>2014-07-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/linux/2014/07/16/crontab</id>
   <content type="html">&lt;p&gt;想做一个服务，能够每天定时调用执行。比如，每天定时推送微信，或者发送邮件，又或者为Android端推送消息。形式多种多样，内容主要是能有一个地方能够提醒展示今天要做的事情。像结婚纪念日、好朋友生日这种，还是挺重要的。&lt;/p&gt;

&lt;p&gt;获取上面提到的这些内容都不难，比较难的是定时执行。&lt;/p&gt;

&lt;p&gt;在语言层面，都实现了计时执行。比如，经过5分钟之后调用。计时器和定时器不同，可以比作是沙漏和闹钟。go语言可以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time.NewTicker&lt;/code&gt;就能实现。但这个并不是定时执行。如果我们想每天早上6点都能执行，用这个就很费劲。我之间就做过这个尝试。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;先获取当前时间，再根据定时时间，相减得到计时器时间；&lt;/li&gt;
  &lt;li&gt;执行改计时器时间，结束后执行操作；&lt;/li&gt;
  &lt;li&gt;然后，将计时器修改为24小时为一个周期。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后来，我在Github上面看别人写的时钟，好像也是这么干的。这样做太蹩脚了。&lt;/p&gt;

&lt;p&gt;再后来，了解到，Android平台可以使用AlarmManager来辅助实现定时器。其实就是能够调用系统自身的闹钟。自己比较熟的常见语言，都没找到语言层面实现的定时器，而Android平台的AlarmManager让我意识到，可能这个东西本来就不是语言负责的东西，而是系统层面的东西。&lt;/p&gt;

&lt;p&gt;意识到这个问题之后，问题就迎刃而解了。直接发现Linux系统内置命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crontab&lt;/code&gt;就支持定时调用，并且服务器端也都是靠这个来做定时调用的。&lt;/p&gt;

&lt;p&gt;定时调用需要写配置文件，Ubuntu默认是使用nano作为默认编辑器，果断不会用，先换成vi。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo export EDITOR=/usr/bin/vim &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着，编写定时调用规则：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crontab -e&lt;/code&gt; 编写调用规则；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在最后添加一行记录。记录分为两部分，执行时间和命令。这里的命令是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/day &amp;gt;&amp;gt; $HOME/test.txt&lt;/code&gt;，执行day命令，并且将结果输出到文件test.txt当中；&lt;/p&gt;

    &lt;p&gt;0 */1 * * * /root/day » $HOME/test.txt&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第一部分，时间规则如下：&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;段&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;含义&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;取值范围&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;第一段&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;代表分钟&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;0—59&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;第二段&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;代表小时&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;0—23&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;第三段&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;代表日期&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1—31&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;第四段&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;代表月份&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1—12&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;第五段&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;代表星期几，0代表星期日&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;0—6&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;最后，重启服务&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;service cron restart&lt;/code&gt;即可。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://blog.csdn.net/sipsir/article/details/3973713&quot;&gt;Linux下crontab命令的用法 - sipsir&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Go 语言简单实现HashSet</title>
   <link href="https://blog.cyeam.com/golang/2014/07/15/go_hashset"/>
   <updated>2014-07-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/15/go_hashset</id>
   <content type="html">&lt;p&gt;公司有个需求，就是能够对列表去重。本屌原本想直接用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;循环实现，后来去查了查Java的实现方式，大开眼界。&lt;/p&gt;

&lt;p&gt;Set，是指数学里的集合。集合当中不能有重复的元素。判断是否有重复，可以使用哈希的方法。Java容器当中有基于哈希实现的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashSet&lt;/code&gt;。把元素都放入HashSet当中，如果有重复，则会插入失败。这样就能判断出来是否重复了。&lt;/p&gt;

&lt;p&gt;而Golang并没有这种高级的容器。只是找了一个大神实现的，稍微改了一下，能够支持字符串检测。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;http://play.golang.org/p/_FvECoFvhq&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;type HashSet struct {
	set map[string]bool
}

func NewHashSet() *HashSet {
	return &amp;amp;HashSet{make(map[string]bool)}
}

func (set *HashSet) Add(i string) bool {
	_, found := set.set[i]
	set.set[i] = true
	return !found //False if it existed already
}

func (set *HashSet) Get(i string) bool {
	_, found := set.set[i]
	return found //true if it existed already
}

func (set *HashSet) Remove(i string) {
	delete(set.set, i)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;内部使用map来保存哈希结果。而哈希函数直接就是使用字符串作为哈希结果。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;还可以看我之后的文章&lt;a href=&quot;https://blog.cyeam.com/golang/2017/04/11/go-empty-struct&quot;&gt;Golang 优化之路——空结构&lt;/a&gt;，这个方案更好一些。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://zhidao.baidu.com/question/172011519.html&quot;&gt;判断int数组中的元素是否重复 - 百度知道&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;https://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang&quot;&gt;Sets Data Structure in Golang - StackExchange&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;https://bbs.csdn.net/topics/120025156&quot;&gt;java里有没有专门判断List里有重复的数据？最好能知道是第几行重复. - CSDN论坛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Android L 预览版试用</title>
   <link href="https://blog.cyeam.com/android/2014/07/14/android_l_preview"/>
   <updated>2014-07-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/android/2014/07/14/android_l_preview</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;官方安装教程 https://developers.google.com/android/nexus/images#instructions
官方下载系统镜像地址 http://developer.android.com/preview/setup-sdk.html&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我的平板是Nexus 7（2013版），选择razor下载即可。安装也很简单。将设备通过USB连上电脑。这里可能需要配置驱动，如果需要，请参考&lt;a href=&quot;https://blog.cyeam.com/kaleidoscope/2014/02/01/linux_android_drivers&quot;&gt;《Linux Mint下安装Nexus 7 驱动》&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;启动设备进入fastboot模式。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adb reboot bootloader &lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;解压镜像文件&lt;/li&gt;
  &lt;li&gt;执行脚本flash-all。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./flash-all.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;安装挺简单的，第一次这么安东西，挺爽。自打安上之后，我就开始找微博上面提到的东西。最喜欢的那个就是开机画面，然后再我真机上面看，还有竖屏变横屏时的问题。。。截了两张图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Screenshot_2014-07-14-21-22-04.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Screenshot_2014-07-14-21-22-04.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接着就是更都的问题。微博启动不了，QQ各种显示问题。虽说是预览版，但是跟我想像还是差了好多。。。今天晚上把系统刷回了4.4。不过电池确实好了许多。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Nginx根据域名转发</title>
   <link href="https://blog.cyeam.com/nginx/2014/07/12/nginx_router"/>
   <updated>2014-07-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/nginx/2014/07/12/nginx_router</id>
   <content type="html">&lt;p&gt;Nginx的安装这里就不提了。安装成功之后，能显示默认的页面。&lt;/p&gt;

&lt;p&gt;首先，要测试通过Nginx将不同的域名转发到不同的服务器，要注册一个域名。我通过免费域名提供商&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dot.tk&lt;/code&gt;注册了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cyeam.tk&lt;/code&gt;这个域名，并且新建了两个A记录，将两个子域名都绑定要同一个IP上面。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/tk_nginx_test.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接着，在服务器上面启动两个服务。我使用beego进行测试。通过命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bee new&lt;/code&gt;很方便的创建了两个应用，然后修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conf/app.conf&lt;/code&gt;里面的监听端口，修改其中一个即可。这两个服务就可以正常启动了。&lt;/p&gt;

&lt;p&gt;最后，就是编写转发规则。Nginx的配置文件在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nginx/conf&lt;/code&gt;里面。之前在公司群里看到过转发规则，后来又去Stackoverflow找了一篇大概介绍配置文件写法的进行参考，照猫画虎了写了，成功了。将访问域名，分别转发到了331端口和8080端口。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host $http_host;

if ($http_host = &quot;blog.cyeam.tk&quot;) {
    proxy_pass http://127.0.0.1:331;
}

if ($http_host = &quot;www.cyeam.tk&quot;) {
    proxy_pass http://127.0.0.1:8080;
}
    root   html;
    index  index.html index.htm;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;修改完之后，通过命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbin/nginx -t&lt;/code&gt;检查配置文件是否正确，然后通过命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbin/nginx -s reload&lt;/code&gt;重新载入配置文件。&lt;/p&gt;

&lt;p&gt;可以通过这两个域名访问到这两个应用，我对页面稍微做了修改，可以看出来转发到了不同的端口。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;昨天成功了，今天再来看，就不行了。。。是因为我没有备案。没有办法展示效果了。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/fuck_miit.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这样做可能还不是很完善，因为可以通过域名加端口的方式访问到具体服务。比如，blog.cyeam.tk设定是转发到331端口，如果通过blog.cyeam.tk:8080依然可以访问8080端口的服务。我觉得还需要对防火墙进行设定，不允许从外网访问除80等一些常见端口以外的端口。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.cnblogs.com/derekchen/archive/2011/02/17/1957209.html&quot;&gt;nginx启动，重启，关闭命令 - 晓风残梦&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://stackoverflow.com/questions/7878334/nginx-conditional-proxy-pass&quot;&gt;nginx conditional proxy pass - Stackoverflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>数据库访问的缓存与最大连接数</title>
   <link href="https://blog.cyeam.com/golang/2014/07/11/db_cache_macconn"/>
   <updated>2014-07-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/11/db_cache_macconn</id>
   <content type="html">&lt;p&gt;今天查看我写的错误日志，里面报出了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Too many connections&lt;/code&gt;这个错误。没见过啊，果断去问我大哥，他说是连接数的问题。我又去看之前已经上线的我的代码，才想起来数据库连接需要设置最大连接数的。当连接数超过范围之后，就是报这个错误。&lt;/p&gt;

&lt;p&gt;我用的是xorm作为ORM工具，直接使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;engine.SetMaxConns(dbMaxConns)&lt;/code&gt;就能设置最大连接数。&lt;/p&gt;

&lt;p&gt;后来，又优化了一下缓存。一般ORM还是支持缓存机制的。如果缓存命中，就不会去数据库查找数据了，而是会直接返回。添加缓存需要增加缓存时间和最大缓存数。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cacher := xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Duration(interval)*time.Second, max)
engine.SetDefaultCacher(cacher)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很简单的问题，但是老是忘记加。今天用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wrk&lt;/code&gt;对项目做了简单的压力测试，就出了这个问题。以前一直觉得压力测试就是用来测性能的，今天有了新的认识。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>数据库访问时区问题</title>
   <link href="https://blog.cyeam.com/golang/2014/07/09/sql_time_zone"/>
   <updated>2014-07-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/09/sql_time_zone</id>
   <content type="html">&lt;p&gt;今天是第二次跳进这个坑里面。&lt;/p&gt;

&lt;p&gt;我要做的接口有一个需求，就是根据设定好的开始时间和结束时间过滤掉未开始和过期的内容。我用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xorm&lt;/code&gt;作为ORM引擎进行数据库开发。直接一条解决了问题。当时还大概测了一下，没有任何问题。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Where(&quot;displayorder&amp;lt;&amp;gt;0 AND effectivetime&amp;lt; ? AND expirationtime&amp;gt; ?&quot;, time.Now(), time.Now())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;今天iOS开发要调用我的接口开发，因为我写的测试数据不完整，他叫测试配了几条测试数据。测试妹子很专业的，加了一条过期的，一条要显示的，一条未开始的，然后我这三条一条都没展示。。。&lt;/p&gt;

&lt;p&gt;问题报给我之后，很明显就知道问题出在哪里了。肯定是那个Where查询错了。xorm支持记录SQL语句日志，一开始开发我就配置上了。果断去看日志。把SQL复制到Navicat里面运行，发现没有任何问题。。。这就邪了。百思不得其解。我就开始瞎猜。是不是传入的time.Now()有问题。我就把日期格式化了一下，可以了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const DATE_LAYOUT = &quot;2006-01-02 15:04:05&quot;
now := time.Now()
now_str := now.Format(DATE_LAYOUT)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这一次歪打正着让我觉得很不对劲（我的直觉还是很准确的！！！），然后就去请教了一下我万能的大哥，他说应该是UTC的问题。意思就是时区的问题，我才恍然大悟，之前我也踩过这个坑。在MySQL连接字符串里面加上时区信息就可以了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;parseTime=true&amp;amp;loc=Asia%2FChongqing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后，我万能的大哥还告诉我，根本不需要传时间进去，直接使用MySQL的函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NOW()&lt;/code&gt;就可以了。嗯，大哥就是大哥。。。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Where(&quot;displayorder&amp;lt;&amp;gt;0 AND effectivetime&amp;lt; NOW() AND expirationtime&amp;gt; NOW()&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;我团的Ruby团队据说是国内比较牛逼的Ruby团队了，因为主站一直用Ruby做开发的。我们Team只有5个人，选用了Golang作为主力开发语言，Golang又比较新，我们Team会不会成为国内比较牛逼的Golang团队呢？哈哈哈&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Go语言接口开发——不确定JSON数据结构的解析</title>
   <link href="https://blog.cyeam.com/golang/2014/07/09/api_result_parse_go"/>
   <updated>2014-07-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/09/api_result_parse_go</id>
   <content type="html">&lt;p&gt;在公司主要做接口的开发，会经常遇到接口对接的情况。有的时候，同一个请求返回的JSON数据格式并不一样。如果是正常，则可能只返回一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;字段，说明正常；如果中间出错，除了在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;字段里面说明错误类型，还会通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_message&lt;/code&gt;附带错误详细信息。比如要给用户加积分，如果加分失败，还会附带用户id等信息。那么，请求一个接口可能的返回值就是不确定的。&lt;/p&gt;

&lt;p&gt;我最初就是定义两个结构体，我处理的数据都共有一个字段&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;，如果能够解析并且&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;表示操作成功，那么用封装成功内容的结构体解析；否则，用封装失败的结构体解析。这就是传说中的&lt;strong&gt;DIRTY HACK&lt;/strong&gt;。。。&lt;/p&gt;

&lt;p&gt;后来，偶然发现封装正确的结构体也会解析错误的字符串，当然，只会解析共有字段。那么，这个问题就好解决多了。把两个结构体放到一起即可，如果没有该字段，就不会被解析放入值。也就是说，未被解析的变量放的是默认值。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)

type Result struct {
	Status       int    `json:&quot;status&quot;`
	Message      string `json:&quot;message&quot;`
	ErrorCode    int    `json:&quot;error_code&quot;`
	ErrorMessage string `json:&quot;error_message&quot;`
}

func main() {
	json_str0 := `{&quot;status&quot;:0,&quot;message&quot;:&quot;success&quot;}`
	json_str1 := `{&quot;status&quot;:1,&quot;error_code&quot;:5,&quot;error_message&quot;:&quot;error&quot;}`

	res0 := Result{}
	res1 := Result{}

	err0 := json.Unmarshal([]byte(json_str0), &amp;amp;res0)
	err1 := json.Unmarshal([]byte(json_str1), &amp;amp;res1)

	fmt.Println(res0, err0)
	fmt.Println(res1, err1)

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这么简单的东西，Go语言的基本语法，但是看书的时候没有注意过，关键是做的时候没有认真分析，就直接DIRTY HACK了，我都不能忍自己了。。。&lt;/p&gt;

&lt;p&gt;最后，还有一点，Go支持未知JSON数据结构的解析。创建一个interface，把它的地址传进去解析就行了，会解析出&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map[string]interface&lt;/code&gt;类型的数据。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】Go语言编程&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>百度云推送——Go语言实现类库</title>
   <link href="https://blog.cyeam.com/golang/2014/07/08/baiduyunpush"/>
   <updated>2014-07-08T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/07/08/baiduyunpush</id>
   <content type="html">&lt;iframe src=&quot;https://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=BaiduYunPush&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;https://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=BaiduYunPush&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;https://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;h1 id=&quot;快速开始&quot;&gt;快速开始&lt;/h1&gt;

&lt;h3 id=&quot;下载安装&quot;&gt;下载安装&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get github.com/mnhkahn/BaiduYunPush
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;创建文件pushtestgo&quot;&gt;创建文件pushtest.go&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;github.com/mnhkahn/BaiduYunPush&quot;
)

var apikey = &quot;**************************&quot;
var seckey = &quot;******************************&quot;
var method = &quot;POST&quot;
var url_base1 = &quot;channel.api.duapp.com/rest/2.0/channel/channel&quot;

func main() {
    push := BaiduYunPush.New(apikey, seckey)
    s, err := push.Push(&quot;推送成功&quot;, &quot;这是我的个人博客blog.cyeam.com&quot;)
    fmt.Println(s, err)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;编译运行&quot;&gt;编译运行&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go run pushtest.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当控制台显示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;之后，推送成功。这时，通过SDK绑定的设备就会展示推送消息。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/baiduyunpush.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/api/list&quot;&gt;百度开放云文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>如何离线完成go get——安装Apache Thrift有感</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/07/03/fuckgfwforgo"/>
   <updated>2014-07-03T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/07/03/fuckgfwforgo</id>
   <content type="html">&lt;p&gt;今天公司事情不多，我手上的项目还需要等其他同事才能继续，有一段时间比较闲。之前有 3 个月不在公司回学校了，我们部门用了一个新的开发工具——Apache Thrift，就趁这个时间了解一下。&lt;/p&gt;

&lt;p&gt;准备把 Thrift 安装到阿里云上面。这个东西大概了解了一下，是 Facebook 开源的一套远程调用的框架，比目前流行的基于 REST 传输 JSON 性能好，更优于基于 SOAP 的 XML。关键是它支持多种语言，当然包括我们 Team 使用的 Golang。&lt;/p&gt;

&lt;p&gt;从官网下载压缩包安装有问题，没有官网上面描述的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootstrap.sh&lt;/code&gt;文件，还会会报错误。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;libtool: link: ar cru .libs/libtestgencpp.a .libs/ThriftTest_constants.o
.libs/ThriftTest_types.o
ar: .libs/ThriftTest_constants.o: No such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 Ubuntu 安装需要安装依赖。后来观察安装输出，发现还需要 Ant 和 Maven。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Thrift 会自动检查当前机子的环境变量里面存在的开发语言进行编译。它帮我自动支持了 Java、Python 和 Golang。&lt;/p&gt;

&lt;p&gt;重点来了。在安装 Golang 的时候，出错了，日志说&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get code.google.com/p/gomock/gomock&lt;/code&gt;连接超时。以我多年的经验来看，被拦截了。我在本机遇到这种情况的时候，都是使用 Goagent，直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export http_proxy=&quot;http://127.0.0.1:8087&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export https_proxy=&quot;http://127.0.0.1:8087&lt;/code&gt;就设置好代理了。但是这次是阿里云，那边没办法代理。后来在本机试了试，能够下载。看来 GFW 是分区域拦的。通过 FTP 上传到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gopath&lt;/code&gt;目录下，进入该目录，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go install&lt;/code&gt;进行编译即可。可以再输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get code.google.com/p/gomock/gomock&lt;/code&gt;，检测是否安装成功。&lt;/p&gt;

&lt;p&gt;准备下个月发工资之后咬咬牙去买 Linode 的$10 的业务，希望今后能愉快的上网。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://my.oschina.net/qinhui99/blog/66560&quot;&gt;脆弱的 Go 远程包 - qinhui99&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://blog.csdn.net/iam333/article/details/18771945&quot;&gt;ubuntu 12.04 中安装 thrift-0.9.1 - iAm333 的专栏&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://thrift.apache.org/docs/install/debian&quot;&gt;Debian or Ubuntu setup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>正则表达式</title>
   <link href="https://blog.cyeam.com/regex/2014/07/02/regex"/>
   <updated>2014-07-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/regex/2014/07/02/regex</id>
   <content type="html">&lt;p&gt;正则表达式有几个用途：用于验证输入、通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt;查看日志、匹配HTML里面的标签。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;正则表达式验证工具Kodos http://kodos.sourceforge.net/&lt;/p&gt;
&lt;/blockquote&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;#&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;正则表达式&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;解释&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hi&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;最常见的查找方式，可以查找任何包含&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hi&lt;/code&gt;这两个连续字符的字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\b&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】代表单词的开头或结尾，也就是单词的分界处。只代表位置，不代表字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】【重复】代表数量，它前面的字符可以重复任意次来匹配&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】【重复】表示除了换行符以外的任意一个字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\d&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】表示一位数字，大括号表示两个连续数字&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\s&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】任意的空白符（space），包括空格，制表符(Tab)，换行符，中文全角空格等&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\w&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】匹配字母或数字或下划线或汉字等（word）&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\b&lt;/code&gt;类似，表示字符串开始位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【元字符】和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\b&lt;/code&gt;类似，表示字符串结束位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【字符转义】查找元字符本身时使用&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【重复】重复一次或更多次&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【重复】前面的字符重复零次或一次&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{n}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【重复】重复n次&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;14&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{n,}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【重复】重复n次或更多次&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;15&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{n,m}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【重复】重复n到m次&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;16&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【范围】里面填入候选集合，这表示存在于集合中的一个字符。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[aeiou]&lt;/code&gt;就匹配任何一个英文元音字母；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0-9]&lt;/code&gt;表示0到9之间任意一个数字&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;17&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【分枝条件】满足其中任意一种规则都应该当成匹配。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\d{5}-\d{4}|\d{5}&lt;/code&gt;可以认为存在优先级，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|&lt;/code&gt;优先级高于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;18&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\W&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【反义】匹配任意不是字母，数字，下划线，汉字的字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;19&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\S&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【反义】匹配任意不是空白符的字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\D&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【反义】匹配任意非数字的字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;21&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\B&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【反义】匹配不是单词开头或结束的位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;22&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[^x]&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【反义】匹配除了x以外的任意字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;23&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【后向引用】【捕获】匹配exp,并捕获文本到自动命名的组里&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;24&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?&amp;lt;name&amp;gt;exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【后向引用】【捕获】匹配exp,并捕获文本到名称为name的组里，也可以写成(?’name’exp)&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;25&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?:exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【后向引用】【捕获】匹配exp,不捕获匹配的文本，也不给此分组分配组号&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;26&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?=exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【零宽断言】匹配exp前面的位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;27&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?&amp;lt;=exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【零宽断言】匹配exp后面的位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;28&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?!exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【零宽断言】匹配后面跟的不是exp的位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;29&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?&amp;lt;!exp)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【零宽断言】匹配前面不是exp的位置&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;30&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【懒惰限定符】重复任意次，但尽可能少重复&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;31&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【懒惰限定符】重复1次或更多次，但尽可能少重复&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;32&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;??&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【懒惰限定符】重复0次或1次，但尽可能少重复&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;33&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{n,m}?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【懒惰限定符】重复n到m次，但尽可能少重复&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;34&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{n,}?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【懒惰限定符】重复n次以上，但尽可能少重复&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;35&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?&apos;group&apos;)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【递归匹配】把捕获的内容命名为group,并压入堆栈(Stack)&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;36&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?&apos;-group&apos;)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【递归匹配】从堆栈上弹出最后压入堆栈的名为group的捕获内容&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;37&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?(group)yes|no)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【递归匹配】如果堆栈上存在以名为group的捕获内容的话，继续匹配yes部分的表达式，否则继续匹配no部分&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;38&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(?!)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;【递归匹配】零宽负向先行断言，由于没有后缀表达式，试图匹配总是失败&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;正则表达式&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;解释&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^\d{5,12}$&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;QQ号必须为5位到12位数字&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^\w+&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;匹配一行的第一个单词&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\(?0\d{2}[) -]?\d{8}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;(010)88886666，或022-22334455，或02912345678&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0\d{2}-\d{8}|0\d{3}-\d{7}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;8位本地号(如010-12345678)和7位本地号(0376-2233445)&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(\d{1,3}\.){3}\d{1,3}&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;IP地址匹配&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\b(\w+)\b\s+\1\b&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;匹配重复的单词&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\b\w+(?=ing\b)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;匹配以ing结尾的单词的前面部分(除了ing以外的部分)，如查找I’m singing while you’re dancing.时，它会匹配sing和danc&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://deerchao.net/tutorials/regex/regex.htm&quot;&gt;正则表达式30分钟入门教程 - deerchao&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F&quot;&gt;正则表达式 - 维基百科&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>grep与日志开发</title>
   <link href="https://blog.cyeam.com/linux/2014/07/01/grep"/>
   <updated>2014-07-01T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/linux/2014/07/01/grep</id>
   <content type="html">&lt;p&gt;后台开发离不开日志，日志能帮助检查bug。日志开发本身没有太高的难度，就是把数据追加输出到文件即可。然而，日志往往数据量非常大。大量的日志并不能通过人工阅读进行检查，一般都是借助grep工具。这里将通过学习grep命令来对日志的打印方式进行分析。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;grep全称是Global Regular Expression Print，表示全局正则表达式版本。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;[options]主要参数：&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-c&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;只输出匹配行的计数&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-I&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;不区分大 小写(只适用于单字符)&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-h&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;查询多文件时不显示文件名&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-l&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;查询多文件时只输出包含匹配字符的文件名&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-n&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;显示匹配行及 行号&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-s&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;不显示不存在或无匹配文本的错误信息&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;-v&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;显示不包含匹配文本的所有行&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;pattern正则表达式主要参数：&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;\&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;忽略正则表达式中特殊字符的原有含义&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;^&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;匹配正则表达式的开始行&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;$&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;匹配正则表达式的结束行&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&amp;lt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;从匹配正则表达 式的行开始&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&amp;gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;到匹配正则表达式的行结束&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;[ ]&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;单个字符，如[A]即A符合要求&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;[ - ]&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;范围，如[A-Z]，即A、B、C一直到Z都符合要求&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;.&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;所有的单个字符&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;*&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;字符，长度可以为0&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;按照目录搜索&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep magic /usr/src/Linux/Doc/*&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;查找单个文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;more size.txt | grep &apos;[a-b]&apos;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;安装日期查找&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  $ cat 1.log | grep &apos;2014-07-02&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;按照中间内容查找&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  $ cat /cygdrive/c/Users/Thinkpad/Desktop/1.log | grep /cyeam/test/url/aaa
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2014-07-01T09:41:00 /cyeam/test/url/aaa 12
2014-07-02T09:41:01 /cyeam/test/url/aaa 12
2014-07-02T09:42:01 /cyeam/test/url/bbb 12
2014-07-02T09:46:01 /cyeam/test/url/ccc 12
2014-07-02T09:49:01 /cyeam/test/url/aaa 12
2014-07-02T09:51:01 /cyeam/test/url/bbb 12
2014-07-02T09:52:01 /cyeam/test/url/aaa 12
2014-07-02T09:59:01 /cyeam/test/url/ccc 12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.cnblogs.com/end/archive/2012/02/21/2360965.html&quot;&gt;linux grep命令 - 风生水起 - 博客园&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Nutch和Solr的安装和简单测试</title>
   <link href="https://blog.cyeam.com/nutch/2014/06/21/nutchsolr"/>
   <updated>2014-06-21T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/nutch/2014/06/21/nutchsolr</id>
   <content type="html">&lt;p&gt;之前在公司使用过Solr，这是基于Lucene实现的索引引擎。后来了解到，还有Nutch这个Java实现的网络爬虫。这两个搭配起来使用，就可以认为是一个完整的搜索引擎了。自己对Solr算是比较熟了，那么Nutch上手应该也不算难。果断尝试一下。&lt;/p&gt;

&lt;p&gt;首先是去下载，网速不够给力，国内下载国外资源老是这样。果断使用360网盘帮我缓冲。我优先使用的百度云，结果他云端没有，还得去下载，速度跟我本机一样。据我猜想，他们两个的原理都一样，服务器只会保留一份文件，如果有人要下载，提供的URL一致，就会为其直接缓冲好。难道程序员都是用的360云？&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;http://yunpan.cn/QhfHpuGMfP84d&quot;&gt;apache-nutch-1.8-bin http://yunpan.cn/QhfHpuGMfP84d&lt;/a&gt; （提取码：bea3） &lt;br /&gt;
&lt;a href=&quot;http://yunpan.cn/QhfHIdV8pjzVT&quot;&gt;solr-4.8.1.zip http://yunpan.cn/QhfHIdV8pjzVT&lt;/a&gt; （提取码：2f5e）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solr启动很简单，直接解压，进入example文件夹，默认提供了Jetty作为Servlet容器，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java -jar start.jar&lt;/code&gt;就能启动了。&lt;/p&gt;

&lt;p&gt;Nutch的安装是按照官网的教程。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;NutchTutorial &lt;a href=&quot;http://wiki.apache.org/nutch/NutchTutorial&quot;&gt;http://wiki.apache.org/nutch/NutchTutorial&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;解压之后，将bin目录加入PATH里；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;修改文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conf/nutch-site.xml&lt;/code&gt;；&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;property&amp;gt;
   &amp;lt;name&amp;gt;http.agent.name&amp;lt;/name&amp;gt;
   &amp;lt;value&amp;gt;My Nutch Spider&amp;lt;/value&amp;gt;
  &amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;接着我就报错了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;No agents listed in ´http.agent.name´ property&lt;/code&gt;。需要再修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conf/nutch-default.xml&lt;/code&gt;，再增加value的值即可；&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;property&amp;gt;
    &amp;lt;name&amp;gt;http.agent.name&amp;lt;/name&amp;gt;
    &amp;lt;value&amp;gt;Cyeam&apos;s Spider&amp;lt;/value&amp;gt;
    &amp;lt;description&amp;gt;HTTP &apos;User-Agent&apos; request header. MUST NOT be empty -
    please set this to a single word uniquely related to your organization.

    NOTE: You should also check other related properties:

          http.robots.agents
          http.agent.description
          http.agent.url
          http.agent.email
          http.agent.version

    and set their values appropriately.

    &amp;lt;/description&amp;gt;
  &amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;创建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;urls&lt;/code&gt;目录并且创建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seed.txt&lt;/code&gt;文件，这个文件用于存放要爬的网站地址。我拿自己的网站来测试，写入了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://blog.cyeam.com/&lt;/code&gt;；&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  mkdir -p urls
  cd urls
  touch seed.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conf/regex-urlfilter.txt&lt;/code&gt;文件，修改成如下的形式；&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  # accept anything else
  #+.
  +^http://([a-z0-9]*\.)*blog.cyeam.com/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;还需要增加字段，向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example/solr/collection1/conf/schema.xml&lt;/code&gt;文件里加入&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &amp;lt;field name=&quot;digest&quot; type=&quot;string&quot; stored=&quot;true&quot; indexed=&quot;false&quot;/&amp;gt;
  &amp;lt;field name=&quot;segment&quot; type=&quot;string&quot; stored=&quot;true&quot; indexed=&quot;false&quot;/&amp;gt;
  &amp;lt;field name=&quot;boost&quot; type=&quot;float&quot; stored=&quot;true&quot; indexed=&quot;false&quot;/&amp;gt;
  &amp;lt;field name=&quot;tstamp&quot; type=&quot;date&quot; stored=&quot;true&quot; indexed=&quot;false&quot;/&amp;gt;

  &amp;lt;field name=&quot;anchor&quot; type=&quot;string&quot; stored=&quot;true&quot; indexed=&quot;true&quot;
          multiValued=&quot;true&quot;/&amp;gt;
  &amp;lt;field name=&quot;cache&quot; type=&quot;string&quot; stored=&quot;true&quot; indexed=&quot;false&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;最后，启动爬虫，爬到的数据就会被存入Solr当中。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  Usage: bin/crawl &amp;lt;seedDir&amp;gt; &amp;lt;crawlID&amp;gt; &amp;lt;solrURL&amp;gt; &amp;lt;numberOfRounds&amp;gt;
  Example: bin/crawl urls/seed.txt TestCrawl http://localhost:8983/solr/ 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用Solr查看数据，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8983/solr/collection1/select?q=*%3A*&amp;amp;rows=100&amp;amp;wt=json&amp;amp;indent=true&lt;/code&gt;。只有22个结果，我的文章不只这么多，而且内容也有问题，第一条好像是爬的首页。大体就搭建完成了。我的博客差不多就搭建好了，在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog.cyeam.com&lt;/code&gt;。接下来是搭建主站，准备工作了去购买Linode。主站也要能够展示一些博客文章。原本想基于jekyll进行修改，现在算了，直接用爬虫搞定好了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    responseHeader: {
        status: 0,
        QTime: 1,
        params: {
        indent: &quot;true&quot;,
        q: &quot;*:*&quot;,
        wt: &quot;json&quot;,
        rows: &quot;100&quot;
    }
    },
        response: {
            numFound: 22,
            start: 0,
            docs: [
            {
            content: [
                &quot;Archive Home Categories Archive Tags Pages 2014 June June 19, 2014 » 流程图打脸 June 17, 2014 » AndroidHTTP库——Asynchronous Http Client for Android June 15, 2014 » Android端RTSP解决方案——libstreaming June 11, 2014 » 百度云推送 May May 15, 2014 » 『hello, world』 如何运行 May 14, 2014 » 笔试题总结 May 7, 2014 » 浪潮之巅——腾讯帝国？ April April 23, 2014 » Android 配置 April 19, 2014 » 查看自己的top10 Linux命令 April 18, 2014 » 基于流媒体的对讲机系统——UI设计 April 18, 2014 » 基于流媒体的对讲机系统——数据库模块 April 18, 2014 » Android 通知 April 18, 2014 » 基于流媒体的对讲机系统——系统用户界面的设计和实现 April 17, 2014 » 视频编解码技术H264 April 17, 2014 » SDP协议及开发设计 April 17, 2014 » 实时流媒体协议RTSP April 17, 2014 » 实时传输协议RTP April 17, 2014 » 基于流媒体的对讲机系统——系统开发准备 April 17, 2014 » 基于流媒体的对讲机系统——Android的开发准备 April 13, 2014 » 基于流媒体的对讲机系统的设计与实现 March March 5, 2014 » SIP协议的分析以及opensips注册和通话的研究 February February 25, 2014 » 上周在知乎上关于朝鲜问题的争吵 February 7, 2014 » iPhone的CSS显示 February 5, 2014 » Android Quick Start February 4, 2014 » 安卓对讲机开发评估 February 1, 2014 » Linux Mint下安装Nexus 7 驱动 January January 28, 2014 » 对联 January 22, 2014 » Linux命令整理 January 21, 2014 » beego介绍 January 14, 2014 » 一些网站架构中常见的术语和技术 January 13, 2014 » 2013读书总结 January 12, 2014 » Markdown January 11, 2014 » Linux Mint 64bit下安装Dota 2 January 7, 2014 » 青帝使用手册 January 2, 2014 » Java面试宝典 2013 November November 25, 2013 » 电影《色|戒》最后王佳芝为什么没有吃下那颗为特工准备的自杀胶囊？ November 23, 2013 » 搜狗2014校园招聘笔试题 November 5, 2013 » 中科曙光2014校园招聘笔试 October October 30, 2013 » 方正国际2014校园招聘笔试 October 25, 2013 » 神州航天软件2014校园招聘笔试 October 24, 2013 » 乐视2014校园招聘笔试 October 24, 2013 » 趣游2014校园招聘笔试 October 21, 2013 » 巨人网络2014校园招聘笔试 October 19, 2013 » 去哪网2014校园招聘Java开发面经 October 19, 2013 » 完美世界2014校园招聘笔试 October 17, 2013 » 广联达2014校园招聘笔试 October 15, 2013 » 触控科技2014校园招聘笔试题 October 13, 2013 » 百度2014校园招聘后台开发笔试 September September 16, 2013 » 阿里巴巴2014校园招聘面经 September 9, 2013 » 神州航天软件发展规划部实习笔试 April April 20, 2013 » 微软2012年实习生笔试题 March March 5, 2013 » 又一年 January January 23, 2013 » 你永远都不知道自己有多傻逼 January 23, 2013 » Connect the dots January 13, 2013 » 2012读书总结 2012 November November 2, 2012 » 请求网页所需协议 May May 23, 2012 » 沉淀心情 March March 26, 2012 » 很多事情感觉都来不及计划 2010 November November 3, 2010 » 3Q大战 May May 31, 2010 » 我的阿凡达 2009 February February 16, 2009 » 又要走了，可是不怎么想走啊 2008 December December 31, 2008 » 又过了一年 © 2014 Bryce with help from Jekyll Bootstrap and Twitter Bootstrap Cyeam by Bryce is licensed under a Creative Commons Attribution 4.0 International License .&quot;
            ],
            title: [
                &quot;Archive&quot;
            ],
            segment: &quot;20140621215120&quot;,
            boost: 0.10709364,
            digest: &quot;c4b54b8331f0759333cd03dbf7e84032&quot;,
            tstamp: &quot;2014-06-21T13:53:08.412Z&quot;,
            id: &quot;http://blog.cyeam.com/archive.html&quot;,
            url: &quot;http://blog.cyeam.com/archive.html&quot;,
            _version_: 1471528486701105200
        },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://blog.csdn.net/panjunbiao/article/details/12171147&quot;&gt; CentOS 6.4环境下的Apache Nutch 1.7 + Solr 4.4.0安装笔记&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>AndroidHTTP库——Asynchronous Http Client for Android</title>
   <link href="https://blog.cyeam.com/android/2014/06/17/asyncandroidhttp"/>
   <updated>2014-06-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/android/2014/06/17/asyncandroidhttp</id>
   <content type="html">&lt;p&gt;项目做了一个REST服务器，提供给Android客户端访问。Android为了保证界面交互，目前不能够在主线程当中使用发起HTTP请求。简单的做法是建立一个线程，在里面发送HTTP请求，收到结果之后，再创建一个Handler，通过Handler发送出更新界面的消息，在Handler里面更新。这样做比较麻烦，要创建Thread和Handler才能完成一次请求。当时我要做的是能够重复发送请求，就在线程中加了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;while&lt;/code&gt;判断，需要更新时更新。这样做功能上是可以了，我就去睡觉了。睡觉以前还有75%的电，起来后发现没电关机了。我被我牛逼的代码震惊了。&lt;/p&gt;

&lt;p&gt;后来去Github找了一个开源的Android Http异步请求库android-async-http，非常好用。&lt;a href=&quot;http://loopj.com/android-async-http/&quot;&gt;官方网站&lt;/a&gt;。调用之后会异步返回，返回之后仍然在界面类当中，所以也不需要再新建Handler发送消息就能够直接处理界面响应了。&lt;/p&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=loopj&amp;amp;repo=android-async-http&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;请求的发送基于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.http.impl.client.DefaultHttpClient&lt;/code&gt;封装。尽管Android官网推荐在2.3及后续版本中使用HttpURLConnection作为网络开发首选类，但在连接管理和线程安全方面，HttpClient还是具有很大优势。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DefaultHttpClient&lt;/code&gt;提供了一套拦截器，用来处理即将离开的请求和即将到达的响应。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
        @Override
        public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
        }
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此外，它还使用了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager&lt;/code&gt;来管理连接。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;httpClient = new DefaultHttpClient(cm, httpParams);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Android端RTSP解决方案——libstreaming</title>
   <link href="https://blog.cyeam.com/2014/06/15/libstreaming"/>
   <updated>2014-06-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/2014/06/15/libstreaming</id>
   <content type="html">&lt;p&gt;本来是想自己实现RTP传输的，看了好多资料，还看了libstreaming的源代码。大体步骤是这样的：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用MediaRecorder采集，放入流当中；&lt;/li&gt;
  &lt;li&gt;然后建立本地发送线程，sipdroid封装UDP直接实现的，而libstreaming是通过多播实现；&lt;/li&gt;
  &lt;li&gt;采集到的音视频数据不能直接发送到发现线程当中，需要再建立两个本地套接字用于发送和接收；&lt;/li&gt;
  &lt;li&gt;在发送线程里，读取H264的流信息，跳过mp4容器的头信息；&lt;/li&gt;
  &lt;li&gt;接着就是获取NALU分割标记，一般是用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00000001&lt;/code&gt;分隔不同的包，而libstreaming源码讲的是NAL头内存放的是包长度数据；&lt;/li&gt;
  &lt;li&gt;然后，放入NAL和视频帧发送；&lt;/li&gt;
  &lt;li&gt;一个RTP包最大容量是1200字节，如果H264包长度大于1300-sizeof(RTP Header)-sizeof(NAL)=1287字节的话，就要分包发送；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RTSP用于建立RTP会话，可以认为是RTP的一层封装。libstreaming也封装了RTP封包发送的代码，我就基于此进行了二次开发。原本准备直接用sipdroid进行RTP二次开发的，后来解了半天，也没有办法得到H264流，后来才发现它用的是H263，一群草你马奔驰而过。自己新建的一个多播和libstreaming的进行通信，可以获取到RTP包。通过解析RTP包的头，发现头信息基本正确，证明此法可用。然后接着开发，将H264流重新组包播放。&lt;/p&gt;

&lt;p&gt;这里却遇到了一个非常大的问题。H264如果是用多个连续的包发送一个大的H264帧的时候，得到的包有可能是乱续的，还需要重新组装排序。我开发的时候想直接丢掉这些包，只解析包含单个H264的RTP包，但是又不成功。我猜测是被分包的大包是视频流的关键帧，而小包是基于关键帧的运动补偿信息（鄙人对视频压缩基本不懂，只能猜测这么多。。。）。如果是这样的话，那么我之前的做法就不行了。由于时间有限，学校催着交论文呢，而且也没找到简单的方法解决组包问题，这个就没继续做了。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;不废话了，下面是本文的重点。。。&lt;/p&gt;

&lt;p&gt;用RTP自己发送不成功，那么还是用libstreaming传输吧。我有一台Nexus 7和借来的联想A3000-H。做Demo我的Nexus 7 没有任何问题，而联想的用同样的代码就不行。一直以为是编码器的问题，因为Logcat一直提示找不到编码器。后来才发现是我太弱了。应该是联想的摄像头支持的参数和Nexus这些主流的不一样，导致的问题。有时候也会提示说分辨率不对，但其实是帧率不对，逗我呢。。。&lt;/p&gt;

&lt;p&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Camera.Parameters.getSupportedPreviewSizes&lt;/code&gt;可以获得预览尺寸，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Camera.Parameters.getSupportedPreviewFpsRange&lt;/code&gt;可以获得预览帧率。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>百度云推送</title>
   <link href="https://blog.cyeam.com/golang/2014/06/11/baiduyunpush"/>
   <updated>2014-06-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/06/11/baiduyunpush</id>
   <content type="html">&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=BaiduYunPush&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;repo=BaiduYunPush&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;170&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;iframe src=&quot;http://ghbtns.com/github-btn.html?user=mnhkahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large&quot; allowtransparency=&quot;true&quot; frameborder=&quot;0&quot; scrolling=&quot;0&quot; width=&quot;185&quot; height=&quot;30&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;百度云推送支持通知、消息和富媒体的发送。我只实现了最简单的群推送通知的功能。还有针对指定ID的发送，指定通知布局，指定打开网页等一系列设置都没有包含。接下来还要用到这个的时候再去进行开发好了。&lt;/p&gt;

&lt;p&gt;关于Android端的推送，有Google的官方支持，但是大家懂得，只能另寻它法。Android不同于iOS，它运行程序在后台有常驻进程。所以就有了其它通知方法。这样就可以由后台进程获取到通知内容后自行展示。发送通知还能用IBM的MQTT，虽然我在他们部门实习过，但是这个东西不会用。。。还有自己实现HTTP长连接Keep-Alive，发送Request之后等待Response。这些都是目前常用的方法，我都不会。那么，我就去找了现成的百度云推送。&lt;/p&gt;

&lt;p&gt;百度提供了两种实现方式：SDK和REST API。SDK包括了PHP、JAVA、Python、Node.js和C#这些语言。而我选择比较熟悉的Go语言开发REST接口发送。接口请参考百度提供的文档。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/api/list&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;发送消息需要验证用户身份，需要按照百度的规则将内容计算出的MD5码一并发送用于验证。先开始验证逻辑的时候，一直用的Javascript的encodeURI和网上找的MD532位加密算法验证，都不成功。十分受挫。后来去Github找了前辈OopsWare用Java写的SDK&lt;a href=&quot;https://github.com/OopsWare/BaiduPush-Server-SDK&quot;&gt;BaiduPush-Server-SDK&lt;/a&gt;。通过调试抓取它的sign码的计算结果，然后我再用Go照着结果去写。结果发现一下子就连上了，可能是我验证用的工具不对的原因把。。。愁&lt;/p&gt;

&lt;p&gt;这个东西我原本是要在毕设当中使用的，毕设用了beego做服务器开发，这里也就一并用来开发了。等到答辩结束之后，争取能够开发出第一个Go语言实现的SDK。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;百度提供的签名算法：http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/api#.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Android客户端的开发可以直接参考百度提供的Demo和&lt;a href=&quot;http://bs.baidu.com/push-sdk-release/Baidu-Push-SDK-Android-L2-4.0.0.zip&quot;&gt;Android开发文档&lt;/a&gt;，照着样子挪到自己的项目里就可以了。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;当时下载了百度提供的PushDemo，看到libs文件夹里的这三个文件夹，瞬间槽点满满。怪不得人们都说：“Android只是玩具，要开发应用，还得靠iOS”。Google管理下乱七八糟的硬件生态圈（包括软件也是。。。），通过NDK开发就要针对三个平台分别编译，如果是大型游戏，那么应用占用空间将会是iOS的三倍。除非你只想支持一种。而且其平台的混乱，还使得基于芯片的代码优化困难重重。这也就是为什么Android至今都没有大型应用的原因。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/android_tucao.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再加上最新得知Google香港也对中国进行了关键字屏蔽，曾经的好感又少了不少。。。每一个Android程序员开发过一阵程序之后，都会说要转iOS开发，我想说我也是这么想的。。。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>『hello, world』 如何运行</title>
   <link href="https://blog.cyeam.com/computer/2014/05/15/gcc"/>
   <updated>2014-05-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2014/05/15/gcc</id>
   <content type="html">&lt;p&gt;以经典的“hello, world”为例，分析编译的各个阶段。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* $begin hello */
#include &amp;lt;stdio.h&amp;gt;
int main() 
{
    printf(&quot;hello, world\n&quot;);
}
/* $end hello */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;预处理阶段。将include要引入的文件载入并替换掉include。将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.c&lt;/code&gt;文件转换为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.i&lt;/code&gt;文件。使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcc&lt;/code&gt;命令加上参数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-E&lt;/code&gt;。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  `gcc -E hello.c -o hello.i`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;预处理结束后，生成了845行的hello.i文件。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;编译阶段。将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.i&lt;/code&gt;文件编译成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.s&lt;/code&gt;文件。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  `gcc -S hello.i -o hello.s`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;将其翻译成汇编语言。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      .file   &quot;hello.c&quot;
      .section    .rodata
  .LC0:
      .string &quot;hello, world&quot;
      .text
      .globl  main
      .type   main, @function
  main:
  .LFB0:
      .cfi_startproc
      pushq   %rbp
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp
      .cfi_def_cfa_register 6
      movl    $.LC0, %edi
      call    puts
      popq    %rbp
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
  .LFE0:.LC0:
      .size   main, .-main
      .ident  &quot;GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1&quot;
      .section    .note.GNU-stack,&quot;&quot;,@progbits
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.section    .rodata&lt;/code&gt;后面定义了一个只读字符串常量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.LC0&lt;/code&gt;，用来储存&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello, world&lt;/code&gt;。&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.text&lt;/code&gt;后面开始是代码区，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.globl  main&lt;/code&gt;定义了主函数入口。&lt;/li&gt;
      &lt;li&gt;.cfi_startproc用在每个函数的开始，用于初始化一些内部数据结构。&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;rbp寄存器是ebp寄存器64位扩展，ebp寄存器扩展基址指针寄存器(extended base pointer)　　其内存放一个指针，该指针指向系统栈最上面一个栈帧的底部。&lt;/p&gt;

        &lt;blockquote&gt;
          &lt;p&gt;以 %r 开头的表示 64-bit 寄存器；以 %e 开头的是 32-bit 寄存器。&lt;/p&gt;
        &lt;/blockquote&gt;

        &lt;p&gt;pushq，64位，所以是q。保存rbp，以便使用rbp作为栈指针。&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;.cfi_endproc在函数结束的时候使用与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.cfi_startproc&lt;/code&gt;相配套使用。&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;movq    %rsp, %rbp&lt;/code&gt;保存栈frame，现在有些编译器开发者致力于优化函数调用，优化这个frame就是其中一项。&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;movl    $.LC0, %edi&lt;/code&gt;把字符串”hello, world”的地址放入edi。&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call    puts&lt;/code&gt;打印并且在后面自动追加换行符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;汇编阶段。将上一步生成的汇编代码通过汇编器编译成目标文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.o&lt;/code&gt;。&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcc -c hello.s -o hello.o&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;链接阶段。将上一步得到的目标文件与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;printf.o&lt;/code&gt;链接合并到可执行文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello&lt;/code&gt;中。编译完成。&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcc hello.o -o hello&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;《深入理解计算机系统》&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/hbuxiaoshe/article/details/6533581&quot;&gt;-E参数在gcc上的好处 - xiaoshe的专栏&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://hi.baidu.com/hp_roc/item/28c3edf7704b1c1ece9f3298&quot;&gt;GCC编程四个过程:预处理-编译-汇编-链接 - 富贵&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/justinyo/archive/2013/03/08/2950718.html&quot;&gt;分析.cpp文件编译生成的汇编文件里语句的作用 - JustinYo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.360doc.com/content/09/1230/12/510771_12295954.shtml&quot;&gt;对gcc编译汇编码解析&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://seekingfun.org/blog/2012/02/25/assembly-note/&quot;&gt;汇编学习笔记一则 - Seeking Fun&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bbs.chinaunix.net/thread-2048159-1-1.html&quot;&gt;简易学习汇编 - ChinaUnix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>笔试题总结</title>
   <link href="https://blog.cyeam.com/cyeam/2014/05/14/exam"/>
   <updated>2014-05-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/cyeam/2014/05/14/exam</id>
   <content type="html">&lt;p&gt;整理准备按照语言进行分类，先做C/C++相关的题，然后是Java相关。最后是算法题。&lt;/p&gt;

&lt;p&gt;##C/C++&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/03/31/constructor&quot;&gt;构造函数成员初始化列表&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/03/21/static&quot;&gt;STATIC数据成员&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/03/20/string&quot;&gt;字符数组、字符指针、字符串&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/03/20/sizeof&quot;&gt;SIZEOF操作符&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/03/14/const&quot;&gt;CONST关键字&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2014/05/15/gcc&quot;&gt;『hello, world』 如何运行&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##Computer Science&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/02/swap&quot;&gt;3种交换值的方法&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/03/11/bitwise_operators&quot;&gt;位运算符(Bitwise Operators)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2011/01/11/cpp_thread&quot;&gt;Windows下线程操作&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2011/01/11/cpp_process&quot;&gt;Windows下进程操作&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##Algorithm&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/08/go_index&quot;&gt;字符串查找算法&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/hash/2014/07/30/bloomfilter&quot;&gt;布隆过滤器&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/15/go_largenumberx&quot;&gt;大数乘法&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/17/quicksort&quot;&gt;快速排序(QUICK SORT)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/16/bubblesort&quot;&gt;冒泡排序(BUBBLE SORT)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/06/heapsort&quot;&gt;堆排序(HEAP SORT)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/02/selectsort&quot;&gt;简单选择排序(SELECT SORT)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##笔试题&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/11/23/sogou&quot;&gt;搜狗2014校园招聘笔试题&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/11/05/sugon&quot;&gt;中科曙光2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/30/founder&quot;&gt;方正国际2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/25/bsast&quot;&gt;神州航天软件2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/24/letv&quot;&gt;乐视2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/24/gamewave&quot;&gt;趣游2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/21/giant&quot;&gt;巨人网络2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/19/qunarinterview&quot;&gt;去哪网2014校园招聘Java开发面经&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/19/perfectworld&quot;&gt;完美世界2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/17/glodon&quot;&gt;广联达2014校园招聘笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/15/chukong&quot;&gt;触控科技2014校园招聘笔试题&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/10/13/baidu&quot;&gt;百度2014校园招聘后台开发笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/09/16/alibabainterview&quot;&gt;阿里巴巴2014校园招聘面经&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/09/09/bsastintern&quot;&gt;神州航天软件发展规划部实习笔试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/05/23/alibaba&quot;&gt;阿里巴巴2013年实习生笔试题&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/04/20/microsoft_intern_2012&quot;&gt;微软2012年实习生笔试题&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/collection/2013/04/07/microsoft_intern_2013&quot;&gt;微软2013年夏季实习生笔试题&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;题图，北京卧佛寺。当时找工作，差点去拜拜。。。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>浪潮之巅——腾讯帝国？</title>
   <link href="https://blog.cyeam.com/ctalk/2014/05/07/tencent_empire"/>
   <updated>2014-05-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ctalk/2014/05/07/tencent_empire</id>
   <content type="html">&lt;p&gt;昨天，腾讯成立了微信事业群。原文查看&lt;a href=&quot;https://tech.qq.com/a/20140506/069700.htm&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://img1.gtimg.com/tech/pics/hv1/233/157/1585/103104893.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;引用一下微信事业群总裁张小龙的鸡汤：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;做对用户有价值的事情。&lt;/li&gt;
    &lt;li&gt;保持我们自身的价值观，因为它会体现在我们的产品和服务中。&lt;/li&gt;
    &lt;li&gt;保持小团队，保持敏捷。&lt;/li&gt;
    &lt;li&gt;学习和快速迭代比过去的经验更重要。&lt;/li&gt;
    &lt;li&gt;系统思维。&lt;/li&gt;
    &lt;li&gt;让用户带来用户，口碑赢得口碑。&lt;/li&gt;
    &lt;li&gt;思辨胜于执行。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;联通、电信、移动这三大电信服务商，作为互联网的载体，在过去的十几年里，除了移动网络从2G升级到3G，3G再到4G，ASDL升级到光纤，就基本没有任何作为了。微信的出现，直接跃居到这三大电信商头上，中国未来的通信方式可能由此改变。&lt;/p&gt;

&lt;p&gt;微信刚开始，还没怎么引起大家的注意，总觉得就是手机QQ。但是发展这些年之后，微信却越来越热，去吃个饭，服务员都叫你扫个二维码，给你打个折。中央五台还天天叫你关注微信，猜球队输赢。不知不觉的变得炙手可热。&lt;/p&gt;

&lt;p&gt;让我觉得眼前一亮的，就是微信公共帐号。从一开始关注Fenng，再到MacTalk，后来吃饭为了打折、买东西为了得到优惠，还关注了宜家这些帐号。招商银行的服务帐号也非常好用，绑定之后可以很简单的使用，都不用去下载它们的App了。&lt;/p&gt;

&lt;p&gt;公共帐号的出现，使得微信从一个简单的IM工具变成了一个内容消费型App，大家有了一个地方可以交流，说出自己的想法。以前也有博客或者牛博网这些地方可以，但是在移动互联网时代，这样的形式更好一些。这几天了解了一下微信的开发文档，微信最近增加了搜搜地图的API支持，未来微信公共号内容的形式又增加了。虽然腾讯自家的地图市场份额不行，但是有微信的支持，赶上百度妥妥的。。。&lt;/p&gt;

&lt;p&gt;前几天想申请一个微信号做开发使用，才发现自从微信5.0 版本之后，就只能通过手机进行注册了，我以前通过QQ号注册的方式不行了(当然也不是绝对不行，去山寨Android App市场下载一个老版本的，安上还是有通过QQ号注册的。。)。微信如果要成功，就必须摆脱QQ，摆脱腾讯原来主要收入群体。所以，微信不能再使用QQ登录，昨天的成为腾讯第七个事业群，和QQ有相同战略地位，也就顺利成章了。众所周知，腾讯的用户主要集中在小学生和较不发达地区(反正我周围是这样)，这里的用户目前依然是QQ的忠实用户。而微信后来不能使用QQ登录的原因也很简单，躲开这些用户。如果每天微信群里都是马化腾老婆生日到了，朋友圈里面都是转发这条保全家平安(朋友圈好像就没这个功能。。。)，那么微信也就是QQ的下场了。&lt;/p&gt;

&lt;p&gt;腾讯一直以来主要的收入群体都是小学生和不发达地区，小学生玩游戏，村子里的办黄钻，玩QQ空间。这虽然让腾讯游戏成为世界上最赚钱的游戏公司，QQ成为中国最牛逼的IM，但是也就如此了。一个产品不能照顾到所有的用户类型，而不照顾社会最中间的力量，也不可能做出改变世界的产品。有舍，有得。&lt;/p&gt;

&lt;p&gt;老对手，阿里，在这方面就做得很好。很多创业公司都是靠淘宝起家的，不管是主要用户和主要卖家，都包含了社会的中坚力量，因为小学生不会用支付宝。最近推出的余额宝，也同样改变了中国，让那些大银行的活期利息提高了10倍。今天，阿里好像就要上市了。最高估值，全球第四，嗯嗯，挺好。未来几年，也就是这两个公司的竞争了。&lt;/p&gt;

&lt;p&gt;最后提一下度厂。刚才提到了用户群体，貌似百度忠实的用户就是宅男，主要的产品就是AV。从最早的百度下吧，到后来的百度影音，现在的百度网盘。还有当年排名贴吧第三的片种吧。我严重怀疑我的老乡Robin老婆是卖卫生纸的。&lt;/p&gt;

&lt;p&gt;最近Robin在内部邮件里说，要求员工10点以前上班。还有狼性。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/47c4717dcf3871c68e6c483b3985f713_r.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;以我这个没有去过百度上班的外人来看，它们的问题主要是出在高层，高层领导利用职权谋私，部门之间利益内斗消耗，是百度在移动互联网上失败的主要原因。改变这个很难，而老板给了压力，Robin只能惹的起码农，邮件就来了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Android 配置</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/23/pager_settings"/>
   <updated>2014-04-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/23/pager_settings</id>
   <content type="html">&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.content.SharedPreferences&lt;/code&gt;配置内容可以在Activity、Service、Broadcast和Content Provider中使用。在程序运行时动态获取配置信息，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.preference.PreferenceManager.getDefaultSharedPreferences()&lt;/code&gt;获取实例。&lt;/p&gt;

&lt;h2 id=&quot;1-读取配置信息&quot;&gt;1. 读取配置信息&lt;/h2&gt;

&lt;p&gt;SharedPreferences 提供如下方法直接获取配置的信息。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;#&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;方法&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;getString&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;getBoolean&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;getInt&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;getLong&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;getFloat&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;2-修改配置信息&quot;&gt;2. 修改配置信息&lt;/h2&gt;

&lt;p&gt;修改配置需要用到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.content.SharedPerferences.Editor&lt;/code&gt;。Editor提供了如下方法修改首选项内容。获取Editor通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SharedPerferences.editor()&lt;/code&gt;获得。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;#&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;方法&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;putString&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;putBoolean&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;putInt&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;putLong&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;putFloat&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Editor edit = settings.edit();
edit.putBoolean(PREF_MESSAGE, true);
edit.commit();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3-preferenceactivity&quot;&gt;3. PreferenceActivity&lt;/h2&gt;

&lt;p&gt;使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PreferenceActivity&lt;/code&gt;，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addPreferencesFromResource&lt;/code&gt;以界面的形式展示，并且可以在此界面中对配置的值进行修改。展示的布局将会按照首选项的配置文件自动生成。每一项都是由指定的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android:title&lt;/code&gt;和该项的值组成。&lt;/p&gt;

&lt;p&gt;用户更新了设置之后，还要将最新用户设置的内容展现出来，能够让用户判断是否选择正确。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;getPreferenceScreen().findPreference(PREF_ROLE).setSummary(role);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;4-首选项内容&quot;&gt;4. 首选项内容&lt;/h2&gt;

&lt;p&gt;单选提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.preference.ListPreference&lt;/code&gt;，它类似于HTML的下拉列表，可以为其提供选项值和选项标签。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;string-array name=&quot;role&quot;&amp;gt;
    &amp;lt;item&amp;gt;指挥&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;协调&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;默认&amp;lt;/item&amp;gt;
&amp;lt;/string-array&amp;gt;
&amp;lt;string-array name=&quot;role_values&quot;&amp;gt;
    &amp;lt;item&amp;gt;commander&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;coordinator&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;default&amp;lt;/item&amp;gt;
&amp;lt;/string-array&amp;gt;

&amp;lt;ListPreference
    android:defaultValue=&quot;默认&quot;
    android:entries=&quot;@array/role&quot;
    android:entryValues=&quot;@array/role&quot;
    android:key=&quot;role&quot;
    android:title=&quot;@string/settings_current_role&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;5-监听配置信息的修改&quot;&gt;5. 监听配置信息的修改&lt;/h2&gt;

&lt;p&gt;设置监听需要实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.content.OnSharedPreferenceChangeListener&lt;/code&gt;接口，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;settings.registerOnSharedPreferenceChangeListener(this);&lt;/code&gt;进行注册监听。事件处理在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onSharedPreferenceChanged&lt;/code&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerOnSharedPreferenceChangeListener&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unregisterOnSharedPreferenceChangeListener&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://stackoverflow.com/questions/4966816/how-to-create-radiobutton-group-in-preference-xml-window&quot;&gt;How to create RadioButton group in preference.xml window? - Stackoverflow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://developer.android.com/reference/android/content/SharedPreferences.html&quot;&gt;SharedPreferences - Android Developers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】Marko Gargenta, Learning Android[M]. 电子工业出版社, 2012.7.&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>查看自己的top10 Linux命令</title>
   <link href="https://blog.cyeam.com/linux/2014/04/19/linux_top10_command"/>
   <updated>2014-04-19T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/linux/2014/04/19/linux_top10_command</id>
   <content type="html">&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;history | awk &apos;{CMD[$2]++;count++;}END { for (a in CMD)\
print CMD[a] &quot; &quot; CMD[a]/count*100 &quot;% &quot; a;}&apos; | grep -v &quot;./&quot; \
| column -c3 -s &quot; &quot; -t | sort -nr | nl | head -n10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;http://www.v2ex.com/t/109153#reply26&quot;&gt;来晒晒你使用过的 Linux 命令的 top 10 吧! - V2EX&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我的结果是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 1	133  26.5469%   git
 2	111  22.1557%   sudo
 3	58   11.5768%   cd
 4	53   10.5788%   ls
 5	24   4.79042%   grep
 6	22   4.39122%   go
 7	10   1.99601%   jekyll
 8	10   1.99601%   gor
 9	10   1.99601%   adb
10	7    1.39721%   ssh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最近都在GitHub Pagers上面写论文，所以git和jekyll都榜上有名。。。&lt;/p&gt;

&lt;p&gt;不过感觉次数少了点，查了一下，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;history&lt;/code&gt;默认保存500条命令(可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo $HISTSIZE&lt;/code&gt;查看)。不过直接用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;history&lt;/code&gt;命令，能看到多于500条的命令，看到了519条。原因是如果你不注销或者关机，那么执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hisotry&lt;/code&gt;命令 只要永久保存，可能记录大于500。如果你注销了以后，.bash_history只保存最近的500条记录。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt;是一个强大的文本分析工具。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt;其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awk&lt;/code&gt;工作流程是这样的：读入有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;\n&apos;&lt;/code&gt;换行符分割的一条记录，然后将记录按指定的域分隔符划分域，填充域，$0则表示所有域,$1表示第一个域,$n表示第n个域。默认域分隔符是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;空白键&quot;&lt;/code&gt; 或 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;[tab]&lt;/code&gt;键”,所以$1表示登录用户，$3表示登录用户ip,以此类推。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;http://www.cnblogs.com/ggjucheng/archive/2013/01/13/2858470.html&quot;&gt;linux awk命令详解 - 简单，可复制&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head&lt;/code&gt;可以用来查看文件头部指定行数，所以最后的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head -n10&lt;/code&gt;就是要显示前10行，就是前10个，去掉就是显示全部了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Android 自定义形状</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/18/pager_ui_design"/>
   <updated>2014-04-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/18/pager_ui_design</id>
   <content type="html">&lt;p&gt;Android中的drawable不仅只能是图片，还可以是自定义的图形(Shape)，用XML文件来描述。Shape可以被其他控件，例如按钮(Button)使用，显示出指定形状的按钮来。这里我们要进行扁平化处理，所以需要一个圆形的Shape。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;shape xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:shape=&quot;oval&quot; &amp;gt;
    &amp;lt;!-- 填充的颜色 --&amp;gt;
    &amp;lt;solid android:color=&quot;#FFFFFF&quot; /&amp;gt;
    &amp;lt;!-- 设置按钮的四个角为弧形 --&amp;gt;
    &amp;lt;corners android:radius=&quot;50dip&quot; /&amp;gt;
&amp;lt;/shape&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们的控件只要设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android:background=&quot;@drawable/round&quot;&lt;/code&gt;，就能限定成圆形的。但这还不够，我们还需要在圆形的控件内绘制图片，需要使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ImageView&lt;/code&gt;控件，为其增加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android:src&lt;/code&gt;属性。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://developer.android.com/guide/topics/resources/drawable-resource.html&quot;&gt;Drawable Resources - Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>基于流媒体的对讲机系统——数据库模块</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/18/pager_sqlite"/>
   <updated>2014-04-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/18/pager_sqlite</id>
   <content type="html">&lt;p&gt;移动设备中的数据库是对在线网络存储的一个重要补充。本课题的需求中，要求能保存关注的联系人和通话历史，所以使用SQLite来实现。SQLite可以像普通数据库一样机型增删改查的操作。&lt;/p&gt;

&lt;p&gt;###游标
查询指令会返回游标，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moveToNext&lt;/code&gt;方法进行遍历处理。&lt;/p&gt;

&lt;p&gt;###android.content.ContentValues
ContentValues是Android提供的一个容器，类似于Map，可以通过Key-Map的方式加入值。&lt;/p&gt;

&lt;p&gt;###SQLiteOpenHelper&lt;/p&gt;

&lt;p&gt;###SQLiteDatabase&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;《Learning Android》 马尔科·加尔根塔 电子工业出版社 ISBN: 9787121172632&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Android 通知</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/18/pager_notification"/>
   <updated>2014-04-18T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/18/pager_notification</id>
   <content type="html">&lt;p&gt;Android在通知栏方面相对于苹果来说比较开放。允许服务常驻后台，所以能够方便实现消息推送。而苹果的进程等待几分钟没有操作之后，就会自动退出，远程发送消息只能经过苹果的远程推送实现。鄙人曾经在公司尝试开发百万级的消息推送接口，说来惭愧，没能成功。&lt;/p&gt;

&lt;h2 id=&quot;1-显示通知&quot;&gt;1. 显示通知&lt;/h2&gt;

&lt;p&gt;发送通知需要用到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.app.NotificationManager&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android.app.Notification&lt;/code&gt;。调用流程如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NotificationManager mNotificationMgr = (NotificationManager) mContext
            .getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification();
// 初始化notifacation
mNotificationMgr.notify(type, notification);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notifaction提供flags、icon、sound等设置通知的类型，图标和声音。可以通过设置contentIntent为通知增加点击启动的Activity。&lt;/p&gt;

&lt;h2 id=&quot;2-远程推送通知&quot;&gt;2. 远程推送通知&lt;/h2&gt;

&lt;p&gt;关于远程推送，发现有使用IBM的MQTT的，还有建立长连接的。待我调研一翻，再来修改这里。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://blog.csdn.net/qinjuning/article/details/6915482&quot;&gt;Android中通知的使用—–Notification详解&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://developer.android.com/reference/android/app/Notification.html&quot;&gt;Notification - Android Developers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】&lt;a href=&quot;http://developer.android.com/reference/android/app/NotificationManager.html&quot;&gt;NotificationManager - Android Developers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【4】&lt;a href=&quot;http://www.360doc.com/content/12/1121/14/7635_249300653.shtml&quot;&gt;Push Notification （2）HTTP长连接&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>视频编解码技术H264</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/17/pager_video"/>
   <updated>2014-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/17/pager_video</id>
   <content type="html">&lt;p&gt;http://blog.csdn.net/tjy1985/article/details/7963531
MPEG正式审核程序是Moving Picture Experts Group的简称。这个名字本来的含义是指一个研究视频和音频编码标准的“动态图像专家组”组织，成立于1988年，致力开发视频、音频的压缩编码技术。现在我们所说的MPEG泛指由该小组制定的一系列视频编码标准正式审核程序。该小组于1988年组成，至今已经制定了MPEG-1、MPEG-2、MPEG-3、MPEG-4、MPEG-7等多个标准，MPEG-21正在制定中。MPEG是ISO和IEC的工作组，它的官方头衔为：第一技术委员会第二十九子委员会第十一号工作组正式审核程序，英文头衔为ISO/IEC JTC1/SC29 WG11。MPEG大约每2-3个月举行一次会议，每次会议大约持续5天，在会议期间，新的建议和技术细节先在小组中讨论，成熟后进入标准化的正式审核程序。与MPEG工作组相关的其他几个视频标准化工作组包括ITU-T VCEG以及JVT。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;MPEG-1：第一个官方的视訊音訊压缩标准，随后在Video CD中被采用，其中的音訊压缩的第三级（MPEG-1 Layer 3）简称MP3，成为比较流行的音频压缩格式。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MPEG-2：广播质量的视訊、音訊和传输协议。被用于無線數位電視-ATSC、DVB以及ISDB、数字卫星电视（例如DirecTV）、数字有线电视信号，以及DVD视频光盘技术中。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MPEG-3：原本目标是为高解析度电视（HDTV）设计，随后發現MPEG-2已足夠HDTV應用，故MPEG-3的研發便中止。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MPEG-4：2003年发布的视訊压缩标准，主要是扩展MPEG-1、MPEG-2等標準以支援視訊／音訊物件（video/audio “objects”）的編碼、3D內容、低位元率編碼（low bitrate encoding）和數位版權管理（Digital Rights Management），其中第10部分由ISO/IEC和ITU-T联合发布，称为H.264/MPEG-4 Part 10。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;H.264/MPEG-4第10部分，或称AVC（Advanced Video Coding，高级视频编码），是一种面向块的基于运动补偿的编解码器标准。H.264因其是蓝光碟片的一种编解码标准而著名，所有蓝光碟片播放器都必须能解码H.264。它也被广泛用于网络流媒體数据如Vimeo、YouTube、以及iTunes Store，网络软件如Adobe Flash Player和Microsoft Silverlight，以及各种高清晰度電視陆地广播（ATSC，ISDB-T，DVB-T或DVB-T2），线缆（DVB-C）以及卫星（DVB-S和DVB-S2）。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;H264的功能分为两层，视频编码层（VCL）和网络提取层（NAL）。视频编码层负责高效的视频内容表示，而网络适配层负责以网络所要求的恰当的方式对数据进行打包和传送。引入NAL并使之与VCL分离带来的好处包括两方面：其一、使信号处理和网络传输分离，VCL 和NAL 可以在不同的处理平台上实现；其二、VCL 和NAL 分离设计，使得在不同的网络环境内，网关不需要因为网络环境不同而对VCL比特流进行重构和重编码。&lt;/p&gt;

&lt;p&gt;H.264 的基本流由一系列NALU （Network Abstraction Layer Unit ）组成，不同的NALU数据量各不相同。H.264 草案指出[2]，当数据流是储存在介质上时，在每个NALU 前添加起始码：0x000001，用来指示一个 NALU的起始和终止位置。在这样的机制下，在码流中检测起始码，作为一个NALU得起始标识，当检测到下一个起始码时，当前NALU结束。每个NALU单元由一个字节的 NALU头（NALU Header）和若干个字节的载荷数据（RBSP）组成。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;https://blog.csdn.net/china_video_expert/article/details/7211302&quot;&gt;H264码流打包分析 - szu030606的专栏&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>SDP协议及开发设计</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/17/pager_sdp"/>
   <updated>2014-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/17/pager_sdp</id>
   <content type="html">&lt;p&gt;会话描述协议SDP(Session Description Protocol)完全是一种会话描述格式 ，它不属于传输协议，它只使用不同的适当的传输协议，包括会话通知协议（SAP）、会话初始协议（SIP）、实时流协议（RTSP）、MIME 扩展协议的电子邮件以及超文本传输协议（HTTP）。本课题是要先通过SIP建立通信连接，再通过RTSP进行传输。连接这两个协议的就是SDP。&lt;/p&gt;

&lt;p&gt;SDP描述多媒体信息与协商具体参数。具体做法是：主叫方将己方SDP消息放入INVITE请求消息的主体中，当被叫方收到后，对SDP消息分析，若兼容对方的编码格式，则创建一个协商后的SDP消息，放入确认响应消息中，若不兼容则发送失败响应。可参考上一节中的SIP请求协议分析。&lt;/p&gt;

&lt;p&gt;SDP基于文本协议，消息包括：会话名称、用户名、地址、媒体类型、传输协议、
媒体编码格式、端口等，主要参数及格式为：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;o：&lt;用户名&gt;&lt;会话编号&gt;&lt;版本&gt;&lt;网络类型&gt;&lt;地址类型&gt;&lt;地址&gt;&lt;/地址&gt;&lt;/地址类型&gt;&lt;/网络类型&gt;&lt;/版本&gt;&lt;/会话编号&gt;&lt;/用户名&gt;&lt;/li&gt;
  &lt;li&gt;c：&lt;网络类型&gt;&lt;地址信息&gt;&lt;连接地址&gt;&lt;/连接地址&gt;&lt;/地址信息&gt;&lt;/网络类型&gt;&lt;/li&gt;
  &lt;li&gt;a：&lt;属性&gt;或&lt;属性&gt;：&lt;值&gt;&lt;/值&gt;&lt;/属性&gt;&lt;/属性&gt;&lt;/li&gt;
  &lt;li&gt;t：&lt;开始时间&gt;&lt;结束时间&gt;&lt;/结束时间&gt;&lt;/开始时间&gt;&lt;/li&gt;
  &lt;li&gt;m：&lt;媒体&gt;&lt;端151&gt;&lt;传输协议&gt;&lt;媒体编码格式列表&gt;&lt;/媒体编码格式列表&gt;&lt;/传输协议&gt;&lt;/端151&gt;&lt;/媒体&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上一节SIP请求中的SDP信息如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;请求用户名mint。&lt;/li&gt;
  &lt;li&gt;请求IPv4地址：10.14.5.218。&lt;/li&gt;
  &lt;li&gt;音频在7078端口，通过RTP进行传输，通过speex编码，支持8000、16000、32000三种采样率。视频在9078端口，支持MP4V-ES和H263-1998两种视频编码格式。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.cnblogs.com/qingquan/archive/2011/08/02/2125585.html&quot;&gt;SDP 协议分析 博水&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>实时流媒体协议RTSP</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/17/pager_rtsp"/>
   <updated>2014-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/17/pager_rtsp</id>
   <content type="html">&lt;p&gt;即时串流协定（Real Time Streaming Protocol，RTSP）是用来控制声音或影像的多媒体串流协议，并允许同时多个串流需求控制，传输时所用的网络通讯协定并不在其定义的范围内，服务器端可以自行选择使用TCP或UDP来传送串流内容，它的语法和运作跟HTTP 1.1类似。RTSP协议提供RTP流媒体发送会话的控制方法。。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/rtsp.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RTSP主要信令如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OPTIONS：获得服务器支持的RTSP消息类型。&lt;/li&gt;
  &lt;li&gt;DESCRIBE：获取服务器返回的SDP消息，其中包含媒体流ID、支持编解码、序列参数集SPS与图像参数集PPS等。&lt;/li&gt;
  &lt;li&gt;SETUP：交互双方的RTP端口号，客户端获取服务器分配的会话ID。&lt;/li&gt;
  &lt;li&gt;PLAY：通知服务器传输流媒体，请求必须包含在SETUP响应中的会话ID。&lt;/li&gt;
  &lt;li&gt;PAUSE：暂停当前会话，即停止媒体流传输，但是并不释放端口与资源。&lt;/li&gt;
  &lt;li&gt;TEARDOWN：结束当前会话，并释放端口、连接与资源。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RTSP流程&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;查询服务器端可用方法
    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;客户端请求(OPTION request):—询问服务器有哪些方法可用&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;OPTIONS rtsp://192.168.1.122/TestSession RTSP/1.0&quot;
  &quot;CSeq: 2&quot;
  &quot;User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;服务器回应(OPTION response):—回复的所有方法在Public字段&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;RTSP/1.0 200 OK&quot;
  &quot;CSeq: 2&quot;
  {&quot;Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE&quot;}
  &quot;&quot;    //最后这个也很重要，最后一个消息头需要有两个CR LF
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;得到媒体描述信息
    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;客户端请求(DESCRIBE request):—–要求得到媒体描述信息&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;DESCRIBE rtsp://192.168.1.122/TestSession RTSP/1.0&quot;
  &quot;CSeq: 3&quot;
  &quot;User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06)&quot;
  &quot;Accept: application/sdp&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;服务器回应(DESCRIBE response):—回应媒体描述信息，一般是sdp信息&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;RTSP/1.0 200 OK&quot;
  &quot;CSeq: 3&quot; //和请求的序号要对应
  {&quot;Server: RTSP Service&quot;
   &quot;Content-Base: rtsp://192.168.1.122/TestSession&quot;
   &quot;Content-Type: application/sdp&quot;         //表示回应的是sdp信息 
   &quot;Content-Length: 367&quot;
  }
  &quot;&quot;    然后再发送生成的sdp信息，sdp信息也可以和上面的字符串组合一起发送。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;建立RTSP会话。SETUP成功之后，服务器就会开始录制，等待客户端播放。
    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;客户端请求(SETUP request):—–通过Transport头字段列出可接受的传输选项，建立会话&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;SETUP rtsp://192.168.1.122/TestSession/trackID=1 RTSP/1.0&quot;
  &quot;CSeq: 4&quot;
  &quot;User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06)&quot;
  &quot;Transport: RTP/AVP;unicast;client_port=2274-2275&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;服务器回应(SETUP response):–建立会话，通过Transport头字段返回选择的具体传输选项，并返回建立的Session ID;&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;RTSP/1.0 200 OK&quot;
  &quot;CSeq: 4&quot;
  &quot;Session: 68422540987712&quot;
  &quot;Transport:RTP/AVP;unicast;source=192.168.1.122;server_port=8000-8001;client_port=2274-2275;ssrc=3969838262&quot;
  &quot;&quot;    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;请求开始传送数据
    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;客户端请求(PLAY request): —–请求服务器开始发送数据&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;PLAY rtsp://192.168.1.122/TestSession RTSP/1.0&quot; 
  &quot;CSeq: 5&quot;
  &quot;User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06)&quot;
  &quot;Session: 68422540987712&quot;
  &quot;Range: npt=0.000-&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;服务器回应(PLAY response):——回应该请求的信息&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;RTSP/1.0 200 OK&quot;
  &quot;CSeq: 5&quot;
  &quot;Session: 68422540987712&quot;
  &quot;RTP-Info: url=rtsp://192.168.1.122/TestSession/trackID=1&quot;
  &quot;&quot;   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;数据传输&lt;/p&gt;

    &lt;p&gt;服务器-&amp;gt;客户端：发送流媒体数据， 通过RTP协议传输数据&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;关闭会话，退出
    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;客户端请求(TEARDOWN request):———请求关闭会话&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;TEARDOWN rtsp://192.168.1.122/TestSession RTSP/1.0&quot;
  &quot;CSeq: 6&quot;
  &quot;User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06)&quot;
  &quot;Session: 68422540987712&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;服务器回应(TEARDOWN response):&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;RTSP/1.0 200 OK&quot;
  &quot;CSeq: 6&quot;
  &quot;Session: 68422540987712&quot;
  &quot;Connection: Close&quot;
  &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://www.cnblogs.com/qingquan/archive/2011/07/14/2106834.html&quot;&gt;RTSP 协议分析 （一） 博水&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://blog.csdn.net/wl_fln/article/details/6444261&quot;&gt;rtsp 交互流程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>实时传输协议RTP</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/17/pager_rtp"/>
   <updated>2014-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/17/pager_rtp</id>
   <content type="html">&lt;h2 id=&quot;1-rtp&quot;&gt;1. RTP&lt;/h2&gt;
&lt;p&gt;实时传输协议（Real-time Transport Protocol，RTP）是一个网络传输协议，它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的。RTP协议详细说明了通过互联网上传递音频和视频的标准数据包格式。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/rtp_rfc1889.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;每一个RTP数据报都由头部（Header）和载体（Payload）两个部分组成，其中头部前12个字节(96 bit)的含义是固定的，而负载则可以是音频或者视频数据。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ver.（2 bits）是目前协定的版本号码，目前版号是 2。&lt;/li&gt;
  &lt;li&gt;P（1 bit）是用于RTP 封包（packet）结束点的预留空间，视封包是否需要多余的填塞空间。&lt;/li&gt;
  &lt;li&gt;X（1 bit）是否在使用延伸空间于封包之中。.&lt;/li&gt;
  &lt;li&gt;CC（4 bits）包含了 CSRC 数目用于修正标头（fixed header）.&lt;/li&gt;
  &lt;li&gt;M (1 bit) 是用于应用等级以及其原型（profile）的定义。如果不为零表示目前的资料有特别的程序解译。&lt;/li&gt;
  &lt;li&gt;PT（7 bits）是指payload的格式并决定将如何去由应用程式加以解译。&lt;/li&gt;
  &lt;li&gt;SSRC 是同步化来源。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;实时传输控制协议（Real-time Transport Control Protocol 或RTP Control Protocol或简写RTCP）是实时传输协议的一个兄弟协议。RTCP为RTP媒体流提供信道外控制。RTCP本身并不传输数据，主要用来和RTP一起协作将要发送的音频和视频数据打包发送。RTCP的主要功能是为RTP所提供的服务质量提供反馈，保证流媒体发送实时同步。&lt;/p&gt;

&lt;p&gt;RTP的同步控制是通过RTCP协议的Sender Report和Receiver Report实现。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Sender Report：发送端报告，所谓发送端是指发出RTP数据报的应用程序或者终端，发送端同时也可以是接收端。&lt;/li&gt;
  &lt;li&gt;Receiver Report：接收端报告，所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2-基于sipdroid完成rtp包解析播放&quot;&gt;2. 基于sipdroid完成RTP包解析播放&lt;/h2&gt;
&lt;p&gt;sipdroid默认建立链接之后会判断当服务器是否是sipdroid官方提供的服务器，如果不是，则只会建立音频通话，不建立视频通话。注释掉该判断语句，发现它实现了发送RTP流媒体功能，通过与linphone的通话，能够在linphone端看到视频信息。然而，无法播放linphone发送的视频信息。又通过查看代码，发现播放视频的方法是通过RTSP接收。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    if (Receiver.call_state == UserAgent.UA_STATE_INCALL &amp;amp;&amp;amp; socket == null
            &amp;amp;&amp;amp; Receiver.engine(mContext).getLocalVideo() != 0
            &amp;amp;&amp;amp; Receiver.engine(mContext).getRemoteVideo() != 0 &amp;amp;&amp;amp; PreferenceManager.getDefaultSharedPreferences(this).getString(org.sipdroid.sipua.ui.Settings.PREF_SERVER, org.sipdroid.sipua.ui.Settings.DEFAULT_SERVER).equals(org.sipdroid.sipua.ui.Settings.DEFAULT_SERVER)))

 mVideoFrame
         .setVideoURI(Uri.parse(&quot;rtsp://&quot;
         + Receiver.engine(mContext).getRemoteAddr() + &quot;/&quot;
         + Receiver.engine(mContext).getRemoteVideo()
         + &quot;/sipdroid&quot;));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;sipdroid播放视频流还需要建立RTSP会话。而linphone直接发送的是RTP包，所以sipdroid无法播放linphone的视频。在此之前一直使用WireShark分析RTSP建立，结果就是没找到建立过程。后来看源码才发现。。。上面提到的代码分别是在包&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.sipdroid.sipua.ui&lt;/code&gt;内的类&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CallScreen&lt;/code&gt;的函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onResume&lt;/code&gt;中和类&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VideoCamera&lt;/code&gt;的函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onResume&lt;/code&gt;中。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;但是现在依然有疑问，上面的代码是通过直接请求远程客户端建立RTSP请求，通过MediaPlayer播放。并没有关系到服务器，那么RTSP是怎么建立的？而且，它专门指定要自己的服务器才能去建立视频连接，既然RTSP建立又没有关系到服务器，这个逻辑又是为什么？而且这两个疑问又互相矛盾，如果大家有想法，不妨屈尊指导一下学渣&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;抛开上面的问题不管，只要自行进行视频解码，还是可以播放的。通过从rtp_socket读取包，发现每次都能读取到1414字节的数据。应该可以证实是从linphone传来的数据，包大小也正常。下面开始着手解包和播放。&lt;/p&gt;

&lt;p&gt;整个RTP包的实现就是基于UDP进行传输的。也可以从代码的实现验证这一点，RtpSocket类内部拥有UDP&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DatagramPacket&lt;/code&gt;数据报。RTP可以认为是为UDP加了12字节协议包。类RtpPacket提供自动解析的能力。下面是解析后的结果和协议头的对比。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;#&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;RTP协议&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;抓取到的内容&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;描述&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Version&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;版本号，为2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;hasPadding&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;false&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;没有加塞空间&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;hasExtension&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;false&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;没有额外空间&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;CscrCount&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Cscr数目&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;hasMarker&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;false&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;没有特别程序解释&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;PayloadType&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;103&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Payload的格式 h263-1988&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;SequenceNumber&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;325&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Timestamp&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;468900&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Sscr&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;488792334&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;CscrList&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Payload&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;12&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;3-rtp解包&quot;&gt;3. RTP解包&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;由于前期调研的疏忽，给自己挖了一个坑。绕了好多弯路。前期通过查看论文，了解到目前流行的方法都是通过RFC 3984进行开发。通过RTP发送H264格式的视频。H264格式的视频数据由分隔符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;00000001&lt;/code&gt;、NAL和VCL组成。进行RTP封包就是去掉分隔符进行发送，解包的时候再加上分隔符即可。还看了C++和Java实现的RTP解包方法，愣是和sipdroid的封包方法对不上。后来才意识到，我看的是H264的，而sipdroid用的是H263。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;经过调研，决定使用libsteaming基于RTSP开发。&lt;strong&gt;Android支持H264的编码和解码，但是没有直接提供编码和解码接口。编码只能使用MediaRecorder编码到文件当中，而解码更是只能依靠MediaPlayer播放文件时才能被调用解码器。所以使用libstreaming编码，直接使用MediaPlayer进行RTSP播放即可。如果要自行封装RTP包的话，还需要跨平台编译H264的解码器。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;##乱续、同步&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;【1】&lt;a href=&quot;http://blog.csdn.net/frankiewang008/article/details/7665547&quot;&gt;RTMP/RTP/RTSP/RTCP的区别 - FrankieWang008的专栏&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【2】&lt;a href=&quot;http://tools.ietf.org/html/rfc3984&quot;&gt;RFC3984  RTP Payload Format for H.264 Video&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;【3】鲍轩. 基于Android手机音视频监控的软件研发与同步实现[D]. 杭州电子科技大学, 2013年10月.&lt;/li&gt;
  &lt;li&gt;【4】fly2700. FU-A分包方式，以及从RTP包里面得到H.264数据和AAC数据的方法. http://www.cnweblog.com/fly2700/archive/2012/02/23/319718.html&lt;/li&gt;
  &lt;li&gt;【5】jasonhwang. Wireshark Lua: 一个从RTP抓包里导出H.264 Payload，变成264裸码流文件（xxx.264）的Wireshark插件. http://blog.csdn.net/jasonhwang/article/details/7359095&lt;/li&gt;
  &lt;li&gt;【6】mcodec, H264解码器源码(Android 1.6 版)[EB/OL], http://www.cnblogs.com/mcodec/articles/1780598.html.&lt;/li&gt;
  &lt;li&gt;【7】熊荣海,刘立柱. H263码流的RTP封装的研究与实现.&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>基于流媒体的对讲机系统——系统开发准备</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/17/pager_prepare"/>
   <updated>2014-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/17/pager_prepare</id>
   <content type="html">&lt;p&gt;工欲善其事，必先利其器。写代码也一样，借鉴优秀的工具辅助开发，可以帮助学习和了解相关知识和技术。&lt;/p&gt;

&lt;h3 id=&quot;1-tcpdump&quot;&gt;1. tcpdump&lt;/h3&gt;
&lt;p&gt;开发网络通信程序需要能监测到网络通信流程以及通信内容，以便了解整个通信流程以及问题。在Linux下，一般是通过tcpdump和WireShark来实现该功能。tcpdump负责监控制定设备，从该设备抓包，而WireShark可以用来查看和解析包内容。甚至可以帮助我们分析整个通信流程。在此项目中，就可以用WireShark监测SIP注册和呼叫流程和RTSP和RTP流的传输过程。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tcpdump -i wlan0 -w cInterphone.cap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在此命令中，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i&lt;/code&gt;用于指出要采集的设备，这里指出的是无线网卡wlan0；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-w&lt;/code&gt;要指出采集到的数据输出的目录，这里指出是文件cInterphone.cap。此外，启动此命令还需要使用管理员权限。本项目中要监控的是手机的网络通信，所以还需要将手机的网络设成PC的网络代理，这样才能通过监控PC的网卡监控到手机的网络访问情况。&lt;/p&gt;

&lt;p&gt;在使用WireShark的时候，采集到的数据量很大，所以需要进行过滤。查看我们需要的数据。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;通过网络协议过滤。如果我们要查看RTP协议或者RTSP协议的数据，在过滤器中输入RTP即可。&lt;/li&gt;
  &lt;li&gt;通过IP过滤。本项目中是两个设备互相访问，采集到的数据也都是两个设备互相传输的全部数据。为了方便分析流程，可以只所以单独查看一个设备的。可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ip.src=192.168.1.103&lt;/code&gt;实现过滤。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-linphone&quot;&gt;2. linphone&lt;/h3&gt;
&lt;p&gt;SIP呼叫部分的开发，如果呼叫和被叫方同时进行开发和测试，难度较大。本课题采用了Linux下的linphone作为辅助开发工作，用于测试SIP呼叫流程。&lt;/p&gt;

&lt;h3 id=&quot;3-vlc&quot;&gt;3. VLC&lt;/h3&gt;
&lt;p&gt;本课题另一个比较重要的部分，就是流媒体的读取和播放。读取和播放展示是两个流程，需要分开进行开发。这就需要到一个辅助工具来进行测试开发。我们选取了VLC。VLC是一个非常优秀的跨平台流媒体播放器。既可以用来做RTSP服务器，也可以用来做RTSP客户端。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;RTSP方式搭建服务器&lt;/p&gt;

    &lt;p&gt;vlc -vvv sample1.avi –sout “#transcode{vcodec=h264,vb=0,scale=0,acodec=mpga,ab=128,channels=2,samplerate=44100}:rtp{sdp=rtsp://:8554/test}”&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;RTSP方式搭建客户端&lt;/p&gt;

    &lt;p&gt;vlc rtsp://127.0.0.1:1234/&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;限于条件有限，只有一台平板，而且由于大Android硬件千变万化，光个摄像头分辨率就像天上的星星数也数不清，找了几台设备，都是因为摄像头分辨率以及支持的帧率不一样没有办法实现视频对讲。只是用Google自家的Nexus 5和我的Nexus 7能够兼容。使用Android设备测试测想法也就此作罢。&lt;/p&gt;

&lt;p&gt;后来写了一个shell脚本，这样测试的时候方便一点。shell是一点不会，大概说一下。定义变量和Python一样，直接用等号就行，只不过不能有空格；启动lc是阻塞式的，所以需要并发启动，在第一个命令后面加上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&lt;/code&gt;就可以了；&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sleep&lt;/code&gt;能够暂停5秒钟；使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt;引用到变量的值。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;port=1234
client=192.168.1.102

{  
    echo &quot;Start vlc server at rtsp://:$port/---------------------------------------------&quot;
    vlc -vvv ~/Downloads/Justice.League.War.2014.720p.BluRay.x264.DTS-HDWinG.mkv --sout &quot;#transcode{vcodec=h264,vb=0,scale=0,acodec=mpga,ab=128,channels=2,samplerate=44100}:rtp{sdp=rtsp://:1234/}&quot;
}&amp;amp;

sleep 5

echo &quot;Start vlc client at rtsp://$client:$port/--------------------------------&quot;

vlc rtsp://192.168.1.102:1234/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4-adb&quot;&gt;4. adb&lt;/h3&gt;
&lt;p&gt;Android官方提供的开发工具，用于控制Android手机，adb服务器等。最好将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adb&lt;/code&gt;加入到环境变量中，调试起来比较方便。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;安装Android程序。在此之前都是先将安装包放入手机再安装。。。&lt;/p&gt;

    &lt;p&gt;adb install *.apk&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;启动和关闭adb服务&lt;/p&gt;

    &lt;p&gt;adb start-server
  adb kill-server&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;神器，用于排查问题。一般adb启动不了都是因为端口被占用。我在Windows下都是被QQ占用。。。&lt;/p&gt;

    &lt;p&gt;adb nodaemon server&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-ffmpeg&quot;&gt;5. ffmpeg&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;ffprobe 查看视频文件编码格式等详细信息。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;6-视频录制&quot;&gt;6. 视频录制&lt;/h3&gt;
&lt;p&gt;Android 4.4 支持了自带录制屏幕视频的功能，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adb&lt;/code&gt;工具就能够实现视频的录制。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;adb shell screenrecord /sdcard/test.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/MikeZhang/archive/2012/09/09/vlcStreamingServer20120909.html&quot;&gt;用vlc搭建简单流媒体服务器（UDP和TCP方式）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>基于流媒体的对讲机系统——Android的开发准备</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/17/pager_android_framework"/>
   <updated>2014-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/17/pager_android_framework</id>
   <content type="html">&lt;p&gt;根据系统的大体设计并结合Android自身特性，将会主要用到下列几个Android自身控件。下面做简单介绍：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;用户交互界面由Activity实现，对该部分的详细介绍。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;系统主要模块都是在后台自动调用并执行的，这样能简化系统设计并提高系统运行速度。Android平台提供了Service组件，支持在后台自动启动和停止，系统的音视频通话模块、SIP模块都可以直接基于Service实现。。&lt;/p&gt;

    &lt;p&gt;Service运行于后台，没有任何用户界面。它们可以于Activity执行相同的操作。
  特别说明一下，Android中的Service是指在后台运行的程序，与Acitivity的区别是没有界面而已。它依然是在主线程中，并不是新创建的子线程，虽然看上去很像。一般的策略是在Service启动之后，为其单独创建一个子线程。&lt;/p&gt;

    &lt;p&gt;在Android 3.0之后，考虑到网络访问影响用户交互，Android禁止在主线程中进行网络访问。如果要实现此功能，就要像前面说的创建子线程在子线程中实现。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;为了将系统设计成高内聚，低耦合，将各个模块进行了封装。用户交互界面和后台通信逻辑等模块进行了分离。这几个模块消息的传输，就要通过一个控制模块来订阅观察系统状态以及及时的对其作出响应。例如，对于SIP注册状态要及时响应，如果注册失败，要及时通知用户，增加系统的友好性和不必要的时间浪费。&lt;/p&gt;

    &lt;p&gt;Android平台提供了Broadcast Receiver。Broadcast Receiver是Android在系统级别对Observer模式(Pub-Sub)的实现。接收器一直等待，直到其订阅的事件发生时，才被激活。系统的核心引擎使用了该设计模式。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Android平台还提供了更加友好的Notification来增加和用户的交互。这个部分将在&lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/18/pager_notification&quot;&gt;5.3.2节&lt;/a&gt;进行详细介绍。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>基于流媒体的对讲机系统的设计与实现</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/04/13/pager"/>
   <updated>2014-04-13T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/04/13/pager</id>
   <content type="html">&lt;p&gt;研究生毕业设计从4月13号开始写，历时了三个版本：复制网络凑字数版、全篇废话凑字数版、和目前的基本完成版。前前后后也码了不少字了。之前准备在这里写论文的，因为这里是Github Pager，而我写的也是论文。后来发现修改起来太麻烦，还是乖乖的去用Office写了。况且之前的版本主要都是凑字数的，就不用拿来在这里献丑了。&lt;/p&gt;

&lt;p&gt;论文为什么有会三个版本？因为这个课题我很不喜欢去做。既不是我相关的东西，做了又没有实际用途，所以就一直不想去开发。先开始就是想毕业就好。后来觉得这样复制来的肯定审查过不了，遂编了一堆系统特性和需求实现。后来又发现，我编的这堆有的没的到了答辩的时候还是会露馅，因为编的实在太烂。最后，觉得好好编一编，第三个版本诞生了。&lt;/p&gt;

&lt;p&gt;对于这个课题，一开始很抵触，觉得没用。后来被迫开始编之后，才真正发现认真做一个东西原来能学到很多。以前觉得特别牛逼，下不了手的，比如网络通信协议的代码实现。之前充其量就是看看包头，看看协议格式，现在能基本会编码，了解协议格式的指定。这也是我心目中一道难以翻越的高山阿！感觉我这种菜鸟主要就是因为态度差，能学的看不起，牛逼的又看不懂。。。编的过程中还熟悉了Android开发，现在觉得对Android已经有一个大体认识了。&lt;/p&gt;

&lt;p&gt;课题本来就是VOIP，但是VIOP算是比较成熟的了，再加上我能力有限，实在没办法在Android端开发出啥新颖的东西，比如H264的RTP传输。后来就寻思着加点啥进去。要在短时间内加点料，就得挑自己最擅长的了。那就是REST接口开发，我用过Java和Go语言开发接口，比较熟悉，一晚上搞定接口。然后就是Android端加点控件，与服务器通信。前几天又灵机一动，因为在公司解除过iOS的消息PUSH开发，知道Android端比iOS简单一万倍，遂又想把这个加进来充数。本来想自己用HTTP长连接自己实现的，后来还是发现能力有限，用了百度云推送。瞬间高上大有木有。我严格按照导师的意思，“如果没有技术含量，就不要让评审觉得也没有工作量”的宗旨，完成了所有的工作。&lt;/p&gt;

&lt;p&gt;而对于RTP协议，鄙人确实是花了大量时间去实现H264的传输，也看了不少论文。在最后解码方面却一直没有能够成功，也算是一憾。回想本科时候毕设的人脸识别，瞬间觉得自己自学能力太过于有限。。。&lt;/p&gt;

&lt;p&gt;论文已经交去盲审了，但是我还是觉得有必要好好整理一下。留给有缘人。&lt;/p&gt;

&lt;p&gt;2014年6月3日 家&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;盲审结果出来了，有惊无险的过了。不过室友挂了。。。为啥每次觉得水水的东西到后来又觉得不是那么水。。。&lt;/p&gt;

&lt;p&gt;2014年6月13日 家&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-开发准备与评估&quot;&gt;1 开发准备与评估&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;####1.1 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/02/04/postgraduate_design_evaluate&quot;&gt;前期开发方案&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;####1.2 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_prepare&quot;&gt;系统开发准备&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2-协议分析与实现&quot;&gt;2 协议分析与实现&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;####2.1 系统采用的多媒体编码标准分析
    &lt;ul&gt;
      &lt;li&gt;2.1.1 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_video&quot;&gt;视频编解码技术&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;####2.2 系统采用的网络通信协议分析
    &lt;ul&gt;
      &lt;li&gt;2.2.1 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/03/05/sip&quot;&gt;会话发起协议SIP&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;2.2.2 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_sdp&quot;&gt;会话描述协议SDP&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;2.2.3 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_rtp&quot;&gt;实时传输协议RTP&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;em&gt;2.2.4 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_rtsp&quot;&gt;实时流媒体协议RTSP&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;3-android控件的实现&quot;&gt;3 Android控件的实现&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;####3.1 &lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_android_framework&quot;&gt;Android的开发准备&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;####3.2 &lt;a href=&quot;https://blog.cyeam.com/golang/2014/06/11/baiduyunpush&quot;&gt;百度云推送&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;4-系统调试与测试&quot;&gt;4 系统调试与测试&lt;/h2&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>SIP协议的分析以及opensips注册和通话的研究</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/03/05/sip"/>
   <updated>2014-03-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/03/05/sip</id>
   <content type="html">&lt;p&gt;本系统要基于网络进行对讲通话，SIP协议(会话发起协议)用于建立、保持、销毁会话，其功能和日常打电话的流程是一致的。SIP是完成是完成本系统要用到的核心协议。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/sip_protocol.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;SIP协议位于网络七层协议中的会话层，位于TCP/IP协议中的应用层。虽然没有做过验证，但是我觉得iOS上的Facetime也是基于此协议。一个手机可以绑定多个SIP地址，注册之前也是要指明要绑定的地址。只不过iOS的注册是自动完成的。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;OPTIONS请求可以作为建立会话的一部分，用来查询对方的SIP能力。常见的返回值，也就是SIP执行能力：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INVITE&lt;/code&gt;。用于发起SIP会话请求。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACK&lt;/code&gt;。用于确认SIP请求。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CANCEL&lt;/code&gt;。用于取消SIP请求。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OPTIONS&lt;/code&gt;。用于查看对方SIP执行能力。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BYE&lt;/code&gt;。用于结束SIP请求。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##SIP注册
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/sip_thoery_register.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;SIP协议注册到服务器的一般过程：向SIP服务器发起请求，此处为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asteriskguide.com&lt;/code&gt;。并发送自己的用户名，此处为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8500&lt;/code&gt;。此外，还要附带本机IP地址，作为将来通信使用。Contact代表的就是当前用户的通信地址，其他用户可以通过此地址联系到当前用户，实现发送呼叫请求。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/register_graph.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我从WireShark&lt;a href=&quot;#anchor_1&quot;&gt;[1]&lt;/a&gt;截到的图，也可以看到此通信过程，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.14.5.218&lt;/code&gt;向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;121.199.55.106&lt;/code&gt;发起注册请求&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REGISTER&lt;/code&gt;。接着，收到了状态码，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 OK&lt;/code&gt;。SIP协议的设计借鉴了HTTP，返回状态码和HTTP的相同。此处为200。&lt;/p&gt;

&lt;p&gt;其中，中间的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUBLISH&lt;/code&gt;是向服务器发送消息，告诉它我们在线&lt;a href=&quot;#anchor_3&quot;&gt;[3]&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/sip_protocol_register.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REGISTER&lt;/code&gt;请求&lt;a href=&quot;#anchor_4&quot;&gt;[4]&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TO&lt;/code&gt; 是为哪一个用户注册的。是一个完整的SIP用户的SIP URI。发出注册请求的并不一定是SIP用户本身。所以要有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FROM&lt;/code&gt;头域&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FROM&lt;/code&gt; 谁发起的注册，就是谁的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIP URI&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Call-ID&lt;/code&gt; 同一个SIP客户端的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Call-ID&lt;/code&gt;是相同的。因为同一个sip客户端会间隔一定的时间就注册一次。在注册服务器里面用户的注册信息保留一定的时间，保存的时间并不是SIP规定的。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CSeq&lt;/code&gt; CSeq 值保证REGISTER 请求适当的排序。对于每个使用相同的Call-ID 的REGISTER 请求，UA 必须逐一增加Cseq 值。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Expires&lt;/code&gt; 注册绑定时间为3600s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/sip_protocol_ok.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此处返回的状态码为200，表示注册成功。此外，相比&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REGISTER&lt;/code&gt;还增加了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contact&lt;/code&gt;，返回当前用户所有注册过的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIP URI&lt;/code&gt;，我用多台设备注册过，所以返回了多个结果。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;##SIP发起会话
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/sip_thoery_invite.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/invite_graph.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用WireShark绘制的连接、通信、以及断开连接的流程图更直观一些。由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sip:mint@121.199.55.106:5060&lt;/code&gt;向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sip:ad@10.14.5.202&lt;/code&gt;发送通话请求。当ad收到请求后，会立刻返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;100 TRYING&lt;/code&gt;响应，等一段时间之后，再返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;180 RINGING&lt;/code&gt;，接通之后，响应&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 OK&lt;/code&gt;&lt;a href=&quot;#anchor_5&quot;&gt;[5]&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/sip_protocol_invite.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;该图中，由&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mint@121.199.55.106:5060&lt;/code&gt;向&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ad@10.14.5.202:10060&lt;/code&gt;发起连接请求，内容格式是SDP描述格式，该格式内容会包含音视频传输参数和传输端口。详细内容参考&lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/04/17/pager_sdp&quot;&gt;下一节&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;系统所用的SIP服务器是opensips。该部分的配置和使用超出了本文范围，不作详细介绍。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.ietf.org/rfc/rfc3261.txt&quot;&gt;RFC3261 SIP: Session Initiation Protocol&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.blogjava.net/amigoxie/archive/2009/08/06/290119.html&quot;&gt;使用Wireshark进行SIP包解析&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;《Building Telephony Systems with OpenSIPS 1.6》&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.163.com/hlz_2599/blog/static/142378474201152243939871&quot;&gt;sip PUBLISH 与 SUBSCRIBE 方法的区别&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/lyyslsw1230_163com/article/details/8454851&quot;&gt;SIP注册过程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/5387017/sip-100-trying-instead-of-180-ringing&quot;&gt;sip “100 trying” instead of “180 ringing”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://wenku.baidu.com/view/854dd3e55ef7ba0d4a733bed.html&quot;&gt;SIP中SDP及其RTP的工作过程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>iPhone的CSS显示</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/02/07/iphone_css"/>
   <updated>2014-02-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/02/07/iphone_css</id>
   <content type="html">&lt;h3 id=&quot;1-通过给图片加css实现&quot;&gt;1. 通过给图片加CSS实现&lt;/h3&gt;
&lt;div class=&quot;iphone&quot;&gt;
    &lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/nexus7_screenshot.png&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;做这个是为了做cInterphone贴图片用的。代码很简单，只要给想加iPhone显示的CSS就行。调用代码如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;iphone&quot;&amp;gt;
    &amp;lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/nexus7_screenshot.png&quot; /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;一位大神提供的前端代码，我也懒得去读了，直接拿来用。以后有兴趣再去研究。CSS代码直接贴这里了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/* iPhone */
/* First, we&apos;ll make the body. After that we&apos;ll use :after and :before elements to make other things like buttons */

.iphone {
	position: relative;
	height: 495px;
	width: 250px;
	border-radius: 35px;
	margin: 50px auto;
	
	/* We are going to use multiple background images (linear-gradients) here to create the main background, the shine and the screen */
	/* This is our first gradient, to draw more, we&apos;ll have to write them before this one separated by a comma */
	/* Now, we&apos;ll draw the screen */
	/* The screen should be below the shine, right? */
	background: 
		linear-gradient(-165deg, rgba(255,255,255,0.4), rgba(255,255,255,0.15) 35%, transparent 35%),
		linear-gradient(top, transparent 85px, #222222 85px, #151515 410px, transparent 410px),
		linear-gradient(top, #000, #0a0a0a);
	
	/* Something isn&apos;t right... */
	/* Now, it&apos;s perfect. Try playing with the values below to get a better understanding */
	background-repeat: no-repeat;
	background-size: 100% 100%, 220px 100%, 100% 100%;
	background-position: 0 0, 15px 0, 0 0;
	
	
	
	/* Now we need the aluminium borders and the top button */
	/* We&apos;ll use box shadows for that */
	box-shadow: 0 0 0 3px black,
							/* A small divider */
							-40px -128px 0 -123px black,
							0 0 0 5px #a09f9d,
							/* Some metallic shine on the button */
							49px -130px 3px -123px #777,
							46px -130px 2px -123px #ddd,
							/* Top button from here */
							62px -111px 0 -105px #8e8d8b,
							62px -112px 0 -105px #b4b3b1,
							62px -113px 0 -105px #666;
}


/* Our phone&apos;s body is complete! Now we&apos;ll move onto the home button, front cam and the speaker.. */
/* We are going to use :after pseudo element for that and a lot of box-shadow awesomeness */
.iphone:after {
	content: &apos;▢&apos;;
	line-height: 46px;
	text-align: center;
	font-size: 28px;
	color: #666;
	position: absolute;
	width: 46px;
	height: 46px;
	border-radius: 50%;
	background: white;
	bottom: 18px;
	left: 100px;
	border: 2px solid #0a0a0a;
	
	
	
	/* We&apos;ll use gradients for the depth */
	background-image:
		linear-gradient(left, rgba(0, 0, 0, 0.85), black); /* Looking good */

	/* Now some box-shadow magic to create speaker and front cam */
	box-shadow:
		/* Front Cam: looking good */
		-39px -410px 0 -23px #000f31,
		-39px -410px 0 -22px #0a1c5a,
		-40px -410px 0 -21px #0d1216,
		-40px -410px 0 -19px #1b191a,
		
		
	
		/* Speaker, it&apos;s a mess. Really. */ 
		/* now the outer covering of speaker, another mess */
		-12px -410px 0 -22px #333,
		-8px -410px 0 -22px #333,
		-4px -410px 0 -22px #333,
		-0px -410px 0 -22px #333,
		4px -410px 0 -22px #333,
		8px -410px 0 -22px #333,
		12px -410px 0 -22px #333,
		16px -410px 0 -22px #333,
		20px -410px 0 -22px #333,
		24px -410px 0 -22px #333,
		
		-12px -410px 0 -18px #0a0a0a,
		-9px -410px 0 -18px #0a0a0a,
		-6px -410px 0 -18px #0a0a0a,
		-3px -410px 0 -18px #0a0a0a,
		0px -410px 0 -18px #0a0a0a,
		3px -410px 0 -18px #0a0a0a,
		6px -410px 0 -18px #0a0a0a,
		9px -410px 0 -18px #0a0a0a,
		12px -410px 0 -18px #0a0a0a,
		15px -410px 0 -18px #0a0a0a,
		18px -410px 0 -18px #0a0a0a,
		21px -410px 0 -18px #0a0a0a,
		24px -410px 0 -18px #0a0a0a;

}

/* Looks good till now. But we are still missing the side buttons and those are pretty easy to make. We&apos;ll only use gradient for that ;) */
.iphone:before {
	position: absolute;
	content: &apos;&apos;;
	width: 2px;
	height: 117px;
	background: transparent;
	top: 40px;
	left: -7px;
	
	/* The shape is building up */
	background-image:
		/* This is going to be a long top to bottom linear gradient so see the output and code */
		/* A final touch ;) */
		linear-gradient(left, transparent 0px, transparent 1px, #7a7879 2px),
		linear-gradient(top, #383838 1px, #b9b9b9 3px, #b9b9b9 10px, #383838 19px, #b9b9b9 23px, transparent 23px, transparent 53px, #383838 53px, #b9b9b9 54px, #dadada 58px, #383838 62px, black 62px, black 66px, #383838 66px, #b9b9b9 67px, #dadada 68px, #383838 70px, transparent 70px, transparent 100px, #383838 100px, #b9b9b9 101px, #dadada 105px, #383838 109px, black 109px, black 113px, #383838 113px, #b9b9b9 114px, #dadada 115px, #383838 117px);
	
}

.iphone img {
	position: absolute;
	left: 6px;
	top: 65px;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;http://cssdeck.com/labs/iphone_with_css3&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;2-通过嵌套div实现&quot;&gt;2. 通过嵌套DIV实现&lt;/h3&gt;

&lt;p&gt;今天无意间在百度Site App里看到的可以模拟Android的，iPhone和Android凑齐了，哈哈。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.phone-bg {
    margin-top: 10px;
    padding-top: 60px;
    background: url(http://siteapp.baidu.com/static/webappservice/pic/new-bg-mobile_dafd5fd.png) no-repeat 0 -10px;
    height: 614px;
    width: 332px;
    margin-right: -10px;
    position: relative;
}
.phone-bg .phone_display {
    margin-left: 34px;
    width: 272px;
    height: 465px;
    background-color: #fff;
}

&amp;lt;div class=&quot;phone-bg &quot;&amp;gt;
    &amp;lt;div class=&quot;phone_display&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个显示需要加入额外的CSS。&lt;/p&gt;

&lt;div class=&quot;phone-bg &quot;&gt;
    &lt;div class=&quot;phone_display&quot;&gt;
		效果效果
	&lt;/div&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Android Quick Start</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/02/05/android_quickstart"/>
   <updated>2014-02-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/02/05/android_quickstart</id>
   <content type="html">&lt;p&gt;MediaCodec
MediaPlayer
MediaRecorder
Handler
Notification
Thread Thread.interrupted()
MulticastSocket
FileWriter http://endual.iteye.com/blog/1128541&lt;/p&gt;

&lt;p&gt;###Activity&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Activity 定义&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;一个Acitvity，通常是指在某一时刻，在设备上看到的单独界面。对用于而言，这就是程序的外观部分。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Activity Manager&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;启动一个活动可能会消耗大量资源。它可能会涉及新建一个Linux进程、为UI对象申请内存空间、从XML布局填充所有对象，以及创建整个界面。既然我们在启动一个活动上花费了这么多工夫，一旦用户离开该界面，如果只是将它销毁那就实在太浪费了。为了避免这种浪费，Android通过活动管理器(Activity Manager)来管理活动的生命周期。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Activity 生命周期
Android编程主要是围绕程序的状态改变做出响应。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/android_activity.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;启动状态&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当一个活动太不存在于内存中时，我们称其处于启动状态。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;从启动状态到运行状态的转换是最耗时的操作，对电池续航也有直接影响。所以不要轻易销毁Activity。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;运行状态&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;获得焦点(in focus)的Activity，是处于运行状态的，与用户进行交互。正在运行的Activity可以优先获得系统资源。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;暂停状态&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;当Activity没有获得焦点，但是仍然显示的情况下(e.g. 上面出现了对话框)，就是暂停(pause)状态。&lt;em&gt;最好在暂停状态执行保存数据的重要工作。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;停止状态&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;当Activity不显示却依然驻留在内存中时(e.g. 切换到其他应用程序)，处于停止(stopped)状态。停止状态的Activity可能随时被从内存中移除。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;销毁状态&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;销毁状态的活动不再留住于内存中。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;创建用户界面&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;创建用户界面分为声明式和编程式两种，分别用到了XML和JAVA实现。这类似于HTML和Javascript的关系。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Layout&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Layout负责为子元素安排位置。布局如果嵌套太深，会浪费较多的CPU时间，电池的续航也会受到影响。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;LinearLayout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;纵向或者横向排列(layout_orientation vertical/horizontal)子元素。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TableLayout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;类似于html中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;table&amp;gt;&lt;/code&gt;标签。stretch columns指定那一列展开并占据所有空间。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FrameLayout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;将其下的子元素重叠起来，只留最后一个在外面。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RelativeLayout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;需要为每一个子元素提供一个ID。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AbsoluteLayout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;灵活性不够，无法自动适应。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Intent&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Intent是指在主要构建之间传递的消息。它们能够触发并启动一个Activity，告诉一个服务启动还是停止，或者只是简单的广播。Intent是异步的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Intent分为显式的和隐式的两种。&lt;em&gt;explicit&lt;/em&gt;需要指明接收Intent的组件。&lt;em&gt;implicit&lt;/em&gt;只需指定接收者的类型。所有能完成该操作的程序”竞相“完成这个操作。&lt;/p&gt;

&lt;p&gt;###Service&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Service运行于后台，没有任何用户界面。它们可以于Activity执行相同的操作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Service 生命周期
Android编程主要是围绕程序的状态改变做出响应。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/service_lifecycle.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;onStartCommand有三种返回值：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;START_STICKY：sticky的意思是“粘性的”。使用这个返回值时，我们启动的服务跟应用程序”粘”在一起，如果在执行完onStartCommand后，服务被异常kill掉，系统会自动重启该服务。当再次启动服务时，传入的第一个参数将为null;&lt;/li&gt;
  &lt;li&gt;START_NOT_STICKY：“非粘性的”。使用这个返回值时，如果在执行完onStartCommand后，服务被异常kill掉，系统不会自动重启该服务。&lt;/li&gt;
  &lt;li&gt;START_REDELIVER_INTENT：重传Intent。使用这个返回值时，如果在执行完onStartCommand后，服务被异常kill掉，系统会自动重启该服务，并将Intent的值传入。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;特别说明一下，Android中的Service是指在后台运行的程序，与Acitivity的区别是没有界面而已。它依然是在主线程中，并不是新创建的子线程，虽然看上去很像。一般的策略是在Service启动之后，为其单独创建一个子线程。&lt;/p&gt;

&lt;p&gt;在Android 3.0之后，考虑到网络访问影响用户交互，Android禁止在主线程中进行网络访问。如果要实现此功能，就要像前面说的创建子线程在子线程中实现。&lt;/p&gt;

&lt;p&gt;###Thread&lt;/p&gt;

&lt;p&gt;###Broadcast Receiver
Broadcast Receiver是Android在系统级别对Observer模式(Pub-Sub)的实现。接收器一直等待，直到其订阅的事件发生时，才被激活。&lt;/p&gt;

&lt;p&gt;###Content Provider&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Content Provider是应用程序之间共享数据的接口，提供了一套很好的符合CRUD(insert(), update(), delete(), query())原则的接口。数据存储与用户界面程序的分离，为系统各部分之间的组合提供了更大的灵活性。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://developer.android.com/images/providers/ContactsDataFlow.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;###Application Context
Android四大组建Activity, Service, Content Provider, Broadcast Receiver构成了整个应用程序，共同处于同一个Application Context中。允许在不同的组建中共享数据和资源。&lt;/p&gt;

&lt;p&gt;###Design Principle(多年来开发深有体会！！！)&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;渐进式开发&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;保持完整，保持可用&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;重构代码&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;《Learning Android》 马尔科·加尔根塔 电子工业出版社 ISBN: 9787121172632&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>安卓对讲机开发评估</title>
   <link href="https://blog.cyeam.com/postgraduate/2014/02/04/postgraduate_design_evaluate"/>
   <updated>2014-02-04T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/postgraduate/2014/02/04/postgraduate_design_evaluate</id>
   <content type="html">&lt;p&gt;毕业设计准备在Android平台上开发一个音视频通话软件，基于流媒体实现。项目的目的就是做一个山寨版的Faceime。目前了解到的实现技术是FFmpeg实现流媒体编码，RTP实现传输控制，SIP实现通话建立。我自己还想把SIP服务器部署到GAE上面，使其成为一个可用的项目。&lt;/p&gt;

&lt;p&gt;#####音视频采集，压缩，发送，解码，显示的流程&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/%E6%B5%81%E7%A8%8B%E5%9B%BE.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;1-流媒体编码&quot;&gt;1. 流媒体编码&lt;/h5&gt;
&lt;p&gt;视频采集可以直接使用硬件编码，而解码需要使用到软件解码。使用开源的C库FFmpeg进行解码。
这是比较熟悉的一个部分，之前做过，而且用JNI调用成功了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello, world&lt;/code&gt;。。。现在考虑去Github找，使用开源的编译好的库。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android的MediaPlayer和MediaRecoder是支持H264和AAC编码的，目前先采用这两个来进行硬件编解码，如果需要使用ffmpeg来软件解码时，再换。&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/havlenapetr/FFMpeg&quot;&gt;havlenapetr / FFMpeg&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/halfninja/android-ffmpeg-x264&quot;&gt;halfninja / android-ffmpeg-x264&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-流媒体传输&quot;&gt;2. 流媒体传输&lt;/h5&gt;
&lt;p&gt;将采集到的音视频分别进行传输。_ref_AndroidRTC里面只有c的源文件，没有编译好的动态链接库。可能还需要自己编译。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;流媒体传输用的RTSP协议，有一个开源的实现libstreaming，还有一个封装了该库的ipcamera实现spydroid-ipcamera。大概看了一下代码，RTSP封装难度应该也不大，到时候可以自行尝试封装。&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;**可以使用VLC Linux&lt;/td&gt;
      &lt;td&gt;iOS&lt;/td&gt;
      &lt;td&gt;Android进行测试开发，验证传输。**&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/Teaonly/_ref_AndroidRTC&quot;&gt;Teaonly / _ref_AndroidRTC&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/fyhertz/libstreaming&quot;&gt;&lt;strong&gt;fyhertz / libstreaming&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/fyhertz/spydroid-ipcamera&quot;&gt;&lt;strong&gt;fyhertz / spydroid-ipcamera&lt;/strong&gt;  &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-sipsession-initiation-protocol&quot;&gt;3. SIP(Session Initiation Protocol)&lt;/h5&gt;
&lt;p&gt;这块因为我想部署到GAE上面，所以不能使用C++，考虑使用Java的开源库。此外，还要调研GAE上面执行C++动态链接库的可行性。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原本打算在GAE开发SIP服务器的，天真了。。。后来找到了opensips，这是用C语言开发的，可以完全支持SIP协议。而且还有支持在线管理opensips的应用SerMyAdmin。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;而在Android端，提供了SipManager，这样可以实现客户端呼叫。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;使用Linphone Linux|Windows|iOS|Android进行测试开发，验证opensips服务器配置和SIP通信建立。&lt;/strong&gt;
&lt;strong&gt;使用tcpdump WireShark监控网络，查看协议调用过程。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;jitsi感觉太过成熟，先放弃使用。如果能移植到GAE，以后可以试试。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://jitsi.org/&quot;&gt;jitsi.org&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jitsi/jitsi&quot;&gt;jitsi / jitsi&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jitsi/jitsi-android&quot;&gt;jitsi / jitsi-android&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/nomousewch/article/details/7012392&quot;&gt;Jitsi（SIP communicator）的环境部署和打包发布&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jitsi.org/Documentation/HomePage&quot;&gt;jitsi Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cuitu.net/book/jitsi-jia-gou-fen-xi&quot;&gt;Jitsi 架构分析&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.opensips.org/&quot;&gt;&lt;strong&gt;OpenSIPS Office Site&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/OpenSIPS/opensips&quot;&gt;&lt;strong&gt;OpenSIPS / opensips&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://sourceforge.net/projects/sermyadmin/&quot;&gt;&lt;strong&gt;SerMyAdmin&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;开发流程&quot;&gt;开发流程&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.cyeam.com/postgraduate/2014/02/05/android_quickstart&quot;&gt;Android Quick Start&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;SIP Sip注册和建立通信&lt;/li&gt;
  &lt;li&gt;RTSP 双通道通信传输&lt;/li&gt;
  &lt;li&gt;Android 视频编码&lt;/li&gt;
  &lt;li&gt;Android 音频编码&lt;/li&gt;
  &lt;li&gt;Android 视频解码&lt;/li&gt;
  &lt;li&gt;Android RTP 传输&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;开发环境&quot;&gt;&lt;em&gt;开发环境&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;Nexus 7
    &lt;ul&gt;
      &lt;li&gt;Android version &lt;strong&gt;4.4.2&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;Kernel version &lt;strong&gt;3.4.0-gac9222c&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933559/cyeam/nexus_7.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Java&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;java version “1.7.0_45”  &lt;br /&gt;
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)  &lt;br /&gt;
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;ADT&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;adt-bundle-linux-x86_64-20131030.zip
&lt;a href=&quot;http://developer.android.com/training/index.html&quot;&gt;API&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;NDK&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;android-ndk-r9c-linux-x86_64.tar.bz2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Google App Engine(Java)
发现一神器，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.appsp0t.com&lt;/code&gt;可以访问自己的域名。这个通过反向代理实现。这样就能实现跨墙访问。Fuck GFW!目前计划在 https://brycelinux.appsp0t.co m测试和发布。
    &lt;ul&gt;
      &lt;li&gt;Google App Engine SDK
        &lt;blockquote&gt;
          &lt;p&gt;Version 1.8.9 &lt;br /&gt;
&lt;a href=&quot;http://googleappengine.googlecode.com/files/appengine-java-sdk-1.8.9.zip&quot;&gt;Download&lt;/a&gt;&lt;/p&gt;
        &lt;/blockquote&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;eclipse&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;Eclipse Java EE IDE for Web Developers. &lt;br /&gt;
Version: Kepler Service Release 1 &lt;br /&gt;
Build id: 20130919-0819&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;opensips&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;opensips-1.10.0-tls&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;SerMyAdmin&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;sermyadmin-install-2.0.1a&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;《基于Android的移动VoIP高清视频通话系统的设计与实现》曹建龙&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.3g-edu.org/news/art014.htm&quot;&gt;Android 中如何使用SIP服务&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/nomousewch/article/details/7012392&quot;&gt;jitsi打包&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/appengine/docs/java/overview?hl=zh-CN&quot;&gt;App Engine Java 概述&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;《Building Telephony Systems with OpenSIPS 1.6》&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Linux Mint下安装Nexus 7 驱动</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/02/01/linux_android_drivers"/>
   <updated>2014-02-01T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/02/01/linux_android_drivers</id>
   <content type="html">&lt;p&gt;整个越狱的大体过程，是按照这篇文章&lt;a href=&quot;http://itsfoss.com/root-nexus-7-2013-ubuntu-linux/&quot;&gt;Steps to Root Nexus 7 2013 in Linux&lt;/a&gt;来做的。先开始讲的挺详细的，最后在Linux下Android设备驱动和fastboot mode下启动少写了一些，对于我这种小白来讲果断是不能自己处理的，所以搞了好几天才搞定。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;里面用到的TWRP无法下载，使用&lt;a href=&quot;http://pan.baidu.com/wap/link?uk=3593604652&amp;amp;shareid=453410427&amp;amp;third=0&quot;&gt;百度网盘&lt;/a&gt;提供的下载&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;在一切都搞定，执行最后一步的时候，出现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;wait for devices&amp;gt;&lt;/code&gt;的错误，是因为Linux下设备的驱动问题，没有配置好。在Windows在需要安装设备对应的驱动就能搞定，Mac下都不需要操作，而Linux下需要配置udev才能正常运行。&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;简单的说一下，Linux是通过udev来管理设备的，udev的入门资料可以参考——&lt;a href=&quot;http://www.ibm.com/developerworks/cn/linux/l-cn-udev/&quot;&gt;《使用 udev 高效、动态地管理 Linux 设备文件》&lt;/a&gt;。一些基本的udev配置文件规则可以在这里找到。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;接着是去写配置文件。基本上所有Google到的文章里面都说了要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/udev/rules.d&lt;/code&gt;里面创建&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;51-android.rules&lt;/code&gt;，里面的写法也是基本一致的，但是没有说明为什么这么写，所以可能具体到个人，那样复制过来就不行了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;51-android.rules&lt;/code&gt;命名的缘由——&lt;a href=&quot;http://www.cnblogs.com/frydsh/archive/2013/03/07/2949089.html&quot;&gt;《为什么是“51-android.rules”？》&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;51-android.rules&lt;/code&gt;的配置。&lt;a href=&quot;http://www.janosgyerik.com/adding-udev-rules-for-usb-debugging-android-devices/&quot;&gt;《Adding udev rules for USB debugging Android devices》&lt;/a&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;使用命令&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsusb&lt;/code&gt;找到Android设备的Bus和Device&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  Bus 003 Device 006: ID 18d1:d001 Google Inc. Nexus 7 (debug)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;看到上面的Google的Bus是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;003&lt;/code&gt;，Device是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;006&lt;/code&gt;，Linux是通过文件来管理设备的，所以该Android设备对应的文件就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/bus/usb/003/006&lt;/code&gt;，查看其文件权限。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls -l /dev/bus/usb/003/006&lt;/code&gt;查看其文件权限。&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  crw-rw----+ 1 root audio 189, 261  2月  2 14:56 /dev/bus/usb/003/006
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;编辑&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/udev/rules.d/51-android.rules&lt;/code&gt;。该文件内需要添加用户组和用户名，之前不确定是什么，试了好几次，也不知道对了没有，可以通过上一步直接查看到其权限。我的配置如下：&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  SUBSYSTEM==&quot;usb&quot;, ATTR{idVendor}==&quot;18d1&quot;, ATTR{idProduct}==&quot;d001&quot;, MODE=&quot;0666&quot;, OWNER=&quot;root&quot;, GROUP=&quot;plugdev&quot;, SYMLINK+=&quot;android%n&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;按照之前查看到的设备vendor和productid，还有访问权限，在配置完之后，重新插上设备，会在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev&lt;/code&gt;目录下生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android&lt;/code&gt;的文件，其后面会带一个数字。&lt;em&gt;前面几步贴的结果都是配置好之后重新执行命令得到的结果，可能不准确&lt;/em&gt;。&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev&lt;/code&gt;下面生成了以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android&lt;/code&gt;开头的文件，表明已经配置好了，可以通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adb devices&lt;/code&gt;看到设备序列号了。接着是在fastboot mode下启动系统。那篇文章中并没有强调说在fastboot mode下执行，而我也不懂。。。所以，这个也搞了很久还发现的。。。还是靠这个提问&lt;a href=&quot;http://stackoverflow.com/questions/8588595/android-fastboot-devices-not-returning-device&quot;&gt;《Android Fastboot devices not returning device》&lt;/a&gt;，才想到可能是在fastboot mode下执行，因为按照之前的步骤，都是在recovery mode下，所以切换了mode，果断可以了。此时，成功的标志是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastboot devices&lt;/code&gt;会返回设备序号。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;div id=&quot;stacktack-21499972&quot;&gt;&lt;/div&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/gexueyuan/article/details/8720570&quot;&gt;ubuntu下galaxy nexus的fastboot连接不上的问题&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://developer.android.com/tools/device.html&quot;&gt;Setting up a Device for Development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/M0Rf30/android-udev-rules/blob/master/51-android.rules&quot;&gt;51-android.rules&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;http://www.janosgyerik.com/adding-udev-rules-for-usb-debugging-android-devices/&lt;/li&gt;
  &lt;li&gt;https://code.google.com/p/51-android/&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>对联</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/01/28/couplet"/>
   <updated>2014-01-28T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/01/28/couplet</id>
   <content type="html">&lt;p&gt;#####大門
宗親平實居寶地  &lt;br /&gt;
美育華果近高鄰  &lt;br /&gt;
橫批：耕讀傳家&lt;/p&gt;

&lt;p&gt;#####屋子
向陽花木三春秀  &lt;br /&gt;
得意馬蹄一路風  &lt;br /&gt;
橫批：一馬當先&lt;/p&gt;

&lt;p&gt;好雨知時群卉盛  &lt;br /&gt;
春風得意四蹄輕  &lt;br /&gt;
橫批：馬到成功&lt;/p&gt;

&lt;p&gt;福照家門萬事興  &lt;br /&gt;
喜居寶地千年旺  &lt;br /&gt;
橫批：萬事如意&lt;/p&gt;

&lt;p&gt;#####廚房
五味烹調香千裏  &lt;br /&gt;
三鮮蒸炸樂萬家  &lt;br /&gt;
橫批：天地同歡&lt;/p&gt;

&lt;p&gt;#####奶奶
壽星伴子子長壽  &lt;br /&gt;
童嬰映老老還童  &lt;br /&gt;
橫批：壽比南山&lt;/p&gt;

&lt;p&gt;#####店铺
錦善秀美窗十裏百鄉  &lt;br /&gt;
厚德載物簾千家萬戶  &lt;br /&gt;
橫批：景上添錦&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/couplet.JPG&quot; alt=&quot;Couplet&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Linux命令整理</title>
   <link href="https://blog.cyeam.com/linux/2014/01/22/linux"/>
   <updated>2014-01-22T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/linux/2014/01/22/linux</id>
   <content type="html">&lt;h3 id=&quot;1-curl&quot;&gt;1. curl&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;查看网页源码&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  curl blog.cyeam.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;显示网页头&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  curl -i blog.cyeam.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;头信息（后面的Body省略）：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  HTTP/1.1 200 OK
  Server: GitHub.com
  Content-Type: text/html; charset=utf-8
  Last-Modified: Wed, 17 Sep 2014 14:02:08 GMT
  Expires: Thu, 25 Sep 2014 03:42:26 GMT
  Cache-Control: max-age=600
  Content-Length: 13070
  Accept-Ranges: bytes
  Date: Thu, 25 Sep 2014 03:33:37 GMT
  Via: 1.1 varnish
  Age: 72
  Connection: keep-alive
  X-Served-By: cache-lo83-LHR
  X-Cache: HIT
  X-Cache-Hits: 1
  X-Timer: S1411616017.770794,VS0,VE0
  Vary: Accept-Encoding
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;显示通信过程&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  curl -v http://blog.cyeam.com/pages.html
  * About to connect() to blog.cyeam.com port 80 (#0)
  *   Trying 199.27.79.133... connected
  * Connected to blog.cyeam.com (199.27.79.133) port 80 (#0)
  &amp;gt; GET /pages.html HTTP/1.1
  &amp;gt; User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.13.1.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
  &amp;gt; Host: blog.cyeam.com
  &amp;gt; Accept: */*

  curl --trace output.txt blog.cyeam.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;设置UserAgent&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  curl -i -A &quot;Cyeam&quot; http://blog.cyeam.com/linux/2014/01/22/linux/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;安静模式&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-s&lt;/code&gt;。只显示结果。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-grep&quot;&gt;2. grep&lt;/h3&gt;

&lt;p&gt;查找文件里符合条件的字符串&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;grep -n &quot;/cyeam&quot; -r .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt;，显示行号。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-r&lt;/code&gt;，递归查找。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-c&lt;/code&gt;，显示数目。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3-git&quot;&gt;3. git&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Force Synchronize&lt;/p&gt;

    &lt;p&gt;git reset –hard HEAD
  git clean -f
  git pull&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Commit&lt;/p&gt;

    &lt;p&gt;git add -A
  git status
  git commit -a””
  git push origin master&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4-mysql&quot;&gt;4. mysql&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;create user &apos;bryce&apos;@&apos;localhost&apos; identified by &apos;&apos;;
	mysqldump -u root -p ROOT &amp;gt; C:/Users/Bryce/Documents/GitHub/Cyeam/data/cyeam.sql
mysql -u bryce -p ROOT &amp;lt; C:/Users/Bryce/Documents/GitHub/Cyeam/data/cyeam.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-unzip&quot;&gt;5. unzip&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	unzip upload.zip -d [DIRECTRY]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;6-lsof&quot;&gt;6. lsof&lt;/h3&gt;

&lt;p&gt;list of open files&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-i select IPv[46] files&lt;/code&gt;查看占用端口&lt;/p&gt;

    &lt;p&gt;lsof -i:8080&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;7-kill&quot;&gt;7. kill&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kill -9 [pid]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;8-history&quot;&gt;8. history&lt;/h3&gt;

&lt;p&gt;Linux系统当你在shell(控制台)中输入并执行命令时，shell会自动把你的命令记录到历史列表中，一般保存在用户目录下的.bash_history文件中。默认保存1000条，你也可以更改这个值。&lt;/p&gt;

&lt;h3 id=&quot;9-aptitude&quot;&gt;9. aptitude&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo aptitude install
sudo aptitude remove #用于完整删除软件
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;10-file&quot;&gt;10. file&lt;/h3&gt;

&lt;p&gt;通过file指令，我们得以辨识该文件的类型&lt;/p&gt;

&lt;h3 id=&quot;11-lsusb&quot;&gt;11. lsusb&lt;/h3&gt;

&lt;p&gt;查看系统中的USB设备&lt;/p&gt;

&lt;h3 id=&quot;12-cat&quot;&gt;12. cat&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;一次显示整个文件&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;从键盘创建一个文件。&lt;/p&gt;

    &lt;p&gt;$ cat &amp;gt; filename&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Cat命令将几个文件合并为一个文件。&lt;/p&gt;

    &lt;p&gt;$cat file1 file2 &amp;gt; file&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;13-top&quot;&gt;13. top&lt;/h3&gt;

&lt;p&gt;top命令是Linux下常用的性能分析工具，能够实时显示系统中各个进程的资源占用状况，类似于Windows的任务管理器。&lt;/p&gt;

&lt;hr /&gt;

&lt;h6 id=&quot;参考文献&quot;&gt;&lt;em&gt;参考文献&lt;/em&gt;&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.ruanyifeng.com/blog/2011/09/curl.html&quot;&gt;《curl网站开发指南》&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/peida/archive/2012/12/24/2831353.html&quot;&gt;每天一个linux命令（44）：top命令&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>beego 介绍</title>
   <link href="https://blog.cyeam.com/golang/2014/01/21/beego"/>
   <updated>2014-01-21T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/golang/2014/01/21/beego</id>
   <content type="html">&lt;h4 id=&quot;beego&quot;&gt;beego&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://beego.me/
go get github.com/astaxie/beego
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;bee工具&quot;&gt;bee工具&lt;/h3&gt;

&lt;h5 id=&quot;1-安装&quot;&gt;1. 安装&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go get github.com/beego/bee
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;2-新建工程&quot;&gt;2. 新建工程&lt;/h5&gt;
&lt;p&gt;在$GOPATH目录下，用命令行输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bee new [PROJECT_NAME]&lt;/code&gt;，就会在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GOPATH/src&lt;/code&gt;目录下新建一个工程。&lt;/p&gt;

&lt;h5 id=&quot;3-运行工程&quot;&gt;3. 运行工程&lt;/h5&gt;
&lt;p&gt;进入创建好的工程根目录下，使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bee run [PROJECT_NAME]&lt;/code&gt;，项目便会启动，此命令支持热编译。&lt;/p&gt;

&lt;h3 id=&quot;beego-1&quot;&gt;beego&lt;/h3&gt;

&lt;h5 id=&quot;1-router&quot;&gt;1. Router&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package routers

import (
	&quot;beego.test/controllers&quot;
	&quot;github.com/astaxie/beego&quot;
)

func init() {
	beego.Router(&quot;/&quot;, &amp;amp;controllers.MainController{}, &quot;*:Get&quot;)
	beego.Router(&quot;/user/:username:string&quot;, &amp;amp;controllers.UserController{}, &quot;get:GetUser&quot;)
	beego.Router(&quot;/user&quot;, &amp;amp;controllers.UserController{}, &quot;post:AddUser&quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注册路由的函数&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt;函数会在import之后被自动执行&lt;/li&gt;
  &lt;li&gt;在这里是注册路由的步骤，采用REST的方式，将请求转发到Controller&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rootpath&lt;/code&gt;绑定的URL路径，以”/”为开头&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt;是个interface，指出要绑定的Controller&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mappingMethods&lt;/code&gt;绑定控制器中的指定方法。格式为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;HTTP_METHOD:FUNC_NAME&quot;&lt;/code&gt;。可以为空，为空时会默认绑定指定Controller中的Get、Post等方法（Get请求调用Get函数，一次类推）&lt;/li&gt;
  &lt;li&gt;所有注册需要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego.Run()&lt;/code&gt;之前进行&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-beegorun&quot;&gt;2. beego.Run()&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
	_ &quot;beego.test/routers&quot;
	&quot;github.com/astaxie/beego&quot;
)

func main() {
	beego.Run()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego.Run()&lt;/code&gt;会启动服务器，在此之前需要做路由监听。可以将所有需要注册路由的代码放到一起，一般放在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[PROJECT]/routers/router.go&lt;/code&gt;里面，通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_ &quot;beego.test/routers&quot;&lt;/code&gt;初始化&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;解析配置文件&lt;/p&gt;

    &lt;p&gt;beego 会自动在 conf 目录下面去解析相应的配置文件 app.conf，这样就可以通过配置文件配置一些例如开启的端口，是否开启 session，应用名称等各种信息。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;开启 session&lt;/p&gt;

    &lt;p&gt;会根据上面配置文件的分析之后判断是否开启 session，如果开启的话就初始化全局的 session。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;编译模板&lt;/p&gt;

    &lt;p&gt;beego 会在启动的时候把 views 目录下的所有模板进行预编译，然后存在 map 里面，这样可以有效的提供模板运行的效率，无需进行多次编译。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;启动管理模块&lt;/p&gt;

    &lt;p&gt;beego 目前做了一个很帅的模块，应用内监控模块，会在 8088 端口做一个内部监听，我们可以通过这个端口查询到 QPS、CPU、内存、GC、goroutine、thread 等各种信息。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;监听服务端口&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-controller&quot;&gt;3. Controller&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Golang中的继承&lt;/p&gt;

    &lt;p&gt;使用Controller的时候需要用到继承，使得每个新建的Controller都能有处理Request的能力，而不必重写一遍该功能。下面先介绍继承的实现。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  type TestController struct {
      beego.Controller
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;自定义的控制器都包含&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego.Controller&lt;/code&gt;这个控制器，模拟继承。这是&lt;strong&gt;匿名字段&lt;/strong&gt;。这里添加了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego.Controller&lt;/code&gt;这个struct，但是没有为其命名，在这种情况下，这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct(beego.Controller)&lt;/code&gt;所拥有的全部字段都被隐式地引入了当前定义的这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct(TestController)&lt;/code&gt;。这样就&lt;em&gt;继承到了父类所有的属性&lt;/em&gt;了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;冲突和遮蔽&lt;/p&gt;

    &lt;p&gt;如果有两个字段具有相同的名字(可能是一个继承类型的名字)，代码将遵循下面规则：&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;外层的名字遮蔽内层的名字。这提供了一个重写字段/方法的方式。&lt;/li&gt;
      &lt;li&gt;如果在同一层次上出现了相同的名字，如果名字被使用，那么将是一个错误。(如果没有使用，不会出现错误)&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;&lt;strong&gt;匿名字段冲突&lt;/strong&gt;提供了一种重写的方法，如果没有冲突，使用父类的字段；如果冲突，使用子类的字段。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func (c *Controller) Get() {
      http.Error(c.Ctx.ResponseWriter, &quot;Method Not Allowed&quot;, 405)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beego/controllers.go&lt;/code&gt;中已经实现了所有方法，例如Get、POST、PUT、DELETE，如果没有为新创建的Controller的GET方法添加Get()处理函数，beego会默认处理，返回405。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;4-model&quot;&gt;4. Model&lt;/h5&gt;
&lt;p&gt;系统中存在数据库访问处理操作，如果整个系统逻辑简单，可以直接在Controller中处理。如果系统比较复杂，有大量并且重复的数据库操作代码，可以抽象到Model层。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Golang命名规范：采用驼峰命名。如果首字母大写，为public，如果为小写，只能被当前package访问到&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;beego会自动解析URL里面的Query，可以通过Controller的函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetInt, GetString&lt;/code&gt;获得&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  GetString(key string) string
  GetStrings(key string) []string
  GetInt(key string) (int64, error)
  GetBool(key string) (bool, error)
  GetFloat(key string) (float64, error)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;5-view&quot;&gt;5. View&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;返回HTML。 &lt;br /&gt;
View层需要用到的模板存放在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[PROJECT]/views&lt;/code&gt;目录下，支持&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.html&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tpl&lt;/code&gt;两种后缀名的文件。在 Controller 里面把数据赋值给了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt;（map 类型），在这里通过进行取值组装。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func (this *MainController) Get() {
      this.Data[&quot;Website&quot;] = &quot;blog.cyeam.com/kaleidoscope/2014/01/12/beego&quot;
      this.Data[&quot;Email&quot;] = &quot;lichao0407@gmail.com&quot;
      this.TplNames = &quot;index.tpl&quot;
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;返回JSON&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func (this *AddController) Get() {
      mystruct := { ... }
      this.Data[&quot;json&quot;] = &amp;amp;mystruct
      this.ServeJson()
  } ### Golang语法及常见包 ##### 1. net/http
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;发送Get请求&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  resp, err := http.Get([URL])   + URL需要以`http://`开头
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;获取resp中的Body(byte[])&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  import &quot;io/ioutil&quot;
  reply, err := ioutil.ReadAll(resp.Body)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-fmt&quot;&gt;2. fmt&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;类型转换&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;string转int&lt;/p&gt;

        &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  fmt.Sprintf(&quot;%d&quot;, (page-1)*size)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-encodingjson&quot;&gt;3. encoding/json&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;解析json到struct&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  err = json.Unmarshal(reply, &amp;amp;solrBody) 

  type SolrBody struct {
      ResponseHeader SolrResponseHeader `json:&quot;responseHeader&quot;`
      SolrResponse   SolrResponse       `json:&quot;response&quot;`	
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;4-array--slice&quot;&gt;4. Array &amp;amp; Slice&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;声明&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  ids := []int{}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;增加值 append&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  ids = append(ids, value)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;5-len&quot;&gt;5. len&lt;/h5&gt;
&lt;p&gt;The len built-in function returns the length of v, according to its type:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Array: the number of elements in v.&lt;/li&gt;
  &lt;li&gt;Pointer to array: the number of elements in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*v&lt;/code&gt; (even if v is nil).&lt;/li&gt;
  &lt;li&gt;Slice, or map: the number of elements in v; if v is nil, len(v) is zero.&lt;/li&gt;
  &lt;li&gt;String: the number of bytes in v.&lt;/li&gt;
  &lt;li&gt;Channel: the number of elements queued (unread) in the channel buffer;&lt;/li&gt;
  &lt;li&gt;if v is nil, len(v) is zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;6-make--new&quot;&gt;6. make &amp;amp; new&lt;/h5&gt;
&lt;p&gt;都可以用来申请内存，make返回对象，new返回指向对象的指针&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>一些网站架构中常见的术语和技术</title>
   <link href="https://blog.cyeam.com/framework/2014/01/14/meeting"/>
   <updated>2014-01-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/framework/2014/01/14/meeting</id>
   <content type="html">&lt;h5 id=&quot;1-kpi&quot;&gt;1. KPI&lt;/h5&gt;

&lt;p&gt;&lt;a href=&quot;https://wiki.mbalib.com/wiki/KPI&quot;&gt;KPI&lt;/a&gt;的理论基础是二八原理，是由意大利经济学家帕累托提出的一个经济学原理，即一个企业在价值创造过程中，每个部门和每一位员工的80%的工作任务是由20%的关键行为完成的，抓住20%的关键，就抓住了主体。&lt;/p&gt;

&lt;p&gt;二八原理为绩效考核指明了方向，即考核工作的主要精力要放在关键的结果和关键的过程上。于是，所谓的绩效考核，一定放在关键绩效指标上，考核工作一定要围绕关键绩效指标展开。&lt;/p&gt;

&lt;h5 id=&quot;2-pvuv&quot;&gt;2. PV/UV&lt;/h5&gt;

&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/PV&quot;&gt;单页点阅率（Page View）&lt;/a&gt;，网页自服务器呼叫后，被读者浏览的次数。
单页点阅率是网站分析的一个术语，用以衡量网站用户访问的网页的数量。对于网站所有者，可以看到网页的变化（如信息和信息的编排方式）是否带来了更大的流量。对于广告主（如果网页上有广告），单页点阅率的关注点在于预期它可以带来多少广告收入。出于这个原因，它成为一种广泛用于基于互联网的市场营销和广告术语。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.liuliangbao.cn/liuliang.htm?id=3&quot;&gt;UV(独立访客)：Unique Visitor&lt;/a&gt;,访问您网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只会被计算一次。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.liuliangbao.cn/liuliang.htm?id=3&quot;&gt;IP(独立IP)&lt;/a&gt;：指独立IP数。00:00-24:00内相同IP地址之被计算一次。&lt;/p&gt;

&lt;h5 id=&quot;3-channel公司&quot;&gt;3. channel公司&lt;/h5&gt;

&lt;p&gt;&lt;a href=&quot;https://baike.baidu.com/view/150440.htm&quot;&gt;中国频道&lt;/a&gt;成立于1996年，是国际知名的为全球企业用户、组织机构、商务人士及个人用户提供互联网应用服务的提供商（ISP）。10几年来，公司在激烈的市场竞争中始终走在行业前列，实现了跨越式发展，为社会各界提供专业的企业邮局、虚拟主机、独立主机、域名注册、网站建设、网站推广和个人邮箱、网络游戏等服务。&lt;/p&gt;

&lt;h5 id=&quot;4-passengerruby&quot;&gt;4. Passenger(ruby)&lt;/h5&gt;

&lt;p&gt;Phusion Passenger is a web server and application server, designed to be fast, robust and lightweight. It takes a lot of complexity out of deploying web apps, adds powerful enterprise-grade features that are useful in production, and makes administration much easier and less complex.&lt;/p&gt;

&lt;h5 id=&quot;5-varnish-应对雪崩-反向代理-1000个请求1个访问后台999个等待访问数据库结束后1000个返回&quot;&gt;5. Varnish 应对雪崩 反向代理 1000个请求，1个访问后台，999个等待，访问数据库结束后，1000个返回&lt;/h5&gt;

&lt;p&gt;&lt;a href=&quot;https://www.ibm.com/developerworks/cn/opensource/os-cn-varnish-intro/index.html?ca=drs-&quot;&gt;Varnish&lt;/a&gt; 是一款高性能且开源的反向代理服务器和 HTTP 加速器，其采用全新的软件体系机构，和现在的硬件体系紧密配合，与传统的 squid 相比，varnish 具有性能更高、速度更快、管理更加方便等诸多优点，很多大型的网站都开始尝试使用 varnish 来替换 squid，这些都促进 varnish 迅速发展起来。&lt;/p&gt;

&lt;h5 id=&quot;6-mangodb存储图片-半线下服务&quot;&gt;6. MangoDB存储图片 半线下服务&lt;/h5&gt;
&lt;p&gt;&lt;a href=&quot;https://www.oschina.net/p/mongodb&quot;&gt;MongoDB&lt;/a&gt;是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。他支持的数据结构非常松散，是类似json的bjson格式，因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大，其语法有点类似于面向对象的查询语言，几乎可以实现类似关系数据库单表查询的绝大部分功能，而且还支持对数据建立索引。&lt;/p&gt;

&lt;p&gt;当附件数量海里去了~~ 那这样存就蛋疼了， 备份是个问题，硬盘IO瓶颈那会也会凸显出来。问题接踵而来。那会，就需要&lt;a href=&quot;https://babyhe.blog.51cto.com/1104064/1096775&quot;&gt;分布式文件存储&lt;/a&gt;了。利用GridFS存储文件， 再利用MangoDB的分片(sharding) 就可以做到海量存储了。&lt;/p&gt;

&lt;h5 id=&quot;7-表冷热不一-innodb行锁死&quot;&gt;7. 表冷热不一 InnoDB行锁死&lt;/h5&gt;

&lt;h5 id=&quot;8-cdn内容分发系统&quot;&gt;8. CDN内容分发系统&lt;/h5&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF&quot;&gt;CDN&lt;/a&gt;节点会在多个地点，多个不同的网络上摆放。这些节点之间会动态的互相传输内容，对用户的下载行为优化，并借此减少内容供应者所需要的带宽成本，改善用户的下载速度，提高系统的稳定性。&lt;/p&gt;

&lt;h5 id=&quot;9-crontab冗余计算-定时计算&quot;&gt;9. Crontab冗余计算 定时计算&lt;/h5&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/Cron&quot;&gt;crontab&lt;/a&gt;命令常见于Unix和类Unix的操作系统之中，用于设置周期性被执行的指令。该命令从标准输入设备读取指令，并将其存放于“crontab”文件中，以供之后读取和执行。&lt;/p&gt;

&lt;h5 id=&quot;10-solr查询&quot;&gt;10. Solr查询&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/solr.jpg&quot; alt=&quot;Alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/Solr&quot;&gt;Solr&lt;/a&gt;是开放源码的企业搜索服务器（Enterprise Search Server）软件，由Apache软件基金会所研发。Solr 使用Lucene程式库以及需要Servlet容器作执行环境。Solr本身提供XML/HTTP与JSON的应用程式接口。&lt;/p&gt;

&lt;h5 id=&quot;11-mysql集群&quot;&gt;11. MySql集群&lt;/h5&gt;
&lt;p&gt;https://www.cnblogs.com/zhishan/p/3385631.html&lt;/p&gt;

&lt;h5 id=&quot;12-首页不应该访问db要么静态文件要么缓存&quot;&gt;12. 首页不应该访问DB，要么静态文件，要么缓存&lt;/h5&gt;

&lt;h5 id=&quot;13-dos攻击&quot;&gt;13. Dos攻击&lt;/h5&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/justdoitflyer/article/details/12870907&quot;&gt;DoS&lt;/a&gt;是Denial of Service的简称，即拒绝服务，造成DoS的攻击行为被称为DoS攻击，其目的是使计算机或网络无法提供正常的服务。&lt;/p&gt;

&lt;h5 id=&quot;14-redis&quot;&gt;14. Redis&lt;/h5&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/Redis&quot;&gt;Redis&lt;/a&gt;是最流行的键值对存储数据库。&lt;/p&gt;

&lt;h5 id=&quot;15-http-502503&quot;&gt;15. HTTP 502/503&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;502 Bad Gateway
The server was acting as a gateway or proxy and received an invalid response from the upstream server.&lt;/li&gt;
  &lt;li&gt;503 Service Unavailable
The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state. Sometimes, this can be permanent as well on test servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;16-部署流程&quot;&gt;16. 部署流程&lt;/h5&gt;

&lt;h5 id=&quot;17-分层-单元测试-回归测试-自动化&quot;&gt;17. 分层 单元测试 回归测试 自动化&lt;/h5&gt;

&lt;h5 id=&quot;18-反向代理和前向代理&quot;&gt;18. 反向代理和前向代理&lt;/h5&gt;
&lt;p&gt;在计算机网络中，&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86&quot;&gt;反向代理&lt;/a&gt;是代理服务器的一种。它根据客户端的请求，从后端的服务器上获取资源，然后再将这些资源返回给客户端。[1]与前向代理不同，前向代理作为一个媒介将互联网上获取的资源返回给相关联的客户端，而反向代理是在服务器端作为代理使用，而不是客户端。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>2013读书总结</title>
   <link href="https://blog.cyeam.com/notebook/2014/01/13/2013_reading"/>
   <updated>2014-01-13T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2014/01/13/2013_reading</id>
   <content type="html">&lt;p&gt;最近在整理之前写过的东西，都准备放到GitHub上面。找到了之前在豆瓣上面放着的日记，有一篇是&lt;a href=&quot;/notebook/2013/01/13/2012_reading&quot;&gt;《2012年读书总结》&lt;/a&gt;。最近也正好有总结2013年的打算，便准备开始写2013年读书总结。写的时候发现上一篇正好是一年前的今天，13号写的。有意思。&lt;/p&gt;

&lt;p&gt;2013年阅读量比2012年大了许多。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;年初开始读《程序
员的自我修养 : 链接、装载与库》。这本书确实很经典，讲述了底层C语言代码编译、链接、运行的原理。我原打算读了去做跨平台编译的，发现学了不少，一点没用上，又全忘了。。。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;《Servlet&amp;amp;JSP学习笔记》。一个台湾人写的，Servlet入门书里面我觉得很不错的。&lt;/li&gt;
  &lt;li&gt;《Struts 2实战》。之前去IBM面试Java的时候被张斌鄙视了。后来寒假的时候用一天的时间读了一下这本书，读完感觉可以完虐张斌啊，^_^。&lt;/li&gt;
  &lt;li&gt;《Learning Android》。一本很不错的Android入门书籍，过几天做毕业设计的时候准备再看一下。&lt;/li&gt;
  &lt;li&gt;《打造Facebook : 亲历Facebook爆发的5年》。一个中国留学生讲述了他在Facebook工作的事情。&lt;/li&gt;
  &lt;li&gt;《明朝那些事儿》。去年影响我一年的书是《乡关何处》。今年影响我的是这本，非常精彩，在IBM实习的半年就在读这本书。作者用自己的观点讲述了他所理解的明朝。以后闲下来要去读一读王阳明的书。&lt;/li&gt;
  &lt;li&gt;《HTML与CSS入门经典》&lt;/li&gt;
  &lt;li&gt;《浪潮之巅》。这本也很精彩，吴军博士讲了硅谷各大公司的发展史。&lt;/li&gt;
  &lt;li&gt;《剑指Offer : 名企面试官精讲典型编程题》。虽然这本书出来时间也很久了，但是各大公司面试题和笔试题还是会考。&lt;/li&gt;
  &lt;li&gt;《慢慢来.一切都来得及 : 心慢下来，行动才能快起来》。蛋疼的时候读一读，还是很管用的。我读过一篇，之后就没继续了。希望以后也不用读了。话说，前几天看了一部电影，《等风来》，差不多。&lt;/li&gt;
  &lt;li&gt;《Java/Java EE软件工程师就业求职手册》。我纠结的就业过程中，是这本书陪伴着我。没错，我自己都不敢相信，我这种只读经典书籍的，居然看了这种书。不过书还可以，知识点都覆盖了。当然，也可以想象，讲的多而不精。不过我还是有基础的，所以搭配上自己的总结，还是学到了不少。&lt;/li&gt;
  &lt;li&gt;《1980年代的爱情》。野夫今年的新书，排行榜很考前，可能是因为去年的《乡关何处》太火了。这本还好，纯纯的爱情。&lt;/li&gt;
  &lt;li&gt;《数学之美》。我曾经因为这本书改了我的毕业设计论文题目。当然，有我牛逼的导师的存在，让这一切化为了泡影。吴军博士将整个自然语言处理的算法讲的我这种行外人都能看得懂，可见其实力。这本书还需要读几遍。&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Markdown</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2014/01/12/markdown"/>
   <updated>2014-01-12T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2014/01/12/markdown</id>
   <content type="html">&lt;h4 id=&quot;1-修改图片css&quot;&gt;1. 修改图片CSS&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;![IMG-THUMBNAIL]({IMAGE URL}) 添加CSS

img[alt=IMG-THUMBNAIL] {
   
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2-超链接&quot;&gt;2. 超链接&lt;/h4&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[{文本}]({超链接地址})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;3-粗斜体&quot;&gt;3. 粗斜体&lt;/h4&gt;
&lt;p&gt;粗体和斜体：用星号&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;或者下划线&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个表示&lt;em&gt;斜体&lt;/em&gt;；&lt;/li&gt;
  &lt;li&gt;两个表示&lt;strong&gt;粗体&lt;/strong&gt;；&lt;/li&gt;
  &lt;li&gt;三个表示&lt;strong&gt;&lt;em&gt;粗斜体&lt;/em&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;4-anchor&quot;&gt;4. Anchor&lt;/h4&gt;
&lt;p&gt;跳到Anchor&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[配置](#config) 创建Anchor

&amp;lt;a name=&quot;config&quot;&amp;gt;&amp;lt;/a&amp;gt;配置
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;5-引用&quot;&gt;5. 引用&lt;/h4&gt;
&lt;blockquote&gt;
  &lt;p&gt;用右尖括号 (&amp;gt;) 表示 blockquote，你一定见过邮件中这样表示引用别人的内容。可以嵌套，可以包含其它的 Markdown 元素&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;6-table&quot;&gt;6. Table&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;这个算是Markdown的扩展语法 https://github.com/trentm/python-markdown2/wiki/wiki-tables&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;|| *Year* || *Temperature (low)* || *Temperature (high)* ||
|| 1900 || -10 || 25 ||
|| 1910 || -15 || 30 ||
|| 1920 || -10 || 32 ||
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Linux Mint 64bit下安装Dota 2</title>
   <link href="https://blog.cyeam.com/toss/2014/01/11/dota2"/>
   <updated>2014-01-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/toss/2014/01/11/dota2</id>
   <content type="html">&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/dota-2-logo.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为了能在Linux下玩Dota 2，前前后后折腾了好几个礼拜，先写一下Linux下安装环境的难度，这也是这个解决问题的思路。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Linux下Nvidia显卡驱动不支持自动切换显卡的功能，需要安装第三方驱动。Nvidia显卡切换技术叫做Optimus，也就是擎天柱
现在在Linux下面柱子无效，因此第三方开发团队的开发了好基友Bumblebee大黄蜂；&lt;/li&gt;
  &lt;li&gt;Bumblebee在开发的时候也对Nvidia原生驱动进行了修改，所以还需要安装第三方的驱动（如果已经安装了Nvidia的驱动，需要删除）;&lt;/li&gt;
  &lt;li&gt;最恶心人的不是这些还不算。Steam是开发的32位版本，而如果你是64位电脑，那么还要注意，Steam提供的解决方案默认是32位的解决方案，所以你还得去找找64位的安装方案。这也是一直让我纠结的地方，所有都安好了，就是一直报错&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;You appear to have OpenGL 1.4.0, but we need at least 2.0.0!&lt;/code&gt;，让我一直以为是OpenGL的原因，在这里绕弯子了。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;1-安装bumblebee的方法参考下面的网址我就不抄了&quot;&gt;1. 安装Bumblebee的方法参考下面的网址，我就不抄了。&lt;/h5&gt;
&lt;p&gt;https://cjenkins.wordpress.com/2013/01/01/steam-for-linux-on-optimus-enabled-computer-running-ubuntu-12-04-64bits/#install_bumblebee&lt;/p&gt;

&lt;h5 id=&quot;2-测试安装要用到glxspheres64位系统的安装方法也比较特殊&quot;&gt;2. 测试安装要用到glxspheres，64位系统的安装方法也比较特殊。。。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://goo.gl/L7rsGZ -O virtualgl_2.3.3_amd64.deb 
sudo dpkg -i --force-depends virtualgl_2.3.3_amd64.deb 
sudo apt-get -f install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行的命令如下，也可以创建一个快捷方式到/usr/bin下，这样用起来方便一些。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/opt/VirtualGL/bin/glxspheres64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;https://www.upubuntu.com/2013/11/how-to-check-3d-acceleration-fps-in.html&lt;/p&gt;

&lt;h5 id=&quot;3-安装好之后出现了这个问题&quot;&gt;3. 安装好之后，出现了这个问题&lt;/h5&gt;
&lt;p&gt;一般等这些都安好以后，安装提示，将运行Dota 2的命令改成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;primusrun %command%&lt;/code&gt;，运行Dota 2，就会出现这个错误了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PROBLEM: You appear to have OpenGL 1.4.0, but we need at least 2.0.0!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;第一次装的时候一直一位是OpenGL的问题，今天又去查了查，无意中发现了Steam文档中的一句话：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Note - Primus must be installed with 32-bit support because Steam for Linux (and most games downloaded from Steam) are 32-bit.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;https://support.steampowered.com/kb_article.php?ref=6316-GJKC-7437&lt;/p&gt;

&lt;p&gt;我意识到可能是我安装成64位Primus造成的。所以开始着手安装32位版本。Steam官方也没特别明确的提供，还得自己找。。。&lt;/p&gt;

&lt;h5 id=&quot;4-linux-64位系统下32位primus安装&quot;&gt;4. Linux 64位系统下32位Primus安装&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get install primus-libs-ia32:i386
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;https://www.webupd8.org/2012/11/primus-better-performance-and-less.html&lt;/p&gt;

&lt;h5 id=&quot;5-查看nvidia显卡是否启动&quot;&gt;5. 查看Nvidia显卡是否启动&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lspci |grep VGA
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;类似结果如下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00:02.0 VGA compatible controller: Intel Corporation 3rd Gen Core processor Graphics Controller (rev 09)
01:00.0 VGA compatible controller: NVIDIA Corporation GF108M [NVS 5400M] (rev ff) rev代表启动状态，ff为未启动。其他为已启动（其实电脑发热量大了，风扇开始响了，就代表启动了。。。）。 https://www.webupd8.org/2012/11/primus-better-performance-and-less.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;6-跳过training&quot;&gt;6. 跳过Training&lt;/h5&gt;
&lt;p&gt;进入Dota 2以后，还要蛋疼的强迫你Training，作为资深Dota玩家的我，果断是要跳过的啊。在Steam里面启动Dota 2的环境变量最后加上如下参数，就可以跳过了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+dota_full_ui 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整的变量如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;primusrun %command% +dota_full_ui 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;7-游戏修改为中文界面&quot;&gt;7. 游戏修改为中文界面&lt;/h5&gt;
&lt;p&gt;增加启动参数，改为完美世界的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-perfectworld -language schinese 完整的变量如下：

primusrun %command% +dota_full_ui 1 perfectworld -language schinese
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;https://www.douban.com/group/topic/41787706/&lt;/p&gt;

&lt;h5 id=&quot;8-连接完美世界服务器游戏&quot;&gt;8. 连接完美世界服务器游戏&lt;/h5&gt;
&lt;p&gt;配置好所有的之后，可以打AI了，但是还是有问题，就是不能和人打。匹配区域只能显示欧洲、东南亚这些，匹配很久也匹配不到。&lt;/p&gt;

&lt;p&gt;解决方案，还是修改启动参数&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-perfectworld steam
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整的变量如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;primusrun %command% +dota_full_ui 1 perfectworld -language schinese -perfectworld steam
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;最后奉上游戏截图，我最爱的Pom
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/dota2_pom.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Java面试宝典</title>
   <link href="https://blog.cyeam.com/java/2014/01/02/javacollection"/>
   <updated>2014-01-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/java/2014/01/02/javacollection</id>
   <content type="html">&lt;h3 id=&quot;1-java标识符命名规范&quot;&gt;1. Java标识符，命名规范？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;不能以数字开头；rake post title=”hello world” category=”life” date=”2013-04-21” tags=”” description=”description”&lt;/li&gt;
  &lt;li&gt;区分大小写；&lt;/li&gt;
  &lt;li&gt;不能有@、‘-‘（运算符）；&lt;/li&gt;
  &lt;li&gt;可以出现中文；&lt;/li&gt;
  &lt;li&gt;java关键字不能做为变量名；&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-java基本类型及其范围&quot;&gt;2. Java基本类型及其范围&lt;/h3&gt;
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;基本类型&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;大小&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;最小值&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;最大值&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;包装器&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;默认值&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Boolean&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;false&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span&gt;没有大小&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;char&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;16bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Unicode 0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Unicode 2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;16&lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Character&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;\u0000‘ ‘&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span&gt;可以用来表示中文&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;byte&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;8bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-128&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;127&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Byte&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span&gt;与C++中的char相同&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;short&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;16bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;15&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;15&lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Short&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;int&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;32bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;31&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;31&lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Integer&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;long&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;64bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;63&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;2&lt;/span&gt;&lt;span class=&quot;c32&quot;&gt;63&lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Long&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;float&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;32bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;IEEE754&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;IEEE754&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Float&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span&gt;指数8位&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;double&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;64bits&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;IEEE754&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;IEEE754&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Double&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span&gt;指数16位&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c29&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;void&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c10&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c23&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c13&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;Void&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c30&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;null&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c9&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span&gt;Void可以定义变量&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;3-java中变量的初始化&quot;&gt;3. Java中变量的初始化&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;局部变量必须初始化，如果没有初始化而使用，会编译错误。数组会自动初始化；&lt;/li&gt;
  &lt;li&gt;类中的变量可以在调用构造函数的时候自动初始化，且有默认值；&lt;/li&gt;
  &lt;li&gt;Java对于变量初始化赋值很严格，float f = 0.0(0.0为double类型)，这在C++中是会有警告，而在Java中是编译不过的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4-java操作有优先级&quot;&gt;4. Java操作有优先级？&lt;/h3&gt;
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;优先级&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;运算符&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;结合性&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;1&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;() [] .&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左到右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;! +(正) &amp;nbsp;-(负) ~ ++ --&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从右向左&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;* / %&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;4&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;+(加) -(减)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;5&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;&amp;lt;&amp;lt; &amp;gt;&amp;gt; &amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;6&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;&amp;lt; &amp;lt;= &amp;gt; &amp;gt;= instanceof&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;7&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;== &amp;nbsp; !=&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;8&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;&amp;amp;(按位与)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;9&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;^&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;10&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;|&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;11&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;12&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;||&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从左向右&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;13&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;?:&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从右向左&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c18&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;14&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c24&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;= += -= *= /= %= &amp;amp;= |= ^= &amp;nbsp;~= &amp;nbsp;&amp;lt;&amp;lt;= &amp;gt;&amp;gt;= &amp;nbsp; &amp;gt;&amp;gt;&amp;gt;=&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c3&quot;&gt;&lt;p class=&quot;c2 c6&quot;&gt;&lt;span&gt;从右向左&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;5-自加自减操作符&quot;&gt;5. 自加++、自减–操作符&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;前缀操作，可以理解为先加再赋值，后缀操作是先赋值再加；&lt;/li&gt;
  &lt;li&gt;如果为后缀自加，加的操作在该语句执行结束后执行；&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;当有多种可以解析的情况下，如下，可以解析成test1+(++test2)或者test1++ + test2，会从左至右就近的匹配。test3是1而不是2。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  int test1 = 0;
  int test2 = 1;
  int test3 = test1+++test2;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;6-负数如何进行取模运算&quot;&gt;6. 负数如何进行取模运算？&lt;/h3&gt;
&lt;p&gt;对于负数，先对其绝对值取模，再加上符号。&lt;/p&gt;

&lt;h3 id=&quot;7-是什么操作符&quot;&gt;7. 是什么操作符？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Java中新增了一种操作符，无符号右移»&amp;gt;，无论正负数，高位都是补0；&lt;/li&gt;
  &lt;li&gt;有符号右移»，正数时高位补0，负数时高位补1(1是符号位)；&lt;/li&gt;
  &lt;li&gt;移位运算符可以高效的实现与2的倍数的乘除法。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;8-与的区别&quot;&gt;8. &amp;amp;与&amp;amp;&amp;amp;的区别？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&amp;amp;是按位与运算，也可以进行boolean运算；&lt;/li&gt;
  &lt;li&gt;与&amp;amp;&amp;amp;的区别是，&amp;amp;&amp;amp;可以进行短路求值。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;9-8--9--10--11的运算结果&quot;&gt;9. 8 | 9 &amp;amp; 10 ^ 11的运算结果？&lt;/h3&gt;
&lt;p&gt;|、&amp;amp;、^操作符的优先顺序是&amp;amp;、^、|，首先&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9(1001)&amp;amp;10(1010)=8(1000)&lt;/code&gt;，然后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8(1000)^11(1011)=3&lt;/code&gt;，最后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0011)8(1000)|3(0011)=11(1011)&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;10-下面代码的输出是什么&quot;&gt;10. 下面代码的输出是什么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int n = 7;
n &amp;lt;&amp;lt;= 3;
n = n &amp;amp; n + 1 | n + 2 ^ n + 3;
n &amp;gt;&amp;gt;= 2;
System.out.println(n); n向左移3位，相当于乘以8，得到56。+的优先级高于按位操作符的优先级，先进行加法，变成`56&amp;amp;57|58^59`，剩下的操作与9题类似，得到14。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;11-下面代码的执行结果是什么&quot;&gt;11. 下面代码的执行结果是什么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;x = 5;
y = 3;
int z = x + (x++) + (++x) + y; 根据优先级，要先执行x++，x后加，先赋值5，最后再加，现在还不加；然后执行++x，先加，再赋值，此时x为6。注意，此时第一个x也是6，这是比较容易出错的地方。最后再加y，6+5+6+3=20。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;12-equals和的区别&quot;&gt;12. equals()和==的区别？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;是二元运算符，在判断基本数据类型的时候，是判断其值，值相等则为true；在判断引用对象的时候，是判断其内存地址，是同一个变量则为true；&lt;/li&gt;
  &lt;li&gt;equals()是Object的一个函数，默认通过hashCode()来判断时候相等，而hashCode()是通过计算引用对象的内存地址得到的。可以重写这两个函数来判断两个引用对象的内容是否相等；&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;用于判断原生类型(primitive)相等，equals()用于判断对象的相等。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;13-描述一下java中的参数传递&quot;&gt;13. 描述一下Java中的参数传递？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;对于基本类型，Java采用按值传递，参数的值是不能够修改的；&lt;/li&gt;
  &lt;li&gt;而如果传递的是对象的引用，则会按引用传递。这个也不矛盾，如果是对象的引用，可以理解成是传递的是对象的地址，这个地址是按值传递的，我们不能修改，而这个地址的对象，我们是可以修改的，这和C++中的是一样的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;14-如何获得数组的长度&quot;&gt;14. 如何获得数组的长度？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int arr[] = new int[4];
arr.length;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;15-private和protected是否可以是class的访问修饰符&quot;&gt;15. private和protected是否可以是class的访问修饰符？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;不能。class只支持public和friendly(不写，默认的)访问修饰符，其余的都不支持；&lt;/li&gt;
  &lt;li&gt;一个.java文件中最多只能有一个public类，作为该文件对外的接口，且该类要与文件名相同(一个文件中可以有多个类)；&lt;/li&gt;
  &lt;li&gt;如果想定义成private那样的类，可以将其构造函数定义成private的访问权限，也可达到目的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;16-finalfinallyfinalize的区别&quot;&gt;16. final、finally、finalize的区别？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;final用来定义常量，使其初始化之后不能修改。定义函数，使其在继承类中不能被覆盖，也能实现内嵌调用，从而提高效率。定义类，该类则不能被继承，例如String；&lt;/li&gt;
  &lt;li&gt;finally是try/catch块之后执行且总是被执行，会在return之前被调用；&lt;/li&gt;
  &lt;li&gt;finalize()是Object中的一个方法，释放内存时会被调用，可以覆盖该函数来实现内存的释放。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;17-java访问修饰符&quot;&gt;17. Java访问修饰符？&lt;/h3&gt;
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c8 c6&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;当前类&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;当前包&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;继承类&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;其他包&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;public&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c6 c17&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;protected&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;×&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;包访问权限(friendly)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;×&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;×&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span&gt;private&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c1 c22&quot;&gt;√&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;×&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;×&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c12&quot;&gt;&lt;p class=&quot;c17 c6&quot;&gt;&lt;span class=&quot;c22 c1&quot;&gt;×&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;18-下面代码的执行结果是什么&quot;&gt;18. 下面代码的执行结果是什么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class AA extends BB {
    private int radius = 1;
    public void draw() {
        System.out.println(&quot;A.draw(),radius=&quot; + radius);
    }
    public AA(int radius) {
        this.radius = radius;
        System.out.println(&quot;A constructor&quot;);
    }
}
class BB {
    private int radius = 10;
    public void draw() {
        System.out.println(&quot;B.draw(),radius=&quot; + radius);
    }
    public BB() {
        System.out.println(&quot;B constructor&quot;);
        draw();
    }
    public BB(int radius) {
        System.out.println(&quot;B constructor with parameter&quot;);
        draw();
    }
}
B constructor
A.draw(),radius=1
A constructor 调用构造函数的一般顺序：
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;父类的静态变量初始化；&lt;/li&gt;
  &lt;li&gt;子类的静态变量初始化；&lt;/li&gt;
  &lt;li&gt;父类的非静态变量初始化；&lt;/li&gt;
  &lt;li&gt;父类的静态代码块初始化(只在声明的时候初始化)；&lt;/li&gt;
  &lt;li&gt;父类的非静态代码块初始化；&lt;/li&gt;
  &lt;li&gt;父类的无参数构造函数初始化；&lt;/li&gt;
  &lt;li&gt;子类的非静态变量初始化；&lt;/li&gt;
  &lt;li&gt;子类的静态代码块初始化；&lt;/li&gt;
  &lt;li&gt;子类的非静态代码块初始化；&lt;/li&gt;
  &lt;li&gt;子类被调用的构造函数初始化。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此题中，先调用父类的无参数构造函数，输出B constructor，再调用draw函数，draw是自己的draw函数(draw函数被覆盖)。&lt;/p&gt;

&lt;h3 id=&quot;19-下面语句有什么问题&quot;&gt;19. 下面语句有什么问题？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (x) {
    x = 0;
} Java中的if条件表达式只支持boolean类型，这不同于C++中还可以支持整型。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;20-可以用于switch条件表达式的类型是什么&quot;&gt;20. 可以用于switch条件表达式的类型是什么？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;switch支持整型表达式，默认支持int；&lt;/li&gt;
  &lt;li&gt;byte、char、short可以自动向下转换，不会造成数据丢失，所以也可以使用；&lt;/li&gt;
  &lt;li&gt;long、float、double转换成int时会丢失数据，不能使用。&lt;/li&gt;
  &lt;li&gt;Java 7之后，switch开始支持字符串String。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;21-下列代码的执行结果是什么&quot;&gt;21. 下列代码的执行结果是什么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int i = 10, j = 18, k = 30;
switch(j - i) {
case 8: System.out.println(++k);
case 9: System.out.println(k+=3);
case 10: System.out.println(k&amp;lt;&amp;lt;=1);
default: System.out.println(k/=j);
} 如果匹配到case，从该case开始执行，直到遇到break为止。该题目中，四个语句都执行过，++k得到31，k+=3得到34，k乘以2得到68，再除以18得到3(小数部分抹掉)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;22-throwable的子类是哪两个&quot;&gt;22. Throwable的子类是哪两个？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Error&lt;/li&gt;
  &lt;li&gt;Exception&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;23-下面代码的输出结果是什么&quot;&gt;23. 下面代码的输出结果是什么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;try {
    String a = null;
    a.length();
} catch(nullPointerException e) {
    System.out.println(&quot;NullPointerException&quot;);
} catch(Exception e) {
    System.out.println(&quot;Exception&quot;);
} finally {
    System.out.println(&quot;finally&quot;);
} 会输出NullPointerException和finally，并列的多个catch，要先写子类，再写父类，匹配到一个catch后，不再匹配，执行finally语句。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;**补充一点： **处理异常的机制是为了保证程序正常的运行。比如，一段代码用来处理一段文本，当文本格式发生变化之后，可以修改代码，增加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;等判断语句；也可以之前就增加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;catch&lt;/code&gt;语句，一开始就能提供默认处理方式。&lt;/p&gt;

&lt;h3 id=&quot;24-throw和throws的区别&quot;&gt;24. throw和throws的区别？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;throws定义函数，声明这个方法会抛出这种类型的异常，使其他地方调用它时知道要捕获这个异常。使用try/catch来捕获；&lt;/li&gt;
  &lt;li&gt;throw是具体向外抛异常的动作，所以它一定会抛出一个异常实例。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;25-java支持多继承么&quot;&gt;25. Java支持多继承么？&lt;/h3&gt;
&lt;p&gt;Java默认只支持单继承extends，C++中的多继承对于继承的概念不是很严谨，因为继承多个对象无法确定该对象到底是属于哪个类型。而Java又提供了接口，可以被实现implements，一个类可以实现多个接口。这也是实现多继承的一种方式，而且也符合继承的概念，一个类只能有一个父类，这样不会造成混乱，实现多个接口，又能增加其他类中才有的功能。&lt;/p&gt;

&lt;h3 id=&quot;26-什么是重载&quot;&gt;26. 什么是重载？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;对于同一个动作，可以有不同的执行方法，这时就需要重载；&lt;/li&gt;
  &lt;li&gt;重载函数名必须相同；&lt;/li&gt;
  &lt;li&gt;参数表必须不同，只能通过参数表来区别重载函数；&lt;/li&gt;
  &lt;li&gt;返回值不能用来区别重载函数。如果只有返回值不同，由于返回值可以转换类型，所以不能用来区别重载函数。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;27-什么是重写&quot;&gt;27. 什么是重写？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;在继承关系中，子类可以重写继承到的父类的函数，来定义自己的功能实现；&lt;/li&gt;
  &lt;li&gt;重写的函数，函数名，返回值，参数表必须完全相同；&lt;/li&gt;
  &lt;li&gt;子类重写的函数的访问修饰符不能比父类的小；&lt;/li&gt;
  &lt;li&gt;子类重写的函数抛出的异常不能比父类的多。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;28-下面代码的执行结果是什么&quot;&gt;28. 下面代码的执行结果是什么？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Parent{
    public int x;
    public int y;
    public Parent(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void increaseX(int x) {
        this.x = getX() + x;
    }
    public int getX() {
        return x;
    }
    public void increaseY(int y) {
        this.y = getY() + y;
    }
    public int getY() {
        return y;
    }
}
public class Child extends Parent {
    private int x;
    private int y;
    public Child(int x, int y) {
        super(x, y);
        this.y = y + 250;
        this.x = x + 150;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
}
Child child = new Child(50, 50);
child.increaseX(100);
child.increaseY(100); x和y结果分别是200和300。在重写当中，具体还可以分为覆盖和隐藏。对于函数，子类中重写的函数会覆盖掉父类的函数，而对于变量，子类中重写的变量将会隐藏掉父类的变量。在new Child(50, 50)的时候，调用父类的带参数构造函数，在这里对x和y赋值，x和y是父类的x和y，不会影响到子类的x和y。接着this.y = y + 250，这里修改的是子类的x和y，得到200和300。接着执行increaseX和increaseY，子类并没有定义这两个函数，调用的是父类的这两个函数，而调用的时候修改的是父类中x和y的值，不会影响到子类的结果。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;29-下列代码插入标记处不会编译出错的是哪一个&quot;&gt;29. 下列代码插入标记处不会编译出错的是哪一个？&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyTest extends Test {
    int count;
    public MyTest(int cnt, int num) {
        super(num);
        count = cnt;
    }
    // insert code here
}
public class Test {
    int number;
    public Test(int i) {
        number = i;
    }
} + MyTest() {} + MyTest(int cnt) {count = cnt} + MyTest(int cnt) {super(); count = cnt;} + MyTest(int cnt) {count = cnt; super(cnt);} + MyTest(int cnt) {this(cnt, cnt);} + MyTest(int cnt) {super(cnt); this(cnt, 0)}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;首先，Java中子类的构造函数会默认有一句super()，来调用父类的构造函数，该语句可以省略。该语句需要放在构造函数第一行；
Java中this()也可以用在构造函数的第一行，用来调用本类的其他构造函数。this()和super()不能同时使用；
如果为super增加参数，那可以调用父类对应带参数的构造函数；
如果父类不定义构造函数，会自动为其分配一个不带参数的构造函数。如果父类定义了构造函数，那么将不再会自动分配。也就是说，定义了一个带参数的构造函数，那么该类就没有不带参数的构造函数了；
上题中，A、B、C、都显示或隐式地调用了super()函数，而父类没有定义不带参数的构造函数。D中super不在第一行。F同时使用了super和this。答案为E。&lt;/p&gt;

&lt;h3 id=&quot;30-java是否可以手动释放内存&quot;&gt;30. Java是否可以手动释放内存？&lt;/h3&gt;
&lt;p&gt;Java提供了一个方法，System.gc()，建议Java虚拟机去释放内存，当JVM决定去释放内存是，会调用该对象的finalize()方法。Java中申请内存由程序员实现，内存会申请到堆中，释放内存由GC实现。&lt;/p&gt;

&lt;h3 id=&quot;31-java是否会出现内存泄漏&quot;&gt;31. Java是否会出现内存泄漏？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/javacollection_gc.gif&quot; alt=&quot;GC&quot; /&gt;&lt;/p&gt;

&lt;p&gt;GC会自动回收垃圾，GC采用有向图的方法，一般情况下，如果一个对象没有被引用，则该对象所占的内存将会被释放。例如，o2=o1后，o2原本引用的内存将会被释放；&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Vector v = new Vector(10);
for (int i=1;i&amp;lt;100; i++)
{
    Object o=new Object();
    v.add(o);
    o=null;        
} 但是当一个对象没有被引用，但是在有向图中又是可达的，而且又没有用处，那么此时就发生了内存泄漏。o=null后，由于之前指向的内存可以从v可达，那么该部分内存将不可被释放，知道v=null或v的内存释放为止。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;32-thread的五个状态&quot;&gt;32. Thread的五个状态？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;创建状态&lt;/li&gt;
  &lt;li&gt;就绪状态&lt;/li&gt;
  &lt;li&gt;运行状态&lt;/li&gt;
  &lt;li&gt;阻塞状态&lt;/li&gt;
  &lt;li&gt;死亡状态&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;33-thread中run和start的区别&quot;&gt;33. Thread中run()和start()的区别？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;start()会立刻返回。调用start()方法，会创建一个线程，处于就绪状态，调用run()方法运行线程体，运行结束后，该线程结束；&lt;/li&gt;
  &lt;li&gt;run()就是一个方法，不会创建新线程。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;34-thread中run和start的区别&quot;&gt;34. Thread中run()和start()的区别？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;start()会立刻返回。调用start()方法，会创建一个线程，处于就绪状态，调用run()方法运行线程体，运行结束后，该线程结束；&lt;/li&gt;
  &lt;li&gt;run()就是一个方法，不会创建新线程。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;35-synchronized和javautilconcurrentlockslock的区别&quot;&gt;35. synchronized和java.util.concurrent.Locks.Lock的区别？&lt;/h3&gt;
&lt;p&gt;synchronized可以作用域函数，代码块，静态函数中。在函数中使用，以当前实例为锁，表示当前实例this在不同的线程中要互斥访问。在代码块中使用，要为synchronized增加参数，要以对象为锁，互斥访问，如果以this同步，和之前的以函数同步含义相同。也可以指定对象来同步。如果同步的是静态函数，函数调用的时候可能还没有实例，不能使用this，可以使用Foo.class作为锁。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Public synchronized void method(){
    //......
}
public void method()
{
    synchronized (this)
    {
        //......
    }
}
public void method(SomeObject so) {
    synchronized(so)
    {
        //......
    }
} + 所有对象都自动含有单一的锁。 JVM负责跟踪对象被加锁的次数。如果一个对象被解锁，其计数变为0。在任务（线程）第一次给对象加锁的时候，计数变为1。每当这个相同的任务（线程）在此对象上获得锁时，计数会递增。 只有首先获得锁的任务（线程）才能继续获取该对象上的多个锁。 + Lock有比Synchronized更精确的线程域城予以和更好的性能。Synchronized会自动释放锁，但是Lock一定要求程序员手工释放，并且必须在finally从句中释放。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;36-waitnotifynotifyall的作用&quot;&gt;36. wait()、notify()、notifyAll()的作用？&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。&lt;/li&gt;
  &lt;li&gt;如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去，然后处于等待状态。&lt;/li&gt;
  &lt;li&gt;如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。&lt;/li&gt;
  &lt;li&gt;如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Java面试中也会常常去问对于Java包源码的理解。下面按照根据Java包来介绍。
java.lang包，Java的基础包，编译时会自动导入，主要包含Java基类Object，包装类Boolean、Character、Byte、Short、Integer、Long、Float、Double，Enum，String、StringBuffer、StringBuilder，Thread，Process，Math，Throwable，Error，Exception。&lt;/p&gt;

&lt;h3 id=&quot;37-javalangobject&quot;&gt;37. java.lang.Object&lt;/h3&gt;
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public final native Class&amp;lt;?&amp;gt; getClass();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回一个对象的运行时类&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public native int hashCode();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回该对象的哈希码值，将对象在内存中的地址转成int并返回&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public boolean equals(Object obj) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return (this == obj);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1 c28&quot;&gt;指示某个其他对象是否与此对象“相等”。默认使用==来判断引用对象的地址。&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;protected native Object clone() throws CloneNotSupportedException;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;创建并返回此对象的一个副本。该对象要实现Cloneable接口，否则会抛出CloneNotSupportedException异常&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String toString() {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return getClass().getName() + &quot;@&quot; + Integer.toHexString(hashCode());&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c28 c1&quot;&gt;返回该对象的字符串表示，用hashCode()辅助实现&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public final native void notify();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c28 c1&quot;&gt;唤醒在此对象监视器上等待的单个线程&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public final native void notifyAll();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c28 c1&quot;&gt;唤醒在此对象监视器上等待的所有线程&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public final native void wait(long timeout) throws InterruptedException;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c28 c1&quot;&gt;导致当前的线程等待，直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法，或者超过指定的时间量&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public final void wait(long timeout, int nanos) throws InterruptedException&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c28 c1&quot;&gt;导致当前的线程等待，直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法，或者其他某个线程中断当前线程，或者已超过某个实际时间量&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public final void wait() throws InterruptedException {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; wait(0);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c28 c1&quot;&gt;导致当前的线程等待，直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c19&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;protected void finalize() throws Throwable { }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c25&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;当垃圾回收器确定不存在对该对象的更多引用时，由对象的垃圾回收器调用此方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;38-javalangmath&quot;&gt;38. java.lang.Math&lt;/h3&gt;
&lt;p&gt;Math是一个final类，不能被继承，提供了一套静态方法，实现数字计算的常见功能。&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static double ceil(double a) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return StrictMath.ceil(a); // default impl. delegates to StrictMath&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;向上取整&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static double floor(double a) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return StrictMath.floor(a); // default impl. delegates to StrictMath&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;向下取整&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static int round(float a) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if (a != 0x1.fffffep-2f) // greatest float value less than 0.5&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return (int)floor(a + 0.5f);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; else&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return 0;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;加0.5后向下取整。&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;ceil和floor返回的都是double，round返回的是int和long&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;39-javalangstring&quot;&gt;39. java.lang.String&lt;/h3&gt;
&lt;p&gt;String是一个final类，不可以被继承。Java里是没有运算符重载的，String+是StringBuffer的append()方法来实现的，如：String str = new String(“abc”);编译时等效于String str = new StringBuffer().append(“a”).append(“b”).append(“c”).toString();&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;类方法或变量&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;private final char value[];&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;String是封装的字符数组，而且被定义成final类型，一经赋值，不能修改&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;private int hash;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;默认为0，hashCode()的返回值&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String() {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.value = new char[0];&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String(String original) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.value = original.value;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.hash = original.hash;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String(char value[]) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.value = Arrays.copyOf(value, value.length);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String(char value[], int offset, int count);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;根据字符数组一部分对象创建字符串对象&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String(int[] codePoints, int offset, int count);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public int length() {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return value.length;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回字符数组长度&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public char charAt(int index) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; if ((index &amp;lt; 0) || (index &amp;gt;= value.length)) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; throw new StringIndexOutOfBoundsException(index);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return value[index];&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public boolean equals(Object anObject);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;根据字符数组逐个比较&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public int compareTo(String anotherString);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;相同返回0，当前字符串大，返回整数；小，返回负数&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public int indexOf(int ch) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return indexOf(ch, 0);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回字符ch首先出现的位置，没有返回-1&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String substring(int beginIndex, int endIndex);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;通过索引和value，创建一个新的String并返回。不包括endIndex所指的字符。&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2 c26 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Java 6之前，String类内部还有两个成员变量count和offset，当调用substring并返回时，同样还是引用的同一个字符数组，只是改变了count和offset。当你有一个非常长的字符串，而你只是想保留其中的一小部分，Java 6会一直保存着整个字符串，这样会造成性能问题。解决的方法是x = x.substring(i, j) + “”;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Java 7会帮你new一个新的字符串并只保留substring要用到的字符。&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String concat(String str);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;创建一个新的字符数组，将当前字符数组和str的都放到该数组中，用这个数组创建一个新的String并返回&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public String replace(char oldChar, char newChar);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;用newChar替换所有当前字符串中oldChar并创建一个新String返回&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;40-javalangstringbuilder&quot;&gt;40. java.lang.StringBuilder&lt;/h3&gt;
&lt;p&gt;StringBuilder是可变长字符串，是Java 5.0新增的，之前的是StringBuffer，相比于StringBuffer，StringBuilder是线程不安全的，但性能得到提升。这两个类的接口是保持一致的。Java 5.0之后，字符串+操作符采用StringBuilder实现&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;类变量或方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;char[] value;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;AbstractStringBuilder类中，可以返回capacity&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;int count;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;AbstractStringBuilder类中&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public StringBuilder(String str) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super(str.length() + 16);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; append(str);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;默认空的StringBuilder的capacity为16，以String初始化的capacity为String的长度加16&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp;public StringBuilder append(String str) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.append(str);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return this;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public StringBuilder delete(int start, int end) {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.delete(start, end);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return this;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public StringBuilder insert(int index, char[] str, int offset,&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; int len)&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; super.insert(index, str, offset, len);&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return this;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public int length() {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return count;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public int capacity() {&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return value.length;&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;capacity与length不同，length()个返回保存的字符串长度，capacity()返回申请的字符数组大小&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public void setLength(int newLength)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26 c5&quot;&gt;&lt;span class=&quot;c16 c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;41-包装类javalanginteger&quot;&gt;41. 包装类java.lang.Integer&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public final class Integer extends Number implements Comparable&amp;lt;Integer&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;类变量或方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static final int &amp;nbsp; MIN_VALUE = 0x80000000;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static final int &amp;nbsp; MAX_VALUE = 0x7fffffff;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static int parseInt(String s, int radix)&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; throws NumberFormatException&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;public static int reverse(int i)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;java.util包包含Date，容器类Collection，日历Calendar，随机数Random。其中，容器类是常见的面试内容。
集合类主要分类两大类，Collection和Map。Collection允许有重复对象。继承Collection的有List、Set、Vector、Stack接口。List要求有序，可以有重复元素。Set表示集合，无序，但不能有重复元素。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/javacollection_collection.png&quot; alt=&quot;Collection&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;42-javautilcollection&quot;&gt;42. java.util.Collection&lt;/h3&gt;
&lt;p&gt;Collection接口中常见操作&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;接口变量或方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;int size();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean isEmpty()&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;判断集合时候有任何元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean contains(Object o);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Iterator iterator()&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回迭代器，用来访问各个元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean add(E e);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;在最后增加元素e&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean remove(Object o);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;从当前List中删除第一次出现的元素o&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean containsAll(Collection c)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26 c5&quot;&gt;&lt;span class=&quot;c16 c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean addAll(Collection c)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26 c5&quot;&gt;&lt;span class=&quot;c16 c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;void clear();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;删除所有元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;void removeAll(Collection c)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c26 c5&quot;&gt;&lt;span class=&quot;c16 c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;void retainAll(Collection c)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;从集合中删除集合c中不包含的元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean equals(Object o);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;int hashCode();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Object[] toArray()&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回含集合所有元素的数组&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Object[] toArray(Object[] a)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回含集合所有元素的数组，返回的数组与参数a的类型相同&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;43-javautillist&quot;&gt;43. java.util.List&lt;/h3&gt;
&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;接口变量或方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;E get(int index);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2&quot;&gt;&lt;span class=&quot;c1&quot;&gt;获得指定位置的元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;E set(int index, E element);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;将指定位置的元素替换为element&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;List&amp;lt;E&amp;gt; subList(int fromIndex, int toIndex);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;不包括toIndex指定的元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;List有两个实现类，ArrayList和LinkedList，都是线程不安全的。LinkedList使用双向链表实现，保存头结点first和尾结点last。ArrayList使用动态数组实现，默认容量为10，每当容量不够的时候，就要重新申请新的内存，并将之前的内容复制进来。
int newCapacity = oldCapacity + (oldCapacity » 1);
申请的内存是之前的1.5倍。ArrayList可以随机访问，但是增加和删除消耗大，每次这样的操作都要移动其余的元素。而LinkedList按索引访问时要按照指针一次遍历，访问消耗大。
然而，如果每次插入和删除都是在队列尾部，ArrayList效率要高，因为不需要移动元素。
Vector同样实现的是List接口，是一个重量级列表，线程安全的。也是采用动态数组实现，每次动态分配的内存和ArrayList不同，默认是2倍。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int newCapacity = oldCapacity + ((capacityIncrement &amp;gt; 0) ?
                                         capacityIncrement : oldCapacity);&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;43-javautilset&quot;&gt;43. java.util.Set&lt;/h3&gt;
&lt;p&gt;Set不存在重复的元素，依靠equals()来检测独一性。与Collection有着完全一样的接口，根据值来确定。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;HashSet。实现了SortedSet接口。其对快速查找进行了优化。存入的元素必须定义hashCode()。&lt;/li&gt;
  &lt;li&gt;TreeSet。底层为树结构。可以从Set中提取有序的序列。元素必须实现Comparable接口。&lt;/li&gt;
  &lt;li&gt;LinkedHashSet。具有HashSet的查询速度，内部使用链表维护元素的顺序(插入的顺序)，使用迭代器遍历Set时，会按照插入的顺序显示。存入的元素必须实现hashCode()方法。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;44-javautiliterator&quot;&gt;44. java.util.Iterator&lt;/h3&gt;
&lt;p&gt;List可以通过get来遍历整个链表，但是对于其他的数据接口，这些遍历方法将不能通用。所以使用Iterator接口来封装遍历操作，使遍历所有的集合，都能有一个共同的接口。&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;接口变量或方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean hasNext();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;E next();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;将指定位置的元素替换为element&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;void remove();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;删除上一次iterator返回的元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;h3 id=&quot;45-javautilmap&quot;&gt;45. java.util.Map&lt;/h3&gt;
&lt;p&gt;Map中不允许有重复的key。&lt;/p&gt;

&lt;table cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;c21&quot;&gt;&lt;tbody&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;接口变量或方法&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;说明&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Object put(Object key, Object value);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Object remove(Object key);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;根据key删除元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;void putAll(Map m);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;将m中所有元素存入当前map&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;void clear();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Object get(Object key);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;根据key获得元素&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;int size();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean isEmpty();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean containsKey(Object key);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;检测是否有指定key的对象&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean containsValue(Object value);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;检测是否有指定value的对象&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;boolean equals(Object o);&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c8&quot;&gt;&lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Set&amp;lt;K&amp;gt; keySet();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回一个Set，里面保存map中所有的key&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Collection&amp;lt;V&amp;gt; values();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回一个Collection，保存map中的所有value&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;Set&amp;lt;Map.Entry&amp;lt;K, V&amp;gt;&amp;gt; entrySet();&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c2 c5&quot;&gt;&lt;span class=&quot;c1&quot;&gt;返回一个Set，保存所有mappings&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr class=&quot;c15&quot;&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;interface Entry&amp;lt;K,V&amp;gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td class=&quot;c0&quot;&gt;&lt;p class=&quot;c17&quot;&gt;&lt;span class=&quot;c1&quot;&gt;存储单个键/值对&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;HashMap。Java 2.0引入，是非同步的，但是提高了性能。基于散列表实现，解决哈希冲突使用链接法。
HashMap哈希的方法，是将key的hashCode与key的总数求余，得到哈希的索引。如果key是常量，则有可能相同，即使不同，与key的总数求余也有可能有哈希冲突。HashMap使用链接法解决冲突。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int hash = key.hashCode();
int index = hash % Entity[].length;
Entity[index] = value; ![Hash](https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/javacollection_hash.png)

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry&amp;lt;K,V&amp;gt; e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash &amp;amp;&amp;amp; ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
} + Hashtable是Dictionary的子类，是同步的。 + TreeMap。采用红黑树实现，可以获得字数subTree()。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;47-javaio&quot;&gt;47. java.io&lt;/h3&gt;
&lt;p&gt;Java IO采用Decorator模式，可以动态装配不同功能的Stream。IO体系分Input/Output和Reader/Writer两类，区别在于Reader/Writer读写文本时自动转换内码。
System.out是PrintStream的一个子类，PrintStream继承了FilterOutputStream类，FilterOutputStream类继承了OutputStream类。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/javacollection_inputstream.png&quot; alt=&quot;InputStream&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/javacollection_reader.png&quot; alt=&quot;Reader&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;48-servlet生命周期&quot;&gt;48. Servlet生命周期&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/javacollection_servlet.png&quot; alt=&quot;Servlet&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;装载Servlet。&lt;/li&gt;
  &lt;li&gt;创建Servlet实例。&lt;/li&gt;
  &lt;li&gt;调用Servlet的init()方法。Servlet只初始化一次。&lt;/li&gt;
  &lt;li&gt;当一个客户端请求到达后，创建一个request对象和一个response对象。&lt;/li&gt;
  &lt;li&gt;调用service()方法，处理数据，并将结果返回客户端。&lt;/li&gt;
  &lt;li&gt;当服务器不再需要Servlet时，调用destroy()方法。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;49-servlet的转发和重定向&quot;&gt;49. Servlet的转发和重定向&lt;/h3&gt;
&lt;p&gt;javax.servlet.RequestDispatcher的forward方法
javax.servlet.http.HttpServletResponse的sendRedirect(String)方法&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>电影《色|戒》最后王佳芝为什么没有吃下那颗为特工准备的自杀胶囊？</title>
   <link href="https://blog.cyeam.com/ctalk/2013/11/25/lust_caution"/>
   <updated>2013-11-25T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ctalk/2013/11/25/lust_caution</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;大学互相喜欢的男生，在需要训练房事的时候找一个爱召妓的同学去，训练这个难道一定要有丰富的经验么，向那个人请教一下，自己也可以来的。她恨他的懦弱。&lt;/li&gt;
  &lt;li&gt;六个大学生，没有什么能耐，居然敢去杀特务头头，她恨他们自己的无知。&lt;/li&gt;
  &lt;li&gt;她的领导，骗她说做完任务就送她去英国，但是最后又来了句要放长线，而且这期间她也肯定没有联系到父亲，也肯定能猜到她交给领导的信都没寄出去。这明摆着从头到尾骗她们。她恨领导的欺骗。&lt;/li&gt;
  &lt;li&gt;还有她的父亲，带他弟弟去英国，却不带她，最后还自己成家了，明显怕她过去影响自己今后的家庭。她的姑妈，卖了她最后唯一的房子，仅仅供她去读个大学。&lt;/li&gt;
  &lt;li&gt;也许就是命运吧，最后差一点就走了，被拦了下来。回想起自己的经历，她就像和这个世界结仇了似的，绝不会轻易的认输。她还想看看她既恨又愧疚的相识多年的同学，所以她没有吃药。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/lust_caution1.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://www.zhihu.com/question/21958104/answer/20311977&quot;&gt;知乎&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</content>
 </entry>
 
 <entry>
   <title>搜狗2014校园招聘笔试题</title>
   <link href="https://blog.cyeam.com/collection/2013/11/23/sogou"/>
   <updated>2013-11-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/11/23/sogou</id>
   <content type="html">&lt;h5 id=&quot;1-以下关于jstl和el表述正确的有cd&quot;&gt;1. 以下关于JSTL和EL表述正确的有：（CD）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. JSTL标签&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;c:if&amp;gt;&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;c:when&amp;gt;&lt;/code&gt;都拥有test属性&lt;/li&gt;
  &lt;li&gt;B. 使用JSTL标签&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;fmt:formatDate format=”yyyy年MM月dd日 HH点mm分ss秒”&lt;/code&gt;可以得到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;”2009年01月05日22点00分23秒”&lt;/code&gt;格式的时间值&lt;/li&gt;
  &lt;li&gt;C. 使用EL表达式可以输出request中的值&lt;/li&gt;
  &lt;li&gt;D. 使用EL表达式可以输出cookie中的值&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-一下程序执行后将有c个字节被写入到文件afiletxt中&quot;&gt;2. 一下程序执行后将有（C）个字节被写入到文件afile.txt中。&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;try {
	FileOutputStream fos = new FileOutputStream(“afile.txt”);
	DataOutputStream dos = new DataOutputStream(fos);
	dos.writeInt(3);
	dos.writeChar(1);
	dos.close();
} catch(IOException e) {}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 3&lt;/li&gt;
  &lt;li&gt;B. 5&lt;/li&gt;
  &lt;li&gt;C. 6&lt;/li&gt;
  &lt;li&gt;D. 不确定，与软硬件环境有关&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;char占两个字节，int占4个。&lt;/p&gt;

&lt;h5 id=&quot;3-下面哪些说法是正确的d&quot;&gt;3. 下面哪些说法是正确的（D）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 如果引用x和引用y表示2个不同的对象，那么x.equals(y)始终为false&lt;/li&gt;
  &lt;li&gt;B. 如果引用x和引用y表示2个不同的对象，那么(x.hashCode()==y.hashCode())始终为false&lt;/li&gt;
  &lt;li&gt;C. Object的hashCode方法被声明为final&lt;/li&gt;
  &lt;li&gt;D. 所有数组都有一个clone()方法&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;4-哪两个说法是正确的&quot;&gt;4. 哪两个说法是正确的？（）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class A {
	A() {}
} 
class B extends A {
} * A. B类的构造函数应该是public * B. B类的构造器应该是没有参数的 * C. B类的构造器应该调用this() * D. B类的构造器应该调用super()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;5-下面代码的输出为c&quot;&gt;5. 下面代码的输出为：（C）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String s = “hello world”;
String s1 = “Hello world”;
s.replace(“hello”, “Hello”);
System.out.println((s == s1) + “,” + s.equals(s1)); * A. true, true * B. false, true * C. false, false * D. true, false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;String的replace不会改变String的内容，只能返回一个被修改的字符串。&lt;/p&gt;

&lt;h5 id=&quot;6-下面那些类不是实现于collection接口c&quot;&gt;6. 下面那些类，不是实现于Collection接口（C）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. TreeSet&lt;/li&gt;
  &lt;li&gt;B. ArrayList&lt;/li&gt;
  &lt;li&gt;C. HashMap&lt;/li&gt;
  &lt;li&gt;D. Vector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ABD都是实现的List接口。HashMap实现Map接口。&lt;/p&gt;

&lt;h5 id=&quot;7-下列哪些不是jsp中的隐含变量a&quot;&gt;7. 下列哪些不是JSP中的隐含变量（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. session&lt;/li&gt;
  &lt;li&gt;B. servletContext&lt;/li&gt;
  &lt;li&gt;C. config&lt;/li&gt;
  &lt;li&gt;D. application&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;8-以下对java垃圾收集的描述中正确的是&quot;&gt;8. 以下对Java垃圾收集的描述中正确的是（）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 发生垃圾回收时，如果一个对象被垃圾收集器认定为不可达，那么它一定会被垃圾回收器回收&lt;/li&gt;
  &lt;li&gt;B. 一个对象的finalize()方法可能会被垃圾收集器调用多次&lt;/li&gt;
  &lt;li&gt;C. 发生垃圾回收时，无论回收之后的内存是否足够，都会回收掉都被SoftReference的内存&lt;/li&gt;
  &lt;li&gt;D. 一个对象是否被PhantomReference引用完全不会对它的生命周期造成影响&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;9-下列java类中哪个类不是线程安全的d&quot;&gt;9. 下列Java类中，哪个类不是线程安全的（D）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. StringBuffer&lt;/li&gt;
  &lt;li&gt;B. Hashtable&lt;/li&gt;
  &lt;li&gt;C. Vector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ABC都是线程安全的。&lt;/p&gt;

&lt;h5 id=&quot;10-下列java程序的输出是什么c&quot;&gt;10. 下列Java程序的输出是什么？（C）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Test{
	public static String output=””;
	public static void test(int i) {
		try {
			if (i == 0) {
				throw new Exception();
			}
			output += “a”;
		} catch (Exception e) {
			output += “b”;
			return ;
		} finally {
			output += “c”;
		}
		output += “d”;
	}
	public static void main(String args[]) {
		test(3);
		test(0);
		System.out.println(output);
	}
} * A. adb * B. acbc * C. acdbc * D. acdbcd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果try执行了，finally不论有没有Exception都会被执行，而且即使之前return了还是会执行完再return。&lt;/p&gt;

&lt;h5 id=&quot;11-对于变量stringbuilder-str--new-stringbuilder搜狗sogou下面描述正确的是c&quot;&gt;11. 对于变量StringBuilder str = new StringBuilder(“搜狗Sogou”)，下面描述正确的是（C）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. str.length()的值为11&lt;/li&gt;
  &lt;li&gt;B. str.capacity()的值为11&lt;/li&gt;
  &lt;li&gt;C. str.indexOf(“S”)的值为2&lt;/li&gt;
  &lt;li&gt;D. str.charAt(6)为g&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Java中char可以表示中文，且每个char占两个字节。该字符串长度为7个单位。catapcity是字符串长度加16，为23。S的下标为2（从0开始算）。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;#####搜狗大厦&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Sogou_Building.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;#####搜狗的茶水间里等待二面&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Sogou-Teapot.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;#####旁边的Google中国&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Google_Sogou.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>中科曙光2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/11/05/sugon"/>
   <updated>2013-11-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/11/05/sugon</id>
   <content type="html">&lt;p&gt;###一、单项选择题&lt;/p&gt;

&lt;h5 id=&quot;1-为了区分重载多态中同名不同方法要求a&quot;&gt;1. 为了区分重载多态中同名不同方法，要求（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 形式参数个数或者类型不同&lt;/li&gt;
  &lt;li&gt;B. 返回值类型不同&lt;/li&gt;
  &lt;li&gt;C. 调用时用类名或对象名做前缀&lt;/li&gt;
  &lt;li&gt;D. 形式参数名称不同&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-给定java代码如下运行时-会产生b&quot;&gt;2. 给定Java代码如下，运行时， 会产生（B）&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String s = null;
s.concat(“abc”);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. ArithmeticException&lt;/li&gt;
  &lt;li&gt;B. NullPointerException&lt;/li&gt;
  &lt;li&gt;C. IOExcepiton&lt;/li&gt;
  &lt;li&gt;D. EOFException&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-给定如下java代码编译时会在c出现错误&quot;&gt;3. 给定如下Java代码，编译时会在（C）出现错误。&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Sugon {
    static int arr[] = new int[10];
    public static void main(String args[]) {
        System.out.println(arr[1]);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 编译时将产生错误&lt;/li&gt;
  &lt;li&gt;B. 编译时正确，运行时将产生错误&lt;/li&gt;
  &lt;li&gt;C. 输出零&lt;/li&gt;
  &lt;li&gt;D. 输出空&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;4-表teacher包含以下列&quot;&gt;4. 表（TEACHER）包含以下列&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ID NUMBER(7) PK
SALARY NUMBER(7, 2)
SUBJECT_ID NUMBER(7)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;分别执行以下两个SQL语句：（B）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT ROUND(SUM(salary), -2) FROM teacher;
SELECT subject_id, ROUND(SUM(salary), -2) FROM teacher GROUP BY id 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 语句1将返回每个老师一个结果&lt;/li&gt;
  &lt;li&gt;B. 语句2将返回多个结果&lt;/li&gt;
  &lt;li&gt;C. 结果相同，显示不同&lt;/li&gt;
  &lt;li&gt;D. 将有一个句子产生错误&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;5-下面哪个是正确的类声明假设每一段文本都作为一个名称为gputestjava的文件的全部内容a&quot;&gt;5. 下面哪个是正确的类声明？假设每一段文本都作为一个名称为GPUTest.java的文件的全部内容？（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public class GPUTest {
      public int x = 0;
      public GPUTest(int x) {
          this.x = x;
      }
  } 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;B.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public class gpuTest {
      public int x = 0;
      public gpuTest(int x) {
          this.x = x;
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;C.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public class GPUTest extends MyBaseClass, MyOtherBaseClass {
      public int x = 0;
      public GPUTest(int xval) {
          x = xval;
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;D.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  protected class GPUTest {
      private int x = 0;
      private GPUTest (int xval) {
          x = xval;
      }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先说明了文件是GPUTest.java，那么，这个名称就要和该文件中public声明的类文件名相同，包括大小写，排除B。Java只有单继承，C错。class只能用public和空修饰，D错。&lt;/p&gt;

&lt;h5 id=&quot;6-如果只想得到1000个元素组成的序列中第5个元素之前的部分排列的序列d方法最快&quot;&gt;6. 如果只想得到1000个元素组成的序列中第5个元素之前的部分排列的序列（D）方法最快。&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 冒泡排序&lt;/li&gt;
  &lt;li&gt;B. 快速排序&lt;/li&gt;
  &lt;li&gt;C. Shell排序&lt;/li&gt;
  &lt;li&gt;D. 堆排序&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;这个题要求查询序列中最小的5个元素。希尔排序和快速排序，都是在整个序列排序结束之后，才能够实现。冒泡和堆排序能够优先获得最小的5个元素，而堆排序被叫做是高级的冒泡排序，交换次数少于冒泡排序。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h5 id=&quot;7在java中下列关于自动类型转换的说法正确的是a&quot;&gt;7.在Java中下列关于自动类型转换的说法正确的是（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 基本类型和String类型相加结果一定是字符串类型&lt;/li&gt;
  &lt;li&gt;B. char类型和int类型相加结果一定是字符&lt;/li&gt;
  &lt;li&gt;C. double类型可以自动转换为int&lt;/li&gt;
  &lt;li&gt;D. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;char + int + double + “”&lt;/code&gt;的结果一定是double&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;8-以下不属于thread类提供的线程控制方法是c&quot;&gt;8. 以下不属于Thread类提供的线程控制方法是（C）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. sleep()&lt;/li&gt;
  &lt;li&gt;B. interrupted&lt;/li&gt;
  &lt;li&gt;C. init()&lt;/li&gt;
  &lt;li&gt;D. yield()&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;9-执行下面plsql代码块被插入到表example中的行数为b&quot;&gt;9. 执行下面PL/SQL代码块，被插入到表EXAMPLE中的行数为（B）&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BEGIN
FOR i IN 1..6 LOOP
IF i=2 OR i=3 THEN null;
ELSE
INSERT INTO example(one) VALUES(i);
END IF;
ROLLBACK;
END LOOP;
COMMIT;
END; + A. 0 + B. 1 + C. 2 + D. 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;###二、填空题&lt;/p&gt;

&lt;h5 id=&quot;1-类变量在类中声明而不是在类的某个方法中声明它的作用域是整个当前类&quot;&gt;1. 类变量在类中声明，而不是在类的某个方法中声明，它的作用域是（整个当前类）。&lt;/h5&gt;

&lt;h5 id=&quot;2-一个完整的url地址由协议类型主机地址端口和文件四部分组成&quot;&gt;2. 一个完整的URL地址由（协议类型）、（主机地址）、端口和文件四部分组成。&lt;/h5&gt;

&lt;h5 id=&quot;3-java中线程4个状态初始状态运行状态阻塞状态阻塞状态&quot;&gt;3. Java中线程4个状态，初始状态、运行状态、阻塞状态、阻塞状态。&lt;/h5&gt;

&lt;h5 id=&quot;4-如下第4行执行后foo的值是basketball&quot;&gt;4. 如下第4行执行后foo的值是（basketball）。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String foo = “base”;
foo.substring(0, 3);
foo.concat(“ket”);
foo += “ball”;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;5-throwable类有两个字类error类和exception类&quot;&gt;5. Throwable类有两个字类：（Error）类和（Exception）类。&lt;/h5&gt;

&lt;h5 id=&quot;6-用thread-t--new-threadr创建一个新的线程的时候表达式r-instanceof-thread的值是false&quot;&gt;6. 用Thread t  new Thread(r)创建一个新的线程的时候，表达式r instanceof Thread的值是（false）。&lt;/h5&gt;

&lt;h5 id=&quot;7-下面选项中有2个可以放在程序中间没有任何问题请选出--来ce&quot;&gt;7. 下面选项中有2个可以放在程序中间没有任何问题，请选出  来（CE）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. private synchronized Object o;&lt;/li&gt;
  &lt;li&gt;B. void go() {synchronized() {}}&lt;/li&gt;
  &lt;li&gt;C. public synchronized void go() {}&lt;/li&gt;
  &lt;li&gt;D. private synchronized(this) void go() {}&lt;/li&gt;
  &lt;li&gt;E. void go() {synchronized(Object.class){}}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;synchronized&lt;/code&gt; 不能用来修饰成员变量，A错。如果用来修饰代码块，并且要指出修饰的对象时，不能为空，B错。synchronized修饰函数时不需要(this)。C正确，用来修饰函数，并且同步的是this对象。E正确，class literals synchronize，同步类的所有实例。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;###三、简答题&lt;/p&gt;

&lt;h5 id=&quot;1-sleep和wait的区别和联系&quot;&gt;1. sleep()和wait()的区别和联系？&lt;/h5&gt;
&lt;p&gt;sleep()不会将线程控制权交出去，线程会暂停指定时间。wait()会交出去，然后处于就绪状态。&lt;/p&gt;

&lt;h5 id=&quot;2-你认为在表上建立索引可以提高数据库系统的效率吗为什么&quot;&gt;2. 你认为在表上建立索引可以提高数据库系统的效率吗？为什么？&lt;/h5&gt;
&lt;p&gt;不一定。建立太多的索引将会影响更新和插入的速度，因为插入和更新之后还需要更新每个索引文件。对于一个经常需要更新和插入的表格，就没有必要为一个很少使用的where子句单独建立索引了，对于比较小的表，排序的开销不会很大，也没有必要建立另外的索引。&lt;/p&gt;

&lt;h5 id=&quot;3-简述synchronized和javautilconcurrentlockslock的异同&quot;&gt;3. 简述synchronized和java.util.concurrent.locks.Lock的异同？&lt;/h5&gt;

&lt;p&gt;Lock有比Synchronized更精确的线程域城予以和更好的性能。Synchronized会自动释放锁，但是Lock一定要求程序员手工释放，并且必须在finally从句中释放。&lt;/p&gt;

&lt;p&gt;###四、代码题&lt;/p&gt;

&lt;h5 id=&quot;1-有一对猫咪一公一母从出生后第三年起每年都生一对小猫假设一公一母孩子长到第三年后每个月又生一对猫假如猫都不死问每年的猫总对数为多少请使用java语言编写一个获取每年猫对数的程序方法&quot;&gt;1. 有一对猫咪，一公一母，从出生后第三年起每年都生一对小猫（假设一公一母），孩子长到第三年后每个月又生一对猫，假如猫都不死，问每年的猫总对数为多少？请使用Java语言编写一个获取每年猫对数的程序方法。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int func(int year) {
    if (year &amp;gt; 0 &amp;amp;&amp;amp; year &amp;lt;= 2) {
        return 2;
    }
    return func(year - 1) + func(year - 2);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>方正国际2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/30/founder"/>
   <updated>2013-10-30T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/30/founder</id>
   <content type="html">&lt;p&gt;###一、选择题&lt;/p&gt;

&lt;h5 id=&quot;1-下列表达式正确时d&quot;&gt;1. 下列表达式正确时（D）？&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte b = 128;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;B. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boolean flag = null;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;C. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;double f = 0.92395917;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;D. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long a = 9223372036854775808L;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;byte范围是-128~127，boolean只有true和false两个值，long范围是-9223372036854775808L~9223372036854775807L，占8个字节。&lt;/p&gt;

&lt;h5 id=&quot;2-下列正确的说法有c&quot;&gt;2. 下列正确的说法有：（C）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 声明抽象方法，大括号必须有。&lt;/li&gt;
  &lt;li&gt;B. 抽象类不能有static方法。&lt;/li&gt;
  &lt;li&gt;C. 抽象类除了有抽象方法外，还可以有普通方法。&lt;/li&gt;
  &lt;li&gt;D. static方法可以访问类的所有属性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;抽象方法如果有大括号，则表明有实现部分，错误。含有抽象方法的类叫做抽象类，所以可以有普通方法和static方法。static方法可以访问类的静态属性，非静态属性是在创建类的对象时才会初始化，所以没有实例化的对象属性不能被访问。&lt;/p&gt;

&lt;h5 id=&quot;3-下列不属于java标示符的事b&quot;&gt;3. 下列不属于Java标示符的事（B）：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_HelloWorld&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;B. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3HelloWorld&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;C. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$HelloWorld&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;D. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelloWorld&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不光是Java，还包括C语言，标示符都不能以数字开头。 &lt;br /&gt;
https://www.zhihu.com/question/20150792&lt;/p&gt;

&lt;h5 id=&quot;4-属于java语言中基本数据类型的事bc&quot;&gt;4. 属于Java语言中基本数据类型的事（BC）：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. var&lt;/li&gt;
  &lt;li&gt;B. char&lt;/li&gt;
  &lt;li&gt;C. long&lt;/li&gt;
  &lt;li&gt;D. String&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;String属于java.lang包。&lt;/p&gt;

&lt;h5 id=&quot;5-下列代码运行结果是c&quot;&gt;5. 下列代码运行结果是（C）：&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int x = 0;
int i = 1;
do {
    if ((i % 5) == 0) {
        i++;
        continue;
    }
    x += ++i;
} while (x &amp;lt; 100);
System.out.println(&quot;x=&quot; + x)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. x=10&lt;/li&gt;
  &lt;li&gt;B. x=101&lt;/li&gt;
  &lt;li&gt;C. x=102&lt;/li&gt;
  &lt;li&gt;D. x=103&lt;/li&gt;
  &lt;li&gt;E. x=104&lt;/li&gt;
  &lt;li&gt;F. x=105&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;6-下列代码运行结果是c&quot;&gt;6. 下列代码运行结果是（C）：&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Parent{
    public int x;
    public int y;
    public Parent(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void increaseX(int x) {
        this.x = getX() + x;
    }
    public int getX() {
        return x;
    }
    public void increaseY(int y) {
        this.y = getY() + y;
    }
    public int getY() {
        return y;
    }
}
public class Child extends Parent {
    private int x;
    private int y;
    public Child(int x, int y) {
        super(x, y);
        this.y = y + 250;
        this.x = x + 150;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
}
Child child = new new Child(50, 50);
child.increaseX(100);
child.increaseY(100);
System.out.println(&quot;x=&quot; + child.getX() + &quot; and y=&quot; + child.getY());
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. x=200 and y=200&lt;/li&gt;
  &lt;li&gt;B. x=250 and y=350&lt;/li&gt;
  &lt;li&gt;C. x=200 and y=300&lt;/li&gt;
  &lt;li&gt;D. 编译错误&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;重写具体又可以分为隐藏和覆盖，父类实例变量和静态变量能被子类同名变量隐藏，父类静态方法被子类同名静态方法隐藏，父类实例方法被子类同名实例方法覆盖。被隐藏的变量和方法是存在的，可以通过该类实例去访问。此题Child继承Parent，Child类中重写了变量x、y，方法getX()、getY()。所以，Parent的变量x和y会被隐藏，getX()和getY()会被覆盖，increaseX()和increaseY()会被子类继承。此题还要考虑子类和父类构造函数的调用顺序。&lt;/p&gt;

&lt;p&gt;Child child = new new Child(50, 50);此行之行时，会先调用父类的super(x, y)，父类的x和y被修改，接着初始化Child的x和y为0。然后，执行this.y = y + 250;this.x = x + 150;修改子类的x和y，此时，child的x和y是200和300。后来的child.increaseX(100);child.increaseY(100);方法都是使用的父类的函数，修改的也是父类的x和y，对于child的不影响。所以结果是C。&lt;/p&gt;

&lt;h5 id=&quot;7-下列选项可以在a的子类中使用的是ac&quot;&gt;7. 下列选项可以在A的子类中使用的是（AC）：&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class A {
    protected int method(int a, int b) {
        return 0;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. public int method(int a, int b) {return 0;}&lt;/li&gt;
  &lt;li&gt;B. private int method(int a, int b) {return 0;}&lt;/li&gt;
  &lt;li&gt;C. private int method(int a, short b) {return a + b;}&lt;/li&gt;
  &lt;li&gt;D. public short method(int a, short b) {return a + b;}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A为重写。B为重写，重写的方法不能比原方法有更低的访问权限。C参数表不同，为重载，对于访问修饰符没有要求。D的参数表不同，为重写，但是int a和short b相加，得到的是int，题目中没有进行类型转换，错误。&lt;/p&gt;

&lt;h5 id=&quot;8-关于以下代码说明正确的是c&quot;&gt;8. 关于以下代码说明正确的是（C）：&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class StaticTest {
    public static int x = 1;
    public void increaseX(int increasement) {
        x += increasement;
    }
    public StaticTest(int original) {
        increaseX(original);
        if (x &amp;gt; 10) {
            x = 1;
        }
    }
}
StaticTest obj1 = new StaticTest(5);
obj1.increaseX(2);
StaticTest obj2 = new StaticTest(3);
obj2.x += 4;
StaticTest obj3 = new StaticTest(1);
StaticTest.x += 3;
System.out.println(&quot;x=&quot; + obj1.x);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 第5行不能编译通过，因为引用了私有静态变量&lt;/li&gt;
  &lt;li&gt;B. 第17行不能编译通过，因为x是私有静态变量&lt;/li&gt;
  &lt;li&gt;C. 能编译通过，结果为9&lt;/li&gt;
  &lt;li&gt;D. 能编译通过，结果为8&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;x为static变量，被所有对象所共享。StaticTest obj1 = new StaticTest(5);x加5变为6。obj1.increaseX(2);x加2成为8。StaticTest obj2 = new StaticTest(3);x加3成为11，大于10，重新赋为1。obj2.x += 4;加4成为5。StaticTest obj3 = new StaticTest(1);加1成为6。StaticTest.x += 3;加3成为9。&lt;/p&gt;

&lt;h5 id=&quot;9-下列选项中不属于jdbc基本功能的是&quot;&gt;9. 下列选项中不属于JDBC基本功能的是：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 与数据库建立连接&lt;/li&gt;
  &lt;li&gt;B. 提交sql语句&lt;/li&gt;
  &lt;li&gt;C. 处理查询结果&lt;/li&gt;
  &lt;li&gt;D. 执行Oracle存储过程&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;10-page指令用于定义jsp文件中的全局属性下列关于该指令用法的描述不正确的是&quot;&gt;10. Page指令用于定义JSP文件中的全局属性，下列关于该指令用法的描述不正确的是：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. &amp;lt;%@ page %&amp;gt;作用于整个JSP页面&lt;/li&gt;
  &lt;li&gt;B. 可以在一个页面中使用多个&amp;lt;%@ page %&amp;gt;指令&lt;/li&gt;
  &lt;li&gt;C. 为增强程序的可读性，建议将&amp;lt;%@ page %&amp;gt;指令放在JSP文件的开头，但不是必须的&lt;/li&gt;
  &lt;li&gt;D. &amp;lt;%@ page %&amp;gt;指令中的属性只能出现一次&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;11-关于jquery是由哪些语言编写的a&quot;&gt;11. 关于jQuery，是由哪些语言编写的（A）：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. JavaScript&lt;/li&gt;
  &lt;li&gt;B. HTML&lt;/li&gt;
  &lt;li&gt;C. Java&lt;/li&gt;
  &lt;li&gt;D. CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;12-在css样式中以下哪些选项属于选择符的分类abd&quot;&gt;12. 在CSS样式中以下哪些选项属于选择符的分类（ABD）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. HTML选择符&lt;/li&gt;
  &lt;li&gt;B. Class选择符&lt;/li&gt;
  &lt;li&gt;C. #选择符&lt;/li&gt;
  &lt;li&gt;D. ID选择符&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此外，还包括通用元素选择符&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;###二、填空题&lt;/p&gt;

&lt;h5 id=&quot;1-java的基本数据类型中long类型占用8字节空间对long类型的赋值不是线程安全的&quot;&gt;1. Java的基本数据类型中，long类型占用（8）字节空间。对long类型的赋值（不是）线程安全的。&lt;/h5&gt;

&lt;p&gt;32位或更少位数是原子性的，按32位为基本操作单位。所以Java中long和double不是原子性的。需要使用volatile保证原子性。&lt;/p&gt;

&lt;h5 id=&quot;2-请写出java语言的三个访问权限修饰符并作简单说明&quot;&gt;2. 请写出Java语言的三个访问权限修饰符，并作简单说明：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;private：当前类访问权限，只能被当前类的实例自己内部调用。&lt;/li&gt;
  &lt;li&gt;protected：继承类修饰，能被子类访问。&lt;/li&gt;
  &lt;li&gt;friendly：包访问权限。&lt;/li&gt;
  &lt;li&gt;public：全局访问。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-请写出一下代码段的输出结果hello-world&quot;&gt;3. 请写出一下代码段的输出结果：（Hello world）&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;StringBuilder word = new StringBuilder(&quot;Hello&quot;);
getWho(word);
System.out.println(word.toString());
public static void getWho(StringBuilder word) {
    word = word.append(&quot; word&quot;);
} StringBuilder的内容是可以更改的。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;4-请填空列举5个您所知道的struts2struts-223之前版本的result-typedispatcherredirectchainfreemarker&quot;&gt;4. 请填空列举5个您所知道的struts2（struts-2.2.3之前版本）的Result Type：（dispatcher、redirect、chain、freemarker）。&lt;/h5&gt;

&lt;h5 id=&quot;5-jaxbjava-architecture-for-xml-binding是一个业界的标准是一项可以根据xml-schema产生java类的技术它通过jdk的annotation在类元素与xml元素之间进行映射其中xmlrootelementannotation用来将java类型或枚举类型映射到xml元素xmlelementannotation用来将java类的一个属性映射到与属性同名的一个xml元素xmlattributeannotation用来将java类的一个属性映射到与属性同名的一个xml属性它通过xmlaccessorderannotation来定类属性对应元素在xml节点中的前后顺序关系&quot;&gt;5. JAXB（Java Architecture for XML Binding）是一个业界的标准，是一项可以根据XML Schema产生Java类的技术。它通过JDK的Annotation在类元素与XML元素之间进行映射。其中，（@XmlRootElement）Annotation用来将Java类型或枚举类型映射到XML元素，（@XmlElement）Annotation用来将Java类的一个属性映射到与属性同名的一个XML元素，（@XmlAttribute）Annotation用来将Java类的一个属性映射到与属性同名的一个XML属性。它通过（@XmlAccessOrder）Annotation来定类属性对应元素在XML节点中的前后顺序关系。&lt;/h5&gt;

&lt;h5 id=&quot;6-ecmascript的5种原始类型是undefinedbooleannumberstringobject-javascript的typeof运算符用来判断一个值是否在某种类型的范围内&quot;&gt;6. ECMAScript的5种原始类型是：（undefined、boolean、number、string、object）。 JavaScript的typeof运算符用来判断一个值是否在某种类型的范围内。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var s1;
var s2 = null;
var type1 = (typeof s1);
var type2 = (typeof s2); 变量type1和type2分别指向（undefined）类型和（object）类型。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;7-请写出3个hibernate的数据查询方式以及它们的使用场景&quot;&gt;7. 请写出3个Hibernate的数据查询方式以及它们的使用场景：&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;query接口&lt;/li&gt;
  &lt;li&gt;Criteria接口&lt;/li&gt;
  &lt;li&gt;SQLQuery接口&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;###三、简答题&lt;/p&gt;

&lt;h5 id=&quot;1-java中stringstringbufferstringbuilder中之间的区别是什么&quot;&gt;1. Java中String、StringBuffer、StringBuilder中之间的区别是什么？&lt;/h5&gt;
&lt;p&gt;String是final类，不允许继承。内部使用final数组实现，一经初始化，不能够再修改。如果要修改一个String对象，只能重新创建一个新的String并使其引用这个对象。StringBuffer和StringBuilder是可变的String。通过封装String实现。其中StringBuilder是Java 5.0之后的较新版本，且不支持多线程。&lt;/p&gt;

&lt;h5 id=&quot;2-java中为什么需要同步列出你所知道的几种同步方法&quot;&gt;2. Java中为什么需要同步？列出你所知道的几种同步方法。&lt;/h5&gt;
&lt;p&gt;在多线程环境下，多个线程会并发访问共享的一个内存地址空间。由于多线程切换的不确定性，运行不能保证每次都按相同的顺序执行，所以就要保证这样运行的情况下与单线程执行的结果是一致的。Java可以使用sychronized关键字同步代码块或者函数，volatile关键字保证long和double类型的原子性操作，wait()和notify()方法保证同步执行。&lt;/p&gt;

&lt;h5 id=&quot;3-无论系统规模有多大web缓存都有助于优化性能和节省带宽web缓存主要有哪两种方式我们常见的memcachd属于哪一种方式它能提高系统性能的原因是什么如果我们要自行设计一个web缓存系统需要关注哪些方面&quot;&gt;3. 无论系统规模有多大，Web缓存都有助于优化性能和节省带宽。Web缓存主要有哪两种方式？我们常见的MemcachD属于哪一种方式？它能提高系统性能的原因是什么？如果我们要自行设计一个Web缓存系统，需要关注哪些方面？&lt;/h5&gt;
&lt;p&gt;Web缓存主要包括文件缓存和分布式缓存。MemcacheD是分布式缓存。MemcacheD使用物理内存作为缓存区，还使用了高校的基于key和value的hash算法来设计存储数据结构。淘汰机制基于LRU算法，所以提高了系统性能。&lt;/p&gt;

&lt;p&gt;###四、编程题&lt;/p&gt;

&lt;h5 id=&quot;1-请使用java语言定义一个具备insertremovefind功能的二叉查找树binarysearchtree摸板类实现以下接口&quot;&gt;1. 请使用Java语言定义一个具备insert、remove、find功能的二叉查找树BinarySearchTree摸板类，实现以下接口：&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface BinarySearchTree&amp;lt;T&amp;gt; {
    void add(T value);
    void remove();
    T find(T value);
    int size();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为了实现对象之间的比较，在BinarySearchTree类的构造方法中，会传入一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&amp;lt;T&amp;gt;&lt;/code&gt;;的接口实例。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&amp;lt;T&amp;gt;&lt;/code&gt;的接口定义如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface Comparable&amp;lt;T&amp;gt; {
    public int compareTo(T o1, T o2);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当o1小于o2时，compareTo方法返回-1，如果o1等于o2，compareTo放回0。如果o1大于o2，compareTo方法返回-1。
在实现BinarySearchTree类时，不需要考虑实现Comparable方法。可以假定调用者会主动传参。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Value implements Comparable &amp;lt;Value&amp;gt;{
    private int value;
    
    public Value(int value) {
        this.value = value;
    }
    
    public int compareTo(Value v) {
        return this.value - v.value;  
    }  
}
public class BinarySearchTree&amp;lt;T extends Comparable&amp;lt;? super T&amp;gt;&amp;gt; {
    private static class BinaryNode&amp;lt;T&amp;gt; {
        BinaryNode(T element) {
            this(element, null, null);
        }
        BinaryNode(T element, BinaryNode&amp;lt;T&amp;gt; left, BinaryNode&amp;lt;T&amp;gt; right) {
            this.element = element;
            this.left = left;
            this.right = right;
        }
        T element;
        BinaryNode&amp;lt;T&amp;gt; left;
        BinaryNode&amp;lt;T&amp;gt; right;
    }
    
    private BinaryNode&amp;lt;T&amp;gt; root;
    private int count;
    
    public BinarySearchTree() {
        root = null;
        count = 0;
    }
    public void add(T value) {
        add(value, root);
    }
    public BinaryNode&amp;lt;T&amp;gt; add(T value, BinaryNode&amp;lt;T&amp;gt; t) {
        if (t == null) {
            count++;
            return new BinaryNode&amp;lt;T&amp;gt;(value) ;
        }
        
        int compareResult = value.compareTo(this.root.element);
        if (compareResult &amp;gt; 0) {
            t.right = add(value, t.right);
        }
        else if (compareResult &amp;lt; 0) {
            t.left = add(value, t.left);
        }
        return t;
    }
    public void remove(T value) {
        remove(value, root);
        count--;
    }
    public BinaryNode&amp;lt;T&amp;gt; remove(T value, BinaryNode&amp;lt;T&amp;gt; t) {
        if (t == null) {
            return t;
        }
        
        int compareResult = value.compareTo(this.root.element);
        if (compareResult &amp;gt; 0) {
            t.right = remove(value, t.right);
        }
        else if (compareResult &amp;lt; 0) {
            t.left = remove(value, t.left);
        }
        else if (t.left != null &amp;amp;&amp;amp; t.right != null) {
            t.element = findMin(t.right).element;
            remove(t.element, t.right);
        }
        else {
            t = (t.right != null) ? t.left : t.right;
            count--;
        }
        return t;
    }
    public boolean find(T value) {
        return find(value, root);
    }
    public boolean find(T value, BinaryNode&amp;lt;T&amp;gt; t) {
        if (t == null)
            return false;
        
        int compareResult = value.compareTo(this.root.element);
        if (compareResult &amp;gt; 0) {
            return find(value, this.root.right);
        }
        else if (compareResult &amp;lt; 0) {
            return find(value, this.root.left);
        }
        else {
            return true;
        }
    }
    public BinaryNode&amp;lt;T&amp;gt; findMin(BinaryNode&amp;lt;T&amp;gt; t) {
        if (t == null) {
            return null;
        }
        if (t.left == null) {
            return t;
        }
        return findMin(t.left);
    }
    public int size() {
        return count;
    }
    
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;2-有两个数组int-a-b这两个数组都已经被进行了数据初始化数据都不重复且都没有排序请设计一种算法利用问题1实现的接口将这两个数组合并同时合并后的数据也要保证都是按照数值从小到大排列同时请分析算法中元素之间比较的复杂度&quot;&gt;2. 有两个数组int a[], b[]。这两个数组都已经被进行了数据初始化，数据都不重复，且都没有排序。请设计一种算法，利用【问题1】实现的接口，将这两个数组合并，同时合并后的数据也要保证都是按照数值从小到大排列。同时请分析算法中元素之间比较的复杂度。&lt;/h5&gt;
&lt;p&gt;用两个数组创建一颗二叉查找树即可。比较的复杂度是O(logn)。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>神州航天软件2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/25/bsast"/>
   <updated>2013-10-25T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/25/bsast</id>
   <content type="html">&lt;h5 id=&quot;1-string-s--new-stringxyz创建了几个string-object&quot;&gt;1. String s = new String(“xyz”);创建了几个String Object？&lt;/h5&gt;
&lt;p&gt;创建了一个String Object。涉及到两个String Object，一个是字符串字面量”xyz”所对应的、驻留（intern）在一个全局共享的字符串常量池中的实例，另一个是通过new String(String)创建并初始化的、内容与”xyz”相同的实例。&lt;/p&gt;

&lt;h5 id=&quot;2-有如下定义&quot;&gt;2. 有如下定义：&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;String str = new String(&quot;hello world&quot;);
String[] arr = {&quot;hello&quot;, &quot;world&quot;}; 请写出如何获取str和arr的长度？

str.length();
arr.length;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;3-你所知道的集合类有哪些主要方法&quot;&gt;3. 你所知道的集合类有哪些？主要方法？&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/java_collection.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ArrayList、LinkedList、Vector、HashMap、HashSet。主要有size()、add()、insert()、remove()、get()、set()、put()。&lt;/p&gt;

&lt;h5 id=&quot;4-运行时异常与一般异常有何异同&quot;&gt;4. 运行时异常与一般异常有何异同？&lt;/h5&gt;
&lt;p&gt;Java提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常，以及SQL异常都是这种异常。对于这种异常，JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以，面对这种异常不管我们是否愿意，只能自己去写一大堆catch块去处理可能的异常。
但是另外一种异常：runtime exception，也称运行时异常，我们可以不处理。当出现这样的异常时，总是由虚拟机接管。比如：我们从来没有人去处理过NullPointerException异常，它就是运行时异常，并且这种异常还是最常见的异常之一。&lt;/p&gt;

&lt;h5 id=&quot;5-error和exception有什么区别&quot;&gt;5. error和exception有什么区别？&lt;/h5&gt;
&lt;p&gt;都继承自Throwable。Error指出现了错误，且不能恢复，只能在系统级处理。Exception是指出现了异常，有可能能处理，可以在应用程序级处理。&lt;/p&gt;

&lt;h5 id=&quot;6-下列异常的含义并举出合适会产生该异常异常出现的场景&quot;&gt;6. 下列异常的含义，并举出合适会产生该异常（异常出现的场景）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;NullPointerException; 空指针异常，访问指向null的对象的操作或方法。&lt;/li&gt;
  &lt;li&gt;IndexOutOfBoundsException; 下标溢出异常，访问的下标超过了范围。&lt;/li&gt;
  &lt;li&gt;NoSuchElementException; 使用nextElement()访问枚举类型时。&lt;/li&gt;
  &lt;li&gt;ClassCastException; 类型转换异常，将父类强制转换为子类时会发生。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;7-jsp中的静态包含和动态包含有什么区别&quot;&gt;7. JSP中的静态包含和动态包含有什么区别？&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;静态包含：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;%@ include file=&quot;被包含文件&quot; %&amp;gt;&lt;/code&gt;；&lt;/li&gt;
  &lt;li&gt;动态包含：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;jsp:include page=&quot;被包含文件&quot; /&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;8-在jsp中requestresponsesession都有什么功能什么情况下使用&quot;&gt;8. 在JSP中，Request、Response、Session都有什么功能？什么情况下使用？&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;Request代表客户端的请求信息，用于接受通过HTTP协议传送到服务器的数据。作用域为一次请求。&lt;/li&gt;
  &lt;li&gt;Response代表服务器对客户端的相应，只在JSP页面内有效。&lt;/li&gt;
  &lt;li&gt;Session是指用户打开浏览器连接到服务器，到关闭浏览器离开服务器这段时间，是一个会话。无论用户打开多少个网页，服务器都能知道这是同一个用户。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;9-写出单件模式&quot;&gt;9. 写出单件模式。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Singleton {
    private static volatile Singleton INSTANCE = null;

    public Singleton() {

    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    return new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>乐视2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/24/letv"/>
   <updated>2013-10-24T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/24/letv</id>
   <content type="html">&lt;p&gt;###试题一&lt;/p&gt;

&lt;h5 id=&quot;1-程序进程线程的区别进程的通信方式有哪些&quot;&gt;1. 程序，进程，线程的区别？进程的通信方式有哪些？&lt;/h5&gt;
&lt;p&gt;程序是用你适当形式描述的算法，是存储在硬盘上的静态的文件。
进程是某种类型的一个活动，是动态的，它有程序、输入、输出以及状态，如果一个程序运行了两次，算过两个进程。
线程是轻量级进程，进程间可以共享地址空间，可以更容易创建和撤销，如果存在大量计算和大量I/O处理，多线程可以使这些过程重叠进行，加快应用程序执行速度。&lt;/p&gt;

&lt;h5 id=&quot;2-删除一个单项链表的最中间的元素要求时间尽可能短代码和思路&quot;&gt;2. 删除一个单项链表的最中间的元素，要求时间尽可能短（代码和思路）。&lt;/h5&gt;

&lt;h5 id=&quot;3-输入一个整形数组数组有正数也有负数数组中连续的一个或多个整数组组成一个子数组每个子数组都有一个和求所有子数组的和的最大值要求时间复杂度为on例如输入的数组为1-2310-472-5和最大的子数组为310-472因此输出为该子数组的和18&quot;&gt;3. 输入一个整形数组，数组有正数也有负数数组中连续的一个或多个整数组组成一个子数组，每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。例如输入的数组为1，-2，3，10，-4，7，2，-5，和最大的子数组为3，10，-4，7，2，因此输出为该子数组的和18。&lt;/h5&gt;

&lt;hr /&gt;

&lt;p&gt;###试题二&lt;/p&gt;

&lt;h5 id=&quot;1-什么是死锁产生的原因是什么必要条件是什么如何预防和解除&quot;&gt;1. 什么是死锁？产生的原因是什么？必要条件是什么？如何预防和解除？&lt;/h5&gt;
&lt;p&gt;如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件，那么，该进程集合就是死锁的。&lt;/p&gt;

&lt;p&gt;######四个必要条件：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;互斥条件。资源每次只能被一个进程访问。&lt;/li&gt;
  &lt;li&gt;占有和等待条件。已经得到某个资源的进程还可以再请求新资源。&lt;/li&gt;
  &lt;li&gt;不可抢占条件。已经分配给一个进程的资源不能被强制地抢占。&lt;/li&gt;
  &lt;li&gt;环路等待条件。每个进程都在等待着下一个进程所占有的资源。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;######死锁的预防：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;互斥。一切都使用假脱机技术。&lt;/li&gt;
  &lt;li&gt;占有和等待。在开始就请求全部资源。&lt;/li&gt;
  &lt;li&gt;不可抢占。抢占资源。&lt;/li&gt;
  &lt;li&gt;环路等待。对资源按序编号。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-假设有一个单向链表请输出该链表中倒数第n个节点&quot;&gt;2. 假设有一个单向链表，请输出该链表中倒数第n个节点。&lt;/h5&gt;

&lt;h5 id=&quot;3-给定数组a大小为n数组元素为1到n的数字不过有的数字出现了多次有的数字没有出现请给出算法和程序统计哪些数字没有出现哪些数字出现了多少次能够在on的时间复杂度o1的空间复杂度要求下完成么&quot;&gt;3. 给定数组A，大小为n，数组元素为1到n的数字，不过有的数字出现了多次，有的数字没有出现。请给出算法和程序，统计哪些数字没有出现，哪些数字出现了多少次。能够在O(n)的时间复杂度，O(1)的空间复杂度要求下完成么？&lt;/h5&gt;

</content>
 </entry>
 
 <entry>
   <title>趣游2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/24/gamewave"/>
   <updated>2013-10-24T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/24/gamewave</id>
   <content type="html">&lt;p&gt;###一、概念题&lt;/p&gt;

&lt;h5 id=&quot;1-引用和指针的区别&quot;&gt;1. 引用和指针的区别？&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int a = 1;
int&amp;amp; b = a;
cout &amp;lt;&amp;lt; &amp;amp;a &amp;lt;&amp;lt; endl;
cout &amp;lt;&amp;lt; &amp;amp;b &amp;lt;&amp;lt; endl; // same address
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;引用访问一个变量是直接访问，而指针是间接访问。&lt;/li&gt;
  &lt;li&gt;引用是一个变量的别名，本身不单独分配自己的内存空间，而指针有自己的内存空间。 + 不存在空引用，引用必须初始化。&lt;/li&gt;
  &lt;li&gt;引用在开始的时候就绑定到了一个内存空间(开始必须赋初值)，所以他只能是这个内存空间的名字，而不能改成其他的，当然可以改变这个内存空间的值。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-new-delete与malloc-free的联系与区别&quot;&gt;2. new delete与malloc free的联系与区别？&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;malloc/free是C/C++语言的标准库函数，new/delete是C++的运算符。&lt;/li&gt;
  &lt;li&gt;maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数， 对象消亡之前要自动执行析构函数。&lt;/li&gt;
  &lt;li&gt;new 内置了sizeof、类型转换和类型安全检查功能。&lt;/li&gt;
  &lt;li&gt;在用delete 释放对象数组时，留意不要丢了符号‘[]’。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-struct和class的联系和区别&quot;&gt;3. struct和class的联系和区别？&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct Shape {
    int id;

    Shape() {
        cout &amp;lt;&amp;lt; &quot;Constructor of Shape&quot; &amp;lt;&amp;lt; endl;
    }

    ~Shape() {
        cout &amp;lt;&amp;lt; &quot;Destructor of Shape&quot; &amp;lt;&amp;lt; endl;
    }
};

struct Circle : Shape {

    Circle() {
        cout &amp;lt;&amp;lt; &quot;Constructor of Circle&quot; &amp;lt;&amp;lt; endl;
    }

    ~Circle() {
        cout &amp;lt;&amp;lt; &quot;Destructor of Circle&quot; &amp;lt;&amp;lt; endl;
    }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;访问权限不同。struct 的默认访问权限是public，class  的默认访问权限是private。如果一个类中只有get/set函数，而没有其他处理数据的函数，使用struct就可以了。&lt;/li&gt;
  &lt;li&gt;struct默认是public继承，class默认是private继承。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;4-什么是面向对象描述它的基本特征&quot;&gt;4. 什么是面向对象，描述它的基本特征。&lt;/h5&gt;
&lt;p&gt;封装、继承、多态。&lt;/p&gt;

&lt;h5 id=&quot;5-重载overload和重写override的区别&quot;&gt;5. 重载（overload）和重写（override）的区别？&lt;/h5&gt;

&lt;h5 id=&quot;6-线程安全的含义&quot;&gt;6. “线程安全”的含义。&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;程序中的每一条语句都是原子操作，则是线程安全的。&lt;/li&gt;
  &lt;li&gt;实例变量是在堆中分配的,并不被属于该实例的所有线程共享，只有一个线程独享，是线程安全的。&lt;/li&gt;
  &lt;li&gt;局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以是线程安全的.&lt;/li&gt;
  &lt;li&gt;静态类不用被实例化,就可直接使用,也不是线程安全的.&lt;/li&gt;
  &lt;li&gt;单线程方式是线程安全的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;7-用c-socket函数描述cs架构的程序两端网络连接建立过程&quot;&gt;7. 用c socket函数描述C/S架构的程序，两端网络连接建立过程。&lt;/h5&gt;

&lt;p&gt;###二、程序编写题&lt;/p&gt;

&lt;h5 id=&quot;1-写一个在一个字符串n中寻找一个子串m第一个位置的函数不能用strlen&quot;&gt;1. 写一个在一个字符串（n）中寻找一个子串（m）第一个位置的函数（不能用strlen）。&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;请描述你解决这个问题的思路；&lt;/li&gt;
  &lt;li&gt;请给出函数代码，以及算法的复杂度。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/08/go_index&quot;&gt;解析&lt;/a&gt;&lt;/p&gt;

&lt;h5 id=&quot;2-链表题一个链表的节点结构&quot;&gt;2. 链表题：一个链表的节点结构&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct Node
{
    int data;
    Node* next;
}; + a. 已知链表的头结点head，写一个函数把这个链表逆序。 + b. 已知两个链表head1和head2各自有序，请把它们合并成一个链表依然有序（保留所有结点，即便大小相同）。 + c. 已知两个链表head1和head2各自有序，请把它们合并成一个链表依然有序，这次要用递归的方法。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;3-用数组来实现一个栈并用面向对象的方法进行封装&quot;&gt;3. 用数组来实现一个栈，并用面向对象的方法进行封装。&lt;/h5&gt;

&lt;p&gt;###三、附加题&lt;/p&gt;

&lt;h5 id=&quot;1-两个数相乘位数没有限制请写一个程序接受键盘输入并输出结果&quot;&gt;1. 两个数相乘，位数没有限制，请写一个程序接受键盘输入并输出结果。&lt;/h5&gt;

&lt;p&gt;解析&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/15/go_largenumberx&quot;&gt;《大数乘法》&lt;/a&gt;。本题目难度就在于先开始不做进位处理，然后还能找出竖式乘法的左移规律。否则写起来会很麻烦。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>巨人网络2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/21/giant"/>
   <updated>2013-10-21T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/21/giant</id>
   <content type="html">&lt;p&gt;###一、改错题&lt;/p&gt;

&lt;h5 id=&quot;1-下列代码的错误之处&quot;&gt;1. 下列代码的错误之处&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Question1 {
    public static boolean isOdd(int i) {
        return i % 2 == 1;
    }
    public static void main(String[] args) {
        for (int i = Integer.MIN_VALUE; i &amp;lt;= Integer.MAX_VALUE; ++i) {
            boolean isOdd = isOdd(i);
            System.out.println(String.format(&quot;i=%d, isOdd = %b&quot;, i, isOdd);
        }
    }
} // 负数求模，取绝对值取模，然后加符号。此处负数的部分，奇数取模之后都是-1，所以返回都是false。检测负数奇偶性时应先判断正负。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;2-下列代码的错误之处&quot;&gt;2. 下列代码的错误之处&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Question2 {
    public static void main(String[] args) {
        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
        System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
    }
} // long 64位 范围合适 // 右边做乘法时，是int类型数值，int是32位，乘法结束后，才转成long型付给MICROS_PER_DAY // 改成：final long MICROS_PER_DAY = 24l * 60l * 60l * 1000l * 1000l;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;3-下列代码的错误之处&quot;&gt;3. 下列代码的错误之处&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Question3 {
    public static void main(String[] args) {
        for (byte b = Byte.MIN_VALUE; b &amp;lt; Byte.MAX_VALUE; b++) {
            if (b == 0x90)
                System.out.print(&quot;Joy!&quot;);
        }
    }
} // byte只有8位，0x90是16位，超出范围，永远不会执行到Joy处。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;###二、问答题&lt;/p&gt;

&lt;h5 id=&quot;1-请解释一下jvm-gc的原理&quot;&gt;1. 请解释一下JVM GC的原理&lt;/h5&gt;
&lt;p&gt;GC garbage coolection，使用一个线程实现，监控内存，如果该块内存没有被对象引用，则会自动释放该内存块。常见的情况下，a = null;后，a原本引用的内存就会被自动释放。程序员也可以通过System.gc()建议JVM去释放内存。&lt;/p&gt;

&lt;h5 id=&quot;2-实现一个高并发的聊天服务器需要克服哪些问题&quot;&gt;2. 实现一个高并发的聊天服务器需要克服哪些问题？&lt;/h5&gt;

&lt;p&gt;###三、编程题&lt;/p&gt;

&lt;h5 id=&quot;1-请用java实现冒泡排序算法&quot;&gt;1. 请用Java实现冒泡排序算法&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class BubbleSort {
    
    public static void BubbleSort(int [] arr) {
        boolean flag;
        for (int i = 0; i &amp;lt; arr.length; i++) {
            flag = false;
            for (int j = i; j &amp;lt; arr.length; j++) {
                if (arr[i] &amp;gt; arr[j]) {
                    arr[i] ^= arr[j];
                    arr[j] ^= arr[i];
                    arr[i] ^= arr[j];
                    
                    flag = true;
                }
            }
            
            if (!flag) {
                return ;
            }
        }
    }
    
    public static void main(String argv[]) {
        int arr[] = {5, 4, 6, 2, 3, 9};
        BubbleSort(arr);
        
        for (int i = 0; i &amp;lt; arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;2-请用java实现二叉树的中序遍历算法&quot;&gt;2. 请用Java实现二叉树的（中序）遍历算法。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public void inOrder(Node node) {
    if (node == null) {
        return ;
    }
    
    if (node.left != null) {
        inOrder(node.left);
    }
    
    System.out.println(node.value);
    
    if (node.right != null) {
        inOrder(node.right);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>去哪网2014校园招聘Java开发面经</title>
   <link href="https://blog.cyeam.com/collection/2013/10/19/qunarinterview"/>
   <updated>2013-10-19T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/19/qunarinterview</id>
   <content type="html">&lt;p&gt;去哪网待遇非常给力，一个FE都能给到14x16。而且刚刚上市，发展很不错。我的面试官是重庆人，看到我的简历上写着本科是重庆大学的，也很照顾我，可惜我很不给力。一直摊开了问，从基础到Java源码、C++源码都问了一圈，还有C语言小技巧。面试难度符合他们公司的工资水平。&lt;/p&gt;

&lt;p&gt;下面是记忆版的面试题目。&lt;/p&gt;

&lt;h5 id=&quot;1--arraylist实现-add方法如何分配内存&quot;&gt;1 . ArrayList实现 add方法如何分配内存&lt;/h5&gt;

&lt;p&gt;ArrayList基于数组实现，封装的数组，但是数组大小是固定了，而ArrayList是动态数组。动态数组是通过ensureCapacity(int minCapacity)实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity &amp;gt;&amp;gt; 1);
    if (newCapacity - minCapacity &amp;lt; 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE &amp;gt; 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;每次可以扩容1.5倍，将老数组拷贝一份到新数组。&lt;/p&gt;

&lt;h5 id=&quot;2-hashmap如何实现&quot;&gt;2. HashMap如何实现？&lt;/h5&gt;

&lt;p&gt;HashMap是基于哈希表的Map接口的非同步实现。此类不保证映射的顺序，特别是它不保证该顺序恒久不变。HashMap实际上是一个“链表散列”的数据结构，即数组和链表的结合体。从上图中可以看出，HashMap底层就是一个数组结构，数组中的每一项又是一个链表。当新建一个HashMap的时候，就会初始化一个数组。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Entry(int h, K k, V v, Entry&amp;lt;K,V&amp;gt; n) {
    value = v;
    next = n;
    key = k;
    hash = h;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/hashtable_link.png&quot; alt=&quot;Alt text&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;3-c容器&quot;&gt;3. C++容器&lt;/h5&gt;

&lt;h5 id=&quot;4-数据库索引建立&quot;&gt;4. 数据库索引建立&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CREATE INDEX idx_test4_name ON   test_tab (name );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;索引要建立在经常进行select操作的字段上。这是因为，如果这些列很少用到，那么有无索引并不能明显改变查询速度。相反，由于增加了索引，反而降低了系统的维护速度和增大了空间需求。&lt;/li&gt;
  &lt;li&gt;索引要建立在值比较唯一的字段上。这样做才是发挥索引的最大效果。，比如主键的id字段，唯一的名字name字段等等。如果索引建立在唯一值比较少的字段，比如性别gender字段，寥寥无几的类别字段等，刚索引几乎没有任何意义。&lt;/li&gt;
  &lt;li&gt;对于那些定义为text、image和bit数据类型的列不应该增加索引。因为这些列的数据量要么相当大，要么取值很少。&lt;/li&gt;
  &lt;li&gt;当修改性能远远大于检索性能时，不应该创建索引。修改性能和检索性能是互相矛盾的。当增加索引时，会提高检索性能，但是会降低修改性能。当减少索引时，会提高修改性能，降低检索性能。因此，当修改性能远远大于检索性能时，不应该创建索引。&lt;/li&gt;
  &lt;li&gt;在WHERE和JOIN中出现的列需要建立索引。&lt;/li&gt;
  &lt;li&gt;在以通配符% 和_ 开头作查询时，mysql索引是无效的。但是这样索引是有效的：select * from tbl1 where name like ‘xxx%’，所以mysql正确建立索引是很重要的。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;5-设计模式&quot;&gt;5. 设计模式&lt;/h5&gt;

&lt;h5 id=&quot;6-如何检测一个数是大端还是小段存储&quot;&gt;6. 如何检测一个数是大端还是小段存储&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;short x = 0x1234;
char* arr;
arr = reinterpret_cast&amp;lt;char*&amp;gt;(&amp;amp;x);
cout &amp;lt;&amp;lt; hex &amp;lt;&amp;lt; static_cast&amp;lt;short&amp;gt;(arr[0]) &amp;lt;&amp;lt; static_cast&amp;lt;short&amp;gt;(arr[1]) &amp;lt;&amp;lt; endl;
char y = x;
cout &amp;lt;&amp;lt; &quot;0x&quot; &amp;lt;&amp;lt; hex &amp;lt;&amp;lt; (short)y &amp;lt;&amp;lt; endl;
union endian {
    int i;
    float f;
    char s;
};
endian e;
e.i = 1;
cout &amp;lt;&amp;lt; static_cast&amp;lt;int&amp;gt;(e.s) &amp;lt;&amp;lt; endl;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;7-json的解析&quot;&gt;7. Json的解析&lt;/h5&gt;

&lt;h5 id=&quot;8-ajax原理&quot;&gt;8. ajax原理&lt;/h5&gt;

&lt;h5 id=&quot;9-快速排序-堆排序-哪个快&quot;&gt;9. 快速排序 堆排序 哪个快&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;建立最大堆（堆顶的元素大于其两个儿子，两个儿子又分别大于它们各自下属的两个儿子… 以此类推）&lt;/li&gt;
  &lt;li&gt;将堆顶的元素和最后一个元素对调（相当于将堆顶元素（最大值）拿走，然后将堆底的那个元素补上它的空缺），然后让那最后一个元素从顶上往下滑到恰当的位置（重新使堆最大化）。&lt;/li&gt;
  &lt;li&gt;重复第2步。
这里的关键问题就在于第2步，堆底的元素肯定很小，将它拿到堆顶和原本属于最大元素的两个子节点比较，它比它们大的可能性是微乎其微的。实际上它肯定小于其中的一个儿子。而大于另一个儿子的可能性非常小。于是，这一次比较的结果就是概率不均等的，根据前面的分析，概率不均等的比较是不明智的，因为它并不能保证在糟糕情况下也能将问题的可能性削减到原本的1/2。可以想像一种极端情况，如果a肯定小于b，那么比较a和b就会什么信息也得不到——原本剩下多少可能性还是剩下多少可能性。
在堆排序里面有大量这种近乎无效的比较，因为被拿到堆顶的那个元素几乎肯定是很小的，而靠近堆顶的元素又几乎肯定是很大的，将一个很小的数和一个很大的数比较，结果几乎肯定是“小于”的，这就意味着问题的可能性只被排除掉了很小一部分。
这就是为什么堆排序比较慢（堆排序虽然和快速排序一样复杂度都是O(NlogN)但堆排序复杂度的常系数更大）。
MacKay也提供了一个修改版的堆排序：每次不是将堆底的元素拿到上面去，而是直接比较堆顶（最大）元素的两个儿子，即选出次大的元素。由于这两个儿子之间的大小关系是很不确定的，两者都很大，说不好哪个更大哪个更小，所以这次比较的两个结果就是概率均等的了。&lt;/li&gt;
&lt;/ol&gt;

&lt;h5 id=&quot;10-tcp建立过程传输过程syn码是否变化&quot;&gt;10. TCP建立过程，传输过程syn码是否变化&lt;/h5&gt;

&lt;h5 id=&quot;11-protobuf&quot;&gt;11. Protobuf&lt;/h5&gt;

&lt;h5 id=&quot;12-单件模式关键点-除了线程安全&quot;&gt;12. 单件模式关键点 除了线程安全&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;double check
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
//import javax.ejb.EntityContext;
import org.apache.struts2.dispatcher.StaticContentLoader;
public class ZSingleton&amp;lt;T&amp;gt; {
    
    private static Object instance;
    
    public ZSingleton() {
        Class&amp;lt;T&amp;gt; entityClass = (Class&amp;lt;T&amp;gt;)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
    
    public static &amp;lt;T&amp;gt; T getInstance() {
//      entityClass.cast(instance);
        
        if (instance == null) {
            // Delay load and synchronized load
            synchronized (ZSingleton.class) {
                if (instance == null) {
//                  instance = entityClass.getConstructors();
                }
            }
        }
        return (T)instance;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;13-http状态码&quot;&gt;13. Http状态码&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;2xx成功&lt;/li&gt;
  &lt;li&gt;3xx重定向
    &lt;ul&gt;
      &lt;li&gt;301 StatusMovedPermanently。第一次收到301之后，浏览器会进行跳转。但是浏览器认为这个站点已经被永久移动到新的站点了，所以，再访问的话会直接访问新网址，不会发生跳转操作。如果需要进行每次跳转，记录跳转来源或者日志等信息，例如短网址服务区，就需要使用此状态码。&lt;/li&gt;
      &lt;li&gt;302 StatusFound。每次收到这个状态码都会进行跳转操作。&lt;/li&gt;
      &lt;li&gt;304 Not Modified。如果客户端发送了一个带条件的GET请求且该请求已被允许，而文档的内容（自上次访问以来或者根据请求的条件）并没有改变，则服务器应当返回这个状态码。304响应禁止包含消息体，因此始终以消息头后的第一个空行结尾。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;4xx请求错误&lt;/li&gt;
  &lt;li&gt;5xx服务器错误&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;14-vector-与普通数组实现区别&quot;&gt;14. Vector 与普通数组实现区别&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void reserve( int newCapacity )  
{  
    Object *oldArray = objects;  
        //1.重设size,capacity  
    int numToCopy = newCapacity &amp;lt; theSize ? newCapacity : theSize;  
    //capacity是在size的基础上加一个常数  
    newCapacity += SPARE_CAPACITY;  
        //2.建立新的数组，并拷贝元素到新数组  
    objects = new Object[ newCapacity ];  
    for( int k = 0; k &amp;lt; numToCopy; k++ )  
        objects[ k ] = oldArray[ k ];  
  
    theSize = numToCopy;  
    theCapacity = newCapacity;  
        //删除原来的数组  
    delete [ ] oldArray;  
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;一面后直接就被委婉的告知没戏了。下午还有完美世界的笔试，走在去清华的路上，拍到的这张北大的博雅塔。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/%E5%8D%9A%E9%9B%85%E5%A1%94.JPG&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>完美世界2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/19/perfectworld"/>
   <updated>2013-10-19T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/19/perfectworld</id>
   <content type="html">&lt;p&gt;###一、单项选择题&lt;/p&gt;

&lt;h5 id=&quot;1-某工作站无法访问wwwwanmeicom的服务器使用ping命令对该服务器的ip地址进行测试响应正常但是对服务器域名进行测试时出现超时错误可能出现的问题是b&quot;&gt;1. 某工作站无法访问www.wanmei.com的服务器，使用ping命令对该服务器的IP地址进行测试，响应正常；但是对服务器域名进行测试时出现超时错误，可能出现的问题是（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 线路故障&lt;/li&gt;
  &lt;li&gt;B. 路由故障&lt;/li&gt;
  &lt;li&gt;C. 域名解析故障&lt;/li&gt;
  &lt;li&gt;D. 服务器网卡故障&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;如果能ping通，那么说明DNS解析是正常的。就好比去访问facebook，肯定ping不通，因为DNS被污染了。同理，线路故障和网卡故障也会导致ping不通。一般服务器前面还会再增加一层负载均衡服务器，一般是Nginx或者Apache，会在这里做域名正则匹配，如果转发到错误的IP上面，有可能导致超时错误。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h5 id=&quot;2-小明每次可以上1级2级或者3级台阶请问小明上6级台阶总共共有多少种方法b&quot;&gt;2. 小明每次可以上1级、2级或者3级台阶、请问小明上6级台阶总共共有多少种方法（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 23&lt;/li&gt;
  &lt;li&gt;B. 24&lt;/li&gt;
  &lt;li&gt;C. 25&lt;/li&gt;
  &lt;li&gt;D. 26&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-师徒四人西天取经途中必需跨过一座桥四个人从桥的同一端出发你得帮助他们到达另一端天色很暗而他们只有一只手电筒一次同时最多可以有两人一起过桥而过桥的时候必须持有手电筒所以就得有人把手电筒带来带去来回桥两端手电筒不能同丢的方式来传递四个人的步行速度各不同若两人同行则以较慢者的速度为准大师兄需花1分钟过桥二师兄需花2分钟过桥三师兄需5分钟过桥-师父需花10分钟过桥请问他们最短在多少分钟内能过桥b&quot;&gt;3. 师徒四人西天取经，途中必需跨过一座桥、四个人从桥的同一端出发，你得帮助他们到达另一端，天色很暗，而他们只有一只手电筒。一次同时最多可以有两人一起过桥，而过桥的时候必须持有手电筒，所以就得有人把手电筒带来带去、来回桥两端。手电筒不能同丢的方式来传递。四个人的步行速度各不同，若两人同行则以较慢者的速度为准。大师兄需花1分钟过桥，二师兄需花2分钟过桥，三师兄需5分钟过桥， 师父需花10分钟过桥，请问他们最短在多少分钟内能过桥？（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 16&lt;/li&gt;
  &lt;li&gt;B. 17&lt;/li&gt;
  &lt;li&gt;C. 18&lt;/li&gt;
  &lt;li&gt;D. 19&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;4-以下说法不正确的是a&quot;&gt;4. 以下说法不正确的是：（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 死锁的充分条件是互斥、占有且等待、非剥夺、循环等待，且缺一不可。&lt;/li&gt;
  &lt;li&gt;B. Raid5至少需要3块磁盘。&lt;/li&gt;
  &lt;li&gt;C. 地址空间和资源（例如打开文件句柄）在进程间相互独立，而同一进程的线程可以共享。&lt;/li&gt;
  &lt;li&gt;D. 局部性原理主要包括时间局部性和空间局部性。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;5-权值为9527的四个叶子构成的哈夫曼树其带权路径长度是b&quot;&gt;5. 权值为9，5，2，7的四个叶子构成的哈夫曼树，其带权路径长度是：（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 23&lt;/li&gt;
  &lt;li&gt;B. 44&lt;/li&gt;
  &lt;li&gt;C. 46&lt;/li&gt;
  &lt;li&gt;D. 50&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;6-程序执行后count的值将会是b&quot;&gt;6. 程序执行后，count的值将会是：（B）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class CrazyCount {
    private static volatile int count = 0;
    public static void main(String[] args) {
        for (int i = 0; i &amp;lt; 1000; i++) {
            new Thread(new Runnable() {
                public void run() {
                    count++;
                }
            }).start();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 无效值，因为对count的代码修改没有同步&lt;/li&gt;
  &lt;li&gt;B. 1到1000中的一个值&lt;/li&gt;
  &lt;li&gt;C. 正好1000&lt;/li&gt;
  &lt;li&gt;D. 至少1000&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// 在JVM中，每个线程都可以有自己的栈空间，volatile的作用是保证栈空间中的值和内存中的一致，并不会保证变量的原子性。保证原子性需要使用synchronized关键字，为cout++保证线程安全。&lt;/p&gt;

&lt;p&gt;// 另：虽然1000个线程并行执行，但是保证了count++的线程安全之后，最终结果肯定是1000&lt;/p&gt;

&lt;h5 id=&quot;7-各处如下代码&quot;&gt;7. 各处如下代码：&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Test {
    public static void main(String[] args) {
        int x = 5;
        boolean b1 = true;
        boolean b2 = false;
        if ((x == 4) &amp;amp;&amp;amp; !b2)
            System.out.print(&quot;1&quot;);
        System.out.print(&quot;2&quot;);
        if ((b2 = true) &amp;amp;&amp;amp; b1)
            System.out.print(&quot;3&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出结果是：（D）&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;A. 2&lt;/li&gt;
  &lt;li&gt;B. 3&lt;/li&gt;
  &lt;li&gt;C. 12&lt;/li&gt;
  &lt;li&gt;D. 23&lt;/li&gt;
  &lt;li&gt;E. 123&lt;/li&gt;
  &lt;li&gt;F. 编译出错&lt;/li&gt;
  &lt;li&gt;G. 运行时程序抛出异常&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;8-编译运行下面的代码会出现哪种情况c&quot;&gt;8. 编译运行下面的代码会出现哪种情况？（C）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Test {
    public void myMethod(Object o) {
        System.out.println(&quot;My Object&quot;);
    }
    public void myMethod(String s) {
        System.out.println(&quot;My String&quot;);
    }
    public static void main(String args[]) {
        Test t = new Test();
        t.myMethod(null);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 不能编译通过&lt;/li&gt;
  &lt;li&gt;B. 编译通过，输出”My Object”&lt;/li&gt;
  &lt;li&gt;C. 编译通过，输出”My String”&lt;/li&gt;
  &lt;li&gt;D. 编译通过，但运行时报错&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;9-以下代码运行时输出的结果是什么b&quot;&gt;9. 以下代码运行时输出的结果是什么？（B）&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class C {
    C() {
        System.out.print(&quot;C&quot;);
    }
}
class A {
    C c = new C();
    A() {
        this(&quot;A&quot;);
        System.out.print(&quot;A&quot;);
    }
    A(String s) {
        System.out.print(s);
    }
}
class B extends A {
    B() {
        super(&quot;B&quot;);
        System.out.print(&quot;B&quot;);
    }
    public static void main(String[] args) {
        new B();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. BB&lt;/li&gt;
  &lt;li&gt;B. CBB&lt;/li&gt;
  &lt;li&gt;C. BAB&lt;/li&gt;
  &lt;li&gt;D. 以上都不是&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;10-栈是一种a&quot;&gt;10. 栈是一种：（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 存取受限的线性结构&lt;/li&gt;
  &lt;li&gt;B. 存取不受限的线性结构&lt;/li&gt;
  &lt;li&gt;C. 存取受限的非线性结构&lt;/li&gt;
  &lt;li&gt;D. 存取不受限的非线性结构&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;11-java中一个char对象可以表示的数值范围是b&quot;&gt;11. java中一个char对象可以表示的数值范围是：（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 0到255&lt;/li&gt;
  &lt;li&gt;B. 0到65535&lt;/li&gt;
  &lt;li&gt;C. -256到255&lt;/li&gt;
  &lt;li&gt;D. -32768到32767&lt;/li&gt;
  &lt;li&gt;E. 平台相关&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Java中char为16位，4个字节，使用Unicode编码&lt;/p&gt;

&lt;h5 id=&quot;12-以下描述正确的是b&quot;&gt;12. 以下描述正确的是：（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. Java支持多重继承，一个类可以实现多个接口；&lt;/li&gt;
  &lt;li&gt;B. Java只支持单重继承，一个类可以实现多个接口；&lt;/li&gt;
  &lt;li&gt;C. Java只支持单重继承，一个类只可以实现一个接口；&lt;/li&gt;
  &lt;li&gt;D. Java支持多重继承，但一个类只可以实现一个接口。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;13-下列关于集合类描述错误的包括c&quot;&gt;13. 下列关于集合类描述错误的包括：（C）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. ArrayList和LinkedList均实现了List接口&lt;/li&gt;
  &lt;li&gt;B. ArrayList的访问速度比LinkedList快&lt;/li&gt;
  &lt;li&gt;C. 添加和删除元素时，ArrayList的表现更佳&lt;/li&gt;
  &lt;li&gt;D. HashMap实现Map接口，它允许任何类型的键和值对象，并允许将null用作键或值&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;14-执行如下程序代码后c的值是a&quot;&gt;14. 执行如下程序代码后，c的值是：（A）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;a = 0; c = 1;
do {
    --c;
    a = a - 1;
} while (a &amp;gt; 0);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. 0&lt;/li&gt;
  &lt;li&gt;B. 1&lt;/li&gt;
  &lt;li&gt;C. -1&lt;/li&gt;
  &lt;li&gt;D. 死循环&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;15-下列关于修饰符混用的说法错误的包括d&quot;&gt;15. 下列关于修饰符混用的说法，错误的包括：（D）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. abstract不能与final并列修饰同一个类&lt;/li&gt;
  &lt;li&gt;B. abstract类中可以有private的成员&lt;/li&gt;
  &lt;li&gt;C. abstract方法必须在abstract类中&lt;/li&gt;
  &lt;li&gt;D. static方法中能处理非static的成员变量&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;###二、填空题&lt;/p&gt;

&lt;h5 id=&quot;1-下面二叉树的中序遍历结果是&quot;&gt;1. 下面二叉树的中序遍历结果是：&lt;/h5&gt;

&lt;h5 id=&quot;2-请至少列举出5个常用的设计模式&quot;&gt;2. 请至少列举出5个常用的设计模式：&lt;/h5&gt;

&lt;h5 id=&quot;3-一个1300字节的ip包包长度为20字节进入一个mtu为500的网络中请问该ip包被拆成段-其中每段的长度分别是&quot;&gt;3. 一个1300字节的IP包，包长度为20字节，进入一个MTU为500的网络中，请问该IP包被拆成（）段， 其中每段的长度分别是（）。&lt;/h5&gt;

&lt;h5 id=&quot;4-完美世界的个性账号注册时要求账号由6-16位小写英文字母及数字组成且首位为字母请写出验证账号合法的正则表达式&quot;&gt;4. 完美世界的个性账号注册时要求账号由6-16位小写英文字母及数字组成且首位为字母，请写出验证账号合法的正则表达式（）。&lt;/h5&gt;

&lt;h5 id=&quot;5-请给出以下程序的输出&quot;&gt;5. 请给出以下程序的输出：&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Value {
    public int i = 15;
}
public class Test {
    public static void main(String argv[]) {
        Test t = new Test();
        t.first();
    }
    public void first() {
        int i = 5;
        Value v = new Value();
        v.i = 25;
        second(v, i);
        System.out.println(v.i);
    }
    public void second(Value v, int i) {
        i = 0;
        v.i = 20;
        Value val = new Value();
        v = val;
        System.out.println(v.i + &quot; &quot; + i);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;###三、简答题&lt;/p&gt;

&lt;h5 id=&quot;1-列举http-11所支持的方法&quot;&gt;1. 列举HTTP 1.1所支持的方法。&lt;/h5&gt;
&lt;p&gt;OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT&lt;/p&gt;

&lt;h5 id=&quot;2-什么是反射反射的作用是什么&quot;&gt;2. 什么是反射？反射的作用是什么？&lt;/h5&gt;
&lt;p&gt;反射就是可以在程序运行时刻动态获得类的信息，动态调用类的方法。
反射的作用是，可以动态的获得对象的内部接口，可以动态对一个对象进行操作。&lt;/p&gt;

&lt;h5 id=&quot;3-方法equals和方法hashcode有什么联系它们各自有什么作用实现这两个方法的时候有些什么要求&quot;&gt;3. 方法equals()和方法hashCode()有什么联系？它们各自有什么作用？实现这两个方法的时候有些什么要求？&lt;/h5&gt;
&lt;p&gt;equals()和hashCode()均继承自Object对象。equals()是通过比较两个对象的内存地址来判断时候相等，可以重写这个函数来进行内容判断。hashCode()用来实现Set等，可以简单的认为是使用物理内存地址来实现，这是一个native方法，会因为平台不同而不同。equals()需要自反性、对称性、传递性、一致性、对任何不适null的x，x.equals(null)一定返回false。&lt;/p&gt;

&lt;p&gt;###四、编程题&lt;/p&gt;

&lt;h5 id=&quot;1-用java实现一个方法从指定的mymap中删除所有值为value的对象并返回删除的对象个数&quot;&gt;1. 用java实现一个方法，从指定的mymap中删除所有值为value的对象，并返回删除的对象个数&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int removeValue(Map&amp;lt;Integer, Integer&amp;gt; mymap, int value);

int removeValue(Map&amp;lt;Integer, Integer&amp;gt; mymap, int value) {
    int count = 0;
    List&amp;lt;Integer&amp;gt; list = new LinkedList&amp;lt;Integer&amp;gt;();
    Iterator&amp;lt;Entry&amp;lt;Integer, Integer&amp;gt;&amp;gt; iter = mymap.entrySet().iterator();
    Entry&amp;lt;Integer, Integer&amp;gt; entry;
    while (iter.hasNext()) {
        entry = iter.next();
        if (entry.getValue() == value) {
            list.add(entry.getKey());
        }
    }
    
    // 
    count = list.size();
    for (int i = 0; i &amp;lt; list.size(); i++) {
        mymap.remove(list.get(i));
    }
    
    return count;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;清华的一家飞机，也不知道是何来历。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/tsinghua_plane.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>广联达2014校园招聘笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/17/glodon"/>
   <updated>2013-10-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/17/glodon</id>
   <content type="html">&lt;p&gt;###卷一&lt;/p&gt;

&lt;h5 id=&quot;1-实现一个函数把一个字符串中的字符从小写转为大写&quot;&gt;1. 实现一个函数，把一个字符串中的字符从小写转为大写。&lt;/h5&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/12/go_upper&quot;&gt;解析。&lt;/a&gt;&lt;/p&gt;

&lt;h5 id=&quot;2-给定一个二叉树的根结点r写一个函数返回该二叉树是否为平衡二叉树&quot;&gt;2. 给定一个二叉树的根结点R，写一个函数返回该二叉树是否为平衡二叉树。&lt;/h5&gt;
&lt;p&gt;二叉树的结点定义如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct node {
    int data;
    struct node* left;
    struct node* right;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;3-写一个函数将两个有序的key-value数组按key累加得到一个新的有序数组&quot;&gt;3. 写一个函数，将两个有序的Key-Value数组按Key累加得到一个新的有序数组。&lt;/h5&gt;
&lt;p&gt;例：
数组 1
1, 400
4, 700
5, 100
数组 2
1, 300
2, 500
3, 400
4, 200
按Key累加之后得到新的数组
1, 700
2, 500
3, 400
4, 900
5, 100
Key-Value定义：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct keyvalue {
    int key;
    double value;
} 函数接口：

void sumKeyValueArray(const keyvalue aArray1[], int nLen1, const keyvalue aArray2[], int nLen2, keyvalue aResult[], int &amp;amp;nResultLen);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;4-不开辟和用于交换数据的临时空间如何完成字符串的逆序&quot;&gt;4. 不开辟和用于交换数据的临时空间，如何完成字符串的逆序。&lt;/h5&gt;
&lt;p&gt;C语言的函数头为：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void change(char* str);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;不开辟临时空间交换值可以使用异或操作或者加法来实现，可以参考&lt;a href=&quot;https://blog.cyeam.com/computer/2013/04/02/swap&quot;&gt;《3种交换值的方法》&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.cyeam.com/golang/2014/08/14/go_reverse&quot;&gt;Golang实现&lt;/a&gt;&lt;/p&gt;

&lt;h5 id=&quot;5-一个学生的信息包括姓名学号性别年龄等信息用一个链表保存所有学生的信息给出一个age在链表中删除学生年龄等于age的学生信息&quot;&gt;5. 一个学生的信息包括：姓名，学号，性别，年龄等信息，用一个链表保存所有学生的信息，给出一个age，在链表中删除学生年龄等于age的学生信息。&lt;/h5&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct student {
    char name`[20]`;
    char sex;
    int no;
    int age;
    student* next;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;###卷六&lt;/p&gt;

&lt;h5 id=&quot;4-在字符串中找出第一个只出现一次的字符如输入abaccdeff则输出b&quot;&gt;4. 在字符串中找出第一个只出现一次的字符，如输入”abaccdeff”，则输出’b’。&lt;/h5&gt;

&lt;h5 id=&quot;5-设计一个算法把一个含有n个元素的数组循环右移k位要求时间复杂度为on空间复杂度为o1&quot;&gt;5. 设计一个算法，把一个含有N个元素的数组循环右移K位，要求时间复杂度为O(N)，空间复杂度为O(1)。&lt;/h5&gt;

</content>
 </entry>
 
 <entry>
   <title>触控科技2014校园招聘笔试题</title>
   <link href="https://blog.cyeam.com/collection/2013/10/15/chukong"/>
   <updated>2013-10-15T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/15/chukong</id>
   <content type="html">&lt;h5 id=&quot;1-实现运行时多态的机制是b&quot;&gt;1. 实现运行时多态的机制是（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 重载函数 // 编译时多态&lt;/li&gt;
  &lt;li&gt;B. 虚函数 // 运行时多态&lt;/li&gt;
  &lt;li&gt;C. 静态函数&lt;/li&gt;
  &lt;li&gt;D. 模板函数&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-fun函数是一个类的常成员函数它无返回值下列表示中正确的是a&quot;&gt;2. fun()函数是一个类的常成员函数，它无返回值，下列表示中，正确的是（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. void fun() const;&lt;/li&gt;
  &lt;li&gt;B. const void fun();&lt;/li&gt;
  &lt;li&gt;C. void const fun();&lt;/li&gt;
  &lt;li&gt;D. void fun(const);&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-下列说明中-const-char-ptrptr应该是a&quot;&gt;3. 下列说明中 const char* ptr，ptr应该是（A）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 指向字符常量的指针&lt;/li&gt;
  &lt;li&gt;B. 指向字符的常量指针&lt;/li&gt;
  &lt;li&gt;C. 指向字符串常量的指针 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;char *p=&quot;hello&quot;;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;D. 指向字符串的常量指针 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const char *p=&quot;hello&quot;;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bjarne在他的The C++ Programming Language里面给出过一个助记的方法：
把一个声明从右向左读。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;char  * const cp; ( * 读成 pointer to ) 
cp is a const pointer to char ---&amp;gt;cp是一个指向字符char的固定指针
const char * ptr; 
ptr is a pointer to const char; ---&amp;gt;ptr是一个指向固定字符char的指针
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;4-这段代码执行后哪项描述是正确的a&quot;&gt;4. 这段代码执行后，哪项描述是正确的（A）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int a = 5, b = 7, c; c = a+++b;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;A. a = 6, b = 7, c = 12&lt;/li&gt;
  &lt;li&gt;B. a = 5, b = 8, c = 12&lt;/li&gt;
  &lt;li&gt;C. a = 5, b = 8, c = 13&lt;/li&gt;
  &lt;li&gt;D. 这段代码不合法&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;就近原则，先执行&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a++&lt;/code&gt;。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a++&lt;/code&gt;的话，是先赋值后做加法。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h5 id=&quot;5-字符串0211xab-的长度为b&quot;&gt;5. 字符串：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;”\\0211\”xab”&lt;/code&gt; 的长度为（B）&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;A. 7&lt;/li&gt;
  &lt;li&gt;B. 9&lt;/li&gt;
  &lt;li&gt;C. 10&lt;/li&gt;
  &lt;li&gt;D. 11&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;6-expr的值为3&quot;&gt;6. expr的值为（3）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int a = 8, b = 4;
int expr = a++ % ++b;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;7-expr的值为8&quot;&gt;7. expr的值为（8）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int expr = (1 % 3) + (5 | 3);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;8-expr的值为22&quot;&gt;8. expr的值为（22）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;char a[6][8];
int expr = a[2] - &amp;amp;a[4][6];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;9-请简述c中overload重载和override覆盖的区别&quot;&gt;9. 请简述C++中overload(重载)和override(覆盖)的区别？&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;Overload(重载)：在C++程序中，可以将语义、功能相似的几个函数用同一个名字表示，但参数或返回值不同（包括类型、顺序不同），即函数重载。（1）相同的范围（在同一个类中）；（2）函数名字相同；（3）参数不同；（4）virtual 关键字可有可无。&lt;/li&gt;
  &lt;li&gt;Override(覆盖)：是指派生类函数覆盖基类函数，特征是：（1）不同的范围（分别位于派生类与基类）；（2）函数名字相同；（3）参数相同；（4）基类函数必须有virtual 关键字。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;10-请简述c中的4种类型转换方式&quot;&gt;10. 请简述C++中的4种类型转换方式？&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;static_cast 静态的_cast&lt;/li&gt;
  &lt;li&gt;dynamic_cast 动态的_cast&lt;/li&gt;
  &lt;li&gt;reinterpret_cast 重新解释的_cast&lt;/li&gt;
  &lt;li&gt;const_cast 常量的_cast&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;11-写出几种在不用第三方参数的情况下交换两个参数的值的方法&quot;&gt;11. 写出几种在不用第三方参数的情况下，交换两个参数的值的方法。&lt;/h5&gt;

&lt;p&gt;交换值是比较常用的步骤，也比较简单，这里总结了3个方法:&lt;/p&gt;

&lt;p&gt;使用临时变量交换。方法很简单，使用临时变量来保存一个值，该值被保存后就可以对其进行赋值操作了;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 通过临时变量交换值
void swap_temp(int *a, int *b) {    // a = 1, b = 3
    int temp = *a;                  // temp = 1
    *a = *b;                        // a = 3
    *b = temp;                      // b = 1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用异或操作(可参考《位运算符》)。具体原理是：对于任何数,都有A^0=A A^A=0;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 通过位运算交换值
void swap_bit(int *a, int *b) {     // a = 1, b = 3
    *a = *a ^ *b;                   // a = a ^ b
    *b = *b ^ *a;                   // b = b ^ a = b ^ (a ^ b) = a = 1
    *a = *a ^ *b;                   // a = a ^ b = (a ^ b) ^ a = b = 3
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用加法和减法也可以实现数字的交换，原理同第二条异或原理基本一致;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void swap_add_substract(int *a, int *b) {   // a = 1, b = 3
    *a = *a + *b;                           // a = a + b
    *b = *a - *b;                           // b = a - b = (a + b) - b = a = 1
    *a = *a - *b;                           // a = a - b = (a + b) - a = b = 3
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;虽然使用异或操作和加减法运算并没有去声明临时变量，但实际运行过程中，会在临时寄存器中保存临时变量用于计算，所以都是将一个数值保存在了一个临时的场所，只是位置不同而已。&lt;/p&gt;

&lt;h5 id=&quot;12-假设我们用16位bit来存储rgb像素数据存储格式定义为rrrrrgggggbbbbbb接下来我们为了加入透明色重新定义格式为aaaarrrrggggbbbb头4个bit用来存储alpha值请写出实现函数&quot;&gt;12. 假设我们用16位bit来存储RGB像素数据，存储格式定义为RRRRRGGGGGBBBBBB，接下来，我们为了加入透明色，重新定义格式为AAAARRRRGGGGBBBB，头4个bit用来存储alpha值，请写出实现函数。&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;short convertRGB2ARGB(short RGB, char alpha) {
	short ARGB = 0;
	short A = alpha &amp;amp; 15;
	short R = (RGB &amp;gt;&amp;gt; 11) &amp;amp; 31;
	short G = (RGB &amp;gt;&amp;gt; 6) &amp;amp; 31;
	short B = RGB &amp;amp; 63;
	ARGB = (A &amp;lt;&amp;lt; 12) + (R &amp;lt;&amp;lt; 8) + (G &amp;lt;&amp;lt; 4) + B;
	return ARGB;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;13-翻译略&quot;&gt;13. 翻译(略)。&lt;/h5&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/chukong.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>百度2014校园招聘后台开发笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/10/13/baidu"/>
   <updated>2013-10-13T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/10/13/baidu</id>
   <content type="html">&lt;p&gt;###一、简答题&lt;/p&gt;

&lt;h5 id=&quot;1-请描述下osi七层模型开放式系统护栏参考模型&quot;&gt;1. 请描述下osi七层模型（开放式系统护栏参考模型）。&lt;/h5&gt;

&lt;h5 id=&quot;2-请列举下不同进程之间共享数据的方式至少三种&quot;&gt;2. 请列举下不同进程之间共享数据的方式（至少三种）？&lt;/h5&gt;

&lt;h5 id=&quot;3-请描述下tcp和udp的差别并且各列举一个上层协议&quot;&gt;3. 请描述下TCP和UDP的差别，并且各列举一个上层协议？&lt;/h5&gt;

&lt;h5 id=&quot;4-大数据列举出至少3中常用的衡量分类特征区分度的计算策略&quot;&gt;4. （大数据）列举出至少3中常用的衡量分类特征区分度的计算策略&lt;/h5&gt;

&lt;p&gt;###二、算法与程序设计题&lt;/p&gt;

&lt;h5 id=&quot;1-给出一个数据aa_0-a_1-a_2--a_n其中n可变打印出该数值元素的所有组合&quot;&gt;1. 给出一个数据A=&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[a_0, a_1, a_2, ……, a_n]&lt;/code&gt;（其中，n可变）打印出该数值元素的所有组合。&lt;/h5&gt;

&lt;h5 id=&quot;2-有这样一个数组a大小为n相邻元素差的绝对值都是1如a45656789109现在给定a和目标数t请找到t在a中的位置&quot;&gt;2. 有这样一个数组A，大小为n，相邻元素差的绝对值都是1，如：A=&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[4,5,6,5,6,7,8,9,10,9]&lt;/code&gt;。现在，给定A和目标数t，请找到t在A中的位置。&lt;/h5&gt;

&lt;h5 id=&quot;3-有一颗二叉树定义树的高度为从根到叶子节点的最长距离树的宽度为每层结点的最大值树的面积定义为高度与宽度的乘积写一个函数计算一个二叉树的面积&quot;&gt;3. 有一颗二叉树，定义树的高度为从根到叶子节点的最长距离，树的宽度为每层结点的最大值，树的面积定义为高度与宽度的乘积。写一个函数计算一个二叉树的面积。&lt;/h5&gt;

&lt;p&gt;###三、系统设计题&lt;/p&gt;

&lt;h5 id=&quot;1-大数据假设在一个k维空间中我们有两个向量u和v其中u&quot;&gt;1. （大数据）假设在一个k维空间中，我们有两个向量u和v，其中u&lt;/h5&gt;

</content>
 </entry>
 
 <entry>
   <title>阿里巴巴2014校园招聘面经</title>
   <link href="https://blog.cyeam.com/collection/2013/09/16/alibabainterview"/>
   <updated>2013-09-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/09/16/alibabainterview</id>
   <content type="html">&lt;p&gt;上周六去中科院参加阿里的笔试，结果阿里那边出了意外，没有打印出题目来。他们应急是去筛选简历，通过的今天直接面试，剩下的下周日笔试。&lt;/p&gt;

&lt;p&gt;昨天下午参加July的讲座的时候就接到了电话，跟我约今天来面试。很不巧，今天还是我新实习第一天入职的日子，果断不去入职了。更不巧的是，今天4号线信号故障，等了20分钟都没一辆车，还是坐公交去海淀黄庄倒的10号线。这样明显绕了。等到了大望路的阿里，时间也就刚刚好。&lt;/p&gt;

&lt;p&gt;昨天预约面试方向时，我选了Java。一个是因为我最近一直在做Java，这个实习也是面的这个方向的，还有就是C++略难，我怕hold不住。今天去了阿里，发现投Java还是有点取巧的，大家都是投的C++。我因为去的比较晚，阿里是按签到次序叫人的，简历排在后面。但是还是很快的叫到了我，而且据我观察，HR那里Java分类的简历好像都快面完了。  &lt;br /&gt;
比较好的
说一说面试的经过。面试是一对一的，一个阿里的人出来叫人，你跟着进去就可以了。面试我的人很nice，说话很亲切，还一直跟我握手，貌似搞技术的公司的员工都这样，不像那些国企那些，那么有距离感。不过面起来还是很认真的。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;先问我学得比较好的一门课，这也是比较常见的开头。我说是数据结构和操作系统。然后叫我讲一个比较印象深刻的自己写过的算法。这个问得我很意外，因为最近除了做了做项目，就是准备了一下面试题，之前些的算法好久不碰，都生疏了。我说我好久没做那些了，有点忘记了，然后就开始扯别的，我扯了一大段自我介绍，然后扯我简历上的项目，因为这个是我准备叫他问我的。&lt;/li&gt;
  &lt;li&gt;他貌似对我最近做那几个项目兴趣不大，转而开始问我本科毕设怎么做的，让我给他讲具体原理。我本科做的人脸检测，算法还是写过的，多少能扯点。不过没想到他问那么仔细，具体到特征值怎么算都开始问了，我说我也忘记了。&lt;/li&gt;
  &lt;li&gt;接着他让我说一下我自己的优势，特长。还让我说一个我周围我觉得比较欣赏的人，叫我说一下他那里值得欣赏。其实就是拐着弯让我自我评价一下自己。&lt;/li&gt;
  &lt;li&gt;他还问我看到Java源代码没有，对Java哪个包比较熟悉。我之前准备了一些Sprindg的面试题，我就想说我知道Spring的一些实现原理，比如利用反射啥的，我自己也用Servlet模拟过Spring的Ioc开发。因为这些我准备过，而且最近我也一直看这些，比较了解，想把他往这边引，结果他不听使唤。。。&lt;/li&gt;
  &lt;li&gt;后来我往Linux下扯，我说我熟悉Linux下开发，这次他终于为我所动，问我vi一些命令，问我vi下替换字符串的命令，用正则表达式替换的命令是什么，尼玛好久不用，忘记了，唉。&lt;/li&gt;
  &lt;li&gt;最后他问我你自己是未来想做Java还是C++方向，我当时扯了一大通，说两个都可以。结果出来我就后悔了，其实他是想变着法问你你举得自己Java和C++那个学得好，我自己答都不行。。。这招太狠了，我太嫩了，当时没反映过来。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本来准备先面一些小公司，积累点经验，再去面自己想去的公司的。结果才试了一家，阿里就来面试了。而且我觉得我的水平确实不够阿里的层次。虽然我开发经验比较多一些，但是他们更关注你对底层实现的理解，说来惭愧，一直在做东西，没有时间关注这些。这次面试，就我本人而言，没有任何亮点，没有说出来一个让人觉得还可以的东西，所以没戏。&lt;/p&gt;

&lt;p&gt;回来的路上，看到1号线上的一个广告词：我想要怒放的生命。路一直都在，继续努力吧。&lt;/p&gt;

&lt;p&gt;2013年9月16日于寝室  回来的路上，看到1号线上的一个广告词：我想要怒放的生命。路一直都在，继续努力吧。&lt;/p&gt;

&lt;p&gt;2013年9月16日于寝室&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/cyeam.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>神州航天软件发展规划部实习笔试</title>
   <link href="https://blog.cyeam.com/collection/2013/09/09/bsastintern"/>
   <updated>2013-09-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/09/09/bsastintern</id>
   <content type="html">&lt;h5 id=&quot;1-修改bug之后的测试&quot;&gt;1. 修改bug之后的测试&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;回归测试（验证修改是否破坏了现有的功能，测试验证修改工作本身）&lt;/li&gt;
  &lt;li&gt;Beta测试可称为确认测试，在一个真实的环境中以实际的数据来运行测试，以确认性能，系统运行有效率，系统撤消与备份作业正常，通过测试让信息系统日后可以更趋完善。&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;2-jsp用于输出的内置对象&quot;&gt;2. jsp用于输出的内置对象&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;out 输出流&lt;/li&gt;
  &lt;li&gt;request 由WEB浏览器或其它客户端生成地HTTP请求的细节&lt;/li&gt;
  &lt;li&gt;response 此对象封装了返回到HTTP客户端的输出&lt;/li&gt;
  &lt;li&gt;session 跟踪对话&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;3-快速排序&quot;&gt;3. 快速排序&lt;/h5&gt;

&lt;h5 id=&quot;4-广度优先排序&quot;&gt;4. 广度优先排序&lt;/h5&gt;

&lt;h5 id=&quot;5-设有图书管理数据库&quot;&gt;5. 设有图书管理数据库：&lt;/h5&gt;
&lt;p&gt;图书(总编号C(6),分类号C(8),书名C(16),作者C(6),出版单位C(20),单价N(6,2))
读者(借书证号C(4),单位C(8),姓名C(6),性别C(2),职称C(6),地址C(20))
借阅(借书证号C(4),总编号C(6),借书日期D(8))&lt;/p&gt;

&lt;h6 id=&quot;a-对于图书管理数据库检索藏书中比高等教育出版社的所有图书的书价更高的书&quot;&gt;a. 对于图书管理数据库，检索藏书中比高等教育出版社的所有图书的书价更高的书。&lt;/h6&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT * FROM 图书 WHERE 单价&amp;gt;ALL(SELECT 单价 FROM 图书 WHERE 出版单位=&quot;高等教育出版社&quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;b-对于图书管理数据库分别求出各个单位当前借阅图书的读者人次&quot;&gt;b. 对于图书管理数据库，分别求出各个单位当前借阅图书的读者人次。&lt;/h6&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT 单位,COUNT(借阅.借书证号) FROM 借阅,读者 WHERE 借阅.借书证号=读者.借书证号 GROUP BY 单位;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;c-对于图书管理数据库检索所有借阅了图书的读者姓名和所在单位&quot;&gt;c. 对于图书管理数据库，检索所有借阅了图书的读者姓名和所在单位。&lt;/h6&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    SELECT DISTINCT 姓名,单位 FROM 读者,借阅 WHERE 读者.借书证号=借阅.借书证号;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;d-对于图书管理数据库求电子工业出版社出版图书的最高单价最低单价和平均单价&quot;&gt;d. 对于图书管理数据库，求电子工业出版社出版图书的最高单价、最低单价和平均单价。&lt;/h6&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT MAX(单价),MIN(单价),AVG(单价)FROM 图书 WHERE 出版单位=&quot;电子工业出版社&quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;e-对于图书管理数据库求cie单位借阅图书的读者的人数&quot;&gt;e. 对于图书管理数据库，求CIE单位借阅图书的读者的人数。&lt;/h6&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT COUNT (DISTINCT 借书证号) FROM 借阅 WHERE 借书证号 IN (SELECT 借书证号 FROM 读者 WHERE 单位=&quot;CIE&quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;f-重名的人&quot;&gt;f. 重名的人。&lt;/h6&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;select 姓名, count(*) from 读者 group by 姓名having count(*) &amp;gt; 1;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>阿里巴巴2013年实习生笔试题</title>
   <link href="https://blog.cyeam.com/collection/2013/05/23/alibaba"/>
   <updated>2013-05-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/05/23/alibaba</id>
   <content type="html">&lt;h3 id=&quot;一单项选择题&quot;&gt;一、单项选择题&lt;/h3&gt;

&lt;h5 id=&quot;1下列说法错误的是&quot;&gt;1.下列说法错误的是：&lt;/h5&gt;

&lt;p&gt;A.SATA硬盘的速度大约为500Mbps/s&lt;/p&gt;

&lt;p&gt;B.读取18XDVD光盘数据的速度为1Gbps&lt;/p&gt;

&lt;p&gt;C.千兆以太网的数据读取速度为1Gpbs&lt;/p&gt;

&lt;p&gt;D.读取DDR3内存数据的速度为100Gbps&lt;/p&gt;

&lt;h5 id=&quot;2不能用于linux中的进程通信&quot;&gt;2.（）不能用于Linux中的进程通信&lt;/h5&gt;

&lt;p&gt;A.共享内存&lt;/p&gt;

&lt;p&gt;B.命名管道&lt;/p&gt;

&lt;p&gt;C.信号量&lt;/p&gt;

&lt;p&gt;D.临界区&lt;/p&gt;

&lt;h5 id=&quot;3设在内存中有p1p2p3三道程序并按照p1p2p3的优先级次序运行其中内部计算和io操作时间由下表给出cpu计算和io资源都只能同时由一个程序占用&quot;&gt;3.设在内存中有P1,P2,P3三道程序，并按照P1,P2,P3的优先级次序运行，其中内部计算和IO操作时间由下表给出（CPU计算和IO资源都只能同时由一个程序占用）：&lt;/h5&gt;

&lt;p&gt;P1:计算60ms—》IO 80ms—》计算20ms&lt;/p&gt;

&lt;p&gt;P2:计算120ms—》IO 40ms—》计算40ms&lt;/p&gt;

&lt;p&gt;P3:计算40ms—》IO 80ms—》计算40ms&lt;/p&gt;

&lt;p&gt;完成三道程序比单道运行节省的时间是（）&lt;/p&gt;

&lt;p&gt;A.80ms&lt;/p&gt;

&lt;p&gt;B.120ms&lt;/p&gt;

&lt;p&gt;C.160ms&lt;/p&gt;

&lt;p&gt;D.200ms&lt;/p&gt;

&lt;h5 id=&quot;4两个等价线程并发的执行下列程序a为全局变量初始为0假设printf操作都是原子性的则输出不可能是&quot;&gt;4.两个等价线程并发的执行下列程序，a为全局变量，初始为0，假设printf、++、–操作都是原子性的，则输出不可能是（）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void foo() {
    if(a &amp;amp;lt;= 0) {
        a++;
    }
    else{
        a--;
    }
    printf(&quot;%d&quot;, a);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A.01&lt;/p&gt;

&lt;p&gt;B.10&lt;/p&gt;

&lt;p&gt;C.12&lt;/p&gt;

&lt;p&gt;D.22&lt;/p&gt;

&lt;h5 id=&quot;5给定fun函数如下那么fun10的输出结果是&quot;&gt;5.给定fun函数如下，那么fun(10)的输出结果是（）&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int fun(intx)
{
    return(x==1)? 1 : (x + fun(x-1));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A.0&lt;/p&gt;

&lt;p&gt;B.10&lt;/p&gt;

&lt;p&gt;C.55&lt;/p&gt;

&lt;p&gt;D.3628800&lt;/p&gt;

&lt;h5 id=&quot;6在c程序中如果一个整型变量频繁使用最好将他定义为&quot;&gt;6.在c++程序中，如果一个整型变量频繁使用，最好将他定义为（）&lt;/h5&gt;

&lt;p&gt;A.auto&lt;/p&gt;

&lt;p&gt;B.extern&lt;/p&gt;

&lt;p&gt;C.static&lt;/p&gt;

&lt;p&gt;D.register&lt;/p&gt;

&lt;h5 id=&quot;7长为n的字符串中匹配长度为m的子串的复杂度为&quot;&gt;7.长为n的字符串中匹配长度为m的子串的复杂度为（）&lt;/h5&gt;
&lt;p&gt;A.O(N)&lt;/p&gt;

&lt;p&gt;B.O(M+N)&lt;/p&gt;

&lt;p&gt;C.O(N+logM)&lt;/p&gt;

&lt;p&gt;D.O(M+logN)&lt;/p&gt;

&lt;h5 id=&quot;8判断一包含n个整数a中是否存在ijk满足ai--aj--ak的时间复杂度最小值是&quot;&gt;8.判断一包含n个整数a[]中是否存在i、j、k满足a[i] + a[j] = a[k]的时间复杂度最小值是（）&lt;/h5&gt;
&lt;p&gt;A.O(n^2) B. O(n^2*logn) C. O(n^3) D. O(nlogn)&lt;/p&gt;

&lt;h5 id=&quot;9下列序排算法中最坏情况下的时间复杂度不是nn-12的是&quot;&gt;9.下列序排算法中最坏情况下的时间复杂度不是n(n-1)/2的是&lt;/h5&gt;
&lt;p&gt;A.快速排序 B.冒泡排序 C.直接插入排序 D.堆排序&lt;/p&gt;

&lt;h5 id=&quot;10发射三次炮弹射中目标的概率是095请问发射一次能击中目标的概率是多少&quot;&gt;10.发射三次炮弹，射中目标的概率是0.95，请问发射一次能击中目标的概率是多少？&lt;/h5&gt;

&lt;p&gt;A.0.63&lt;/p&gt;

&lt;p&gt;B.0.50&lt;/p&gt;

&lt;p&gt;C.0.32&lt;/p&gt;

&lt;p&gt;D.0.86&lt;/p&gt;

&lt;h3 id=&quot;二不定向选择题&quot;&gt;二、不定向选择题&lt;/h3&gt;

&lt;h5 id=&quot;1进程状态转换下列转换会发生的有-a就绪-运行-----b运行-就绪-c运行--阻塞-d阻塞--运行&quot;&gt;1.进程状态转换，下列转换会发生的有（） A就绪 运行     B运行 就绪 C运行  阻塞 D阻塞  运行&lt;/h5&gt;

&lt;h5 id=&quot;2一个栈的入栈数列为123456下列哪个是可能的出栈顺序&quot;&gt;2.一个栈的入栈数列为：1、2、3、4、5、6；下列哪个是可能的出栈顺序。&lt;/h5&gt;
&lt;p&gt;A 123465  B 154623  C 312546  D 325641&lt;/p&gt;

&lt;h5 id=&quot;3下列哪些代码可以使得a和b交换数值&quot;&gt;3.下列哪些代码可以使得a和b交换数值&lt;/h5&gt;
&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b=a+b; a=a+b; b=b-a&lt;/code&gt;; B &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a=a|b; b=b+a; a=b-a&lt;/code&gt;; C &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a=a-b; b=a+b; a=b-a&lt;/code&gt;; D &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a=a+b;b=a-b;a=b|a&lt;/code&gt;;&lt;/p&gt;

&lt;h5 id=&quot;4两人数星星每回数k个20k30设定数完最后一批的人获胜当a先数则星星总数为多少时a才胜出-a-2013--b-2888--c-3935--d-4026--e-25051&quot;&gt;4.两人数星星，每回数k个（20&amp;lt;=k&amp;lt;=30），设定数完最后一批的人获胜。当A先数，则星星总数为多少时，A才胜出 A 2013  B 2888  C 3935  D 4026  E 25051&lt;/h5&gt;

&lt;h3 id=&quot;三填空问答题&quot;&gt;三、填空问答题&lt;/h3&gt;

&lt;h5 id=&quot;1给你一个整型数组an完成一个小程序代码20行之内使得an逆向即原数组为1234逆向之后为4321&quot;&gt;1.给你一个整型数组A[N]，完成一个小程序代码（20行之内），使得A[N]逆向，即原数组为1，2，3，4，逆向之后为4，3，2，1&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	void revense(int * a,int n) {
        int begin = 0, end = n-1;
        int tmp;
        while(begin &amp;amp;lt; end)
        {
                tmp = a[begin];
                a[begin] = a[end];
                a[end] = tmp;
                ++begin;
                --end;
	}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;2自选调度方面的问题题目很长就是给你三个线程分别采用先来先分配的策略和最短执行之间的调度策略然后计算每个线程从提交到执行完成的时间题目实在太长还有几个表格考察的是操作系统里面作业调度算法先进先出和最短作业优先&quot;&gt;2.自选调度方面的问题，题目很长，就是给你三个线程，分别采用先来先分配的策略和最短执行之间的调度策略，然后计算每个线程从提交到执行完成的时间。题目实在太长，还有几个表格。考察的是操作系统里面作业调度算法先进先出和最短作业优先。&lt;/h5&gt;

&lt;h5 id=&quot;3有个苦逼的上班族他每天忘记定闹钟的概率为02上班堵车的概率为05如果他既没定闹钟上班又堵车那他迟到的概率为10如果他定了闹钟但是上班堵车那他迟到的概率为09如果他没定闹钟但是上班不堵车他迟到的概率为08如果他既定了闹钟上班又不堵车那他迟到的概率为00那么求出他在60天里上班迟到的期望&quot;&gt;3.有个苦逼的上班族，他每天忘记定闹钟的概率为0.2，上班堵车的概率为0.5，如果他既没定闹钟上班又堵车那他迟到的概率为1.0，如果他定了闹钟但是上班堵车那他迟到的概率为0.9，如果他没定闹钟但是上班不堵车他迟到的概率为0.8，如果他既定了闹钟上班又不堵车那他迟到的概率为0.0，那么求出他在60天里上班迟到的期望。&lt;/h5&gt;

&lt;h5 id=&quot;4战报交流战场上不同的位置有n个战士n4每个战士知道当前的一些战况现在需要这n个战士通过通话交流互相传达自己知道的战况信息每次通话可以让通话的双方知道对方的所有情报设计算法使用最少的通话次数是的战场上的n个士兵知道所有的战况信息不需要写程序代码得出最少的通话次数&quot;&gt;4.战报交流：战场上不同的位置有N个战士（n&amp;gt;4），每个战士知道当前的一些战况，现在需要这n个战士通过通话交流，互相传达自己知道的战况信息，每次通话，可以让通话的双方知道对方的所有情报，设计算法，使用最少的通话次数，是的战场上的n个士兵知道所有的战况信息，不需要写程序代码，得出最少的通话次数。&lt;/h5&gt;

&lt;h5 id=&quot;5有n个人其中一个明星和n-1个群众群众都认识明星明星不认识任何群众群众和群众之间的认识关系不知道现在如果你是机器人r2t2你每次问一个人是否认识另外一个人的代价为o1试设计一种算法找出明星并给出时间复杂度没有复杂度不得分&quot;&gt;5.有N个人，其中一个明星和n-1个群众，群众都认识明星，明星不认识任何群众，群众和群众之间的认识关系不知道，现在如果你是机器人R2T2，你每次问一个人是否认识另外一个人的代价为O(1)，试设计一种算法找出明星，并给出时间复杂度（没有复杂度不得分）。）&lt;/h5&gt;

&lt;h3 id=&quot;四综合题&quot;&gt;四、综合题&lt;/h3&gt;

&lt;p&gt;有一个淘宝商户，在某城市有n个仓库，每个仓库的储货量不同，现在要通过货物运输，将每次仓库的储货量变成一致的，n个仓库之间的运输线路围城一个圈，即1-&amp;gt;2-&amp;gt;3-&amp;gt;4-&amp;gt;…-&amp;gt;n-&amp;gt;1-&amp;gt;…，货物只能通过连接的仓库运输，设计最小的运送成本（运货量*路程）达到淘宝商户的要求，并写出代码。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>微软2012年实习生笔试题</title>
   <link href="https://blog.cyeam.com/collection/2013/04/20/microsoft_intern_2012"/>
   <updated>2013-04-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/04/20/microsoft_intern_2012</id>
   <content type="html">&lt;p&gt;1.Suppose that a selection sort of 80 items has completed 32 iterations of the main loop. How many items are now guaranteed to be in their final spot (never to be moved again)? （&lt;strong&gt;C&lt;/strong&gt;）&lt;/p&gt;

&lt;p&gt;A、16 B、31 C、32 D、39 E、40&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;80个元素进行选择排序，进行了32次循环，问有哪些元素已经在最终位置上了。考察选择排序基本原理。每一趟遍历都会从剩余待排元素中选择一个最大(小)的元素放在最终位置上，所以进行32次循环就是有32个元素在最终位置上了，选C。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;2.Which synchronization mechanism(s) is/are used to avoid race conditions among processes/threads in operating system?(&lt;strong&gt;&lt;em&gt;AC&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、Mutex B、Mailbox C、Semaphore D、Local procedure call&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Mutex,(Mutual exclusion, 互斥锁)，可以通过互斥的方式来保护临界资源。有硬件和软件两种实现方式；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Semaphore，信号量，用户多线程同步。值为2的信号量就是互斥锁；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;Local procedure call，本地过程调用。&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3.There is a sequence of n numbers 1,2,3,…,n and a stack which can keep m numbers at most. Push the n numbers into the stack following the sequence and pop out randomly . Suppose n is 2 and m is 3,the output sequence may be 1,2 or 2,1,so we get 2 different sequences . Suppose n is 7,and m is 5,please choose the output sequence of the stack. (&lt;strong&gt;&lt;em&gt;AC&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、1,2,3,4,5,6,7&lt;br /&gt;
B、7,6,5,4,3,2,1&lt;br /&gt;
C、5,6,4,3,7,2,1&lt;br /&gt;
D、1,7,6,5,4,3,2&lt;br /&gt;
E、3,2,1,7,5,6,4&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;有一个大小为5的栈，有1～7的5个数，随机的进出栈，判断出栈结果。常见的数据结构栈的题目。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;如果每push进去一个数字，立刻pop出来，结果就是A，此时需要栈空间最小为1；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;B中第一个pop出来的是7，说明是将这7个数字都push进栈之后才pop，需要栈空间最小为7；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;C中第一个pop出来的是5，说明栈中之前有5个元素(1,2,3,4,5)，pop之后，还剩4个(1,2,3,4)。然后push进6，pop出4和3，push 7，pop2和1，此过程中，需要最小的栈空间为5；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;D中push 1之后立刻pop，之后pop出来的是7，说明pop 7之前栈中有6个元素，需要最小的栈空间是6；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;E中首先pop 3，2，1，此时栈为空，然后将4，5，6，7依次push进栈。此时战中是(4,5,6,7)，pop的话结果只有一种，就是7,6,5,4，此选项的出栈序列是错误的。&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4.Which is the result of binary number 01011001 after multiplying by 0111001 and adding 1101110? (&lt;strong&gt;&lt;em&gt;A&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、0001010000111111&lt;br /&gt;
B、0101011101110011&lt;br /&gt;
C、0011010000110101&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;如果数字二进制乘法和加法的话可以考虑计算一下，这里是将二进制数字转换成十进制后计算再转成二进制。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;题目要计算01011001(89)&lt;/em&gt;0111001(57)+1101110(110)，得到5183，转换为二进制为0001010000111111。&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;5.What is output if you compile and execute the following c code?(&lt;strong&gt;&lt;em&gt;C&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int main() 
{ 
    int i = 11; 
    int const * p = &amp;amp;i; 
    p++; 
    printf(&quot;%d&quot;, *p); 
    return 0; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;此处，第4行声明了指向常量的指针，说明指针p所指向的int类型的变量i不可以更改。接着，第五行执行p++，p指向了后四个字节的内存，由于该内存并没有进行初始化操作，所以此时p的值不确定，为Garbage value，选C。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A、11 B、12 C、Garbage value D、Compile error E、None of above&lt;/p&gt;

&lt;p&gt;6.Which of following C++ code is correct ?(&lt;strong&gt;&lt;em&gt;C&lt;/em&gt;&lt;/strong&gt;)
A.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int f() 
{ 
    int *a = new int(3); 
    return *a; 
} B.

int *f() 
{ 
    int a[3] = {1,2,3}; 
    return a; 
} C.

vector&amp;lt;int&amp;gt; f() 
{ 
    vector&amp;lt;int&amp;gt; v(3); 
    return v; 
} D.

void f(int *ret) 
{ 
    int a[3] = {1,2,3}; 
    ret = a; 
    return ; 
} E. None of above
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;(A)选项，会动态分配一个指向int类型，值为3的指针，但是这样并不会释放内存，导致内存泄漏；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;(B)a[3]是局部变量，会分配在栈中，函数返回后就会释放。这样编译的过程中会有警告；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;(C)STL中的ector是动态分配到堆中的，正确；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;(D)ret只是赋给了指针a的值，而指针a保存的是指向局部变量的地址，错误同(B)。&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7.Given that the 180-degree rotated image of a 5-digit number is another 5-digit number and the difference between the numbers is 78633, what is the original 5-digit number？(&lt;strong&gt;&lt;em&gt;D&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、60918 B、91086 C、18609 D、10968 E、86901&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;算是一道智力题。五位数旋转180度得到另一个五位数，两个数的差是78633。问原来的那个五位数是多少。10968旋转后是89601，差正好是78633。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;8.Which of the following statements are true?(&lt;strong&gt;&lt;em&gt;ACD&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、We can create a binary tree from given inorder and preorder traversal sequences.
B、We can create a binary tree from given preorder and postorder traversal sequences.
C、For an almost sorted array,Insertion sort can be more effective than Quciksort.
D、Suppose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T(n)&lt;/code&gt; is the runtime of resolving a problem with n elements, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T(n)=O(1)&lt;/code&gt; if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n=1&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T(n)=2*T(n/2)+O(n)&lt;/code&gt; if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&amp;gt;1&lt;/code&gt;; so T(n) is O(nlgn)
E、None of above&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;根据前序和后序序列无法唯一确定一颗二叉树，例如前序为abc，后序为cba，就会有两种结果；必须要有中序遍历；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;前序和中序，或者后序和中序，可以确定根结点位置，从而得到二叉树；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;对于基本有序的序列，插入排序比快速排序有效。快速排序适于基本无序的情况；&lt;/em&gt;&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;&lt;em&gt;T(1)=1;T(2)=2T(2)+2=4=2&lt;/em&gt;2;T(4)=2T(2)+4=12=4&lt;em&gt;3=N&lt;/em&gt;(k+1)=2^{k}times (k+1);T(N)=Ntimes (log_2 N+1).&lt;/strong&gt;*&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;9.Which of the following statements are true?(&lt;strong&gt;&lt;em&gt;ABD&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、Insertion sort and bubble sort are not efficient for large data sets.
B、Qucik sort makes O(n^2) comparisons in the worst case.
C、There is an array :7,6,5,4,3,2,1. If using selection sort (ascending),the number of swap operations is 6
D、Heap sort uses two heap operations:insertion and root deletion （插入、堆调整）
E、None of above&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;插入排序和冒泡排序时间复杂度都是o(n^{2})，对于大数据集效率很低；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;快序排序最差情况，就是序列是倒序的时候，此时快速排序会退化成成冒泡排序，复杂度变为o(n^{2});&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;7,6,5,4,3,2,1，使用选择排序，首先选到最小的1，与队列头7交换，变成1,6,5,4,3,2,7。然后选到2，与队列头6交换，变成1,2,5,4,3,6,7，选择到3，与队列头5交换，得到1,2,3,4,5,6,7，只交换了3次；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;堆排序两个操作是建堆和堆调整。&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;10.Assume both x and y are integers, which one of the following returns the minimun of the two integers?(&lt;strong&gt;&lt;em&gt;A&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y^((x^y) &amp;amp; -(x&amp;lt;y))&lt;/code&gt;
B、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y^(x^y)&lt;/code&gt;
C、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x^(x^y)&lt;/code&gt;
D、 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x^y)^(y^x)&lt;/code&gt;
E、 None of the above&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;据异或操作的自反性，(B)的结果是x，(C)的结果是y，(D)的结果是0。下面来看(A)，如果x&amp;lt;y，(x&amp;lt;y)结果是1，而-1的补码是11111111，所以原式变为：&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;y^((x^y) &amp;amp; 11111111)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;结果是x；如果x&amp;gt;y，(x&amp;lt;y)结果是0，而0的补码是00000000，所以原式化简为&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;y^((x^y) &amp;amp; 00000000)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;结果是y，(A)正确。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;11.The Orchid Pavilion(兰亭集序) is well known as the top of “行书”in history of Chinese literature. The most fascinating sentence is “Well I know it is a lie to say that life and death is the same thing, and that longevity and early death make no difference Alas!”(固知一死生为虚诞，齐彭殇为妄作).By counting the characters of the whole content (in Chinese version),the result should be 391(including punctuation). For these characters written to a text file,please select the possible file size without any data corrupt.（&lt;strong&gt;ABCD&lt;/strong&gt;）&lt;/p&gt;

&lt;p&gt;A、782 bytes in UTF-16 encoding
B、784 bytes in UTF-16 encoding
C、1173 bytes in UTF-8 encoding
D、1176 bytes in UTF-8 encoding
E、None of above&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;关于编码，最早是ACSII，里面之后256个字符。这只能满足英文编码。我国发展出了GBK、Big5等中文编码，但是问题是，每个国家都定义了自己国家语言的编码，用不同的方式可以解出不同的文字。后来为了统一，出现了Unicode。Unicode的实现有几种方式。常见的就是UTF8、UTF16。UTF8以8位为单位，可以有1字节、2字节、3字节、4字节四种长度。而中文在UTF8编码当中是占3个字节。UTF16是以16位作为单位长度，有2字节和4字节两种。中文在UTF16当中占两个字节。Unicode也可以指是UTF16编码。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Unicode有多种实现方式，所以需要在开头定义出所用的编码形式：&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;UTF编码　&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Byte Order Mark&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;UTF-8&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　EF BB BF&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;UTF-16LE&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;FF FE&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;UTF-16BE&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　FE FF&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;UTF-32LE&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　FF FE 00 00&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;UTF-32BE&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;　00 00 FE FF&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;UTF8的Bom头占3个字节，UTF16的Bom头占2个字节。而Bom头并未规定是必须加的，也可以不加。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;下面看这道题，391个字，UTF8将会占1173个字节，加上Bom头占1176个字节。UTF16是782个字节，加上Bom头是784个字节。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;12.Fill the blanks inside class definition&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Test 
{ 
public: 
    ____ int a; 
    ____ int b; 
    public: 
    Test::Test(int _a , int _b) : a( _a ) 
    { 
        b = _b; 
    } 
}; 
int Test::b; 

int main(void) 
{ 
    Test t1(0 , 0) , t2(1 , 1); 
    t1.b = 10; 
    t2.b = 20; 
    printf(&quot;%u %u %u %u&quot;,t1.a , t1.b , t2.a , t2.b); 
    return 0; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running result : 0 20 1 20&lt;/p&gt;

&lt;p&gt;(&lt;strong&gt;&lt;em&gt;C&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、static/const B、const/static C、–/static D、conststatic/static E、None of above&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;在17、18行，分别修改了对象t1和t2的成员变量b，输出之后，值一样，同为20，说明b为静态变量，可以被所有对象共享，所以b是static静态变量；变量a的值是通过Test类的构造函数的参数表进行初始化的，所以此处可以是空或者是const(const对象可以使用参数表进行初始化)。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;13.A 3-order B-tree has 2047 key words,what is the maximum height of the tree?&lt;/p&gt;

&lt;p&gt;A、11 B、12 C、13 D、14&lt;/p&gt;

&lt;p&gt;14.In C++,which of the following keyword(s) can be used on both a variable and a function?(&lt;strong&gt;&lt;em&gt;ACE&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、static B、virtual C、extern D、inline E、const&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;static关键字，可以用来声明静态变量或者静态函数。static声明后在全局中唯一存在；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;virtual关键字，C++语法，用来声明虚函数。虚函数声明之后，可以在派生类中实现。这与Java中的接口定义类似，可以帮助实现多态；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;extern关键字。对于一个全局变量，它既可以在本源文件中被访问到，也可以在同一个工程的其它源文件中被访问，只需用extern进行声明即可。在C语言中，修饰符extern用在变量或者函数的声明前，用来说明此变量/函数是在别处定义的，要在此处引用。在C++中extern还有另外一种作用，用于指示C或者C＋＋函数的调用规范。比如在C++中调用C库函数，就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的，告诉链接器在链接的时候用C函数规范来链接。主要原因是C＋＋和C程序编译完成后在目标代码中命名规则不同，用此来解决名字匹配的问题；&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;inline关键字，用来指示内联函数。每一次调用函数，都会使用到内存中的栈，用来保存此函数的地址、参数表、返回值等信息。如果此函数频繁被使用并且函数逻辑简单，就可以使用inline关键字。在C语言中，#define的作用类似于inline关键字。&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;const关键字，可以用来声明常量，常函数void Func(int a) const。&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;15.What is the result of the following program?(&lt;strong&gt;&lt;em&gt;D&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;char* f(char* str, char ch) 
{ 
    char* it1 = str; 
    char* it2 = str; 
    while(*it2 != &apos;\0&apos;) 
    { 
        while(*it2 == ch) 
        { 
            it2++; 
        } 
        *it1++ = *it2++; 
    } 
    return str; 
} 
 
int main(int argc, char* argv[]) 
{ 
    char* a = new char[10]; 
    strcpy(a, &quot;abcdcccd&quot;); 
    cout&amp;lt;&amp;lt;f(a, &apos;c&apos;); 
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A、abdcccd B、abdd C、abcc D、abddcccd E、Access violation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;简单考察了C-style string概念的理解。首先将常量字符串”abcdcccd”赋值给变量a，然后调用函数f。while(&lt;/em&gt;it2 != ‘’)用来遍历整个字符串。内存循环&lt;/strong&gt;*&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;while(*it2 == ch)  
{  
      it2++;  
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;用来将指定字符去除，这里是要将字符’c’去除，所以去除之后是”abdd”。然而，虽然去除了指定字符，但是并没有在该字符串末尾追加’’，所以”abdd”并不是最终结果，当输出的时候，结果是输出到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;为止，所以输出完”abdd”后还会继续输出。所以原字符串的cccd也会输出。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;16.Consider the following definition of a recursive function,power,that will perform exponentiation.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int power(int b , int e) 
{ 
    if(e == 0) 
        return 1; 
    if(e % 2 == 0) 
        return power(b*b , e/2); 
    else 
        return b * power(b*b , e/2); 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Asymptotically（渐进地） in terms of the exponent e,the number of calls to power that occur as a result of the call power(b,e) is&lt;/p&gt;

&lt;p&gt;A、logarithmic
B、linear
C、quadratic
D、exponential&lt;/p&gt;

&lt;p&gt;17.Assume a full deck of cards has 52 cards,2 blacks suits (spade and club) and 2 red suits(diamond and heart). If you are given a full deck,and a half deck(with 1 red suit and 1 black suit),what is the possibility for each one getting 2 red cards if taking 2 cards?(&lt;strong&gt;&lt;em&gt;B&lt;/em&gt;&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;A、1/2 1/2
B、25/102 12/50
C、50/51 24/25
D、25/51 12/25
E、25/51 1/2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;一副扑克牌共52张，其中红色花色(红桃、方片)各13张，黑色花色(梅花、黑桃)各13张。现在有一副全套扑克牌，还有一套半套的扑克牌(有13张黑牌和13张红牌)，从这两副牌中抽取不放回两张牌，两次都是红色花色的概率。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;第一副全套牌，从54张牌中抽取1张红色的概率是frac{26}{52}，此时还剩51张牌，红色还剩25张，再抽出一张红色的概率是frac{25}{51}，两次都是红色的概率是frac{26}{52}&lt;/em&gt;frac{25}{51}=frac{25}{102}；&lt;/strong&gt;*&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;em&gt;第二副牌，计算方法相同，从26张牌中抽取一张红色牌概率是frac{13}{26}，从剩余25张牌中抽取12张红色中的一张的概率是frac{12}{25}，两次都是红色的概率是frac{13}{26}&lt;/em&gt;frac{12}{25}=frac{6}{25}。选B。&lt;/strong&gt;*&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;18.There is a stack and a sequence of n numbers(i.e. 1,2,3,…,n), Push the n numbers into the stack following the sequence and pop out randomly . How many different sequences of the n numbers we may get? Suppose n is 2 , the output sequence may 1,2 or 2,1, so wo get 2 different sequences .&lt;/p&gt;

&lt;p&gt;A、C_2n^n
B、C_2n^n - C_2n^(n+1)
C、((2n)!)/(n+1)n!n!
D、n!
E、None of above&lt;/p&gt;

&lt;p&gt;19.Longest Increasing Subsequence(LIS) means a sequence containing some elements in another sequence by the same order, and the values of elements keeps increasing.
For example, LIS of {2,1,4,2,3,7,4,6} is {1,2,3,4,6}, and its LIS length is 5.
Considering an array with N elements , what is the lowest time and space complexity to get the length of LIS？&lt;/p&gt;

&lt;p&gt;A、Time : N^2 , Space : N^2
B、Time : N^2 , Space : N
C、Time : NlogN , Space : N
D、Time : N , Space : N
E、Time : N , Space : C&lt;/p&gt;

&lt;p&gt;20.What is the output of the following piece of C++ code ？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt; 
using namespace std; 

struct Item 
{ 
    char c; 
    Item *next; 
}; 

Item *Routine1(Item *x) 
{ 
    Item *prev = NULL, 
    *curr = x; 
    while(curr) 
    { 
        Item *next = curr-&amp;gt;next; 
        curr-&amp;gt;next = prev; 
        prev = curr; 
        curr = next; 
    } 
    return prev; 
} 

void Routine2(Item *x) 
{ 
    Item *curr = x; 
    while(curr) 
    { 
        cout&amp;lt;&amp;lt;curr-&amp;gt;c&amp;lt;&amp;lt;&quot; &quot;; 
        curr = curr-&amp;gt;next; 
    } 
} 

int main(void) 
{ 
    Item *x, 
    d = {&apos;d&apos; , NULL}, 
    c = {&apos;c&apos; , &amp;amp;d}, 
    b = {&apos;b&apos; , &amp;amp;c}, 
    a = {&apos;a&apos; , &amp;amp;b}; 
    x = Routine1( &amp;amp;a ); 
    Routine2( x ); 
    return 0; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A、 c b a d B、 b a d c C、 d b c a D、 a b c d E、 d c b a&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>快速排序(QUICK SORT)</title>
   <link href="https://blog.cyeam.com/computer/2013/04/17/quicksort"/>
   <updated>2013-04-17T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/04/17/quicksort</id>
   <content type="html">&lt;p&gt;冒泡排序是通过遍历整个链表，进行两两比较、交换操作的排序方法。然而，该方法在每一趟遍历过程中，只是简单的在两个相邻的元素中操作，如果能对每一次的比较结果进行利用，有可能提高排序效率。&lt;/p&gt;

&lt;p&gt;快速排序就是这样的一种排序方法。同样是利用比较交换的思想，只是每次比较的元素选取不同：冒泡排序是通过比较相邻的两个元素，而快速排序是列表中元素与枢轴(Pivot)进行比较，大的元素交换到枢轴后面，小的元素交换到枢轴前面。这样就利用到了每一次的比较结果。&lt;/p&gt;

&lt;p&gt;如果一个队列中只有三个元素，分别是2、3、1，如果以2为枢轴，3比2大，1比2小，那么只需要将最小的1放在枢轴2的位置上，最大的3放到最小的1的位置上，2放在最大的3的位置上。&lt;/p&gt;

&lt;p&gt;快速排序一般步骤：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;选取枢轴，一般选取队列的第一个元素作为枢轴pivotkey；&lt;/li&gt;
  &lt;li&gt;分别从队列的首和尾进行遍历。从首部找到第一个大于枢轴的元素为止，位置记为i，从尾部找到第一个小于枢轴的元素为止，位置记为j；&lt;/li&gt;
  &lt;li&gt;交换操作是这三个元素进行交换。和前面提到的三个元素交换方法相类似，最小的元素j放在枢轴pivotkey上，最大的元素i放在最小的元素j的位置上，枢轴pivotkey放在最大的位置i上；&lt;/li&gt;
  &lt;li&gt;从两端想中间遍历，一趟遍历直到交汇为止，即i==j；&lt;/li&gt;
  &lt;li&gt;每一趟结束后，都会将当前队列分割成两个子队列，对两个子队列分别进行1～4步操作。直到子队列大小为1时，排序结束。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;快速排序特性：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;快速排序每一趟排序结束，都会有一个元素(即枢轴Pivot)在最终位置上，其前面的元素都比它小，后面的元素都比它大；&lt;/li&gt;
  &lt;li&gt;一趟排序结束，会产生两个子队列，分别对这两个子队列进行排序。这是分治的思想，分而治之；&lt;/li&gt;
  &lt;li&gt;如果待排序队列基本有序，例如正序或者逆序，此时枢轴为队列中最大(小)的值，所以从两个开始遍历的操作只能找到一个队列中最小(大)的值来进行交换，此时快速排序变退化成了冒泡排序。例如队列1、2、3和2、3、1，前者虽然不需要交换操作，但是需要两趟遍历，第一趟确定1的位置，第二趟确实2和3的位置，后者只需要一次交换操作就可以完成排序；&lt;/li&gt;
  &lt;li&gt;排序的效率和初始化队列排序情况与枢轴的选取有关。枢轴越接近队列的中间值越好，队列越混乱越好；&lt;/li&gt;
  &lt;li&gt;快速排序通过使用分治的思想，减少了排序趟数，冒泡排序数量级是O(n)，而快速排序数量级是O(log_{2} n)，所以时间复杂度是O(nlog_{} n)；&lt;/li&gt;
  &lt;li&gt;排序的过程中只是交换的时候需要申请临时空间，空间复杂度是O(1)；&lt;/li&gt;
  &lt;li&gt;快速排序不稳定。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;快速排序实例：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;{49, 38, 65, 97, 76, 13, 27, 49} 初始序列；&lt;/li&gt;
  &lt;li&gt;{27, 38, , 97, 76, 13, 65, 49} 将49作为枢轴，并将其保存在临时空间内，从38开始遍历，找到第一个大于49的元素65，然后从49开始遍历，找到第一个小于49的元素27。将这3个元素进行交换，枢轴的位置存放最小的元素27，最小元素13的位置存放最大元素65，最大元素位置存放枢轴49。继续遍历，找到最小元素13，最大元素97。一趟排序结束；&lt;/li&gt;
  &lt;li&gt;{27, 38, 13}, 49, {76, 97, 65, 49} 一趟排序结果；&lt;/li&gt;
  &lt;li&gt;{13}, 27, {38}, 49, {49, 65}, 76, {97} 对两个子序列分别进行快速排序；&lt;/li&gt;
  &lt;li&gt;13, 27, 38, 49, 49, 65, 76, 97 最终结果。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>冒泡排序(BUBBLE SORT)</title>
   <link href="https://blog.cyeam.com/computer/2013/04/16/bubblesort"/>
   <updated>2013-04-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/04/16/bubblesort</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;如果一个队列只有两个元素且为逆序，只要将这两个元素交换即可&lt;/strong&gt;。冒泡排序就是一种基于“交换”思想的排序方法，每次比较两个元素，如果为逆序，就将其交换。而这两个元素的选取，就是遍历整个队列来得到。&lt;/p&gt;

&lt;p&gt;冒泡排序一般步骤：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第一趟冒泡。遍历整个队列，将当前元素和身后的元素进行比较，逆序的进行交换。这样会进行(n-1)次；&lt;/li&gt;
  &lt;li&gt;然后再进行遍历冒泡，直到该趟遍历过程中没有发生交换操作为止，此时队列已为正序。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;冒泡排序特性：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;冒泡的过程中，每一次交换都是大元素向下移动，小元素向上移动，所以整个一趟排序的趋势就是大元素向下移动，小元素想上移动。因为在每一次交换的过程中，大元素都会被交换到下方，所以，一趟排序结束之后，都会有一个当前队列中最大的元素被交换到了队列最后，到达最终位置。&lt;/li&gt;
  &lt;li&gt;如果该队列为逆序，那就需要遍历(n-1)趟，一共sum_{i=n}^{2}(i-1)比较；如果为正序，那么只需要遍历一趟，进行(n-1)比较即可。平均时间复杂度是O(n^{2})；&lt;/li&gt;
  &lt;li&gt;该排序方法是就地排序，只是在交换的过程中需要申请一个临时变量，一个变量来记录当前遍历是否发生过交换操作，所以空间复杂度是O(1)；&lt;/li&gt;
  &lt;li&gt;冒泡排序基于交换的思想，所以可以保证两个相同的元素不会被交换，该算法是&lt;strong&gt;稳定&lt;/strong&gt;的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;冒牌排序实例：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;初始序列：{49, 38, 65, 97, 76, 13, 27, 49}&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 97, 76, 13, 27, 49} 元素49和38进行比较，逆序，交换；&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 76, 97, 13, 27, 49} 元素49行业64进行比较，正序，不交换；&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 76, 13, 27, 97, 49} 元素65和76进行比较，正序；&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 76, 13, 27, 49, 97} 元素76和13进行比较，逆序，交换；&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 13, 76, 27, 49, 97} 元素76和27进行比较，逆序，交换；&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 13, 27, 76, 49, 97} 元素76和49进行比较，逆序，交换；&lt;/li&gt;
  &lt;li&gt;{38, 49, 65, 13, 27, 49, 76}, 97 元素76和97进行比较，正序，不交换。一趟遍历结束，97到达最终位置；&lt;/li&gt;
  &lt;li&gt;{38, 49, 13, 27, 49, 65}, 76, 97 第二趟排序，76到达最终位置；&lt;/li&gt;
  &lt;li&gt;{38, 13, 27, 49, 49}, 65, 76, 97 第三趟排序结束，选出了65；&lt;/li&gt;
  &lt;li&gt;{13, 27, 38, 49}, 49, 65, 76, 97 第四趟排序&lt;/li&gt;
  &lt;li&gt;{13, 27, 38}, 49, 49, 65, 76, 97 第五趟排序&lt;/li&gt;
  &lt;li&gt;{13, 27}, 38, 49, 49, 65, 76, 97 第六趟排序&lt;/li&gt;
  &lt;li&gt;13, 27, 38, 49, 49, 65, 76, 97    最终结果&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>微软2013年夏季实习生笔试题</title>
   <link href="https://blog.cyeam.com/collection/2013/04/07/microsoft_intern_2013"/>
   <updated>2013-04-07T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/collection/2013/04/07/microsoft_intern_2013</id>
   <content type="html">&lt;h5 id=&quot;1-which-of-the-following-convensions-supports-support-variable-length-parameter-eg-printf-3-points&quot;&gt;1. Which of the following convension(s) support(s) support variable length parameter (e.g. printf)? (3 Points)&lt;/h5&gt;

&lt;p&gt;A. cdecl&lt;/p&gt;

&lt;p&gt;B. stdcall&lt;/p&gt;

&lt;p&gt;C. pascal&lt;/p&gt;

&lt;p&gt;D. fastcall&lt;/p&gt;

&lt;h5 id=&quot;2whats-the-output-of-the-following-code-3-points&quot;&gt;2.What’s the output of the following code? (3 Points)&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class A
{
public:
    virtual void f()
    {
        cout &amp;lt;&amp;lt; &quot;A::f()&quot; &amp;lt;&amp;lt; endl;
    }
    void f() const
    {
        cout &amp;lt;&amp;lt; &quot;A::f() const&quot; &amp;lt;&amp;lt; endl;
    }
};

class B: public A
{
public:
    void f()
    {
        cout &amp;lt;&amp;lt; &quot;B::f()&quot; &amp;lt;&amp;lt; endl;
    }
    
    void f() const
    {
        cout &amp;lt;&amp;lt; &quot;B::f() const&quot; &amp;lt;&amp;lt; endl;
    }
};

void g(const A *a)
{
    a-&amp;gt;f();
}

int main()
{
    A *a = new B();
    a-&amp;gt;f();
    g(a);
    delete a;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B::f() B::f() const&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;B. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B::f() A::f() const&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;C. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A::f() B::f() const&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;D. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A::f() A::f() const&lt;/code&gt;&lt;/p&gt;

&lt;h5 id=&quot;3what-is-the-difference-between-a-linked-list-and-an-array-3-points&quot;&gt;3.What is the difference between a linked list and an array? (3 Points)&lt;/h5&gt;

&lt;p&gt;A. Search complexity when both are sorted&lt;/p&gt;

&lt;p&gt;B. Dynamically add/remove&lt;/p&gt;

&lt;p&gt;C. Random access efficiency&lt;/p&gt;

&lt;p&gt;D. Data storage type&lt;/p&gt;

&lt;h5 id=&quot;4about-the-thread-and-process-in-windows-which-descriptions-is-are-correct-3-points&quot;&gt;4.About the Thread and Process in Windows, which description(s) is (are) correct? (3 Points)&lt;/h5&gt;

&lt;p&gt;A. One application in OS must have one Process, but not a necessary to have one Thread&lt;/p&gt;

&lt;p&gt;B. Thre process could have its own Stack but the thread only could share the Stack of its parent Process&lt;/p&gt;

&lt;p&gt;C. Thread must belong to a Process&lt;/p&gt;

&lt;p&gt;D. Thread could change its belonging Process&lt;/p&gt;

&lt;h5 id=&quot;5what-is-the-output-of-the-following-code-3-points&quot;&gt;5.What is the output of the following code? (3 Points)&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    int x = 10;
    int y = 10;
    x = x++;
    y = ++y;
    
    printf(&quot;%d, %d\n&quot;, x, y);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A. 10, 10&lt;/p&gt;

&lt;p&gt;B. 10, 11&lt;/p&gt;

&lt;p&gt;C. 11, 10&lt;/p&gt;

&lt;p&gt;D. 11, 11&lt;/p&gt;

&lt;h5 id=&quot;6for-the-following-java-or-c-code-3-points&quot;&gt;6.For the following Java or C# code (3 Points)&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int[][] myArray3 =
    new int[3][] {
        new int[3]{5, 6, 2},
        new int[5]{6, 9, 7, 8, 3},
        new int[2]{3, 2}
    };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What will &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myArray3[2][2]&lt;/code&gt; return?&lt;/p&gt;

&lt;p&gt;A. 9&lt;/p&gt;

&lt;p&gt;B. 2&lt;/p&gt;

&lt;p&gt;C. 6&lt;/p&gt;

&lt;p&gt;D. overflow&lt;/p&gt;

&lt;h5 id=&quot;7please-choose-the-right-statement-about-const-usage-3-points&quot;&gt;7.Please choose the right statement about const usage: (3 Points)&lt;/h5&gt;

&lt;p&gt;A. const int a; // const integer&lt;/p&gt;

&lt;p&gt;B. int const a; // const integer&lt;/p&gt;

&lt;p&gt;C. int const *a; // a pointer which point to const integer&lt;/p&gt;

&lt;p&gt;D. const int *a; // a const pointer which point to integer&lt;/p&gt;

&lt;p&gt;E. int const *a; // a const pointer which point to integer&lt;/p&gt;

&lt;h5 id=&quot;8given-the-following-code-3-points&quot;&gt;8.Given the following code: (3 Points)&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
class A
{
public:
    long a;
};

class B: public A
{
public:
    long b;
}

void seta(A *data, int idx)
{
    data[idx].a = 2;
}

int _tmain(int argc, _TCHAR *argv[])
{
    B data[4];
    
    for (int i = 0; i &amp;lt; 4; ++i)
    {
        data[i].a = 1;
        data[i].b = 1;
        seta(data, i);
    }
    
    for (int i = 0; i &amp;lt; 4; ++i)
    {
        std::cout &amp;lt;&amp;lt; data[i].a &amp;lt;&amp;lt; data[i].b;
    }
    
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What is the correct result?&lt;/p&gt;

&lt;p&gt;A. 11111111&lt;/p&gt;

&lt;p&gt;B. 12121212&lt;/p&gt;

&lt;p&gt;C. 11112222&lt;/p&gt;

&lt;p&gt;D. 21212121&lt;/p&gt;

&lt;h5 id=&quot;91-of-1000-bottles-of-water-is-poisoned-which-will-kill-a-rat-in-1-week-if-the-rat-drunk-any-amount-of-the-water-given-the-bottles-of-water-have-no-visual-difference-how-many-rates-are-needed-at-least-to-find-the-poisoned-one-in-1-week-5-points&quot;&gt;9.1 of 1000 bottles of water is poisoned which will kill a rat in 1 week if the rat drunk any amount of the water. Given the bottles of water have no visual difference, how many rates are needed at least to find the poisoned one in 1 week? (5 Points)&lt;/h5&gt;

&lt;p&gt;A. 9&lt;/p&gt;

&lt;p&gt;B. 10&lt;/p&gt;

&lt;p&gt;C. 32&lt;/p&gt;

&lt;p&gt;D. 999&lt;/p&gt;

&lt;p&gt;E. None of the above&lt;/p&gt;

&lt;p&gt;***为每瓶水编号，从1～1000，然后转成二进制，从0000000001~1111100111，找十只老鼠，按照二进制位对应得给老鼠喝水。最后查看死掉的老鼠编号，就能对应找到水的二进制编码。&lt;/p&gt;

&lt;h5 id=&quot;10which-of-following-statements-eauals-value-1-in-c-programming-language-5-points&quot;&gt;10.Which of following statement(s) eaual(s) value 1 in C programming language? (5 Points)&lt;/h5&gt;

&lt;p&gt;A. the return value of main function if program ends normally&lt;/p&gt;

&lt;p&gt;B. return (7 &amp;amp; 1);&lt;/p&gt;

&lt;p&gt;C. char *str = “microsoft”; return str == “microsoft”;&lt;/p&gt;

&lt;p&gt;D. return “microsoft” == “microsoft”;&lt;/p&gt;

&lt;p&gt;E. None of the above&lt;/p&gt;

&lt;h5 id=&quot;11if-you-computed-32-bit-signed-integers-f-and-g-from-32-bit-signed-integer-x-using-f--x2-and-g--x-1-and-if-you-found-f--g-this-implies-that-5-points&quot;&gt;11.If you computed 32 bit signed integers F and G from 32 bit signed integer X using F = X/2 and G = (X » 1), and if you found F != G, this implies that (5 Points)&lt;/h5&gt;

&lt;p&gt;A. There’s a compiler error&lt;/p&gt;

&lt;p&gt;B. x is odd&lt;/p&gt;

&lt;p&gt;C. X is negative&lt;/p&gt;

&lt;p&gt;D. F - G = 1&lt;/p&gt;

&lt;p&gt;E. G - F = 1&lt;/p&gt;

&lt;h5 id=&quot;12how-many-rectangles-you-can-find-from-3--4-gridd-5-points&quot;&gt;12.How many rectangles you can find from 3 * 4 grid?&lt;strong&gt;&lt;em&gt;D&lt;/em&gt;&lt;/strong&gt; (5 Points)&lt;/h5&gt;

&lt;p&gt;A. 18&lt;/p&gt;

&lt;p&gt;B. 20&lt;/p&gt;

&lt;p&gt;C. 40&lt;/p&gt;

&lt;p&gt;D. 60&lt;/p&gt;

&lt;p&gt;E. None of the above is correct&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;3&lt;/em&gt;4的网格，内部包含多少个矩形。长度取值有三种情况（1、2、3），宽度取值有四种情况（1、2、3、4），一共是C_{3}^{1}times C_{4}^{1}=12次。按照长宽的不同取值去计算，这样就不会少了。&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;1&lt;em&gt;1：12个；
1&lt;/em&gt;2：9个；
1&lt;em&gt;3：4个；
2&lt;/em&gt;1：8个；
2&lt;em&gt;2：6个；
2&lt;/em&gt;3：3个；
3&lt;em&gt;1：6个；
3&lt;/em&gt;2：4个；
3&lt;em&gt;3：2个；
4&lt;/em&gt;1：3个；
4&lt;em&gt;2：2个；
4&lt;/em&gt;3：1个。一共60个。&lt;/p&gt;

&lt;h5 id=&quot;13one-line-can-split-of-a-surface-to-2-parts-2-lines-can-split-a-surface-to-4-parts-given-100-lines-no-two-parallel-lines-no-three-lines-join-at-same-point-how-many-parts-can-100-lines-split-5-points&quot;&gt;13.One line can split of a surface to 2 parts, 2 lines can split a surface to 4 parts. Given 100 lines, no two parallel lines, no three lines join at same point, how many parts can 100 lines split? (5 Points)&lt;/h5&gt;

&lt;p&gt;A. 5051&lt;/p&gt;

&lt;p&gt;B. 5053&lt;/p&gt;

&lt;p&gt;C. 5510&lt;/p&gt;

&lt;p&gt;D. 5511&lt;/p&gt;

&lt;h5 id=&quot;14which-of-the-following-sorting-algorithms-isare-stabe-sorting-5-points&quot;&gt;14.Which of the following sorting algorithm(s) is(are) stabe sorting? (5 Points)&lt;/h5&gt;

&lt;p&gt;A. bubble sort&lt;/p&gt;

&lt;p&gt;B. quicksort&lt;/p&gt;

&lt;p&gt;C. heap sort&lt;/p&gt;

&lt;p&gt;D. merge sort&lt;/p&gt;

&lt;p&gt;E. Selection sort&lt;/p&gt;

&lt;h5 id=&quot;15model-view-contrller-mvc-is-an-architectural-pattern-that-is-frequently-used-in-web-applications-which-of-the-following-statements-isare-correct-5-points&quot;&gt;15.Model-View-Contrller (MVC) is an architectural pattern that is frequently used in web applications. Which of the following statement(s) is(are) correct? (5 Points)&lt;/h5&gt;

&lt;p&gt;A. Models often represent data and the business logics needed to manipulate the data in the application&lt;/p&gt;

&lt;p&gt;B. A View is a (visual) representation of its model. It renders the model into a form suitable for interaction, typically a user interface element&lt;/p&gt;

&lt;p&gt;C. A controller is the linke between a user and the system. It accepts input from the user and instructs the model and a view to perform actions based on that input&lt;/p&gt;

&lt;p&gt;D. The common practice of MVC in web applications is, the model receives GET or POST input from user and decides what to do with it, handing over to controller and which hand control to views (HTML-generating components)&lt;/p&gt;

&lt;h5 id=&quot;16we-can-recover-the-binary-tree-if-given-the-output-of-5-points&quot;&gt;16.We can recover the binary tree if given the output of (5 Points)&lt;/h5&gt;

&lt;p&gt;A. Preorder traversal and inorder traversal&lt;/p&gt;

&lt;p&gt;B. Preorder traversal and postorder traversal&lt;/p&gt;

&lt;p&gt;C. Inorder traversal and postorder traversal&lt;/p&gt;

&lt;p&gt;D. Postorder traversal&lt;/p&gt;

&lt;h5 id=&quot;17given-a-string-with-n-characters-suppose-all-the-characters-are-different-from-each-other-how-many-substrings-do-we-have-5-pointsc&quot;&gt;17.Given a string with n characters, suppose all the characters are different from each other, how many substrings do we have? (5 Points)&lt;strong&gt;&lt;em&gt;C&lt;/em&gt;&lt;/strong&gt;&lt;/h5&gt;

&lt;p&gt;A. n + 1&lt;/p&gt;

&lt;p&gt;B. n^2&lt;/p&gt;

&lt;p&gt;C. n(n+1)/2&lt;/p&gt;

&lt;p&gt;D. 2^n-1&lt;/p&gt;

&lt;p&gt;E. n!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;首先想到的是，定位一个开头，一个结束位置。开头位置好定义，是n，结束位置要受开头位置影响，因为必须要在它后面。如果定义在最前面，那么结束位置有n-1个选择；如果是第二个，那么是n-2。依次类推，如果开头定位在最后一位，那么结束也只能是最后一位，为1。是一个等差数列相加。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;还有一种解法，按照长度计算。如果长为1，有n中情况，长为2，有n-1中情况……长为n，只有一种情况。结果就是1+2+…n=n(n+1)/2&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h5 id=&quot;18given-the-following-database-table-how-many-rows-will-the-following-sql-statement-update-5-points&quot;&gt;18.Given the following database table, how many rows will the following SQL statement update? (5 Points)&lt;/h5&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;update Books set NumberofCopies = NumberOfCopies + 1 Where AuthorID
in
Select AutorID from Books
group by AuthorID
having sum(NumberOfCopies) &amp;lt;= 8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;BookID&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Title&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Category&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;NumberOfCopies&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;AuthorID&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;SQL Server 2008&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;MS&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;SharePoint 2007&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;MS&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;SharePoint 2010&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;MS&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;DB2&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;IBM&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;SQL Server 2012&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;MS&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;A. 1&lt;/p&gt;

&lt;p&gt;B. 2&lt;/p&gt;

&lt;p&gt;C. 3&lt;/p&gt;

&lt;p&gt;D. 4&lt;/p&gt;

&lt;p&gt;E. 5&lt;/p&gt;

&lt;h5 id=&quot;19what-is-the-sortest-path-between-s-and-node-t-given-the-graph-below-note-the-numbers-represent-the-lengths-of-the-connected-nodes-13-points&quot;&gt;19.What is the sortest path between S and node T, given the graph below? Note: the numbers represent the lengths of the connected nodes (13 Points)&lt;/h5&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/msintern2013.png&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;20given-a-set-of-n-balls-and-one-of-which-is-defective-weighs-less-than-others-you-are-allowed-to-weigh-with-a-balance-3-times-to-find-the-defective-which-of-the-following-are-possible-n-13-points&quot;&gt;20.Given a set of N balls and one of which is defective (weighs less than others), you are allowed to weigh with a balance 3 times to find the defective. Which of the following are possible N? (13 Points)&lt;/h5&gt;

&lt;p&gt;A. 12&lt;/p&gt;

&lt;p&gt;B. 16&lt;/p&gt;

&lt;p&gt;C. 20&lt;/p&gt;

&lt;p&gt;D. 24&lt;/p&gt;

&lt;p&gt;E. 28&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>堆排序(HEAP SORT)</title>
   <link href="https://blog.cyeam.com/computer/2013/04/06/heapsort"/>
   <updated>2013-04-06T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/04/06/heapsort</id>
   <content type="html">&lt;p&gt;堆排序是简单选择排序的进化版本，也是使用了选择的思想。简单选择排序是每趟进行顺序遍历选择出最大(小)的元素。而堆排序是通过使用数据结构堆进行选择最大(小)的元素。这两个排序算法唯一的区别也只有这个地方。&lt;/p&gt;

&lt;p&gt;下面先介绍数据结构堆的定义：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;小顶堆 left { frac{k_{i}leqslant k_{2i+1}}{k_{i}leqslant k_{2i+2}}；&lt;/li&gt;
  &lt;li&gt;大顶堆 left { frac{k_{i}geqslant k_{2i+1}}{k_{i}geqslant k_{2i+2}}；&lt;/li&gt;
  &lt;li&gt;其中，i=0,1,…,left lfloor frac{n}{2} right rfloor。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;堆排序的一般步骤(以小顶堆为例)：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;从最后一个非终端结点left lfloor frac{n}{2} right rfloor开始遍历；&lt;/li&gt;
  &lt;li&gt;对于每一个遍历到的根结点，比较其左右孩子，选择出最小的结点值，与根结点交换值；&lt;/li&gt;
  &lt;li&gt;为了保证交换后堆的特性，堆子结点与根节点交换时，如果该子结点还有孩子结点，还要向下进行交换建堆；&lt;/li&gt;
  &lt;li&gt;堆建成后，删除根结点，用最后一个结点n代替(此操作与简单选择排序操作相似)；&lt;/li&gt;
  &lt;li&gt;继续执行这4步，直到排序完成。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;堆的定义类似于完全二叉树，可以理解为完全二叉树所有非终端结点的值不大于(小于)其左、右孩子结点的值。依此规则建堆的话，我们遍历寻找最大(小)结点的步骤就得以简化，堆顶就是我们需要找的值。之前简单选择排序是逐个顺序比较，一趟遍历要执行(n-i+1)次，堆排序是从最后一个非终端结点left lfloor frac{n}{2} right rfloor开始建堆，所以只执行left lfloor frac{n-i+1}{2} right rfloor次。效率增强显而易见。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;堆排序使用就地排序，只是删除结点时需要一个临时空间存储变量，其余操作不需要额外的空间，空间复杂度是O(n);&lt;/li&gt;
  &lt;li&gt;在建堆的比较过程中，从最后一个非空结点开始，所以最多是left lfloor log_{2}n right rfloor+1次，需要执行(n-1)次，所以时间复杂度是Oleft ( nlog n right )；&lt;/li&gt;
  &lt;li&gt;在建堆交换的过程中，只是子结点和双亲比较，所以不稳定。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;堆排序实例(以小顶堆为例)：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;初始序列为：{49,38,65,97,76,13,27,49}；&lt;/li&gt;
  &lt;li&gt;开始建堆，从left lfloor frac{n}{2} right rfloor开始，本例中是第四个结点97，和子结点49比较，子结点较小，进行交换；第三结点65，和子结点13和27进行比较，与较小的13进行交换；第二结点38，与子结点49和76比较，不需要交换；根结点49，与子结点38和13进行比较，与较小的13进行交换。此时第三个结点发生了变动，为了保证堆的特性，还要将该结点与子结点进行比较，使用较小的右子结点27与第三结点49进行交换。{13,38,27,49,76,65,49,97}；&lt;/li&gt;
  &lt;li&gt;删除根结点13，使用最后一个结点97代替；{97,38,27,49,76,65,49},13；&lt;/li&gt;
  &lt;li&gt;重复建堆，直到所有结点都已排序完成。下面只附上结果，不再复述重复的步骤；
调整堆：{27,38,49,49,76,65,97},13；&lt;/li&gt;
  &lt;li&gt;删除根结点：{97,38,49,49,76,65},27,13；&lt;/li&gt;
  &lt;li&gt;调整堆：{38,49,49,97,76,65},27,13；&lt;/li&gt;
  &lt;li&gt;删除根结点：{65,49,49,97,76},38,27,13；&lt;/li&gt;
  &lt;li&gt;调整堆：{49,65,49,97,76},38,27,13；&lt;/li&gt;
  &lt;li&gt;删除根结点：{76,65,49,97},49,38,27,13；&lt;/li&gt;
  &lt;li&gt;调整堆：{49,65,76,97},49,38,27,13；&lt;/li&gt;
  &lt;li&gt;删除根结点：{97,65,76},49,49,38,27,13；&lt;/li&gt;
  &lt;li&gt;调整堆：{65,97,76},49,49,38,27,13；&lt;/li&gt;
  &lt;li&gt;删除根结点：{76,97},65,49,49,38,27,13；&lt;/li&gt;
  &lt;li&gt;调整堆：{76,97},65,49,49,38,27,13；&lt;/li&gt;
  &lt;li&gt;删除根结点：97,76,65,49,49,38,27,13。排序结束，这样建立排序的结果是倒序的，建立小顶堆，结果是递减的序列，建立大顶堆，得到的递增的序列。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>3种交换值的方法</title>
   <link href="https://blog.cyeam.com/computer/2013/04/02/swap"/>
   <updated>2013-04-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/04/02/swap</id>
   <content type="html">&lt;p&gt;交换值是比较常用的步骤，也比较简单，这里总结了3个方法:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;使用临时变量交换。方法很简单，使用临时变量来保存一个值，该值被保存后就可以对其进行赋值操作了;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  // 通过临时变量交换值
  void swap_temp(int *a, int *b) {	// a = 1, b = 3
      int temp = *a;					// temp = 1
      *a = *b;						// a = 3
      *b = temp;						// b = 1
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用异或操作(可参考《位运算符》)。具体原理是：对于任何数,都有A^0=A A^A=0;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  // 通过位运算交换值
  void swap_bit(int *a, int *b) {		// a = 1, b = 3
      *a = *a ^ *b;					// a = a ^ b
      *b = *b ^ *a;					// b = b ^ a = b ^ (a ^ b) = a = 1
      *a = *a ^ *b;					// a = a ^ b = (a ^ b) ^ a = b = 3
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用加法和减法也可以实现数字的交换，原理同第二条异或原理基本一致;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  void swap_add_substract(int *a, int *b) {	// a = 1, b = 3
      *a = *a + *b;							// a = a + b
      *b = *a - *b;							// b = a - b = (a + b) - b = a = 1
      *a = *a - *b;							// a = a - b = (a + b) - a = b = 3
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然使用异或操作和加减法运算并没有去声明临时变量，但实际运行过程中，会在临时寄存器中保存临时变量用于计算，所以都是将一个数值保存在了一个临时的场所，只是位置不同而已。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;我大Golang在语言级别支持了多重赋值，所以值的交换异常简单&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a, b = b, a&lt;/code&gt;。&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>简单选择排序(SELECT SORT)</title>
   <link href="https://blog.cyeam.com/computer/2013/04/02/selectsort"/>
   <updated>2013-04-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/04/02/selectsort</id>
   <content type="html">&lt;p&gt;简单选择排序的原理比较简单：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;遍历整个待排序序列，选择出最小(大)的值，并与待排序序列的第一个元素进行交换；&lt;/li&gt;
  &lt;li&gt;每一选择，都会选择出一个剩余元素中最小(大)的元素，放在最终的位置上;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;选择排序的一些特征总结：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第i回合的剩余元素是(n-i+1)个，其中第一次是n个。&lt;/li&gt;
  &lt;li&gt;排序要进(n-1)次(最后一趟只剩一个元素，元素合适的最后一位置上)；&lt;/li&gt;
  &lt;li&gt;每一趟要在(n-i+1)个元素中选择一个最小(大)的元素，要进行(n-i)次比较，一共要进行比较(n-1)趟，所以比较次数是：(n-1)+(n-2)+…+(1)=frac{n(n-1)}{2}。时间复杂度为：O(n^{2});&lt;/li&gt;
  &lt;li&gt;最好情况为有序递增，不需要交换，只进行遍历；最差情况为每次都进行交换；&lt;/li&gt;
  &lt;li&gt;每一次交换操作(可参考《3种交换值的方法》)要使用临时变量来保存，需要进行3次移动操作，最差进行3(n-1)次移动；&lt;/li&gt;
  &lt;li&gt;就地排序，空间复杂度为O(1)；&lt;/li&gt;
  &lt;li&gt;每次交换是选择出的元素与剩余序列中第一个序列进行交换，这个操作可能会导致排序结果不稳定。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;简单选择排序演示&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;初始序列：{49,38,65,97,76,13,27,49}；&lt;/li&gt;
  &lt;li&gt;第一趟遍历选出最小的数字13，与49交换：13 {38,65,97,76,49,27,49}；&lt;/li&gt;
  &lt;li&gt;选出最小的27，13,27  {65,97,76,49,38,49}；&lt;/li&gt;
  &lt;li&gt;选出38，13,27,38 {97,76,49,65,49}；&lt;/li&gt;
  &lt;li&gt;这次选出的是靠左的49，默认选出最先遍历到的最小的元素，13,27,38,49 {76,97,65,49}；&lt;/li&gt;
  &lt;li&gt;选出49，13,27,38,49,49, {97,65,76}；&lt;/li&gt;
  &lt;li&gt;选择65，13,27,38,49,49,65, {97,76}；&lt;/li&gt;
  &lt;li&gt;选择76，13,27,38,49,49,65,76,97。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>构造函数成员初始化列表</title>
   <link href="https://blog.cyeam.com/computer/2013/03/31/constructor"/>
   <updated>2013-03-31T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/31/constructor</id>
   <content type="html">&lt;p&gt;我们平时习惯于在C++构造函数体中使用赋值操作符来初始化数据成员，其实这并不是真正的初始化过程。初始化是指使用参数表初始化。初始化列表位于构造函数参数表之后。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class A{
private:
	A m_a;
	A() : m_a(a);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，初始化就在类的代码被执行之前调用，由编译器来实现。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;派生类可以在参数表中直接调用基类的构造函数；&lt;/p&gt;

    &lt;p&gt;class A {
  public:
      A(int x);
  };
  class B : public A {
  private:
      B(int x, int y);
  };
  B::B(int x, int y) : A(y) { // 在初始化列表内调用A的初始化函数
  }&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;非静态&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const&lt;/code&gt;成员只能在参数表中初始化；&lt;/li&gt;
  &lt;li&gt;如果采用赋值操作符初始化，具体是先调用构造函数创建一个变量，然后在调用赋值函数进行赋值，效率较低；&lt;/li&gt;
  &lt;li&gt;使用参数表来初始化数据成员时，初始化顺序并不一定是初始化列表的顺序，而且按照在类中声明的次序来初始化。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>蒙特卡洛求圆周率</title>
   <link href="https://blog.cyeam.com/kaleidoscope/2013/03/27/pi"/>
   <updated>2013-03-27T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/kaleidoscope/2013/03/27/pi</id>
   <content type="html">&lt;p&gt;使用蒙特卡洛方法求圆周率的方法比较简单。其原理是：在一个边长为l的正方形中有一个半径也为l的四分之一圆，如图所示。在这个正方形中仍石子，统计命中园的次数S1和一共仍的次数S2。假设半径足够大，仍的次数足够多。那么，S1和S2便可以代表四分之一园的面积和正方形的面积。所以工具如下公式，可以得到圆周率的计算方法。这个过程与半径无关。&lt;/p&gt;

&lt;p&gt;实现的过程中，需要使用随机数来模拟扔石子的过程。在(0,l)的范围内随机出一对横坐标x和纵坐标y，计算这个点到原点(0,0)的距离来判断是否仍到了圆内。在理论上，圆周率的计算和半径无关，但是在实际计算过程中，因为半径会影响仍石子随机坐标的范围，所以还是会影响到精确度，半径越大越好。产生随机数试用stdlib库中的rand和srand分别来产生随机数和随机种子。随机种子调用时间来生成。具体实现如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstdlib&amp;gt;
#include &amp;lt;ctime&amp;gt;
#include &amp;lt;cmath&amp;gt;
using namespace std;
int main(int argc, char *argv[]) {
	int times = atoi(argv[1]); // 获得用户输入的模拟次数

	const int R = 1000; // 半径
	int count = 0;

	srand((unsigned)time(0)); // 通过获得时间来产生随机数种子
	int i;
	for (i = 0; i &amp;lt; times; i++)
	{
		int x = rand() % (R + 1); // 生成随机横坐标和纵坐标
		int y = rand() % (R + 1);
		if (x * x + y * y &amp;lt;= R * R) // 判断是否在圆内
		{
			count++;
		}
	}
	float pi = count * 4.0f / times; // 根据统计结果计算出圆周率
	cout &amp;lt;&amp;lt; pi &amp;lt;&amp;lt; endl;

	return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>STATIC数据成员</title>
   <link href="https://blog.cyeam.com/computer/2013/03/21/static"/>
   <updated>2013-03-21T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/21/static</id>
   <content type="html">&lt;p&gt;如果要在程序任意点统计已创建特定类型的数量，就需要一个全局变量。但是全局变量会破坏封装。static数据成员可以解决这个问题，并且可以通过将该成员定义成private来实现封装。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;static是存储说明符，其具有唯一性。static数据成员对全体对象共享，全体对象共享一个static数据成员。&lt;/li&gt;
  &lt;li&gt;static数据成员可以声明为任意类型，常量、引用、数组、类类型。&lt;/li&gt;
  &lt;li&gt;静态数据成员实际上是类域中的全局变量，static并不能通过类构造函数来进行初始化。所以，静态数据成员的定义(初始化)不应该被放在头文件中。必须在类定义体外部定义一次。static 成员变量在类内部声明，在类外部定义，定义的时候才真正分配空间。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;static成员不是类对象的组成部分。&lt;/p&gt;

    &lt;p&gt;#include &lt;iostream&gt;
  using namespace std;&lt;/iostream&gt;&lt;/p&gt;

    &lt;p&gt;class Test {
  public:
      static int counter; // counter被所有对象共享
      Test() {
          counter++; // 每调用一次构造函数，counter加1
      }
  };&lt;/p&gt;

    &lt;p&gt;int Test::counter; // static变量定义和初始化&lt;/p&gt;

    &lt;p&gt;int main() {
      Test a; // 自动调用构造函数
      Test b; // 第二次调用构造函数
      cout « Test::counter « endl;
      return 0;
  }&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;const static。static定义的类成员变量不能在类中初始化，使用const static来实现类中初始化。但是在类外部仍然需要进行定义。不过由于const的作用，该变量只能共享使用不能修改其值。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>字符数组、字符指针、字符串</title>
   <link href="https://blog.cyeam.com/computer/2013/03/20/string"/>
   <updated>2013-03-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/20/string</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;字符数组，就是元素是字符的数组。&lt;/li&gt;
  &lt;li&gt;字符串(C-Sytle String)，是字符变量的数组，以’’结尾。可见，字符串是特殊的字符数组。&lt;/li&gt;
  &lt;li&gt;字符指针，即char *a。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;字符串操作函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strlen&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strcpy&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strlen&lt;/code&gt;定义在string.h中。&lt;/li&gt;
  &lt;li&gt;如果用字符串字面常量初始化一个字符数组，数组长度会加1，因为要保存&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;调用字符串操作函数只能使用字符指针作为参数，所以传递的既可以是字符数组，也可以是字符串。如果传递的是字符数组，而且没有加入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;的话，遍历的时候会因为找不到结束符而持续遍历，可能出现内存访问错误或结果不正确的情况。&lt;/p&gt;

    &lt;p&gt;char arrChar3[3];
  arrChar3[0] = ‘a’;
  arrChar3[1] = ‘b’;
  arrChar3[2] = ‘c’;
  printf(“%dn”, strlen(arrChar3)); // 7。结果明显错误&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;字符数组、字符指针、字符串实例：&lt;/p&gt;

    &lt;p&gt;#include &lt;stdio.h&gt;
  #include &lt;string.h&gt;&lt;/string.h&gt;&lt;/stdio.h&gt;&lt;/p&gt;

    &lt;p&gt;main() {
      char arrChar1[] = {‘a’, ‘b’, ‘’, ‘d’, ‘e’};
      char arrChar2[] = “hello”;
      char *p = “hello”;
      printf(“%dn”, sizeof(arrChar1)); // 5 返回数组大小
      printf(“%dn”, strlen(arrChar1)); // 2 提前遇到了字符串结束符’’
      printf(“%dn”, sizeof(arrChar2)); // 6 数组长度加1
      printf(“%dn”, strlen(arrChar2)); // 5
      printf(“%dn”, sizeof(p)); // 4 结果指的是指针大小
      printf(“%dn”, strlen(p)); // 5
  }&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>SIZEOF操作符</title>
   <link href="https://blog.cyeam.com/computer/2013/03/20/sizeof"/>
   <updated>2013-03-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/20/sizeof</id>
   <content type="html">&lt;p&gt;sizeof是C/C++中的单目操作符，在编译期间计算，测量这个变量占用的内存大小，它并不是函数。返回值是size_t(无符号整数)。可以求出数据类型、函数、变量，表达式的大小。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;数据类型&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizeof(type)&lt;/code&gt;。可以求出指定数据类型的大小，单位是字节。&lt;strong&gt;&lt;em&gt;注意：指向任何数据类型的指针通过sizeof运算都是4。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  printf(&quot;%dn&quot;, sizeof(int*)); // 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;变量&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sizeof a&lt;/code&gt;。计算变量大小时并不需要加括号。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  char a[] = &quot;hello&quot;;
  printf(&quot;%dn&quot;, sizeof(a));  // 6。字符串赋给数组时，还要考虑末尾的&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;表达式。这里的表达式求值不是在求表达式结果，而是得到表达式结果的占用内存大小。编译器在编译的时候判断表达式结果类型，求得结果。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  printf(&quot;%dn&quot;, sizeof(1 + 1)); // 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;函数。计算函数返回值大小，并不会执行函数。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  int getArrChar() {
      printf(&quot;hello&quot;);
      return 3;
  }
  main() {
      printf(&quot;%dn&quot;, sizeof(getArrChar())); // 4
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>内存分配方式</title>
   <link href="https://blog.cyeam.com/computer/2013/03/20/memory"/>
   <updated>2013-03-20T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/20/memory</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;全局变量和static变量从静态存储区(static)分配。内存在编译阶段就已经分配好了。其中，初始化过的全局变量和static变量存放在.data区；未初始化的全局变量和static变量存放在.bss区，该区的变量值均为0。在程序整个生命周期中都会存在。&lt;/li&gt;
  &lt;li&gt;局部变量、形参在堆栈(stack)上分配。在函数调用过程中进行分配，函数返回后自动释放。其生命周期在其作用域内。&lt;/li&gt;
  &lt;li&gt;通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delete&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free&lt;/code&gt;动态分配到堆(heap)。由程序员来管理其生命周期。C++STL中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector&lt;/code&gt;是动态分配内存，所以分配在堆中。如果分配内存之后并没有进行释放，改内存会一直被占用，知道程序结束，导致内存泄漏。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>CONST关键字</title>
   <link href="https://blog.cyeam.com/computer/2013/03/14/const"/>
   <updated>2013-03-14T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/14/const</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;常变量
const修饰的变量不可以修改该变量的值。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;常引用
在函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void Func(A a)&lt;/code&gt;中，传递了一个临时对象a，该对象要进行构造、拷贝、析构这些过程，运行效率会大大降低。由于该函数是按值传递，所以使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void Func(const A &amp;amp;a)&lt;/code&gt;，这样不会重新创建临时变量，只是常引用原始变量，不会进行构造、析构等过程，可以提高执行效率。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;const修饰函数返回值
const修饰过的函数返回值是常量，形如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const getA()&lt;/code&gt;，这样，可以避免如下代码错误：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  A a;
  getA() = a;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;常函数
形如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void Func(int a) const&lt;/code&gt;，在该函数体内，变量a的值不可以更改，包含该函数的类的其他成员变量的值，不会更改。同时，也会通知使用该方法的程序员：这个函数是常函数，不会改变任何值，放心使用。&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;指向const对象的指针&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  const double *ptr;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;把&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;放在离变量近的位置比较方便理解。ptr是一个指针，指向double类型，ptr指向的值(*ptr)是常量，不可以更改。同时，const对象指针也只能赋值给const对象指针，因为如果可以赋值，之前的const限定也就无效了。也可以像下面这样定义，含义是一样的。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  double const *ptr;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;const指针&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  int * const a;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;表明该指针是常量，不可以更改。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>位运算符(Bitwise Operators)</title>
   <link href="https://blog.cyeam.com/computer/2013/03/11/bitwise_operators"/>
   <updated>2013-03-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2013/03/11/bitwise_operators</id>
   <content type="html">&lt;h3 id=&quot;按位与bitwise-and&quot;&gt;按位与&amp;amp;(bitwise AND)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;两个都是1时结果才为1&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e.g. 1&amp;amp;0=0, 0&amp;amp;1=0, 0&amp;amp;0=0, 1&amp;amp;1=1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;任意一位，和0与操作，结果都为0；和1与操作，结果不变&lt;/li&gt;
  &lt;li&gt;经常用来屏蔽某些二进制位&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;按位或bitwise-inclusive-or&quot;&gt;按位或|(bitwise inclusive OR)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;两个有一个是1结果就为1&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e.g. 1|1=1, 1|0=1, 0|0=0, 0|1=1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;任意一位，与0或操作，结果不变；与1或操作，变为1&lt;/li&gt;
  &lt;li&gt;经常用来将某些二进制位设为1&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;按位异或bitwise-exclusive-or&quot;&gt;按位异或^(bitwise exclusive OR)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;当两位相异时，结果为1&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e.g. 1^1=0, 1^0=1, 0^1=1, 0^0=0&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;可以简单的理解为不进位加法,1+1=0, 0+0=0, 1+0=1,  0+1=1&lt;/li&gt;
  &lt;li&gt;任意一位，和0异或操作不变，和自身异或操作为0&lt;/li&gt;
  &lt;li&gt;对于任何数,都有A^0=A A^A=0&lt;/li&gt;
  &lt;li&gt;自反性：A^B^A=B^0=B&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;左移运算符left-shift&quot;&gt;左移运算符«(left shift)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;高位左移后溢出,右边用0补&lt;/li&gt;
  &lt;li&gt;左右移位比乘除法效率高。左移用来计算乘法，n«2等效于n*2&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;右移运算符right-shift&quot;&gt;右移运算符»(right shift)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;由于数字采用补码表示，右移时，正数低位右移溢出，左边用0补；负数右移溢出后用1补&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;e.g. 00001010&amp;gt;&amp;gt;2 = 00000010 10001010&amp;gt;&amp;gt;3 = 11110001&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;右移用来计算正数的除法(向下取整)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;取反操作符ones-complement&quot;&gt;取反操作符~(one’s complement)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;求该数反码，该数二进制位上1变为0，0变为1&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;常见面试题&quot;&gt;常见面试题：&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;设置一个数的某一位为1&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  n |= 1 &amp;lt;&amp;lt; x;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;清除一个数的某一位&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  if (n &amp;amp; (1 &amp;lt;&amp;lt; x))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;开关某一位&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  n &amp;amp;= ~(1 &amp;lt;&amp;lt; x);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;检查某一位状态&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  n ^= 1 &amp;lt;&amp;lt; x;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;实例代码&quot;&gt;实例代码：&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int n = 5; // 101(binary)
int x = 1;

// 设置一个数的某一位(x)为1
n |= 1 &amp;lt;&amp;lt; x;
// x = 00000000000000000000000000000111(binary)
printf(&quot;n=%dn&quot;, n);

// 检查某一位状态
if (n &amp;amp; (1 &amp;lt;&amp;lt; x))
printf(&quot;onn&quot;);
else
printf(&quot;offn&quot;);

// 清除一个数的某一位
n &amp;amp;= ~(1 &amp;lt;&amp;lt; x);
// x = 00000000000000000000000000000101(binary)
printf(&quot;n=%dn&quot;, n);

// 开关某一位(如果打开，则关闭；反之，则打开)
n ^= 1 &amp;lt;&amp;lt; x;
// x = 00000000000000000000000000000111(binary)
printf(&quot;n=%dn&quot;, n);

//使用异或交换两个整数
//a&apos; = a ^ b
//b&apos; = b ^ a&apos; = b ^ a ^ b = a
//a&apos; = a&apos; ^ b = a ^ b ^ b = b
//00000000000000000000000000001011 ^ 00000000000000000000000000101101 = 0000000000000000000
//00000000000000000000000000101101 ^ 00000000000000000000000000100110 = 0000000000000000000
//00000000000000000000000000001011 ^ 00000000000000000000000000100110 = 0000000000000000000
int a = 11;
int b = 45;
a = a ^ b;
b = b ^ a;
a = a ^ b;
printf(&quot;使用异或交换11和45: %d %dn&quot;, a, b);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;计算一个正数的二进制数中1的个数。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以借鉴上面的题目2，逐位判断是否为1。从最低位开始判断，下次判断倒数第二位，以此类推，但是不能进行通过右移操作来实现(因为右移涉及到正负数问题，会引入新的1进来)。可以移动用来判断的位。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int NumberOfOne(int n) {
	int on = 0;
	unsigned int flat = 1;
	while (flag)
	{
		if (n &amp;amp; flag)
		on++;
		flag = flag &amp;lt;&amp;lt; 1;
	}
	return on;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;一个数组中有多个整数，其中只有一个没有重复过，求出该数。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;考虑使用异或操作帮助实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int AppearOnce(int data[], int length)
{
	int i;
	int once = 0;
	for (i = 0; i &amp;lt; length; i++)
	{
		once ^= data[i];
	}
	return once;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;不用+、-、*、/四则运算计算处两个数的和。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;异或操作可以理解为不进位加法，所以先将两个数异或，然后再加入进位来实现。进位只要在1+1的时候才会出现，所以可以使用1&amp;amp;1来代替，如果两个数都是1，与操作得到1，然后左移1位得到10，实现了进位。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;int add(int a, int b) {
	int sum, carry;
	do {
		sum = a ^ b;
		carry = (a &amp;amp; b) &amp;lt;&amp;lt; 1;
		
		a = sum; 
		b = carry;
	} while (b != 0);
	
	return a;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>又一年</title>
   <link href="https://blog.cyeam.com/notebook/2013/03/05/yet_another_year"/>
   <updated>2013-03-05T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2013/03/05/yet_another_year</id>
   <content type="html">&lt;p&gt;一年前的今天，我来北京复查考研成绩，蛋疼的政治44。孤身一人第一次出门，就想着说历练一下吧。来北京查了查分，顺便见了几个同学，一个人绕着西单啥的逛了逛。&lt;/p&gt;

&lt;p&gt;感觉一切都是新的，都没见过，当时很屌丝的去了商场跟服务员都不怎么敢说话。呵呵。四月份又来了一趟，考研复试。那时对北京已经比较熟了，趁着蛋疼复试的时候又到处走了走，很多地方都混熟了。&lt;/p&gt;

&lt;p&gt;现在已经在北理读研一个多学期了，基本混熟了，正好一年。&lt;/p&gt;

&lt;p&gt;从上个学期开始找实习，面了两次，挺伤心的，面得特别差，第一次面没办法，真是艰辛。这个学期提前几天过来，玩了几天，就开始准备。面的第三个，就录了。之前压力是多么的大，生怕找不到，准备还那么认真，结果就是第三个就录了嘛，哈哈，得瑟一下。IBM是我一直比较喜欢的公司，能去这里实习结果算很好的了，而且是研发岗位。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/ibm.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最近还在和大牛搞网站的事情，应该就要上线了。一直觉得当时的想法太草率，现在就硬着头皮上吧，做技术博客，写个半年，我应该也会有点竞争力了。&lt;/p&gt;

&lt;p&gt;这一年，对我来说，是成长的一年，我确确实实感觉到自己成熟了好多，面试会去争取主动权了，融入这个城市了，懂得理解父母了，前天睡觉想起来家里的事情，就觉得好幸福啊。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>你永远都不知道自己有多傻逼</title>
   <link href="https://blog.cyeam.com/notebook/2013/01/23/u_will_never_know_how_stupid_u_r"/>
   <updated>2013-01-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2013/01/23/u_will_never_know_how_stupid_u_r</id>
   <content type="html">&lt;p&gt;最近的一些小感慨。乱七八糟。&lt;/p&gt;

&lt;p&gt;编码也有很多了，但是一直没有特别好的编码感觉。也不知道感觉对不对，自从复读以来，做事情就没有特别得意的时候。自己变得很教条，一切都是按着规定好的东西在做。这本来也无可厚非，但是自己在一中时期灵活聪明的一面，貌似已经失去了。其实周围的人大多也都是这样，都很教条，这可能也是我们教育的悲哀吧。我需要接触一些新的人，多学习一下新思路。&lt;/p&gt;

&lt;p&gt;自己一心想成为一个高富帅，却发现自己做的事情从来都是这么傻逼，离那个还有很远的距离。自己的定位很矛盾。除了装逼，我基本啥都没干好。怎么才能成为一个内心强大的人，这是一个问题。&lt;/p&gt;

&lt;p&gt;自己有一堆一堆的想法，而且我觉得如果都能实现的话，在应用方面应该会有很大的前景。但是再盘算一下，根本没有时间来实现，我可能也没有那个能力搞定。唉，这是我唯一引以为豪的东西了。&lt;/p&gt;

&lt;p&gt;写这个东西，自己都不知道想说什么。我很迷茫。现在社会上有各种励志大师，我也读了一些，都是扯淡的。到底要怎么变牛逼，对我来说还是一个问题。不过当下，我需要改变思路，我要灵活起来，找回当年那个傻逼、快乐、自信的我。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Connect the dots</title>
   <link href="https://blog.cyeam.com/notebook/2013/01/23/connect_the_dots"/>
   <updated>2013-01-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2013/01/23/connect_the_dots</id>
   <content type="html">&lt;p&gt;转眼就要过年了，半年的研究生生活还差两天就结束了。2012年，对于我来说，是不平凡的一年，这一整年的经历，让我突然明白了许多，慢慢得有了目标，有了规划。&lt;/p&gt;

&lt;p&gt;上半年的复试，让我第一次感受到了帝都的生活，我是多么的渺小，我甚至很自卑，一个山西屌丝从重庆跑到北京来参加复试，没技术没GPA。复试过了之后，我又是多么的屌丝，忘记了复试时候的冷嘲热讽，各种冷眼，突然觉得自己多么的了不起，尤其是刚开始读研的时候，觉得终于来到了大城市了。当然，在帝都这种城市，有这种想法是难免的，不过好在我现在已经看得很淡了。&lt;/p&gt;

&lt;p&gt;从4月份开始，我在汉奸的推荐下开始阅读。从《南方周末》开始，后来是《看天下》，接着读了一些书。我和汉奸真是屌丝中的绝配，如果我俩是gay，绝对可以一见钟情，因为我俩的想法总是如此的相似。其实从大一开始就有计划去读一些书，但只是计划，到大四毕业才真正开始，输在起跑线上了，呵呵，扯远了。后来我也不怎么去看报纸了，我去读了一些书，《世界因你不同》、《我的奋斗》，这些在我看来的文化鸡肋，当时居然会选择去读，没文化真可怕。现在阅读算是步入正轨了。&lt;/p&gt;

&lt;p&gt;那天在豆瓣写书评，突然认识到我为什么要读书了，之前确实有些盲目，看别人推荐的，但是后来也知道根据自己喜好来了，慢慢发现，其实我读书，一直在寻找一个答案：我的青春要怎么过？我的路要怎么走？我要坚持怎么样的价值观？我的理想是什么？&lt;/p&gt;

&lt;p&gt;还有一次打击，是后半年的实习生面试。当我信心满满的跑去面后，幼小的心灵再一次受挫。但是IBM的那次面试，让我真正做了一次自己，做了一次男人。我有勇气去跟面试官去说我想说的话，去展示我自己，这很好。他也鼓励我去坚持梦想。&lt;/p&gt;

&lt;p&gt;之后才意识到，天天放在嘴边的“follow my heart”放在我这，是多么的扯淡，因为我倒那时，才想到我真正想做什么。&lt;/p&gt;

&lt;p&gt;这几天终于读了几本像样的书了，读完这基本之后，我应该又能上一个台阶，找实习应该不愁了，完成我台湾游的梦想也不难了，哈哈。台北、东京、纽约、拉萨，等我。&lt;/p&gt;

&lt;p&gt;尽瞎扯了，写得好乱。&lt;/p&gt;

&lt;p&gt;码农人生，游戏人生～&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>2012读书总结</title>
   <link href="https://blog.cyeam.com/notebook/2013/01/13/2012_reading"/>
   <updated>2013-01-13T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2013/01/13/2012_reading</id>
   <content type="html">&lt;p&gt;《正说清朝十二帝》研究生复试的时候，问我清朝有多少个皇帝，我说10个，老师都笑了。回去找了这本书读了读。这也是我读历史的起点。
《世界因你不同》开复老师讲了不少，但是回头想想，其实也没啥，都是虚的。微博也是，都是炒自己，没劲。
《青春》都一次读韩寒的东西，他很有自己的想法，敢作敢当。但是从中没有找到我想要的答案。
《历史是个什么玩意儿1》“读史以明智”。
《我的奋斗》老罗主要强调了要坚持原则，多读书，还是挺好的，不过自传那部分写得不怎么样。
《乡关何处》现在回想，其实内容大都忘掉了，但是还是让我坚定了我的理想。&lt;/p&gt;

&lt;p&gt;头一次一年读了这么多所谓的课外书，好友成就感。
有人说，一年读4本，我一下完整的读完了6本，还有基本在读，没读完就没写了。就可以成为一个牛逼人，我一下读了这么多，就当为以前补补课吧。
话说之前读的几本书，好烂啊，没读过书，不会挑，就喜欢读《世界因你不同》、《我的奋斗》这种，还读过《说话之道》，我都没坚持读完。所谓的励志书，不适合我。
今天找我读过的书，发现其实我一直在找我自己的影子，我想更好的理解我自己。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>请求网页所需协议</title>
   <link href="https://blog.cyeam.com/network/2012/11/02/Protocol_Of_HTTP"/>
   <updated>2012-11-02T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/network/2012/11/02/Protocol_Of_HTTP</id>
   <content type="html">&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;请求页面&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://www.bit.edu.cn/index.htm&lt;/code&gt;后，首先在本地查找DNS缓存。本机已有缓冲，故不再向DNS服务器请求DNS解析。&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;www.bit.edu.cn
----------------------------------------
Record Name . . . . . : www.bit.edu.cn
Record Type . . . . . : 1
Time To Live  . . . . : 84020
Data Length . . . . . : 4
Section . . . . . . . : Answer
A (Host) Record . . . : 10.0.6.30
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;清空本机的DNS缓存后，重新请求该页面。访问本地DNS服务器&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.0.0.10&lt;/code&gt;，DNS服务器返回该域名的地址&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.0.6.30&lt;/code&gt;。这个获得的是IPv4的地址，随后还执行了一次请求，请求IPv6的地址。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Protocol1.png&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;随后与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.0.6.30&lt;/code&gt;建立TCP连接，请求&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.htm&lt;/code&gt;页面。使用了基于TCP的HTTP协议，所以基于80端口。建立TCP连接的过程使用了3次握手。第一次握手，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Flags: 0x002 (SYN)  Sequence number: 0    (relative sequence number)&lt;/code&gt;；第二次握手，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sequence number: 0    (relative sequence number)  Acknowledgment number: 1    (relative ack number)  Flags: 0x012 (SYN, ACK)&lt;/code&gt;；第三次握手，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sequence number: 1    (relative sequence number)  Acknowledgment number: 1    (relative ack number)  Flags: 0x010 (ACK)&lt;/code&gt;。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Protocol2.png&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;服务器成功响应，发送过来index.htm文件。其中，Server: Apache/2.2.6 (Unix) mod_jk/1.2.27\r\n可以知道北京理工大学使用的服务器是Unix版本的Apache服务器2.2.6版。部分HTML代码如下。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Protocol3.png&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &amp;lt;html xmlns=&quot;https://www.w3.org/1999/xhtml&quot;&amp;gt;
     &amp;lt;head&amp;gt;
     &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&amp;gt;
     &amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;
     &amp;lt;SCRIPT type=text/javascript src=&quot;js/common.js&quot; &amp;gt;&amp;lt;/SCRIPT&amp;gt;
     &amp;lt;link href=&quot;https://www.bit.edu.cn/css/newcss1.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&amp;gt;
     &amp;lt;link href=&quot;https://www.bit.edu.cn/css/style.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&amp;gt;
     &amp;lt;link href=&quot;https://www.bit.edu.cn/favicon.ico&quot; rel=&quot;shortcut icon&quot; type=&quot;image/x-icon&quot; /&amp;gt;
     &amp;lt;link href=&quot;https://www.bit.edu.cn/favicon.ico&quot; rel=&quot;icon&quot; type=&quot;image/x-icon&quot; /&amp;gt;
 &amp;lt;/head&amp;gt;
 &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;获取到index.htm之后，根据HTML代码下载Javascript脚本、CSS文件、显示用的图片。每一次获取，都要建立TCP连接。这是请求资源的过程。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Protocol4.png&quot; alt=&quot;Alt Text&quot; /&gt;
 登陆126.com与之前描述相当，只是在登陆时需要提交表单。其中，用户名和密码已经经过加密。
&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/Protocol5.png&quot; alt=&quot;Alt Text&quot; /&gt;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; var=%3C%3Fxml%20version%3D%221.0%22%3F%3E%3Cobject%3E%3Cobject%20name%3D%22attrs%22%3E%3Cstring%20name%3D%22helptips%22%3E0000000300000085%3C%2Fstring%3E%3C%2Fobject%3E%3C%2Fobject%3E
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>沉淀心情</title>
   <link href="https://blog.cyeam.com/notebook/2012/05/23/precipitation_mood"/>
   <updated>2012-05-23T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2012/05/23/precipitation_mood</id>
   <content type="html">&lt;p&gt;我从小到大都是一个听话的孩子，我家里人也一直以此为傲，因为我读了一个还算名牌的大学，一个名牌的研究生。我可以没人任何杂念的去准备高考，准备考研，完全不会有其他杂念：谈恋爱、玩……&lt;/p&gt;

&lt;p&gt;自从考研结束后，接二连三发生了一些事情，我也开始意识到自己的问题：我是个优柔寡断的人，不太会选择。尤其是在研究生复试和之前的找工作的事情上表现的非常明显，一些不大不小的事情被我扩大化了。随之而来的就是焦虑，再者就是找人倾诉。本来也不是什么大事，我自己其实也没啥好说的，姚宁一和孙黎就这么每天听我罗嗦。他们也都知道我是怎么回事，还有心情搭理我，真心感谢你们~&lt;/p&gt;

&lt;p&gt;我是一个胸有大志的人，有各种理想、各种报复，可能因为我的性格的原因，至今还是一菜鸟。我感觉很多时候不是我自己的能力问题，而我一直最需要的是一个能指引我的人，告诉我接下来该怎么做。可能我从小听命令听习惯了，也可能我缺少机遇，我大学四年的状态就是有劲没处使。&lt;/p&gt;

&lt;p&gt;一直很羡慕老姚，他经历了我认为的比较困难的阶段，没有因为抉择而过于苦恼，反正没有找我倾诉过。之前看蓝昊的视频，很多时候必须忍受寂寞，而我们去找人寻求帮助，其实是想要一个自己心里已经设定好的回复。&lt;/p&gt;

&lt;p&gt;是时候尝试去改变一下了，果断一些，多出去接触一下社会，我不能再是小孩子了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>在main函数中创建2个线程，一个运行冒泡排序算法，另一个运行选择排序算法，分别对两个一样的数组进行</title>
   <link href="https://blog.cyeam.com/computer/2011/01/11/cpp_thread"/>
   <updated>2011-01-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2011/01/11/cpp_thread</id>
   <content type="html">&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include&amp;lt;Windows.h&amp;gt;
#include&amp;lt;iostream&amp;gt;
#include&amp;lt;stdlib.h&amp;gt;
#include&amp;lt;strsafe.h&amp;gt;
#include&amp;lt;time.h&amp;gt;
 
#defineMAX_THREADS 2
#defineDATASIZE 100000
usingnamespacestd;
 
voidErrorHandler(LPTSTRlpszFunction);
DWORDWINAPIBubble( LPVOIDlpParam);
DWORDWINAPISelectSort(LPVOIDlpParam); 
 
DWORDtime1 = 0, time2= 0;
 
typedefstructMyData {
        intvar[DATASIZE];
} MYDATA,*PMYDATA;
 
int main()
{
    DWORD   dwThreadIdArray[MAX_THREADS];
    HANDLE  hThreadArray[MAX_THREADS];
    PMYDATApDataArray[MAX_THREADS];

    for (inti = 0; i &amp;lt; MAX_THREADS; i++)
    {
            pDataArray[i] =(PMYDATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                    sizeof(MYDATA));

            if( pDataArray[i] == NULL )
            {
                    ExitProcess(2);
            }
    }
    srand((unsignedint)time(NULL));
    for (intj = 0; j &amp;lt; DATASIZE; j++)
    {
            pDataArray[0]-&amp;gt;var[j] = pDataArray[1]-&amp;gt;var[j] = rand();
    }

    hThreadArray[0] = CreateThread(
            NULL,
            0,
            Bubble,
            pDataArray[0],
            0,
            &amp;amp;dwThreadIdArray[0]);

    if (hThreadArray[0]== NULL) 
    {
            ErrorHandler(TEXT(&quot;CreateThread&quot;));
            ExitProcess(3);
    }

    hThreadArray[1] = CreateThread(
            NULL,
            0,
            SelectSort,
            pDataArray[1],
            0,
            &amp;amp;dwThreadIdArray[1]);

    if (hThreadArray[1]== NULL) 
    {
            ErrorHandler(TEXT(&quot;CreateThread&quot;));
            ExitProcess(3);
    }
    
    WaitForMultipleObjects(MAX_THREADS,hThreadArray, TRUE,INFINITE);

    for(inti=0; i&amp;lt;MAX_THREADS; i++)
    {
            CloseHandle(hThreadArray[i]);
    }

    return 0;
}
 
voidErrorHandler(LPTSTRlpszFunction) 
{ 
    // Retrieve the system error message for the last-errorcode.

    LPVOIDlpMsgBuf;
    LPVOIDlpDisplayBuf;
    DWORDdw = GetLastError(); 

    FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | 
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
            (LPTSTR) &amp;amp;lpMsgBuf,
            0,NULL );

    // Display the error message.

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
            (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
            LocalSize(lpDisplayBuf)/ sizeof(TCHAR),
            TEXT(&quot;%s failedwith error %d: %s&quot;), 
            lpszFunction, dw,lpMsgBuf); 
    MessageBox(NULL,(LPCTSTR)lpDisplayBuf,TEXT(&quot;Error&quot;),MB_OK); 

    // Free the error-handling buffer allocations.

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}
 
DWORDWINAPIBubble( LPVOIDlpParam) 
{
    time1 = GetTickCount();
    int *p = (int*)lpParam;
    inttemp;
    for(inti=DATASIZE - 1;i &amp;gt; 0; i--)
    {
            for(intj = 0; j &amp;lt; i; j++)
            {
                    if(p[j] &amp;gt; p[j + 1])
                    {
                            temp=p[j];
                            p[j]=p[j + 1];
                            p[j + 1]=temp;
                    }
            }
    }
    time2 = GetTickCount();
    cout &amp;lt;&amp;lt; &quot;Timeof Bubble:&quot; &amp;lt;&amp;lt; time2 - time1 &amp;lt;&amp;lt; &quot;ms&quot;&amp;lt;&amp;lt; endl;
    return 0;
}
 
DWORDWINAPISelectSort(LPVOIDlpParam) 
{
    time1 = GetTickCount();
    int *p = (int*)lpParam;
    intpos;
    inttemp;
    for (inti = 0; i &amp;lt; DATASIZE; i++)
    {
            pos = i;
            for (intj = i; j &amp;lt; DATASIZE;j++)
            {
                    if (p[pos] &amp;gt; p[j])
                    {
                            pos = j;
                    }
            }
            temp = p[pos];
            p[pos] = p[i];
            p[i] = temp;
    }
    time2 = GetTickCount();
    cout &amp;lt;&amp;lt; &quot;Timeof SelectSort:&quot; &amp;lt;&amp;lt; time2 -time1 &amp;lt;&amp;lt; &quot;ms&quot;&amp;lt;&amp;lt; endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;计算结果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/64dadbf99418bf16242df233.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在main函数中加入&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SetThreadPriority(hThreadArray[0], THREAD_PRIORITY_HIGHEST);
SetThreadPriority(hThreadArray[1], THREAD_PRIORITY_LOWEST);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;修改两个线程的优先级，得到的结果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/a208a344cc0786cdb3b7dc3c.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>用CreateProcess创建线程，指定运行QQ.EXE</title>
   <link href="https://blog.cyeam.com/computer/2011/01/11/cpp_process"/>
   <updated>2011-01-11T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/computer/2011/01/11/cpp_process</id>
   <content type="html">&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;STARTUPINFOsi;
PROCESS_INFORMATIONpi;

ZeroMemory( &amp;amp;si,sizeof(si));
si.cb = sizeof(si);
ZeroMemory( &amp;amp;pi,sizeof(pi));


if (!CreateProcess(NULL,
        &quot;D:\\Program Files\\Tencent\\QQ\\Bin\\QQ.exe&quot;,
        NULL,
        NULL,
        FALSE,
        0,
        NULL,
        NULL,
        &amp;amp;si,
        &amp;amp;pi)
        )
{
        cout &amp;lt;&amp;lt; &quot;CreateProcessfailed:&quot; &amp;lt;&amp;lt; GetLastError()&amp;lt;&amp;lt; endl;
}
cout &amp;lt;&amp;lt; &quot;CreateProcesssuccess&quot; &amp;lt;&amp;lt; endl;
return 0;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/fec64b549c09021b574e002b.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;用SPY++查看&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/f3891238056f24bdb311c735.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>DX基础知识</title>
   <link href="https://blog.cyeam.com/directx/2011/01/09/dx"/>
   <updated>2011-01-09T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/directx/2011/01/09/dx</id>
   <content type="html">&lt;ol&gt;
  &lt;li&gt;D3D中任何复杂的图形由什么图元组成： 三角形&lt;/li&gt;
  &lt;li&gt;向量的属性包括：长度 和 方向&lt;/li&gt;
  &lt;li&gt;三维空间的变换：（3种）平移、放缩、旋转&lt;/li&gt;
  &lt;li&gt;像素的格式:  ARGB 和 XRGB （常用）&lt;/li&gt;
  &lt;li&gt;如果贴上图片，顶点格式必须有D3DFVF_TEX1，取值范围&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0,1]&lt;/code&gt;  。&lt;/li&gt;
  &lt;li&gt;最基础的对象是 D3D对象，由它创建 Device  ，再由Device创建索引、顶点等。&lt;/li&gt;
  &lt;li&gt;创建和释放应符合 栈（先进后出） 的顺序。&lt;/li&gt;
  &lt;li&gt;D3D在设备创建时选择两种方式，用硬件是 HAL ，用软件是 REF 。&lt;/li&gt;
  &lt;li&gt;表示纯红颜色的颜色的常量是： 0xFFFF0000&lt;/li&gt;
  &lt;li&gt;在D3D中，本地空间指 局部坐标系，世界空间指 世界坐标系   。&lt;/li&gt;
  &lt;li&gt;使用索引缓冲区相对于顶点缓冲区的优点： 对于可重用的点，用索引代替，省去重复空间。&lt;/li&gt;
  &lt;li&gt;D3D中，绘制基本图元的函数是： DrawPrimitive（）。&lt;/li&gt;
  &lt;li&gt;实现光照的效果需要通过光照和模型的 法线 来实现。法线分为 顶点法线 和 面法线。&lt;/li&gt;
  &lt;li&gt;D3D中，顶点是程序员的定义格式，叫做： 灵活顶点格式（FVF），FVF的值必须与 顶点结构体 一一对应。&lt;/li&gt;
  &lt;li&gt;处理纹理坐标的值在 （0,1） 边界外，纹理坐标的方式叫 纹理寻址。三种纹理寻址的方式： 包装、边框、镜面、夹取。&lt;/li&gt;
  &lt;li&gt;D3D中，模型表面的材质是指 物体表明对光的反射能力 ，也包括 α通道。&lt;/li&gt;
  &lt;li&gt;在DX中，渲染需要占用大量的CPU时间，要想获得稳定的帧速率，调用 PeekMessage()。&lt;/li&gt;
  &lt;li&gt;只要不去修改，就会一直保持状态，则这些状态叫做 开关量。&lt;/li&gt;
  &lt;li&gt;设置渲染状态的函数是SetRenderState()。&lt;/li&gt;
  &lt;li&gt;纹理尺寸最好符合什么标准？ 长宽最好是2的n次幂，矩形也行。&lt;/li&gt;
  &lt;li&gt;DX最多支持同时 8 个光源， 8 层纹理。&lt;/li&gt;
  &lt;li&gt;DX中正确体系物体遮挡关系，使用  Z-Buffer(深度缓冲)。&lt;/li&gt;
  &lt;li&gt;为了增加场景的真实感，通常添加 雾效。&lt;/li&gt;
  &lt;li&gt;DX中，专门封装了一个处理2D图形的接口：D3DXSprite&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;DX图形库的开发方式，使用这种机制有什么好处？
DX图形库的开发方式是基于Windows的COM机制（ComponentObject Model组件对象模型）。COM对象是对一组特定功能的抽象集合，应用程序不能直接访问COM对象，而是必须通过COM对象接口（Interface）的指针执行COM对象的功能。
使用这种机制的好处是：
    &lt;ul&gt;
      &lt;li&gt;COM接口永远不会改变&lt;/li&gt;
      &lt;li&gt;COM是独立于语言的（language-independent）&lt;/li&gt;
      &lt;li&gt;COM只能通过方法来访问，而不能直接访问数据成员。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;DX程序运行的基本流程是什么？
    &lt;ol&gt;
      &lt;li&gt;创建窗口&lt;/li&gt;
      &lt;li&gt;创建D3D对象&lt;/li&gt;
      &lt;li&gt;创建图形&lt;/li&gt;
      &lt;li&gt;载入资源&lt;/li&gt;
      &lt;li&gt;设置变换&lt;/li&gt;
      &lt;li&gt;设置状态&lt;/li&gt;
      &lt;li&gt;在BeginScene()和EndScene()之间绘制&lt;/li&gt;
      &lt;li&gt;创建图元&lt;/li&gt;
      &lt;li&gt;销毁图元&lt;/li&gt;
      &lt;li&gt;销毁窗口&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;简述光的类型以及特性。&lt;/p&gt;

    &lt;p&gt;光的类型有：环境光、漫反射光、镜面反射光
 环境光：是经其他表面反射到达物体表面，并照亮整个场景。这时物体并不处于光源的直接照射下，照亮物体的光是其他物体反射到该物体上的光线。光线经过多次折射后看起来就像是来自四面八方一样，这种光不依赖于光源的位置。
 漫反射光：有特定的传播方向，但当它到达某一表面时，将沿着各个方向均匀反射。这种光无论从哪个方位观察，表面亮度均相同，所以无须考虑观察者的位置，仅需考虑光传播的方向以及表面的朝向。从一个光源发出的光一般都是这种类型的。
 镜面反射光：用于显示物体表面（光滑和光泽表面）的高光效果，有特定的传播方向。但当它到达一个表面时，将严格的沿着另一个方向反射。是只能在一定的角度范围内才能观察到的高亮度照射。因为光照均沿着同一个方向反射，所以不仅要考虑光的入射方向和表面的朝向，还需要考虑观察者的位置。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;简述α混合和α测试的原理，相同点和不同点。&lt;/p&gt;

    &lt;p&gt;α通道就是记录像素的透明度。
 α混合：先绘制目标颜色，再绘制源颜色，用两个颜色值分别乘以各自的α系数再相加，即得到最后的颜色。所以，最终颜色里既包含了源颜色又包含的目标颜色。
 α测试：设置一个测试函数，把参考值和将要测试按照比较函数来运算，通过测试的颜色显示出来，未通过测试的颜色不显示出来。&lt;/p&gt;

    &lt;p&gt;按照“Alpha”混合向量的值来混合源像素和目标像素。是一种让3D物件产生透明感的技术。若3D环境中像素拥有一个α通道，记载像素的透明度。就是处理两个物件在萤幕画面上叠加的时候，还会将α值列入考虑，使其呈现接近真实物件的效果。&lt;/p&gt;

    &lt;p&gt;具体就是： 
 第一步，先把源像素和目标像素的RGB 三个颜色分量分离，然后把源像素的三个颜色分量分别乘上Alpha 的值，并把目标像素的三个颜色分量分别乘上Alpha 的反值，接下来把结果按对应颜色分量相加，再对最后求得的每个分量结果除以Alpha 的最大值，最后把三个颜色分量重新合成为一个像素输出。 
 透过那些透明度非常高的物体看其他物体，例如透过几乎完全透明的玻璃看其他物体，会感到玻璃好像不存在，在三维图形程序中渲染时就可以不渲染这些透明度非常高的物体，从而可以提高渲染速度，这可以通过alpha测试来实现。&lt;/p&gt;

    &lt;p&gt;alpha测试根据当前像素是否满足alpha测试条件（即是否达到一定的透明度）来控制是否绘制该像素，图形程序应用alpha测试可以有效地屏蔽某些像素颜色。与alpha混合相比，alpha测试不将当前像素的颜色与颜色缓冲区中像素的颜色混合，像素要么完全不透明，要么完全透明。由于无需进行颜色缓冲区的读操作和颜色混合，因此alpha测试在速度上要优于alpha混合。
 相同：都要使用到Alpha通道
 不同：Alpha混合中，无论无题Alpha值的大小，只要》0，就会保留附体的像素值，使它能在屏幕上显示
 Alpha测试则是一种非此即彼的选择，设置一个参考值，给出一个测试方法，对每一个物体进行测试。当物体Alpha值&amp;gt;参考值,保留其在屏幕上的显示，反之则不显示。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;简述基本图元的类型和使用方法。
    &lt;ul&gt;
      &lt;li&gt;点列（D3DPT_POINTLIST）：点列由一系列的顶点组成。按照在顶点结构体数组的标识的点的位置画出单个点。&lt;/li&gt;
      &lt;li&gt;线列（D3DPT_LINELIST）：线列由一系列的线段组成。D3D依次读取两个点来构成一个图元，即一条线段。&lt;/li&gt;
      &lt;li&gt;线带（D3DPT_LINESTRIP）：线带由一系列的线段组成，前一个线段的终点是下一条线段的起点。D3D依次读取一组（2个）顶点画出一条线段，上一组的最后一个顶点是下一组的第一个顶点。&lt;/li&gt;
      &lt;li&gt;三角形列（D3DPT_TRIANGLELIST）：三角系列由一系列的三角形组成。D3D依次读取一组（3个）顶点画出一个三角形，只要顶点声明的不一样，各组中就没有任何一个顶点相同，但是当D3D读取的三个构成三角形的三个点是逆时针时，在默认情况下就不绘制该三角形。&lt;/li&gt;
      &lt;li&gt;三角形带（D3DPT_TRIANGLESTRIP）：三角形带又一系列的三角形组成，除了第一个三角形，其他的三角形只需要输入第三个顶点，其他两个顶点与前一个三角形重合。D3D依次读取一组（3个）顶点来构造一个三角形，后一组和前两个顶点时前一组的后两个顶点，在这种模式下，D3D保证在默认情况下以顺时针来画三角形。&lt;/li&gt;
      &lt;li&gt;三角形扇（D3DPT_TRANGLEFAN）：三角形扇也是由一系列的三角形组成，每两个三角形有两个顶点重合。D3D读取顶点数组中的第一个顶点作为三角扇的顶点，然后依次读取一组（2个顶点），前一组的第二个顶点是后一组的第一个顶点，若顶点顺序为逆时针时，在默认情况下仍然不画出。&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;绘制函数&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; HRESULT DrawPrimitive(          
 D3DPRIMITIVETYPE PrimitiveType,                              //基本图元类型
     UINT StartVertex,                                                      //起始顶点
     UINTPrimitiveCount                                     //绘制的图元的数量
 );
	 
 g_pDevice-&amp;gt;DrawPrimitive(type,startvertex,vertexnumber);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;DX中非其次坐标顶点同三维空间坐标通过变换显示到2D上，经历了那几个变换？摄像机有几大要素，每个要素的含义。
    &lt;ul&gt;
      &lt;li&gt;对每个物体建立局部坐标系&lt;/li&gt;
      &lt;li&gt;将每个物体铜鼓偶世界变换的运算变换到世界坐标系。&lt;/li&gt;
      &lt;li&gt;几何体和摄像机都是嫌贵世界坐标系定义的，将摄像机变换至世界坐标系的原点，并将其旋转，使摄像机的光轴与世界坐标系Z轴正方向一致。&lt;/li&gt;
      &lt;li&gt;通过投影将3D场景用2D表示&lt;/li&gt;
      &lt;li&gt;用视口变换把投影窗口转换到屏幕的一个矩形区域中。&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;三大要素：视点、焦点、正方向&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;介绍DX渲染管线的流程。
    &lt;ul&gt;
      &lt;li&gt;建立局部坐标系：用于定义构成物体的三角形单元列表的坐标系。此时，构建模型时无需考虑位置、大小、或相对于场景中其他物体的朝向。&lt;/li&gt;
      &lt;li&gt;建立世界坐标系：将位于自身局部坐标系中的物体组织在一起构成世界坐标系中的场景。通过平移、旋转以及比例运算，分别设定该物体在世界坐标系中的位置、方向以及模型的大小。&lt;/li&gt;
      &lt;li&gt;观察坐标系：我们将摄像机变换至世界坐标系的原点，并将其旋转，使摄像机的光轴与世界坐标系z轴正方向一致。同时，世界空间中的所有几何体都随着摄像机一同进行变换，以保证摄像机的现场恒定。&lt;/li&gt;
      &lt;li&gt;背面消隐：多边形都有两个侧面，一个侧面标记机为正面，另一个侧面标记为背面。通常，多边形的背面是不可见的。利用正面朝向多边形遮挡位于其后的背面朝向的多边形，将背面朝向多边形加以剔除。&lt;/li&gt;
      &lt;li&gt;光照：光源定义在世界坐标系中，必须经过取经变换至观察坐标系方可使用。光源照亮了场景中的物体，从而可以获得较为逼真的显示效果。&lt;/li&gt;
      &lt;li&gt;裁剪：将位于视域体外的几何体剔除掉。&lt;/li&gt;
      &lt;li&gt;投影：获取3D场景的2D表示。定义了视域体，并负责将视域体中的几何体投影到投影窗口中。&lt;/li&gt;
      &lt;li&gt;视口变换：将顶点坐标从投影窗口转换到屏幕的一个矩形区域中，该矩形区域成为视口。&lt;/li&gt;
      &lt;li&gt;光栅化：计算构成三角形单元的每个像素的颜色值，以绘制每个三角形单元。其最终结果是显示在屏幕上一副2D图像。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>动画与游戏设计基础</title>
   <link href="https://blog.cyeam.com/game/2010/12/29/game"/>
   <updated>2010-12-29T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/game/2010/12/29/game</id>
   <content type="html">&lt;ol&gt;
  &lt;li&gt;面向对象的四大特征：抽象、封装、继承、多态。&lt;/li&gt;
  &lt;li&gt;C++全局函数重载的语法特点：（重载是什么？）函数声明的参数列表不同。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Int function （int， int）&lt;/code&gt;是函数原型，那么怎么样定义函数指针：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int(*f)(intx, int y)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;常用的数据结构有哪些：（3个）堆栈、队列、堆&lt;/li&gt;
  &lt;li&gt;在游戏开发中，都有哪些分工：（3种）美术、程序、策划。&lt;/li&gt;
  &lt;li&gt;在C++种将一个概念封装成类，类的数据成员的类型都是确定的，那么有一种高级的抽象叫做什么：模板。&lt;/li&gt;
  &lt;li&gt;在编译中，C++代码如果出现无法解析的外部符号说明：已声明未定义&lt;/li&gt;
  &lt;li&gt;C++中虚函数的关键字是：virtual&lt;/li&gt;
  &lt;li&gt;一个子类继承了带有纯虚函数的父类，怎样才能被实例化：实现父类中的所有虚函数&lt;/li&gt;
  &lt;li&gt;Windows程序驱动的机制是：消息机制&lt;/li&gt;
  &lt;li&gt;Windows编程中的窗口句柄：HWND&lt;/li&gt;
  &lt;li&gt;什么代表现实的屏幕：HDC&lt;/li&gt;
  &lt;li&gt;用某种变量代表某种资源：句柄&lt;/li&gt;
  &lt;li&gt;Windows中获取现实设备的函数是GetDC()，释放资源的函数是：ReleaseDC()。&lt;/li&gt;
  &lt;li&gt;在Windows中，只有一个地方的消息机制会被执行一次：WM_CREATE：&lt;/li&gt;
  &lt;li&gt;Windows中用什么机制来消除闪烁：双缓冲。&lt;/li&gt;
  &lt;li&gt;在游戏程序中，要把  绘制  和逻辑分开写，以免造成代码混乱。&lt;/li&gt;
  &lt;li&gt;响应键盘按下的消息是：WM_KEYDOWN，鼠标移动的消息是：WM_MOUSEMOVE&lt;/li&gt;
  &lt;li&gt;实现镂空的方法是：TransparentBlt()&lt;/li&gt;
  &lt;li&gt;需要链接的库函数：msimg32(msimg.lib)&lt;/li&gt;
  &lt;li&gt;场景调度分为：角色调度、场景调度、镜头调度&lt;/li&gt;
  &lt;li&gt;从三维动画工程角度看，角色的设定要考虑：造型和工程难度&lt;/li&gt;
  &lt;li&gt;运动的规律分为：起始、预备、过级、复位&lt;/li&gt;
  &lt;li&gt;基本的三维除了前期：模型制作、材质贴图、绑定蒙皮、动画渲染、后期合成（题目不清楚）&lt;/li&gt;
  &lt;li&gt;影视语言中的运动分为：画面内部运动、画面外部运动。&lt;/li&gt;
  &lt;li&gt;影视语言中的声音主要有：音乐、音效、对白。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;ol&gt;
  &lt;li&gt;广角镜头的特点是什么？
    &lt;ul&gt;
      &lt;li&gt;拍摄范围广&lt;/li&gt;
      &lt;li&gt;透视角度大&lt;/li&gt;
      &lt;li&gt;画面易于变形&lt;/li&gt;
      &lt;li&gt;适合表现大场面，小环境拍摄和处理特殊的艺术效果&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;摄像机的运动方式有哪些？&lt;/p&gt;

    &lt;p&gt;推、拉、摇、移、跟、甩、综合运动镜头&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;谈谈对轴线的认识：&lt;/p&gt;

    &lt;p&gt;轴线是指根据物体的运动方向或朝向假定的一条虚线；在拍摄当中应该在轴线一侧180度安排机位；如果摄像机超过这范围就叫做越轴；为了合理的越轴，主要有以下几种处理方式：1、空镜头2、齐轴镜头3、跟随运动镜头4、主观视角镜头5、特写镜头&lt;/p&gt;

    &lt;p&gt;有时候根据影片的具体情节，艺术效果的需求，也可以有意越轴。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;解释缩略词：&lt;/p&gt;

    &lt;p&gt;SDK：（SoftwareDevelopment Kit）软件开发工具包
 MSDN：(MicrosoftDeveloper Network) 
 GDI：(Graphic DeviceInterface)图形装置界面
 STL：(StandardTemplate Library)标准模板库&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;简要描述一个游戏引擎在游戏开发中的作用。&lt;/p&gt;

    &lt;p&gt;游戏引擎指挥控制着游戏中各种资源，包含：渲染器、物理引擎、碰撞检测系统、音效、脚本引擎、电脑动画、人工智能、网络引擎以及场景管理。游戏引擎提供了以上种种功能的实现，并把底层的细节封装，提供给用户接口，提供开发工具，它大大提高开发速度，降低开发难度，让用户可以直接使用已经实现过的功能，不必再做别的程序员做过的事情，帮助用户实现一些很难的效果，是游戏开发的必然趋势。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;是否存在指针的引用，是否存在指向引用的指针？为什么？&lt;/p&gt;

    &lt;p&gt;不存在指向引用的指针，引用是对象的一个别名，引用不被分配存储空间，而指针是指向一个内存地址的，所以没有能指向引用的指针&lt;/p&gt;

    &lt;p&gt;指针的引用指针的引用是利用指针变量，提供对变量的一种间接访问形式。对指针变量的引用形式为：*指针变量&lt;/p&gt;

    &lt;p&gt;其含义是指针变量所指向的值。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;PC游戏的开发为什么首选C++，而非C#或Java&lt;/p&gt;

    &lt;p&gt;C++是一种高级语言同时也是一种编译性语言，他具有高级语言的特性，同时也具有低级语言的特性，可以直接操作硬件，效率很高。而像C#或Java这些解释性语言必然会有一个虚拟机，降低了效率；&lt;/p&gt;

    &lt;p&gt;pc游戏它通常会有非常巨大的绘制图形，玩家频繁的输入操作，在输入输出的时候对响应速度要求非常高，并且网络通信的负荷大，要求通信量大，速度快。所以PC游戏的开发首选C++，而非C#或Java。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如何检查单向链表是否存在封闭环？&lt;/p&gt;

    &lt;p&gt;方法思想：例如两个人在环状操场赛跑,一名快者，一名慢者，一直跑下去，总有一个时刻，两人会相遇。但是如果赛跑场地是一条无限延伸的直路，则快者将一直领先，不会与慢者相遇。&lt;/p&gt;

    &lt;p&gt;例如检查下面链表是否存在封闭环：&lt;/p&gt;

    &lt;p&gt;检测方法如下：&lt;/p&gt;

    &lt;p&gt;定义两个指针P1（每次移动一格）： 1,2,3,4,5&lt;/p&gt;

    &lt;p&gt;P2（每次移动两个格）：1,3,5,3,5   此时相遇，则证明该单向链表存在封闭环。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>3Q大战</title>
   <link href="https://blog.cyeam.com/ctalk/2010/11/03/360"/>
   <updated>2010-11-03T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/ctalk/2010/11/03/360</id>
   <content type="html">&lt;p&gt;360老总周鸿炜以前在雅虎工作时，带队研发了3721恶意插件，在我记忆中能算是中国恶意软件的鼻祖了吧。后来周鸿炜出走，创建了奇虎网，带队研发了360安全卫士，主要就是针对3721恶意软件（后来更名为雅虎助手），结果是如果安装了360，他会提示删除雅虎助手，而雅虎助手会提示删除360。&lt;/p&gt;

&lt;p&gt;周鸿炜对他自己这个矛盾的行为的解释是：他打开了这个潘多拉盒子，他要亲手消灭它。&lt;/p&gt;

&lt;p&gt;今年，360又推出一个隐私保护器，跟上次一样，两家打起来了，又是一场闹剧。&lt;/p&gt;

&lt;p&gt;我们就当看笑话吧，反正我个人觉得这两个公司都不怎么样，一个免费杀毒，也杀不了什么厉害的病毒，到现在为止也不能把一个U盘病毒彻底杀掉吧；一个各种山寨，如果不是里面好友多，我才懒得用。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/cyeam/image/upload/v1537933530/cyeam/zhouhongyi.jpg&quot; alt=&quot;IMG-THUMBNAIL&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>又要走了，可是不怎么想走啊</title>
   <link href="https://blog.cyeam.com/notebook/2009/02/16/do_not_wanna_leave"/>
   <updated>2009-02-16T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2009/02/16/do_not_wanna_leave</id>
   <content type="html">&lt;p&gt;又要开学了，我也要离开阳泉了。&lt;/p&gt;

&lt;p&gt;有一万分不愿，但还是得走啊。&lt;/p&gt;

&lt;p&gt;前两天送了几个哥们，就有点伤感了，一年也就见两次，以后毕业了估计最多也就每年一次了。他们走的时候不怎么高兴，搞得我这几天也不怎么舒服啊。&lt;/p&gt;

&lt;p&gt;要走了，奶奶天天看着日历算日子，算还能和我呆几天。每天想着法给我做我喜欢的吃的，每次我说有事不去吃时，她总是很伤心的，可又不好说什么。每次我吃了饭要走了，她总是要送我，直到我在她的视野里消失。每次给我做饭都忙到很晚的，走得时候还硬要我带东西走。记得刚回来的时候回去看她，她哭了，说快想死了。暑假去学校的时候她病了，很厉害，可担心孙子，一夜一夜的睡不着，电话里总骗我说病好了，其实我走了两个月才好的。这次又走，不知道她会哭成什么样了，也不知道她会几个晚上睡不着。&lt;/p&gt;

&lt;p&gt;也总想和父母谈些什么，可代沟太深了，实在没什么能说的，我爱玩电脑，他们不懂什么，他们看电视，我也不怎么喜欢看啊，也就吃饭能坐到一起，可又说不了什么。他们总想和我说些什么，可又开不了口，我也一样，我恨代沟啊，也不知道怎么会有啊。这几天也是一样，每天给我吃饺子，忙活一阵子，我说在重庆也能吃得到，可他们还是要做。说白了，就是有点不想我走啊。&lt;/p&gt;

&lt;p&gt;重庆什么时候变天，我家人比我还清楚啊，每天看天气预报，真是儿行千里母担忧啊！&lt;/p&gt;

&lt;p&gt;真是后悔考这么远啊，去个北京什么的，周末还能回去！&lt;/p&gt;

&lt;p&gt;回想回来的几天啊，每天就周末玩游戏，哎，虚度啊！&lt;/p&gt;

&lt;p&gt;真想早点毕业，让他们和我一起住，就不用和家人分开了！&lt;/p&gt;

&lt;p&gt;下学期，我要继续努力啊，争取奖学金，不是为了我，让家人欣慰啊！我能做的也就这么多了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>又过了一年</title>
   <link href="https://blog.cyeam.com/notebook/2008/12/31/another_year"/>
   <updated>2008-12-31T00:00:00+00:00</updated>
   <id>https://blog.cyeam.com/notebook/2008/12/31/another_year</id>
   <content type="html">&lt;p&gt;今年发生了好多事啊，接触了一生中好多没见过的。&lt;/p&gt;

&lt;p&gt;前半年，在艰苦的环境中挣扎。补习好难啊，压力大，条件差，老师素质不高，好郁闷啊。当时没什么感觉，现在觉得好苦啊，好在挺过来了，考上了。真不知道如果没考上的话会怎么样。从去年高考成绩出来后，就郁闷到了今年分数出来，父母跟我承受着压力，虽然嘴上没说，可看得出来啊。我也是啊，面对同样的东西，一遍一遍的看，厌倦了，又没办法，逼着自己去看，就这么过着。每天12点睡，早上6点起，重复着，根本来不及想该休息了，没从没想过如果考上了会怎么去玩，不敢想啊。从考完那天起，我感觉我疯了，那是第一年没有的感觉，尤其是填了志愿后。我没白天没黑夜的玩，到处跑。父母也终于放下了包袱，开心了。呵呵，我妈也舍得让我花钱了，那是最美好的一段日子了。&lt;/p&gt;

&lt;p&gt;后来就来了重大。当初也没想很多，就是根据分报的。来了感觉也不错，呵呵，相当不错。我适应能力不错，我很快爱上这里了。在这里认识了很多朋友，好开心的。&lt;/p&gt;

&lt;p&gt;我报了软件工程（含数字媒体方向），我热爱这个专业，不管他将来就业怎么样。我一开始也是在努力的学的，而且相当不错，可后来，尤其是最近，堕落了，呵呵。&lt;/p&gt;

&lt;p&gt;来了这也遇到过以前没遇到的问题。忘了银行卡密码，帮人打架……&lt;/p&gt;

&lt;p&gt;总之，还有不到15分，今年就这么过去了。呵呵，心情好复杂啊。&lt;/p&gt;

&lt;p&gt;2008，就这么过去了，我还要加油啊。努力吧，Superman.&lt;/p&gt;

</content>
 </entry>
 
 
</feed>