[{"content":"初识RAG RAG: Retrieval Augmented Generation检索增强生成\n任务说明：了解到现有大模型的缺陷和RAG的优点和流程\n任务要求：\n了解大模型现有的缺点 理解RAG的流程和实现步骤 清楚RAG的需要的技术 打卡要求：阅读LangChain与RAG的文章 579和LangChain的官方文档 228，列举LangChain能实现的功能。\n当前大模型的缺点：\n只能基于大模型训练时所提供的知识进行回答，如果想要定制私人化的知识，需要使用特定的外挂信息来增强模型的知识 大模型现存问题： 模型幻觉问题：生成的内容可能不一致或不准确 时效性问题：不满足当下的时效性，因为模型训练时使用的数据时很老的 安全性问题：可能存在敏感信息泄露风险（RAG如何规避这个风险？） RAG的优点： 提高准确性和相关性 改善时效性，是模型能适应当前事件和知识 降低生成错误的风险，依赖检索提供准确信息 ‍\n​ ​\n‍\nLangChain提供以下组件来帮助构建RAG应用： DocumentLoader： 负责加载文档，包括 page_content（str） + metadata（dict），为文本内容和元数据 DocumentSpliter： 负责分割文档，小段落的文档更容易检索，且聊天模型的输入窗口是有限制的 Embeddings： 将文本转化为向量 VectorStore： 负责向量的存储和检索，如Faiss 、 Annoy Retriever： 负责取回符合搜索条件的内容，通过输入找到对于文档 ChatModel： 聊天模型，负责生成答案，如GPT ​ ​\nLangChain和RAG的优势和应用场景 LangChain和RAG的结合可以带来以下的优势：\n灵活性：你可以根据你的需求和数据源选择不同的组件和参数，定制你的RAG应用。你也可以使用自定义的组件，只要它们遵循LangChain的接口规范。 可扩展性：你可以使用LangChain的云服务来部署和运行你的RAG应用，无需担心资源和性能的限制。你也可以使用LangChain的分布式计算功能来加速你的RAG应用，利用多个节点的并行处理能力。 可视化：你可以使用LangSmith来可视化你的RAG应用的工作流程，查看每个步骤的输入和输出，以及每个组件的性能和状态。你也可以使用LangSmith来调试和优化你的RAG应用，发现和解决潜在的问题和瓶颈。 LangChain和RAG的结合可以应用于多种场景，例如：\n专业问答（Professional Question Answering）：你可以使用LangChain和RAG来构建一个专业领域的问答应用，例如医疗、法律或金融。你可以从专业领域的数据源中检索相关的信息，帮助大模型回答用户的问题。例如，你可以从医学文献中检索疾病的诊断和治疗方案，帮助大模型回答医疗相关的问题。 文本摘要（Text Summarization）：你可以使用LangChain和RAG来构建一个文本摘要应用，例如新闻摘要或论文摘要。你可以从多个数据源中检索相关的文本，帮助大模型生成一个综合的摘要。例如，你可以从多个新闻网站中检索关于同一事件的报道，帮助大模型生成一个全面的摘要。 文本生成（Text Generation）：你可以使用LangChain和RAG来构建一个文本生成应用，例如诗歌生成或故事生成。你可以从不同的数据源中检索灵感，帮助大模型生成更有趣和更有创意的文本。例如，你可以从诗歌、歌词或小说中检索相关的文本，帮助大模型生成一首诗、一首歌或一个故事。 ‍\nRAG 的三个流程\n问题理解：准确把握用户的意图 \u0026ndash; 挑战：但是，用户有时候提问的描述不准确，比较难以识别，比如提问的内容是很短的几个关键字，或者模糊提问，但是知识库中的文档却是长文档； 知识检索：用户提问可能以多种方式表达，需要检索的文档类型也可能是多种多样的，如PDF，Doc，html等 答案生成 ‍\n在RAG技术流程中，涉及多个关键模块，每个模块承担着特定的任务，协同工作以实现准确的知识检索和生成自然语言回答。\n技术模块 描述 意图理解 意图理解模块负责准确把握用户提出的问题，确定用户的意图和主题。处理用户提问的模糊性和不规范性，为后续流程提供清晰的任务目标。 文档解析 文档解析模块用于处理来自不同来源的文档，包括PDF、PPT、Neo4j等格式。该模块负责将文档内容转化为可处理的结构化形式，为知识检索提供合适的输入。 文档索引 文档索引模块将解析后的文档分割成短的Chunk，并构建向量索引。或通过全文索引进行文本检索，使得系统能够更快速地找到与用户问题相关的文档片段。 向量嵌入 向量嵌入模块负责将文档索引中的内容映射为向量表示，以便后续的相似度计算。这有助于模型更好地理解文档之间的关系，提高知识检索的准确性。 知识检索 知识检索模块根据用户提问和向量嵌入计算的相似度检索或文本检索打分。这一步骤需要解决问题和文档之间的语义关联，确保检索的准确性。 重排序 重排序模块在知识检索后对文档库进行重排序，以避免“Lost in the Middle”现象，确保最相关的文档片段在前面。 大模型回答 大模型回答模块利用大型语言模型生成最终的回答。该模块结合检索到的上下文，以生成连贯、准确的文本回答。 其他功能模块 可根据具体应用需求引入其他功能模块，如查询搜索引擎、融合多个回答等。模块化设计使得系统更加灵活，能够根据不同场景选择合适的功能模块组合。 ","permalink":"https://zihuanling.github.io/post/01first-recognition-rag-z6wl3c.html","summary":"\u003ch1 id=\"初识rag\"\u003e初识RAG\u003c/h1\u003e\n\u003cp\u003eRAG: Retrieval Augmented Generation检索增强生成\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e任务说明：了解到现有大模型的缺陷和RAG的优点和流程\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e任务要求：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e了解大模型现有的缺点\u003c/li\u003e\n\u003cli\u003e理解RAG的流程和实现步骤\u003c/li\u003e\n\u003cli\u003e清楚RAG的需要的技术\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e打卡要求：阅读LangChain与RAG的\u003ca href=\"https://juejin.cn/post/7310569308916334604\"\u003e文章 579\u003c/a\u003e和LangChain的\u003ca href=\"https://python.langchain.com/docs/tutorials/rag/\"\u003e官方文档 228\u003c/a\u003e，列举LangChain能实现的功能。\u003c/p\u003e","title":"初识RAG"},{"content":"vagrant + centos7创建3节点集群 坑 设置节点的网络的时候，需要给一个属性name​，确保节点启动的时候，集群里面的节点会绑定到同一个网络平面：**node.vm.network \u0026quot;private_network\u0026quot;, name: \u0026quot;VirtualBox Host-Only Enthernet Adapter\u0026quot;**​ 为了防止节点启动的时候，22端口被随机分配绑定宿主机端口，我们可以配置将其转发到一个固定的端口：node.vm.network \u0026quot;forwarded_port\u0026quot;, guest: 22, host: \u0026quot;230#{i}\u0026quot;​，需要注意的是，需要先将原来的ssh转发端口禁用掉：node.vm.network \u0026quot;forwarded_port\u0026quot;, guest: 22, host: 2222, id: \u0026quot;ssh\u0026quot;, disabled: \u0026quot;true\u0026quot;​ 配置文件：\n# Vagrantfile Vagrant.configure(\u0026#34;2\u0026#34;) do |config| # config.vm.network \u0026#34;private_network\u0026#34;, auto_config: false (1..3).each do |i| config.vm.define \u0026#34;node#{i}\u0026#34; do |node| # 设置虚拟机的Box node.vm.box = \u0026#34;generic/centos7\u0026#34; # 设置虚拟机的主机名 node.vm.hostname=\u0026#34;node#{i}\u0026#34; # 设置虚拟机的IP，绑定一个固定的网卡，确保 node.vm.network \u0026#34;private_network\u0026#34;, ip: \u0026#34;192.168.59.10#{i}\u0026#34;, name: \u0026#34;VirtualBox Host-Only Ethernet Adapter\u0026#34; # 设置端口转发 node.vm.network \u0026#34;forwarded_port\u0026#34;, guest: 22, host: 2222, id: \u0026#34;ssh\u0026#34;, disabled: \u0026#34;true\u0026#34; node.vm.network \u0026#34;forwarded_port\u0026#34;, guest: 22, host: \u0026#34;230#{i}\u0026#34; node.vm.network \u0026#34;forwarded_port\u0026#34;, guest: 2181, host: \u0026#34;218#{i}\u0026#34; # 设置主机与虚拟机的共享目录 node.vm.synced_folder \u0026#34;D:/Vagrants/share\u0026#34;, \u0026#34;/home/vagrant/share\u0026#34; # VirtaulBox相关配置 node.vm.provider \u0026#34;virtualbox\u0026#34; do |v| # 设置虚拟机的名称 v.name = \u0026#34;node#{i}\u0026#34; # 设置虚拟机的内存大小 v.memory = 6144 # 设置虚拟机的CPU个数 v.cpus = 1 end end end end ‍\n","permalink":"https://zihuanling.github.io/post/vagrant-centos7-create-3-node-cluster-z1lgxuu.html","summary":"\u003ch1 id=\"vagrant--centos7创建3节点集群\"\u003evagrant + centos7创建3节点集群\u003c/h1\u003e\n\u003ch2 id=\"坑\"\u003e坑\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e设置节点的网络的时候，需要给一个属性\u003ccode\u003ename\u003c/code\u003e​，确保节点启动的时候，集群里面的节点会绑定到同一个网络平面：**\u003ccode\u003enode.vm.network \u0026quot;private_network\u0026quot;, name: \u0026quot;VirtualBox Host-Only Enthernet Adapter\u0026quot;\u003c/code\u003e**​\u003c/li\u003e\n\u003cli\u003e为了防止节点启动的时候，22端口被随机分配绑定宿主机端口，我们可以配置将其转发到一个固定的端口：\u003ccode\u003enode.vm.network \u0026quot;forwarded_port\u0026quot;, guest: 22, host: \u0026quot;230#{i}\u0026quot;\u003c/code\u003e​，需要注意的是，需要先将原来的ssh转发端口禁用掉：\u003ccode\u003enode.vm.network \u0026quot;forwarded_port\u0026quot;, guest: 22, host: 2222, id: \u0026quot;ssh\u0026quot;, disabled: \u0026quot;true\u0026quot;\u003c/code\u003e​\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e配置文件：\u003c/p\u003e","title":"vagrant + centos7创建3节点集群"},{"content":"使用python argparse实现简单的click python3 argparse 文档\npython3 ArgParse教程\n需求 实现一个类似click command的类，支持将函数作为命令注册进去，调用这个类的时候，解析命令行参数，执行对应的注册函数。\n可以用装饰器的方法进行函数注册以及参数添加\n类名： Entrance\n方法：\n注册： Entrance.register 参数：Entrance.option 实现 entrance.py\n# encoding: utf-8 \u0026#34;\u0026#34;\u0026#34;the only entrance for the program\u0026#34;\u0026#34;\u0026#34; import argparse class EntranceConflict(Exception): pass class Entrance: def __init__(self): self.parser = argparse.ArgumentParser() self.sub_parser = self.parser.add_subparsers() self.entrances = {} def register(self, name=None): \u0026#34;\u0026#34;\u0026#34; 注册一个函数作为入口 可以指定name，否则默认使用函数的名字 \u0026#34;\u0026#34;\u0026#34; def decorator(f): entrance_name = name or f.__name__ if entrance_name in self.entrances: raise EntranceConflict(f\u0026#34;entrance {entrance_name} already registered.\u0026#34;) # make parser _parser = self.sub_parser.add_parser(name=entrance_name) _params = f.__entrance_params__ for _args, _kwargs in _params: _parser.add_argument(*_args, **_kwargs) _parser.set_defaults(processor=f) # add parser to entrances self.entrances[entrance_name] = _parser return f return decorator def option(self, *args, **kwargs): \u0026#34;\u0026#34;\u0026#34; 注册一个函数的参数 参数格式参考 argparse.ArgumentParser.add_argument \u0026#34;\u0026#34;\u0026#34; def decorator(f): # set tmp attrs for f if not hasattr(f, \u0026#39;__entrance_params__\u0026#39;): f.__entrance_params__ = [] f.__entrance_params__.append((args, kwargs)) return f return decorator def __call__(self, *args, **kwargs): args = self.parser.parse_args() # extrac args params = vars(args) processor = params.get(\u0026#39;processor\u0026#39;) if processor: del params[\u0026#39;processor\u0026#39;] return processor(**params) else: print(\u0026#39;un processable entrance: {}\u0026#39;.format(args)) self.parser.print_help() entrance = Entrance() 调用 写一个简单的调用demo\nmain.py\nfrom entrance import entrance @entrance.register() @entrance.option(\u0026#39;--user-name\u0026#39;, required=True) @entrance.option(\u0026#39;--age\u0026#39;) def add_user(user_name, age): print(\u0026#34;Added user: {}, age={}\u0026#34;.format(user_name, age)) @entrance.register() @entrance.option(\u0026#39;--user-id\u0026#39;, required=True) def del_user(user_id): print(\u0026#34;deleted user whose id = {}\u0026#34;.format(user_id)) if __name__ == \u0026#39;__main__\u0026#39;: entrance() 运行\n$ python main.py add_user --user-name Mike --age 19 Added user: Mike, age=19 $ python main.py del_user --user-id 10 deleted user whose id = 10 ","permalink":"https://zihuanling.github.io/posts/argparse-entrance-like-click/","summary":"\u003ch1 id=\"使用python-argparse实现简单的click\"\u003e使用python argparse实现简单的click\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://docs.python.org/zh-cn/3/library/argparse.html#module-argparse\"\u003epython3 argparse 文档\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://docs.python.org/zh-cn/3/howto/argparse.html#id1\"\u003epython3 ArgParse教程\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"需求\"\u003e需求\u003c/h2\u003e\n\u003cp\u003e实现一个类似click command的类，支持将函数作为命令注册进去，调用这个类的时候，解析命令行参数，执行对应的注册函数。\u003c/p\u003e\n\u003cp\u003e可以用装饰器的方法进行函数注册以及参数添加\u003c/p\u003e\n\u003cp\u003e类名： Entrance\u003c/p\u003e\n\u003cp\u003e方法：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e注册： Entrance.register\u003c/li\u003e\n\u003cli\u003e参数：Entrance.option\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"实现\"\u003e实现\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eentrance.py\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# encoding: utf-8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;the only entrance for the program\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e argparse\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEntranceConflict\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eException\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003epass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEntrance\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e __init__(self):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparser \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e argparse\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eArgumentParser()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esub_parser \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eadd_subparsers()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eentrances \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eregister\u003c/span\u003e(self, name\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34; 注册一个函数作为入口\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        可以指定name，否则默认使用函数的名字\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edecorator\u003c/span\u003e(f):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            entrance_name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e name \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e__name__\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e entrance_name \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eentrances:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eraise\u003c/span\u003e EntranceConflict(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;entrance \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003eentrance_name\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e already registered.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#75715e\"\u003e# make parser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            _parser \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esub_parser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eadd_parser(name\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eentrance_name)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            _params \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e__entrance_params__\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _args, _kwargs \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e _params:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                _parser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eadd_argument(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e_args, \u003cspan style=\"color:#f92672\"\u003e**\u003c/span\u003e_kwargs)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            _parser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eset_defaults(processor\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ef)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#75715e\"\u003e# add parser to entrances\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eentrances[entrance_name] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e _parser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e f\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e decorator\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eoption\u003c/span\u003e(self, \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eargs, \u003cspan style=\"color:#f92672\"\u003e**\u003c/span\u003ekwargs):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34; 注册一个函数的参数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        参数格式参考 argparse.ArgumentParser.add_argument\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edecorator\u003c/span\u003e(f):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#75715e\"\u003e# set tmp attrs for f\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e hasattr(f, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__entrance_params__\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e__entrance_params__ \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e__entrance_params__\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend((args, kwargs))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e f\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e decorator\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e __call__(self, \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eargs, \u003cspan style=\"color:#f92672\"\u003e**\u003c/span\u003ekwargs):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        args \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparse_args()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# extrac args\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        params \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e vars(args)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        processor \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e params\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;processor\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e processor:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003edel\u003c/span\u003e params[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;processor\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e processor(\u003cspan style=\"color:#f92672\"\u003e**\u003c/span\u003eparams)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;un processable entrance: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(args))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparser\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eprint_help()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eentrance \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Entrance()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"调用\"\u003e调用\u003c/h2\u003e\n\u003cp\u003e写一个简单的调用demo\u003c/p\u003e","title":"Argparse Entrance Like Click"},{"content":"有些时候，我们在写入mongo数据的时候，可能需要记录这条数据的插入时间，我们一般情况下回给记录增加一个 create_time。\n实际上，如果我们插入的数据含有 ObjectId 的话，那么其实这个id是包含了生成时间的，同时也可以作为记录的主键，一举多得。\n以python为例，我们有这样一条记录：\ndocument = { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;), \u0026#34;user\u0026#34;: \u0026#34;mike\u0026#34;, \u0026#34;age\u0026#34;: 19, } 这时候，我们可以使用ObjectId自带的generation_time属性，获取这条记录的插入时间：\ntime = ObjectId(\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;).generation_time print(time) 输出：\ndatetime.datetime(2019, 12, 20, 8, 48, 5, tzinfo=\u0026lt;bson.tz_util.FixedOffset object at 0x7f874030f9a0\u0026gt;) 需要注意到的是，里面的时区信息，显示的是一个 FixedOffset，即固定偏移，我们看 generation_time 属性的定义：\nutc = FixedOffset(0, \u0026#34;UTC\u0026#34;) \u0026#34;\u0026#34;\u0026#34;Fixed offset timezone representing UTC.\u0026#34;\u0026#34;\u0026#34; class ObjectId(object): # ... 省略其他代码 ... @property def generation_time(self): \u0026#34;\u0026#34;\u0026#34;A :class:`datetime.datetime` instance representing the time of generation for this :class:`ObjectId`. The :class:`datetime.datetime` is timezone aware, and represents the generation time in UTC. It is precise to the second. \u0026#34;\u0026#34;\u0026#34; timestamp = struct.unpack(\u0026#34;\u0026gt;I\u0026#34;, self.__id[0:4])[0] return datetime.datetime.fromtimestamp(timestamp, utc) 可以看到这个属性是从一个时间戳生成的，并且生成的时候指定了固定的 UTC-0 时区，而我们是处于东八区，因此，如果直接使用 generation_time 属性的话，得到的时间并非我们当前时区的时间，还需要做一个转化，只需要简单的对 datetime.datetime 对象调用astimezone方法，即可：\nObjectId(\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;).generation_time.astimezone(None) # output: datetime.datetime(2019, 12, 20, 16, 48, 5, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800), \u0026#39;CST\u0026#39;)) 可以看到，现在输出的时间信息，跟之前的输出相差了8个小时。调用astimezone传入None的时候，就会默认转化为我们当前所在的时区（代码运行系统所在的时区，东8）\n可能我们不想要看到输出这么一长串的信息，展示出来的时候去掉时区信息，我们可以调用 replace 方法：\nObjectId(\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;).generation_time.astimezone(None).replace(tzinfo=None) # output: datetime.datetime(2019, 12, 20, 16, 48, 5) 又或许，我们需要的只是一个整型的时间戳，可以这样做：\nimport struct from bson import ObjectId oid = ObjectId(\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;) timestamp = struct.unpack(\u0026#34;\u0026gt;I\u0026#34;, oid.binary[0:4])[0] # timestamp = 1576831685 ","permalink":"https://zihuanling.github.io/posts/python-mongo-generation-time/","summary":"\u003cp\u003e有些时候，我们在写入mongo数据的时候，可能需要记录这条数据的插入时间，我们一般情况下回给记录增加一个 create_time。\u003c/p\u003e\n\u003cp\u003e实际上，如果我们插入的数据含有 \u003ccode\u003eObjectId\u003c/code\u003e 的话，那么其实这个id是包含了生成时间的，同时也可以作为记录的主键，一举多得。\u003c/p\u003e\n\u003cp\u003e以python为例，我们有这样一条记录：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocument \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e: ObjectId(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;age\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e19\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这时候，我们可以使用\u003ccode\u003eObjectId\u003c/code\u003e自带的\u003ccode\u003egeneration_time\u003c/code\u003e属性，获取这条记录的插入时间：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etime \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ObjectId(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5dfc8ac5d3fa12967b8888ec\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egeneration_time\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(time)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003edatetime.datetime(2019, 12, 20, 8, 48, 5, tzinfo=\u0026lt;bson.tz_util.FixedOffset object at 0x7f874030f9a0\u0026gt;)\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e需要注意到的是，里面的时区信息，显示的是一个 FixedOffset，即固定偏移，我们看 \u003ccode\u003egeneration_time\u003c/code\u003e 属性的定义：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eutc \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e FixedOffset(\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;UTC\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;Fixed offset timezone representing UTC.\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eObjectId\u003c/span\u003e(object):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# ... 省略其他代码 ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003e@property\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egeneration_time\u003c/span\u003e(self):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;A :class:`datetime.datetime` instance representing the time of\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        generation for this :class:`ObjectId`.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        The :class:`datetime.datetime` is timezone aware, and\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        represents the generation time in UTC. It is precise to the\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        second.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e        \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        timestamp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e struct\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eunpack(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026gt;I\u0026#34;\u003c/span\u003e, self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e__id[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e])[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e datetime\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edatetime\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efromtimestamp(timestamp, utc)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以看到这个属性是从一个时间戳生成的，并且生成的时候指定了固定的 UTC-0 时区，而我们是处于\u003cstrong\u003e东八区\u003c/strong\u003e，因此，如果直接使用 \u003ccode\u003egeneration_time\u003c/code\u003e 属性的话，得到的时间并非我们当前时区的时间，还需要做一个转化，只需要简单的对 \u003ccode\u003edatetime.datetime\u003c/code\u003e 对象调用\u003cstrong\u003eastimezone\u003c/strong\u003e方法，即可：\u003c/p\u003e","title":"使用mongodb自带的ObjectID获取记录生成时间"},{"content":"项目地址： https://pypi.org/project/pip-tools/\npip-tools 是一个命令行工具集合，主要有两个命令： pip-compile，pip-sync，现在主要用的是 pip-compile\n在写python项目的时候，我们通常会依赖到其他的包，比如写一个后端的服务，我们可能会依赖到flask/pymysql这些基础的包，随着开发的进行，我们还可能会引入更多的依赖。\n这些python依赖包构成了一个完整的服务，开发完成后，我们将这些服务部署到服务器上时，只需要安装好对应的依赖，便可以直接运行服务。\n但是，如果我们不将生产环境的依赖固定下来的话，整个服务可能会因为某一次服务更新，其中某个的更新带来的不兼容导致整个服务崩溃。比如pyjwt，在1.7.1版本之前生成的jwt-token是一个bytes类型，我们将其转换成为字符串需要使用到decode()方法。但是在更新到2.0之后，生成的jwt-token是一个字符串类型，如果我们对其再调用decode方法的话，就会发生错误。\n这就显现出了固定依赖的重要性了。\n通常，固定依赖，我们可以在开发环境使用 pip freeze命令，直接将开发环境的所有版本固定下来到一个 requirements.txt 文件里面，这是懒人做法，当然也很有效。但是文件里面会带有一些系统的依赖版本，比如urllib 之类的，这些我们是不需要再次安装的，所以，如果这些依赖出现在 requirements.txt 文件里面，就会显得不那么干净。\n这时，我们可以使用 pip-tools里面的 pip-compile 工具，对依赖进行编译，生成一份漂亮的依赖文件。\n直接生成最新依赖 首先，我们将项目中直接依赖的包写到requirements.in文件里面 requirements.in\npymysql flask 使用 pip-compile 自动使用最新依赖版本生成依赖文件 命令： pip-compile, 输出requirements.txt文件：\nrequirements.txt\n# # This file is autogenerated by pip-compile with python 3.8 # To update, run: # # pip-compile # --index-url https://pypi.doubanio.com/simple --trusted-host pypi.doubanio.com click==8.1.3 # via flask flask==2.1.2 # via -r requirements.in importlib-metadata==4.11.4 # via flask itsdangerous==2.1.2 # via flask jinja2==3.1.2 # via flask markupsafe==2.1.1 # via jinja2 pymysql==1.0.2 # via -r requirements.in werkzeug==2.1.2 # via flask zipp==3.8.0 # via importlib-metadata 这样，我们就得到了一份漂亮的依赖文件，我们还可以清晰的看到某一个依赖包是哪个包的依赖。\n从本地已有依赖生成文件 有时候，我们的服务已经部署上云了，这时候，我们想要使用已有的（本地已安装的）依赖版本来生成依赖文件，而不是从pypi上查找最新的，因为现有正在运行的相对稳定，而更新后不确定会引入哪些不兼容问题。\n我们可以这样做：\n还是先将我们的直接依赖写到 requirements.in 文件里面 到生产环境中，使用 pip freeze --exclude-editable \u0026gt; requirements.txt 命令，将当前环境的所有包版本写到requirements.txt 文件里面 使用命令 pip-compile --no-upgrade，只用本地依赖生成一份漂亮的依赖文件。 重新生成的依赖文件：\nrequirements.txt\n# # This file is autogenerated by pip-compile with python 3.8 # To update, run: # # pip-compile # --index-url https://pypi.doubanio.com/simple --trusted-host pypi.doubanio.com click==7.1.2 # via flask flask==1.1.4 # via -r requirements.in itsdangerous==1.1.0 # via flask jinja2==2.11.3 # via flask markupsafe==2.1.1 # via jinja2 pymysql==0.10.1 # via -r requirements.in werkzeug==1.0.1 # via flask 加上参数 --no-upgrade，pip-compile 就会优先使用requirements.txt里面，我们freeze的依赖版本，而不是到pypi中寻找最新的版本。\n可以看到，重新生成的 requirements.txt 文件的各个版本都相对落后一些，依赖总数也少一点。\n","permalink":"https://zihuanling.github.io/posts/pip-tools-generate-dependencies/","summary":"\u003cp\u003e项目地址： \u003ca href=\"https://pypi.org/project/pip-tools/\"\u003ehttps://pypi.org/project/pip-tools/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003epip-tools 是一个命令行工具集合，主要有两个命令： \u003ccode\u003epip-compile\u003c/code\u003e，\u003ccode\u003epip-sync\u003c/code\u003e，现在主要用的是 \u003ccode\u003epip-compile\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e在写python项目的时候，我们通常会依赖到其他的包，比如写一个后端的服务，我们可能会依赖到\u003ccode\u003eflask/pymysql\u003c/code\u003e这些基础的包，随着开发的进行，我们还可能会引入更多的依赖。\u003c/p\u003e\n\u003cp\u003e这些python依赖包构成了一个完整的服务，开发完成后，我们将这些服务部署到服务器上时，只需要安装好对应的依赖，便可以直接运行服务。\u003c/p\u003e\n\u003cp\u003e但是，如果我们不将生产环境的依赖固定下来的话，整个服务可能会因为某一次服务更新，其中某个的更新带来的不兼容导致整个服务崩溃。比如pyjwt，在1.7.1版本之前生成的jwt-token是一个bytes类型，我们将其转换成为字符串需要使用到\u003ccode\u003edecode()\u003c/code\u003e方法。但是在更新到2.0之后，生成的jwt-token是一个字符串类型，如果我们对其再调用\u003ccode\u003edecode\u003c/code\u003e方法的话，就会发生错误。\u003c/p\u003e\n\u003cp\u003e这就显现出了\u003cstrong\u003e固定依赖\u003c/strong\u003e的重要性了。\u003c/p\u003e\n\u003cp\u003e通常，固定依赖，我们可以在开发环境使用 \u003ccode\u003epip freeze\u003c/code\u003e命令，直接将开发环境的所有版本固定下来到一个 requirements.txt 文件里面，这是懒人做法，当然也很有效。但是文件里面会带有一些系统的依赖版本，比如urllib 之类的，这些我们是不需要再次安装的，所以，如果这些依赖出现在 requirements.txt 文件里面，就会显得\u003cstrong\u003e不那么干净\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e这时，我们可以使用 pip-tools里面的 \u003ccode\u003epip-compile\u003c/code\u003e 工具，对依赖进行编译，生成一份漂亮的依赖文件。\u003c/p\u003e\n\u003ch2 id=\"直接生成最新依赖\"\u003e直接生成最新依赖\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e首先，我们将项目中直接依赖的包写到\u003ccode\u003erequirements.in\u003c/code\u003e文件里面\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cem\u003erequirements.in\u003c/em\u003e\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-in\" data-lang=\"in\"\u003epymysql\nflask\n\u003c/code\u003e\u003c/pre\u003e\u003col start=\"2\"\u003e\n\u003cli\u003e使用 \u003ccode\u003epip-compile\u003c/code\u003e 自动使用最新依赖版本生成依赖文件\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e命令： \u003ccode\u003epip-compile\u003c/code\u003e, 输出requirements.txt文件：\u003c/p\u003e\n\u003cp\u003e\u003cem\u003erequirements.txt\u003c/em\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-txt\" data-lang=\"txt\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# This file is autogenerated by pip-compile with python 3.8\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# To update, run:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#    pip-compile\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e--index-url https://pypi.doubanio.com/simple\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e--trusted-host pypi.doubanio.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eclick==8.1.3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via flask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eflask==2.1.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via -r requirements.in\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimportlib-metadata==4.11.4\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via flask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eitsdangerous==2.1.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via flask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ejinja2==3.1.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via flask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emarkupsafe==2.1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via jinja2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epymysql==1.0.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via -r requirements.in\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewerkzeug==2.1.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via flask\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ezipp==3.8.0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    # via importlib-metadata\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样，我们就得到了一份漂亮的依赖文件，我们还可以清晰的看到某一个依赖包是哪个包的依赖。\u003c/p\u003e","title":"使用Pip Tools生成好看的依赖文件"},{"content":"假如我们有一个json文件：example.json，我们想要将这个文件的连接放到网站上提供下载。\n在HTML中这样写：\n\u0026lt;a href=\u0026#34;http://lingzihuan.icu/leetcode-submissions.json\u0026#34;\u0026gt;点击下载\u0026lt;/a\u0026gt; 点击下载 当我们点击链接的时候，chrome浏览器会自动打开一个页面，然后将json文件的内容加载进来，但是，当我们想要将文件下载到本地的时候，还得自己右键点击，然后选择”另存为“。\n看过 Stack Overflow 上的提示，看到可以给其设定 download属性：\n\u0026lt;a href=\u0026#34;http://lingzihuan.icu/leetcode-submissions.json\u0026#34; download\u0026gt;点击下载\u0026lt;/a\u0026gt; 点击下载 需要注意的是，href里面的内容需要是与当前网页同源的url才可使用。\n","permalink":"https://zihuanling.github.io/posts/html-a-data-download/","summary":"\u003cp\u003e假如我们有一个json文件：\u003ccode\u003eexample.json\u003c/code\u003e，我们想要将这个文件的连接放到网站上提供下载。\u003c/p\u003e\n\u003cp\u003e在HTML中这样写：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehref\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://lingzihuan.icu/leetcode-submissions.json\u0026#34;\u003c/span\u003e\u0026gt;点击下载\u0026lt;/\u003cspan style=\"color:#f92672\"\u003ea\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\u003ca href=\"http://lingzihuan.icu/leetcode-submissions.json\"\u003e点击下载\u003c/a\u003e\n\n\n\u003cp\u003e当我们点击链接的时候，chrome浏览器会自动打开一个页面，然后将json文件的内容加载进来，但是，当我们想要将文件下载到本地的时候，还得自己右键点击，然后选择”另存为“。\u003c/p\u003e\n\u003cp\u003e看过 Stack Overflow 上的提示，看到可以给其设定 \u003ccode\u003edownload\u003c/code\u003e属性：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehref\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://lingzihuan.icu/leetcode-submissions.json\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edownload\u003c/span\u003e\u0026gt;点击下载\u0026lt;/\u003cspan style=\"color:#f92672\"\u003ea\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ca href=\"http://lingzihuan.icu/leetcode-submissions.json\" download\u003e点击下载\u003c/a\u003e\n\n\u003cp\u003e需要注意的是，href里面的内容需要是与当前网页同源的url才可使用。\u003c/p\u003e","title":"HTML a标签下载json数据"},{"content":"functools 模块应用于高阶函数，即参数或（和）返回值为其他函数的函数，如装饰器、sorted函数的key参数等。通常来说，此模块功能适用于所有可调用对象。\nhttps://docs.python.org/zh-cn/3/library/functools.html\n发现functools里面有几个有意思的函数，记录一下：\ncmp_to_key 将(旧式的)比较函数转换为新式的 key function . 在类似于 sorted() ， min() ， max() ， heapq.nlargest() ， heapq.nsmallest() ， itertools.groupby() 等函数的 key 参数中使用。此函数主要用作将 Python 2 程序转换至新版的转换工具，以保持对比较函数的兼容。\n比较函数意为一个可调用对象，该对象接受两个参数并比较它们，结果为小于则返回一个负数，相等则返回零，大于则返回一个正数。key function则是一个接受一个参数，并返回另一个用以排序的值的可调用对象。\n实例：\n使用sorted + cmp_to_key，实现逆序排序\nfrom functools import cmp_to_key def mycmp(a, b): return b - a arr = [1,9,3,7,8,2] arr = sorted(arr, key=cmp_to_key(mycmp)) print(arr) 输出：\n[9, 8, 7, 3, 2, 1] 一般的 key function 只接受一个参数，使用cmp_to_key，可以实现自定义的排序逻辑，如上述，也可以实现其他逻辑，比较经典的是 leetcode-179最大数，要求我们将数字按照特定的规律排序，然后返回一个长的字符串，参考答案：\nclass Solution: def largestNumber(self, nums: List[int]) -\u0026gt; str: # 第一步：定义比较函数，把最大的放左边 # 第二步：排序 # 第三步：返回结果 def compare(x, y): return int(y+x) - int(x+y) nums = sorted(map(str, nums), key=cmp_to_key(compare)) print(cmp_to_key) return \u0026#34;0\u0026#34; if nums[0]==\u0026#34;0\u0026#34; else \u0026#34;\u0026#34;.join(nums) 如上述的排序，也可以做一个自定义的排序逻辑，如：如果是偶数，排序在前面，奇数排序在后面，然后奇数和偶数各自排序：\ndef mycmp(a, b): # 排序函数 if a % 2 == b % 2: # 同为奇数或者偶数，较小者排在前面 return a - b elif a % 2 == 0: # a 为偶数，排在前面 return 1 else: # b为偶数，排在前面 return -1 输出：\n[2, 8, 1, 3, 7, 9] partial 返回一个新的 partial对象，当被调用时其行为类似于 func 附带位置参数 args 和关键字参数 keywords 被调用。 如果为调用提供了更多的参数，它们会被附加到 args。 如果提供了额外的关键字参数，它们会扩展并重载 keywords。 大致等价于:\ndef partial(func, /, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = {**keywords, **fkeywords} return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc 例子：比如封装int，构造一个二进制转10进制的函数：\nfrom functools import partial bin2dec = partial(int, base=2) bin2dec.__doc__ = \u0026#34;Convert binary string to decimal int\u0026#34; print(bin2dec(\u0026#34;100100\u0026#34;)) 输出：36\nwrapps 这是一个便捷更新 装饰器wrapper的函数，一般情况下，函数使用装饰器包装过后，调用 func.__name__输出的是装饰器的wrapper名称，如：\ndef decorator(f): def wrapper(*args, **kwargs): return f(*args, **kwargs) return wrapper @decorator def func(): print(\u0026#39;in func.\u0026#39;) print(func.__name__) 输出： wrapper\n如果 decorator 加上了 wraps：\nfrom functools import wraps def decorator(f): @wraps(f) def wrapper(*args, **kwargs): return f(*args, **kwargs) return wrapper @decorator def func(): print(\u0026#39;in func.\u0026#39;) print(func.__name__) 输出：func\n其实这个wraps调用的是 functools.update_wrapper，可以写成这样：\nfrom functools import update_wrapper def decorator(f): def wrapper(*args, **kwargs): return f(*args, **kwargs) return update_wrapper(wrapper=wrapper, wrapped=f) 实际上用的是 partial(update_wrapper, wrapped=f)，函数定义：\ndef wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): \u0026#34;\u0026#34;\u0026#34;Decorator factory to apply update_wrapper() to a wrapper function Returns a decorator that invokes update_wrapper() with the decorated function as the wrapper argument and the arguments to wraps() as the remaining arguments. Default arguments are as for update_wrapper(). This is a convenience function to simplify applying partial() to update_wrapper(). \u0026#34;\u0026#34;\u0026#34; return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) singledispatch 将一个函数转换为 单分派 generic function。\n需要注意的是，它是单分派的，即只能根据一个参数进行选择。\n假设有这么一个需求：实现一个encoder，对不同类型的数据进行编码，我们有4种数据类型：date、list、dict，str。\n如果不用singledispatch的话，我们应该是写成这样的：\nimport datetime as dt def encode(obj): if isinstance(obj, dt.date): return obj.strftime(\u0026#34;%Y/%m/%d\u0026#34;) elif isinstance(obj, list): return \u0026#34;,\u0026#34;.join(obj) elif isinstance(obj, dict): return \u0026#34;,\u0026#34;.join([f\u0026#34;{k}={v}\u0026#34; for k, v in obj.items()]) elif isinstance(obj, str): return obj 调用：\nprint(encode(dt.date(2022, 5, 9))) print(encode([\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;])) print(encode({\u0026#34;name\u0026#34;: \u0026#34;mike\u0026#34;, \u0026#34;age\u0026#34;: 19})) print(encode(\u0026#34;pure string.\u0026#34;)) 输出：\n2022/05/09 hello,world name=mike,age=19 pure string. 可以看到，encoder正常使用，但是，如果我们有新的类型需要加入进来的话，就需要定义更多的类型，加入更多的elif语句，比较繁琐，引入singledispatch，我们可以改编成这样：\nimport datetime as dt from functools import singledispatch @singledispatch def encode(obj): return obj @encode.register def _(obj:dt.date): return obj.strftime(\u0026#34;%Y/%m/%d\u0026#34;) @encode.register def _(obj:list): return \u0026#34;,\u0026#34;.join(obj) @encode.register def _(obj:dict): return \u0026#34;,\u0026#34;.join([f\u0026#34;{k}={v}\u0026#34; for k, v in obj.items()]) 如上，我们使用了singledispatch重写了encoder，并注册了3个分发函数，对应的类型为：date、list、dict，当encoder接收到这3中类型的obj参数的时候，会自动分发到对应的函数中去。否则，会使用默认的encoder函数。\n重新运行，输出与上述保持一致。\n这样，我们需要添加特定的类型解析的时候，只需要使用 @encode.register 为其注册一个解析器即可。可以在编写服务端的时候，为特定的ORM模型编写JSON序列化泛型函数。\n如果我们定义的函数，不使用类型标注，我们也同样可以向register显式中传递一个类型：\n@encode.register(list) def _(obj): return \u0026#34;,\u0026#34;.join(obj) 也可以传递多个：\n@encode.register(list) @encode.register(dict) def _(obj): return \u0026#34;,\u0026#34;.join(obj) 我们可以使用 registry属性，访问singledispatcher都有注册哪些函数：\nprint(encode.registry.items()) # 输出：dict_items([(\u0026lt;class \u0026#39;object\u0026#39;\u0026gt;, \u0026lt;function encode at 0x7f8f53b934c0\u0026gt;), (\u0026lt;class \u0026#39;datetime.date\u0026#39;\u0026gt;, \u0026lt;function _ at 0x7f8f53b93280\u0026gt;), (\u0026lt;class \u0026#39;list\u0026#39;\u0026gt;, \u0026lt;function _ at 0x7f8f53b93c10\u0026gt;), (\u0026lt;class \u0026#39;dict\u0026#39;\u0026gt;, \u0026lt;function _ at 0x7f8f53b931f0\u0026gt;)]) 其他： cache：简单轻量级未绑定函数缓存 cached_property lru_cache reduce ","permalink":"https://zihuanling.github.io/posts/python-functools/","summary":"\u003cp\u003efunctools 模块应用于高阶函数，即参数或（和）返回值为其他函数的函数，如装饰器、sorted函数的key参数等。通常来说，此模块功能适用于所有可调用对象。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://docs.python.org/zh-cn/3/library/functools.html\"\u003ehttps://docs.python.org/zh-cn/3/library/functools.html\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e发现functools里面有几个有意思的函数，记录一下：\u003c/p\u003e\n\u003ch2 id=\"cmp_to_key\"\u003ecmp_to_key\u003c/h2\u003e\n\u003cp\u003e将(旧式的)比较函数转换为新式的 \u003ca href=\"https://docs.python.org/zh-cn/3/glossary.html#term-key-function\"\u003ekey function\u003c/a\u003e . 在类似于 \u003ca href=\"https://docs.python.org/zh-cn/3/library/functions.html#sorted\"\u003e\u003ccode\u003esorted()\u003c/code\u003e\u003c/a\u003e ， \u003ca href=\"https://docs.python.org/zh-cn/3/library/functions.html#min\"\u003e\u003ccode\u003emin()\u003c/code\u003e\u003c/a\u003e ， \u003ca href=\"https://docs.python.org/zh-cn/3/library/functions.html#max\"\u003e\u003ccode\u003emax()\u003c/code\u003e\u003c/a\u003e ， \u003ca href=\"https://docs.python.org/zh-cn/3/library/heapq.html#heapq.nlargest\"\u003e\u003ccode\u003eheapq.nlargest()\u003c/code\u003e\u003c/a\u003e ， \u003ca href=\"https://docs.python.org/zh-cn/3/library/heapq.html#heapq.nsmallest\"\u003e\u003ccode\u003eheapq.nsmallest()\u003c/code\u003e\u003c/a\u003e ， \u003ca href=\"https://docs.python.org/zh-cn/3/library/itertools.html#itertools.groupby\"\u003e\u003ccode\u003eitertools.groupby()\u003c/code\u003e\u003c/a\u003e 等函数的 key 参数中使用。此函数主要用作将 Python 2 程序转换至新版的转换工具，以保持对比较函数的兼容。\u003c/p\u003e\n\u003cp\u003e比较函数意为一个可调用对象，该对象接受两个参数并比较它们，结果为小于则返回一个负数，相等则返回零，大于则返回一个正数。key function则是一个接受一个参数，并返回另一个用以排序的值的可调用对象。\u003c/p\u003e\n\u003cp\u003e实例：\u003c/p\u003e\n\u003cp\u003e使用sorted + cmp_to_key，实现逆序排序\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e functools \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e cmp_to_key\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emycmp\u003c/span\u003e(a, b):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e b \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e a\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003earr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e9\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e7\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003earr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e sorted(arr, key\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ecmp_to_key(mycmp))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(arr)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e[9, 8, 7, 3, 2, 1]\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e一般的 \u003ccode\u003ekey function\u003c/code\u003e 只接受一个参数，使用\u003ccode\u003ecmp_to_key\u003c/code\u003e，可以实现自定义的排序逻辑，如上述，也可以实现其他逻辑，比较经典的是 \u003ca href=\"https://leetcode-cn.com/problems/largest-number/\"\u003eleetcode-179最大数\u003c/a\u003e，要求我们将数字按照特定的规律排序，然后返回一个长的字符串，参考答案：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSolution\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elargestNumber\u003c/span\u003e(self, nums: List[int]) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e str:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# 第一步：定义比较函数，把最大的放左边\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# 第二步：排序\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# 第三步：返回结果\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecompare\u003c/span\u003e(x, y): \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e int(y\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ex) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e int(x\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ey)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        nums \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e sorted(map(str, nums), key\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ecmp_to_key(compare))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        print(cmp_to_key)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e nums[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(nums)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e如上述的排序，也可以做一个自定义的排序逻辑，如：如果是偶数，排序在前面，奇数排序在后面，然后奇数和偶数各自排序：\u003c/p\u003e","title":"Python Functools: 几个有意思的工具函数"},{"content":"参考地址：\nhttps://linuxconfig.org/how-to-write-a-simple-systemd-service\nhttps://www.freedesktop.org/software/systemd/man/systemd.service.html\n前言 systemd是系统的服务和进程的管理工具，在linux系统下，我们使用 ps aux | head，我们可以看到，系统启动的第一个进程就是systemd。\n有时候，我们想要让程序运行在后台，而不是一直在前台，开一个终端挂着。（使用nohup可以实现这个）\n有时候，想要在开机的时候就自动启动某个服务，比如redis、mysql、nginx这些，这时候，我们就可以将这些程序的启动编写成为一个服务，这样，在系统启动的时候，systemd会自动加载服务配置，然后启动这些服务。\n现在，我想要创建一个python jupyter notebook服务。\n创建文件 systemd services存在路径 /etc/systemd/system 下，我们在这路径下创建的 .service 文件，都可以作为系统服务运行。\n我们创建一个 notebook.service\nvi notebook.service 文件创建完毕后，我们开始编写里面的内容，一个 .service文件里面包含三个关键部分： Unit, Service, Install\nUnit 对于一个简单服务来说，我们在 [Unit] 这个块只要写上服务描述就可以了，字段为Description。\n[Unit] Description=Jupyter notebook service at port[8080] Serivce [Service] 块是服务配置的集合，这里面的配置声明了该如何去运行这个服务。\n[Service] Type=simple ExecStart=/usr/bin/env /root/miniconda3/bin/jupyter notebook Restart=on-failure User=root WorkingDirectory=/tmp/notebook 在Serivce声明中，我们首先要说明这个服务的类型 Type=simple\n然后，是最重要的部分，我们需要告诉系统应该去执行什么东西： ExecStart=/usr/bin/env /root/miniconda3/bin/jupyter notebook， 这里，我们指定了执行 jupyter notebook 这个命令，前面加载了用户的环境变量。\n随后指定这个服务的重启策略：Restart=on-failure， 我要求的是启动失败后重启\n最后，这个服务运行的角色：User=root，以及运行时所在路径： WorkingDirectory=/tmp/notebook\nInstall [Install] 块声明了当前服务应该应该怎样启用，常用的就是 WantedBy=multi-user.target\n[Install] WantedBy=multi-user.target 启动服务 服务写完了，我们需要将其启动起来：\n# 启用 systemctl enable notebook # 启动 systemctl start notebook ok，至此，一个简单的 notebook.service 就创建并启动完成了。\n还有更多高级的用法，可以参考：https://www.freedesktop.org/software/systemd/man/init.html#\n","permalink":"https://zihuanling.github.io/posts/linux-create-service/","summary":"\u003cp\u003e参考地址：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://linuxconfig.org/how-to-write-a-simple-systemd-service\"\u003ehttps://linuxconfig.org/how-to-write-a-simple-systemd-service\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://www.freedesktop.org/software/systemd/man/systemd.service.html\"\u003ehttps://www.freedesktop.org/software/systemd/man/systemd.service.html\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"前言\"\u003e前言\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003esystemd\u003c/code\u003e是系统的服务和进程的管理工具，在linux系统下，我们使用 \u003ccode\u003eps aux | head\u003c/code\u003e，我们可以看到，系统启动的第一个进程就是systemd。\u003c/p\u003e\n\u003cp\u003e有时候，我们想要让程序运行在后台，而不是一直在前台，开一个终端挂着。（使用nohup可以实现这个）\u003c/p\u003e\n\u003cp\u003e有时候，想要在开机的时候就自动启动某个服务，比如redis、mysql、nginx这些，这时候，我们就可以将这些程序的启动编写成为一个服务，这样，在系统启动的时候，systemd会自动加载服务配置，然后启动这些服务。\u003c/p\u003e\n\u003cp\u003e现在，我想要创建一个\u003cstrong\u003epython jupyter notebook\u003c/strong\u003e服务。\u003c/p\u003e\n\u003ch1 id=\"创建文件\"\u003e创建文件\u003c/h1\u003e\n\u003cp\u003esystemd services存在路径 \u003ccode\u003e/etc/systemd/system\u003c/code\u003e 下，我们在这路径下创建的 \u003ccode\u003e.service\u003c/code\u003e 文件，都可以作为系统服务运行。\u003c/p\u003e\n\u003cp\u003e我们创建一个 \u003ccode\u003enotebook.service\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003evi notebook.service\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e文件创建完毕后，我们开始编写里面的内容，一个 \u003ccode\u003e.service\u003c/code\u003e文件里面包含三个关键部分： \u003ccode\u003eUnit, Service, Install\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"unit\"\u003eUnit\u003c/h2\u003e\n\u003cp\u003e对于一个简单服务来说，我们在 \u003ccode\u003e[Unit]\u003c/code\u003e 这个块只要写上服务描述就可以了，字段为Description。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[Unit]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eDescription\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eJupyter notebook service at port[8080]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"serivce\"\u003eSerivce\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003e[Service]\u003c/code\u003e 块是服务配置的集合，这里面的配置声明了该如何去运行这个服务。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[Service]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eType\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003esimple\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eExecStart\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/usr/bin/env /root/miniconda3/bin/jupyter notebook\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eRestart\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eon-failure\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eroot\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eWorkingDirectory\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/tmp/notebook\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在Serivce声明中，我们首先要说明这个服务的类型 \u003ccode\u003eType=simple\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e然后，是最重要的部分，我们需要告诉系统应该去执行什么东西： \u003ccode\u003eExecStart=/usr/bin/env /root/miniconda3/bin/jupyter notebook\u003c/code\u003e， 这里，我们指定了执行 jupyter notebook 这个命令，前面加载了用户的环境变量。\u003c/p\u003e\n\u003cp\u003e随后指定这个服务的重启策略：\u003ccode\u003eRestart=on-failure\u003c/code\u003e， 我要求的是启动失败后重启\u003c/p\u003e\n\u003cp\u003e最后，这个服务运行的角色：\u003ccode\u003eUser=root\u003c/code\u003e，以及运行时所在路径： \u003ccode\u003eWorkingDirectory=/tmp/notebook\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"install\"\u003eInstall\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003e[Install]\u003c/code\u003e 块声明了当前服务应该应该怎样启用，常用的就是 \u003ccode\u003eWantedBy=multi-user.target\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ini\" data-lang=\"ini\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003e[Install]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eWantedBy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003emulti-user.target\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"启动服务\"\u003e启动服务\u003c/h1\u003e\n\u003cp\u003e服务写完了，我们需要将其启动起来：\u003c/p\u003e","title":"Linux创建简单的systemd服务"},{"content":"今天做了一道有点意思的算法题目，在这里记录下。提交记录\nleetcode 179: 最大数 给定一组非负整数 nums，重新排列每个数的顺序（每个数不可拆分）使之组成一个最大的整数。\n注意：输出结果可能非常大，所以你需要返回一个字符串而不是整数。\n示例 1：\n输入：nums = [10,2] 输出：\u0026ldquo;210\u0026rdquo; 示例 2：\n输入：nums = [3,30,34,5,9] 输出：\u0026ldquo;9534330\u0026rdquo;\n提示：\n1 \u0026lt;= nums.length \u0026lt;= 100 0 \u0026lt;= nums[i] \u0026lt;= 109\n上最终AC代码： def greater(a, b): # 对比函数，a \u0026gt; b # a, b expect str return int(a + b) - int(b + a) \u0026gt; 0 # 快排 def pivot(arr, low, high): x = arr[high] j = low - 1 for i in range(low, high): if greater2(arr[i], x): j += 1 arr[i], arr[j] = arr[j], arr[i] j += 1 arr[j], arr[high] = arr[high], arr[j] return j def quick_sort(arr, low, high): if low \u0026lt; high: pi = pivot(arr, low, high) quick_sort(arr, low, pi - 1) quick_sort(arr, pi + 1, high) class Solution: def largestNumber(self, nums: List[int]) -\u0026gt; str: nums = [str(n) for n in nums] quick_sort(nums, 0, len(nums) - 1) if nums[0] == \u0026#39;0\u0026#39;: return \u0026#39;0\u0026#39; return \u0026#39;\u0026#39;.join(nums) 自己写一个快排有点多此一举的意思，完全可以用到python自带的sorted函数。\n分析 这是一道中等难度的题目，看到题目的第一眼，思路很简单：\n对数组排序 将结果构造字符串返回 其主要难度在对数组的排序上，跟一般意义上的数值排序不一样，这不是简单的对比各自的数值，而是有一定规律的。\n比如：9 \u0026gt; 83 \u0026gt; 333，一开始想到的是定义一个比较函数，用来做字符串的对比，写成这样：\ndef greater(a, b): size_a, size_b = len(a), len(b) if size_a == size_b and a == b: return True mn = min(size_a, size_b) pre_a, pre_b = a[:mn], b[:mn] if pre_a == pre_b: # 前缀相等，尾部重新对比 return greater(a[mn:], b) if size_a \u0026gt; size_b else greater(a, b[mn:]) # 前缀不相等的情况 elif pre_a \u0026gt; pre_b: return True else: return False 这里直接是用字符串对比的方式，来判断两个字符串的大小关系，比较繁琐。\n然后用的是快排，自己写了个 quick_sort，用来对nums数组进行排序。这时候，我还不知道 有个叫functools.cmp_to_key的神奇东西，可以用来构建自己的比较器，传入sorted函数里面！\n需要注意的是，数组里面可能全部是0，这是一个边界条件，这时候应该返回 0， 而不是0000之类的。\n参考题解得到的惊人提示 提示1：使用 int(a + b) - int(b + a) \u0026gt; 0 虽然提交的代码AC了，但是，看了下 官方题解，太强了，真是太强了！对于数学渣渣来说，看到的一通分析，完全蒙了。\n大概可以总结为：\n有两个非负整数x, y（字符串），想要对比他们拼接的字符串大小，可以直接用 int(x + y) - int(y + x) \u0026gt; 0 来判断x和y的拼接顺序。\n有了这个，我就可以简化我的对比代码了：\ndef greater(a, b): # 对比函数，a \u0026gt; b # a, b expect str return int(a + b) - int(b + a) \u0026gt; 0 惊呆了.jpg\n将对比代码简化成了1行！数学和算法的魅力。。。\n提示2：functools.cmp_to_key 本来对比较函数的优化已经让人惊讶了，然后我看到了这个：\nclass Solution: def largestNumber(self, nums: List[int]) -\u0026gt; str: # 第一步：定义比较函数，把最大的放左边 # 第二步：排序 # 第三步：返回结果 def compare(x, y): return int(y+x) - int(x+y) nums = sorted(map(str, nums), key=cmp_to_key(compare)) print(cmp_to_key) return \u0026#34;0\u0026#34; if nums[0]==\u0026#34;0\u0026#34; else \u0026#34;\u0026#34;.join(nums) 满打满算，也就4行吧，看到用了 cmp_to_key 这个东西，我一开始寻思着： 这也没有定义，也没有import呀，哪儿来的\ngoogle一搜：functools.cmp_to_key，简单来说，这是一个可以把函数构造成 **可用于sorted排序的key **的函数，返回的是一个对象。\n源代码定义如下：\ndef cmp_to_key(mycmp): \u0026#34;\u0026#34;\u0026#34;Convert a cmp= function into a key= function\u0026#34;\u0026#34;\u0026#34; class K(object): __slots__ = [\u0026#39;obj\u0026#39;] def __init__(self, obj): self.obj = obj def __lt__(self, other): return mycmp(self.obj, other.obj) \u0026lt; 0 def __gt__(self, other): return mycmp(self.obj, other.obj) \u0026gt; 0 def __eq__(self, other): return mycmp(self.obj, other.obj) == 0 def __le__(self, other): return mycmp(self.obj, other.obj) \u0026lt;= 0 def __ge__(self, other): return mycmp(self.obj, other.obj) \u0026gt;= 0 __hash__ = None return K 有了这玩意儿，我之前写的快排可以说是浪费功夫（为啥没有早点遇上你）\n最后 刷完题后，看一下官方或者是大神题解，可能有意想不到的收获，对自己的提升也是很大的\npython的functools看起来有很多有意思的东西，找个时间扒一扒！\n","permalink":"https://zihuanling.github.io/posts/leetcode-179/","summary":"\u003cp\u003e今天做了一道有点意思的算法题目，在这里记录下。\u003ca href=\"https://leetcode-cn.com/submissions/detail/306795694/\"\u003e提交记录\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"leetcode-179-最大数httpsleetcode-cncomproblemslargest-number\"\u003e\u003ca href=\"https://leetcode-cn.com/problems/largest-number/\"\u003eleetcode 179: 最大数\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e给定一组非负整数 nums，重新排列每个数的顺序（每个数不可拆分）使之组成一个最大的整数。\u003c/p\u003e\n\u003cp\u003e注意：输出结果可能非常大，所以你需要返回一个字符串而不是整数。\u003c/p\u003e\n\u003cp\u003e示例 1：\u003c/p\u003e\n\u003cp\u003e输入：nums = [10,2]\n输出：\u0026ldquo;210\u0026rdquo;\n示例 2：\u003c/p\u003e\n\u003cp\u003e输入：nums = [3,30,34,5,9]\n输出：\u0026ldquo;9534330\u0026rdquo;\u003c/p\u003e\n\u003cp\u003e提示：\u003c/p\u003e\n\u003cp\u003e1 \u0026lt;= nums.length \u0026lt;= 100\n0 \u0026lt;= nums[i] \u0026lt;= 109\u003c/p\u003e\n\u003ch3 id=\"上最终ac代码\"\u003e上最终AC代码：\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egreater\u003c/span\u003e(a, b):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# 对比函数，a \u0026gt; b\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# a, b expect str\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e int(a \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e b) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e int(b \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e a) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 快排\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epivot\u003c/span\u003e(arr, low, high):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    x \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e arr[high]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    j \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e low \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e i \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e range(low, high):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e greater2(arr[i], x):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            j \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            arr[i], arr[j] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e arr[j], arr[i]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    j \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    arr[j], arr[high] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e arr[high], arr[j]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e j\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003equick_sort\u003c/span\u003e(arr, low, high):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e low \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e high:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        pi \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pivot(arr, low, high)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        quick_sort(arr, low, pi \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        quick_sort(arr, pi \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, high)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSolution\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elargestNumber\u003c/span\u003e(self, nums: List[int]) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e str:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        nums \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [str(n) \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e n \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e nums]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        quick_sort(nums, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, len(nums) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e nums[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;0\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;0\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(nums)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cem\u003e自己写一个快排有点多此一举的意思，完全可以用到python自带的sorted函数。\u003c/em\u003e\u003c/p\u003e","title":"LeetCode 179：最大数 -- 一道有点意思的题目"},{"content":"前言 HDF5 (Hierarchical Data Format) 是由美国伊利诺伊大学厄巴纳-香槟分校，是一种跨平台传输的文件格式，存储图像和数据\n优势 通用数据模型，可以通过无限多种数据类型表示非常复杂、异构的数据对象和各种各样的元数据 高速原始数据采集 可移植和可扩展，文件大小没有限制 自描述的，不需要外部信息应用程序来解释文件的结构和内容 拥有用于管理、操作、查看和分析数据的开源工具和应用程序软件生态系统 在各种计算平台和编程语言（包括C、C++、Fortran90和Java）上运行的软件库。 参考文章链接： 大数据存储 hdf5简介\n实践 环境安装 对于go语言，hdf5已经有了支持的库： gonum/hdf5，我们可以直接安装这个包，并且使用。\n需要注意的是，这个包使用了 cgo，依赖了hdf5的C语言库，因此，需要我们自己预先安装，使用centos系统，可以很方便安装，直接使用命令：\nyum install -y hdf5 hdf5-devel 当然，如果系统没有软件源的话，hdf5也提供了源码安装，下载 hdf5-1.12.1，解压后，执行安装：\ncd hdf5-1.12.1 ./configure --prefix /usr/local make -j 2 \u0026amp;\u0026amp; make install 然后我们初始化一个项目，名为 h5，用于编写go、hdf5 的简单测试用例\nmkdir -p ~/go/src/h5 cd ~/go/src/h5 go mod init # 安装 go get -v gonum.org/v1/hdf5 使用 创建文件 func main() { // 创建hdf5文件 f, err := hdf5.CreateFile(\u0026#34;data.h5\u0026#34;, hdf5.F_ACC_TRUNC) if err != nil { panic(fmt.Errorf(\u0026#34;failed to create hdf5 file: %e\u0026#34;, err)) } defer f.Close() fmt.Println(\u0026#34;File created.\u0026#34;) } 创建文件的代码就一行： hdf5.CreateFile(\u0026quot;data.h5\u0026quot;, hdf5.F_ACC_TRUNC)，其中 data.h5 为文件的名称，使用flag为hdf5.F_ACC_TRUNC，如果文件已存在的话，会清除原始文件里面的内容。\n写入数据 func write() { var f *hdf5.File var err error if f, err = hdf5.OpenFile(\u0026#34;data.h5\u0026#34;, hdf5.F_ACC_RDWR); err != nil { panic(fmt.Errorf(\u0026#34;failed to create hdf5 file: %e\u0026#34;, err)) } defer f.Close() // 写入 10x10 矩阵 data := [10][10]int32{} for i := 0; i \u0026lt; 10; i ++ { for j := 0; j \u0026lt; 10; j++ { data[i][j] = int32((i * 10) + (j + 1)) } } // 创建datatype dtype, err := hdf5.NewDataTypeFromType(reflect.TypeOf(data[0][0])) if err != nil { panic(fmt.Errorf(\u0026#34;failed to create datatype: %s\u0026#34;, err))} // 创建dataspace dims := []uint{10, 10} dspace, err := hdf5.CreateSimpleDataspace(dims, dims) if err != nil { panic(fmt.Errorf(\u0026#34;failed to create datasapce: %s\u0026#34;, err))} // 创建dataset ds, err := f.CreateDataset(\u0026#34;data\u0026#34;, dtype, dspace) if err != nil {\tpanic(fmt.Errorf(\u0026#34;failed to create dataset: %s\u0026#34;, err)) } // 写入数据 if err = ds.Write(\u0026amp;data); err != nil { panic(fmt.Errorf(\u0026#34;write dataset error: %s\u0026#34;, err)) } } 写入的过程，可以分为以下几个步骤：\nhdf5.OpenFile 打开一个hdf5文件 准备好需要写入的数据data hdf5.NewDataTypeFromType 从数据创建DataType，表明当前dataset存储的数据类型 hdf5.CreateSimpleDataspace 创建DataSpace，表明当前dataset的数据存储space，有数据的形状是怎样的 hdf5.CreateDataset 创建dataset，使用前两个步骤创建的 datatype和dataspace，这是存储数据的主空间 最后，调用dataset.Write 将数据写入dataset中 写入完成后，我们使用 hdf5 工具 h5ls 查看数据的内容：\n$ h5ls -d data.h5/data data Dataset {10, 10} Data: 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, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 命令 h5ls -d data.h5/data 查看文件 data.h5里面名为data的dataset，参数 -d 要求打印出里面的数据内容\n从内容可以表明，这是一个 10x10的数据集合。而从输出表明，数据在hdf5 里面是线性存储的。\n读取数据 func read() { var f *hdf5.File var err error if f, err = hdf5.OpenFile(\u0026#34;data.h5\u0026#34;, hdf5.F_ACC_RDWR); err != nil { panic(fmt.Errorf(\u0026#34;failed to create hdf5 file: %e\u0026#34;, err)) } defer f.Close() // 读取 10x10 矩阵 data := [10][10]int32{} ds, err := f.OpenDataset(\u0026#34;data\u0026#34;) if err != nil { panic(fmt.Errorf(\u0026#34;failed to open dataset: %s\u0026#34;, err))} ds.Read(\u0026amp;data) fmt.Printf(\u0026#34;data: %v\\n\u0026#34;, data) } 读取比较简单，这里是读取所有数据\nhdf5.OpenFile 打开文件 file.OpenDataset 打开dataset ds.Read 将dataset中的数据读取到 data 中 运行结果\n$ go run main.go data: [[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 27 28 29 30] [31 32 33 34 35 36 37 38 39 40] [41 42 43 44 45 46 47 48 49 50] [51 52 53 54 55 56 57 58 59 60] [61 62 63 64 65 66 67 68 69 70] [71 72 73 74 75 76 77 78 79 80] [81 82 83 84 85 86 87 88 89 90] [91 92 93 94 95 96 97 98 99 100]] 定义的 data 是 10x10 的，dataset中的数据也是 10x10 的，刚好可以容纳所有的数据。\n如果定义的data不足10x10，则只会读取 m x n 的数据量存储于 data 中，比如data为 3x3 矩阵，则只会读取 1,2,3,4,5,6,7,8,9 这几个数值\n如果定义的data超过 10x10，则会按照顺序存储到data中，比如 10 x 11, 则第一行为 1\u0026hellip;11 , 第二行 12\u0026hellip;22 \u0026hellip; 以此类推。这也说明了h5文件中的数据是线性存储的。 需要注意的是，如果data大小超过了dataspace，则data的剩下的空间为对应的零值。\nend 更多测试用例例子，见：https://github.com/gonum/hdf5/blob/master/h5d_dataset_test.go\n","permalink":"https://zihuanling.github.io/posts/go-hdf5-practise/","summary":"\u003ch1 id=\"前言\"\u003e前言\u003c/h1\u003e\n\u003cp\u003eHDF5 (Hierarchical Data Format) 是由美国伊利诺伊大学厄巴纳-香槟分校，是一种跨平台传输的文件格式，存储图像和数据\u003c/p\u003e\n\u003ch2 id=\"优势\"\u003e优势\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e通用数据模型，可以通过无限多种数据类型表示非常复杂、异构的数据对象和各种各样的元数据\u003c/li\u003e\n\u003cli\u003e高速原始数据采集\u003c/li\u003e\n\u003cli\u003e可移植和可扩展，文件大小没有限制\u003c/li\u003e\n\u003cli\u003e自描述的，不需要外部信息应用程序来解释文件的结构和内容\u003c/li\u003e\n\u003cli\u003e拥有用于管理、操作、查看和分析数据的开源工具和应用程序软件生态系统\u003c/li\u003e\n\u003cli\u003e在各种计算平台和编程语言（包括C、C++、Fortran90和Java）上运行的软件库。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e参考文章链接： \u003ca href=\"https://cloud.tencent.com/developer/article/1786168\"\u003e大数据存储 hdf5简介\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"实践\"\u003e实践\u003c/h1\u003e\n\u003ch2 id=\"环境安装\"\u003e环境安装\u003c/h2\u003e\n\u003cp\u003e对于go语言，hdf5已经有了支持的库： \u003ca href=\"https://github.com/gonum/hdf5\"\u003egonum/hdf5\u003c/a\u003e，我们可以直接安装这个包，并且使用。\u003c/p\u003e\n\u003cp\u003e需要注意的是，这个包使用了 \u003ccode\u003ecgo\u003c/code\u003e，依赖了hdf5的C语言库，因此，需要我们自己预先安装，使用centos系统，可以很方便安装，直接使用命令：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eyum install -y hdf5 hdf5-devel\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e当然，如果系统没有软件源的话，hdf5也提供了源码安装，下载 \u003ca href=\"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.12/hdf5-1.12.1/src/hdf5-1.12.1.tar.bz2\"\u003ehdf5-1.12.1\u003c/a\u003e，解压后，执行安装：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd hdf5-1.12.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e./configure --prefix /usr/local\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emake -j \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e make install\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后我们初始化一个项目，名为 \u003ccode\u003eh5\u003c/code\u003e，用于编写go、hdf5 的简单测试用例\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir -p ~/go/src/h5\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd ~/go/src/h5\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego mod init\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 安装\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego get -v gonum.org/v1/hdf5\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"使用\"\u003e使用\u003c/h2\u003e\n\u003ch3 id=\"创建文件\"\u003e创建文件\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 创建hdf5文件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehdf5\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCreateFile\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;data.h5\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ehdf5\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eF_ACC_TRUNC\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tpanic(\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eErrorf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;failed to create hdf5 file: %e\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClose\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;File created.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e创建文件的代码就一行： \u003ccode\u003ehdf5.CreateFile(\u0026quot;data.h5\u0026quot;, hdf5.F_ACC_TRUNC)\u003c/code\u003e，其中 data.h5 为文件的名称，使用flag为\u003ccode\u003ehdf5.F_ACC_TRUNC\u003c/code\u003e，如果文件已存在的话，会清除原始文件里面的内容。\u003c/p\u003e","title":"Go与Hdf5，数据读写实践"},{"content":"很久之前玩过的python socket，今天用来做个双向的通信程序。\n服务端代码： server.py\n# coding: utf-8 # tcp stream server import socket import logging import time import datetime as dt from threading import Thread, currentThread # 配置logging logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s %(levelname)s %(process)s %(threadName)s |%(message)s\u0026#34;, ) class Server: \u0026#34;\u0026#34;\u0026#34; socket 服务端 \u0026#34;\u0026#34;\u0026#34; def __init__(self, host=\u0026#39;localhost\u0026#39;, port=8099): self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sock.bind((host, port)) self.msg = None def read(self, conn: socket.socket = None): \u0026#34;\u0026#34;\u0026#34; 从tcp连接里面读取数据 \u0026#34;\u0026#34;\u0026#34; while True: try: data = conn.recv(1024).decode() except Exception as e: logging.info(\u0026#34;recv failed: %s\u0026#34;, e) return logging.info(\u0026#34;[R %s]\u0026lt;\u0026lt; %s\u0026#34;, currentThread().getName(), data) self.msg = data time.sleep(1) def write(self, conn: socket.socket = None): \u0026#34;\u0026#34;\u0026#34; 向tcp连接里面写入数据 \u0026#34;\u0026#34;\u0026#34; while True: msg = f\u0026#34;{dt.datetime.now()} - {self.msg}\u0026#34; logging.info(\u0026#34;[W %s]\u0026gt;\u0026gt; %s\u0026#34;, currentThread().getName(), msg) try: conn.send(msg.encode()) except Exception as e: logging.info(\u0026#34;send failed: %s\u0026#34;, e) return time.sleep(1) def serve(self): \u0026#34;\u0026#34;\u0026#34; 开启服务 \u0026#34;\u0026#34;\u0026#34; self._sock.listen() logging.info(\u0026#34;Serving...\u0026#34;) while True: logging.info(\u0026#34;Waiting for connection...\u0026#34;) conn, addr = self._sock.accept() logging.info(\u0026#34;Recived new conn: %s from %s\u0026#34;, conn, addr) # 开启读写线程处理当前连接 Thread(target=self.read, args=(conn, )).start() Thread(target=self.write, args=(conn, )).start() if __name__ == \u0026#39;__main__\u0026#39;: s = Server() s.serve() 以上就是服务端代码，简单的开启了一个socket服务器，接受连接，然后开启两个线程，每隔一秒，同时向连接中读写数据。\n当然，这个读写可能不是同时发生的。\n客户端代码 client.py\n# coding: utf-8 # tcp stream client import socket import logging import time import os from threading import Thread, currentThread # 配置logging logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s %(levelname)s %(process)s %(threadName)s |%(message)s\u0026#34;, ) class Client: \u0026#34;\u0026#34;\u0026#34; socket 客户端 \u0026#34;\u0026#34;\u0026#34; def __init__(self, host=\u0026#39;localhost\u0026#39;, port=8099): self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._addr = (host, port) def read(self): \u0026#34;\u0026#34;\u0026#34; 向连接中接受数据 \u0026#34;\u0026#34;\u0026#34; while True: try: data = self._sock.recv(1024).decode() except Exception as e: logging.info(\u0026#34;recv failed: %s\u0026#34;, e) return logging.info(\u0026#34;[R %s]\u0026lt;\u0026lt; %s\u0026#34;, currentThread().getName(), data) time.sleep(1) def write(self): \u0026#34;\u0026#34;\u0026#34; 向连接中发送随机数 \u0026#34;\u0026#34;\u0026#34; while True: msg = os.urandom(4).hex() logging.info(\u0026#34;[W %s]\u0026gt;\u0026gt; %s\u0026#34;, currentThread().getName(), msg) try: self._sock.send(msg.encode()) except Exception as e: logging.info(\u0026#34;send failed: %s\u0026#34;, e) return time.sleep(1) def run(self): \u0026#34;\u0026#34;\u0026#34; 开启连接 \u0026#34;\u0026#34;\u0026#34; self._sock.connect(self._addr) logging.info(\u0026#34;New connection: %s\u0026#34;, self._sock) r = Thread(target=self.read) r.start() w = Thread(target=self.write) w.start() r.join() w.join() if __name__ == \u0026#39;__main__\u0026#39;: client = Client() client.run() 客户端同服务端是一样的，向服务端发起一个连接，然后启用两个线程，每隔一秒向连接中发送/接受数据\n运行 运行server和client，就可以看到了正常的输出：\n服务端输出\n$ python server.py 2022-04-22 14:22:11,901 INFO 76227 MainThread |Serving... 2022-04-22 14:22:11,901 INFO 76227 MainThread |Waiting for connection... 2022-04-22 14:22:20,788 INFO 76227 MainThread |Recived new conn: \u0026lt;socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(\u0026#39;127.0.0.1\u0026#39;, 8099), raddr=(\u0026#39;127.0.0.1\u0026#39;, 56814)\u0026gt; from (\u0026#39;127.0.0.1\u0026#39;, 56814) 2022-04-22 14:22:20,789 INFO 76227 Thread-2 |[W Thread-2]\u0026gt;\u0026gt; 2022-04-22 14:22:20.789066 - None 2022-04-22 14:22:20,789 INFO 76227 MainThread |Waiting for connection... 2022-04-22 14:22:20,789 INFO 76227 Thread-1 |[R Thread-1]\u0026lt;\u0026lt; 8903a9aa 2022-04-22 14:22:21,793 INFO 76227 Thread-2 |[W Thread-2]\u0026gt;\u0026gt; 2022-04-22 14:22:21.793070 - 8903a9aa 客户端输出\n$ python client.py 2022-04-22 14:22:20,788 INFO 76320 MainThread |New connection: \u0026lt;socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(\u0026#39;127.0.0.1\u0026#39;, 56814), raddr=(\u0026#39;127.0.0.1\u0026#39;, 8099)\u0026gt; 2022-04-22 14:22:20,789 INFO 76320 Thread-3 |[W Thread-3]\u0026gt;\u0026gt; 8903a9aa 2022-04-22 14:22:20,789 INFO 76320 Thread-2 |[R Thread-2]\u0026lt;\u0026lt; 2022-04-22 14:22:20.789066 - None 2022-04-22 14:22:21,793 INFO 76320 Thread-3 |[W Thread-3]\u0026gt;\u0026gt; 5606048f 2022-04-22 14:22:21,793 INFO 76320 Thread-2 |[R Thread-2]\u0026lt;\u0026lt; 2022-04-22 14:22:21.793070 - 8903a9aa 工作正常，看起来是没有问题的。\n优化 线程化客户端 将每个客户端作为一个线程，然后同时开启多个\nclient.py\nclass Client(Thread): pass if __name__ == \u0026#39;__main__\u0026#39;: cs = [Client() for _ in range(3)] for c in cs: c.start() for c in cs: c.join() 优化服务端 服务端，有个msg变量是线程冲突的，存在资源抢夺问题，我们使用信息池，不同连接的信息独立存储\nserver.py\nclass Server: def __init__(self, host=\u0026#39;localhost\u0026#39;, port=8099): # ... self.msg = {} def read(self, conn: socket.socket = None): # ... self.msg[conn.fileno()] = data def write(self, conn: socket.socket = None): # ... msg = f\u0026#34;{dt.datetime.now()} - {self.msg.get(conn.fileno())}\u0026#34; def serve(self): # ... while True: # ... conn, addr = self._sock.accept() self.msg[conn.fileno()] = \u0026#39;\u0026#39; # ... 搞个聊天服务程序 想要干什么？\n搞一个类似微信聊天服务器一样的东西，主要用作消息的中转，客户端通过服务端，发现其他用户，并向其他用户发送消息。\n就像这样：\ngraph LR client1 --\u0026gt; server --\u0026gt; client2 client2 --\u0026gt; server --\u0026gt; client1 需要解决哪些问题：\n怎么存储一个连接 制定用户的在线机制（连接超时） 用户认证？（没必要搞这么复杂） 其他。。。。待续。\n","permalink":"https://zihuanling.github.io/posts/python-socket-bothway-connection/","summary":"\u003cp\u003e很久之前玩过的python socket，今天用来做个双向的通信程序。\u003c/p\u003e\n\u003ch3 id=\"服务端代码\"\u003e服务端代码：\u003c/h3\u003e\n\u003cp\u003eserver.py\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# coding: utf-8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# tcp stream server\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e socket\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e logging\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e time\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e datetime \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e dt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e threading \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e Thread, currentThread\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 配置logging\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elogging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebasicConfig(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    level\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003elogging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eINFO,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    format\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%(asctime)s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%(levelname)s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%(process)s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%(threadName)s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e |\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%(message)s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eServer\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34; socket 服务端 \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e __init__(self, host\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;localhost\u0026#39;\u003c/span\u003e, port\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e8099\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_sock \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e socket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esocket(socket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAF_INET, socket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSOCK_STREAM)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_sock\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebind((host, port))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emsg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eread\u003c/span\u003e(self, conn: socket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esocket \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34; 从tcp连接里面读取数据 \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTrue\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                data \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e conn\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erecv(\u003cspan style=\"color:#ae81ff\"\u003e1024\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edecode()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eexcept\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eException\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;recv failed: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;[R \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e]\u0026lt;\u0026lt; \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, currentThread()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetName(), data)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emsg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e data\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            time\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esleep(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewrite\u003c/span\u003e(self, conn: socket\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esocket \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34; 向tcp连接里面写入数据 \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTrue\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            msg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003edt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edatetime\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enow()\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e - \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emsg\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;[W \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e]\u0026gt;\u0026gt; \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, currentThread()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetName(), msg)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                conn\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esend(msg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eencode())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eexcept\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eException\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;send failed: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            time\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esleep(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eserve\u003c/span\u003e(self):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34; 开启服务 \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_sock\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elisten()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Serving...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eTrue\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Waiting for connection...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            conn, addr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_sock\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eaccept()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            logging\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Recived new conn: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e from \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, conn, addr)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#75715e\"\u003e# 开启读写线程处理当前连接\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Thread(target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eread, args\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e(conn, ))\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estart()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            Thread(target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewrite, args\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e(conn, ))\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estart()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e __name__ \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__main__\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    s \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Server()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eserve()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e以上就是服务端代码，简单的开启了一个socket服务器，接受连接，然后开启两个线程，每隔一秒，同时向连接中读写数据。\u003c/p\u003e","title":"使用python socket实现双向的tcp通信"},{"content":"学习网站： https://gobyexample.com/reading-files\n可以直接读取文件里面的所有内容到内存里面：\ndata, err := os.ReadFile(\u0026#34;/tmp/data\u0026#34;) fmt.Println(string(data)) 有时候想要自定义一些操作，比如读取文件里面的某些内容，从某个位置开始读取等，这时候，我们可以用 os.Open 打开一个文件，返回一个 os.File 对象。\nf, err := os.Open(\u0026#34;/tmp/data\u0026#34;) 我们可以定义一个缓存（5个字节的[]byte类型），然后从文件中读取内容，读取的内容大小上限为5：\nb1 := make([]byte, 5) n1, err := f.Read(b1) fmt.Printf(\u0026#34;%d bytes read from file: %s\\n\u0026#34;, n1, string(b1)) 调用 f.Read来读取文件，返回两个结果，第一个为实际读取的内容长度，第二个为error。我们定义了一个长度为5的 b1 来存储文件内容，但是文件里面的内容长度可能只有3，因此，n1不一定等于5，它是实际读取的长度。\n同样，我们还能用 os.Seek，来查找文件的位置，第一个参数为 offset，意味着偏移量，第二个参数为 whence，\n0表示从文件的开始位置进行偏移查找 1表示从文件的当前位置开始偏移，如果之前已经设置过偏移，再次设置则从当前位置开始偏移 2表示从文件的末尾开始偏移 o2, err := f.Seek(6, 0) b2 := make([]byte, 2) n2, err := f.Read(b2) fmt.Printf(\u0026#34;%d bytes read at %d\\n\u0026#34;, n2, o2) fmt.Printf(\u0026#34;Value is : %s\\n\u0026#34;, string(b2[:n2])) golang 的 io包提供了一些有用的函数，帮助我们高效读取。比如 io.ReadAtLeast，可以更健壮地实现上的读取操作：\no3, err := f.Seek(6, 0) b3 := make([]byte, 2) n3, err := io.ReadAtLeast(f, b3, 2) fmt.Printf(\u0026#34;%d bytes @ %d: %s\\n\u0026#34;, n3, o3, string(b3)) 如果想要将文件指针倒回到最初的位置，没有内置的函数，但是使用 f.Seek(0, 0) 可以实现同样的效果：\n_, err := f.Seek(0, 0) golang的 bufio 包提供了 缓冲读取器，它提供和额外的读取方法，对于少量的读取很有用：\nr4 := bufio.NewReader(f) b4, err := r4.Peek(5) fmt.Printf(\u0026#34;5 bytes: %s\\n\u0026#34;, string(b4)) 最后，我们应该在结束的时候关闭文件：\nf.Close() 但是一般来说，我们应该在打开文件之后，使用 defer f.Close() 来进行优雅的关闭文件：\nf, err := os.Open(\u0026#34;/tmp/data\u0026#34;) defer f.Close() 完整代码：\nfunc check(err error) { if err != nil { panic(err) } } func main() { // ReadFile data, err := os.ReadFile(\u0026#34;/tmp/data\u0026#34;) check(err) fmt.Printf(\u0026#34;data: %s\\n\u0026#34;, string(data)) // Open file f, err := os.Open(\u0026#34;/tmp/data\u0026#34;) check(err) b1 := make([]byte, 5) n1, err := f.Read(b1) check(err) fmt.Printf(\u0026#34;%d size of data read: %s\\n\u0026#34;, n1, string(b1)) // Seek o1, err := f.Seek(6, 0) check(err) b2 := make([]byte, 2) n2, err := f.Read(b2) check(err) fmt.Printf(\u0026#34;%d bytes read @ %d: %s\\n\u0026#34;, n2, o1, string(b2)) // io.ReadAtLeast o3, err := f.Seek(6, 0) b3 := make([]byte, 2) n3, err := io.ReadAtLeast(f, b3, 2) check(err) fmt.Printf(\u0026#34;io.ReadAtLeast, %d bytes read @ %d: %s\\n\u0026#34;, n3, o3, string(b3)) // rewind _, err = f.Seek(0, 0) check(err) // bufio r4 := bufio.NewReader(f) b4, err := r4.Peek(5) check(err) fmt.Printf(\u0026#34;5 bytes read from bufio: %s\\n\u0026#34;, string(b4)) // close file f.Close() } ","permalink":"https://zihuanling.github.io/posts/go-readfiles/","summary":"\u003cp\u003e学习网站： \u003ca href=\"https://gobyexample.com/reading-files\"\u003ehttps://gobyexample.com/reading-files\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e可以直接读取文件里面的所有内容到内存里面：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eReadFile\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/tmp/data\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(string(\u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e有时候想要自定义一些操作，比如读取文件里面的某些内容，从某个位置开始读取等，这时候，我们可以用 \u003ccode\u003eos.Open\u003c/code\u003e 打开一个文件，返回一个 \u003ccode\u003eos.File\u003c/code\u003e 对象。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOpen\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/tmp/data\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e我们可以定义一个缓存（5个字节的[]byte类型），然后从文件中读取内容，读取的内容大小上限为5：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eb1\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make([]\u003cspan style=\"color:#66d9ef\"\u003ebyte\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en1\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRead\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eb1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%d bytes read from file: %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003en1\u003c/span\u003e, string(\u003cspan style=\"color:#a6e22e\"\u003eb1\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e调用 \u003ccode\u003ef.Read\u003c/code\u003e来读取文件，返回两个结果，第一个为实际读取的内容长度，第二个为\u003ccode\u003eerror\u003c/code\u003e。我们定义了一个长度为5的 b1 来存储文件内容，但是文件里面的内容长度可能只有3，因此，n1不一定等于5，它是实际读取的长度。\u003c/p\u003e\n\u003cp\u003e同样，我们还能用 \u003ccode\u003eos.Seek\u003c/code\u003e，来查找文件的位置，第一个参数为 \u003ccode\u003eoffset\u003c/code\u003e，意味着偏移量，第二个参数为 \u003ccode\u003ewhence\u003c/code\u003e，\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e0表示从文件的开始位置进行偏移查找\u003c/li\u003e\n\u003cli\u003e1表示从文件的当前位置开始偏移，如果之前已经设置过偏移，再次设置则从当前位置开始偏移\u003c/li\u003e\n\u003cli\u003e2表示从文件的末尾开始偏移\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eo2\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSeek\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eb2\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make([]\u003cspan style=\"color:#66d9ef\"\u003ebyte\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en2\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRead\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eb2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%d bytes read at %d\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003en2\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eo2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Value is : %s\\n\u0026#34;\u003c/span\u003e, string(\u003cspan style=\"color:#a6e22e\"\u003eb2\u003c/span\u003e[:\u003cspan style=\"color:#a6e22e\"\u003en2\u003c/span\u003e]))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003egolang 的 \u003ccode\u003eio\u003c/code\u003e包提供了一些有用的函数，帮助我们高效读取。比如 \u003ccode\u003eio.ReadAtLeast\u003c/code\u003e，可以更健壮地实现上的读取操作：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eo3\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSeek\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eb3\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make([]\u003cspan style=\"color:#66d9ef\"\u003ebyte\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003en3\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eio\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eReadAtLeast\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb3\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%d bytes @ %d: %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003en3\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eo3\u003c/span\u003e, string(\u003cspan style=\"color:#a6e22e\"\u003eb3\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e如果想要将文件指针倒回到最初的位置，没有内置的函数，但是使用 \u003ccode\u003ef.Seek(0, 0)\u003c/code\u003e 可以实现同样的效果：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSeek\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003egolang的 \u003ccode\u003ebufio\u003c/code\u003e 包提供了 \u003cstrong\u003e缓冲读取器\u003c/strong\u003e，它提供和额外的读取方法，对于少量的读取很有用：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003er4\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebufio\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewReader\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ef\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eb4\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003er4\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePeek\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5 bytes: %s\\n\u0026#34;\u003c/span\u003e, string(\u003cspan style=\"color:#a6e22e\"\u003eb4\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e最后，我们应该在结束的时候关闭文件：\u003c/p\u003e","title":"go: 读取文件"},{"content":"gRPC 一个高性能、开源的通用RPC框架。\ngPPC是一个现代的开源高性能的远程过程调用框架，并且可以运行在任何环境中。它可以有效地连接数据中心内和跨数据中心的服务，支持负载均衡、跟踪、健康检查和身份验证。它也适用于分布式计算的最后一英里，将设备、移动应用程序和浏览器连接到后端服务。\ngRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.\ngithub 地址： https://github.com/grpc/grpc\n原来是用 C++ 写的，后来也有了go语言版本：https://github.com/grpc/grpc-go\ngRPC 和 go gPPC支持多个语言和平台，包括GO、Python、C++、Nodejs等，我们接下来学习的是使用go来体验一下gRPC的功能。\n学习地址：https://www.grpc.io/docs/languages/go/quickstart/\n前期准备： golang安装 protobuf 编译器，protoc，第三个版本 之前已经简单体验过了protobuf： 文章：go-proto\n安装支持go语言的protoc编译器插件 $ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 ok，至此，前期的环境准备就绪，我们可以开始体验golang版本的gRPC了。\n我们接下来使用上次写的protobuf学习例子，修改一下，让其可以用上gRPC。\n上手体验 编译命令 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-gprc_opt=paths=source_relative proto/media.proto 相比上次的单纯编译protobuf，我们这次添加对grpc的支持，主要是新增了两个flag：--go-grpc_out=. --go-grpc_opt=paths=source_relative\n运行上面的编译命令，我们发现，当前的路径下并没有生成别的东西，这是因为，我们的proto中没有定义grpc相关的服务。\n定义gRPC服务 我们可以在proto文件中这样写：\nproto/media.proto\n// 上传的请求体 message UploadRequest { Picture pic = 1; } // 上传图片的响应体 message UploadReply { int32 status = 1; string msg = 2; } // 定义一个rpc多媒体服务 service Media { rpc UploadPicture(UploadRequest) returns (UploadReply) {} } 我们上次定义了一个名为 Picture 的message，用来表示图片消息，这次，我们新增了两个消息，UploadRequest和UploadReply，分别表示上传的消息内容和返回的上传结果。\n另外定义了一个服务：Media，里面有一个方法 UploadPicture，用于上传图片。\n重新运行protoc编译命令，就会看到，proto目录下，生成了一个 media_grpc.pb.go文件，里面实现了我们在proto文件中定义的gRPC服务。\n实现rpc服务的逻辑 我们在proto文件中定义了相关的协议，以及服务。同时编译也生成了对应的服务代码，但是，生成的代码只是我们服务的一个抽象，而具体的业务处理逻辑，还需要我们自己去实现。\nserver/server_main.go\n// 实现媒体服务 type server struct { proto.UnimplementedMediaServer } // UploadPicture 实现上传图片接口 func (s *server) UploadPicture(ctx context.Context, req *proto.UploadRequest) (*proto.UploadReply, error) { log.Println(\u0026#34;Processing upload picture request.\u0026#34;) picture := req.GetPic() log.Printf(\u0026#34;Got picture: %v\\n\u0026#34;, picture) return \u0026amp;proto.UploadReply{ Status: 1, Msg: \u0026#34;Good job.\u0026#34;, }, nil } func main() { l, _ := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8099\u0026#34;) s := grpc.NewServer() proto.RegisterMediaServer(s, \u0026amp;server{}) log.Printf(\u0026#34;Server listening at: %v\\n\u0026#34;, l.Addr()) if err := s.Serve(l); err != nil { log.Fatalf(\u0026#34;server error: %v\\n\u0026#34;, err) } } 上述代码中，我们通过组合 proto.UnimplementedMediaServer，来实现抽象的MediaServer接口，然后再实现 UploadPicture这个方法，方法很简单，我们从请求体中获取对应的数据，打印出来，然后给客户端返回一句 “good job”。\n然后是开启服务，这里的关键句是 s := grpc.NewServer 来实例化一个grpc服务，然后调用 proto.RegisterMediaServer 来向这个grpc服务中注册我们定义的媒体服务。最后，s.Serve开启服务。\n实现客户端调用 客户端的调用相对来说简单，不需要再去实现什么接口，只要简单的向连接中发送请求即可\nfunc main() { conn, err := grpc.Dial(\u0026#34;localhost:8099\u0026#34;, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf(\u0026#34;gRPC failed to dial, error: %v\u0026#34;, err) } defer conn.Close() client := proto.NewMediaClient(conn) pic := \u0026amp;proto.Picture{ Name: \u0026#34;pandas\u0026#34;, Type: proto.Picture_JPG, Data: []byte{1, 2, 3, 4, 5, 6, 7}, } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() req := proto.UploadRequest{Pic: pic} resp, err := client.UploadPicture(ctx, \u0026amp;req) if err != nil { log.Fatalf(\u0026#34;client call grpc service error: %v\u0026#34;, err) } log.Printf(\u0026#34;rpc response message: %v\\n\u0026#34;, resp.GetMsg()) } 客户端中，我们这次使用 grpc.Dial来向服务端发起一个连接，然后第二个参数是 grpc.WithTransportCredentials(insecure.NewCredentials())，意味着向服务端发起请求需要进行一定的验证。（？）\n然后直接调用 proto.UploadPicture，即可向服务端中发送上传图片请求。\n运行 运行上述代码\n服务端输出：\n2022/04/17 00:00:25 Server listening at: [::]:8099 2022/04/17 00:10:07 Processing upload picture request. 2022/04/17 00:10:07 Got picture: name:\u0026#34;pandas\u0026#34; type:JPG data:\u0026#34;\\x01\\x02\\x03\\x04\\x05\\x06\\x07\u0026#34; 客户端输出：\n2022/04/17 00:10:07 rpc response message: Good job. 可以看到，表现符合预期。\n总结 想要在go上使用grpc，有以下几个主要步骤：\n前期准备，golang、protobuf编译器（protoc）、两个go语言的protobuf插件：protoc-gen-go、protoc-gen-go-grpc 编写服务的protobuf代码，需要使用service关键字，去定义一个grpc服务 使用protoc编译proto文件，生成go代码，编译参数为：protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative 服务端实现对应的服务逻辑，并开启grpc服务。生成的只是简单的服务代码，以及服务接口，我们在proto中定义的rpc服务具体业务逻辑并没有被实现，还需要我们手动实现具体的服务接口，然后，使用 proto.RegisterMediaServer来向对应的grpc服务中注册我们自己实现的服务 客户端发起连接，需要使用grpc.Dial，并且第二个参数传 grpc.WithTransportCredentials(insecure.NewCredentials())，否则会报错。 ","permalink":"https://zihuanling.github.io/posts/go-grpc-protobuf/","summary":"\u003ch1 id=\"grpc\"\u003egRPC\u003c/h1\u003e\n\u003cp\u003e一个高性能、开源的通用RPC框架。\u003c/p\u003e\n\u003cp\u003egPPC是一个现代的开源高性能的远程过程调用框架，并且可以运行在任何环境中。它可以有效地连接数据中心内和跨数据中心的服务，支持负载均衡、跟踪、健康检查和身份验证。它也适用于分布式计算的最后一英里，将设备、移动应用程序和浏览器连接到后端服务。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003egRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003egithub 地址： \u003ca href=\"https://github.com/grpc/grpc\"\u003ehttps://github.com/grpc/grpc\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e原来是用 \u003ccode\u003eC++\u003c/code\u003e 写的，后来也有了go语言版本：https://github.com/grpc/grpc-go\u003c/p\u003e\n\u003ch1 id=\"grpc-和-go\"\u003egRPC 和 go\u003c/h1\u003e\n\u003cp\u003egPPC支持多个语言和平台，包括GO、Python、C++、Nodejs等，我们接下来学习的是使用go来体验一下gRPC的功能。\u003c/p\u003e","title":"go: grpc和protobuf"},{"content":"Google protocol buffers 简称protobuf，它是谷歌提供的跨语言，跨平台的，可扩展的用来将结构化的数据进行序列化的一种机制。就像XML，但是更加精简、快速和简单。我们可以先预先定义我们的协议（写到一个 .proto 文件里面），里面声明了我们需要怎么去将数据结构化，然后，我们可以使用这份proto，去生成我们指定的语言源代码文件，我们就能够很方便地从各种数据流中读取结构化数据，或者是写入结构化数据。并且是跨语言的。\nProtocol buffers are Google\u0026rsquo;s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.\n安装protoc编译器 .proto文件只是我们定义的协议文件，一般来讲，编程语言是不能直接使用这份文件的（除非是自己针对proto写一个自定义的解析逻辑，但是没人会这么闲），我们想要的，应该是简单高效的开发，可以即写即用那种。\n因此，我们使用特定的编程语言编写服务时，需要将我们定义的 .proto 文件翻译（编译）成为对应的语言文件，这时，google 给我们提供了 protoc。\n下载 我们可以在这个地方下载： protobuf v3.20.0 ，里面有很多的版本，我们可以找到对应的平台。\n安装 将其解压之后，我们得到一个 bin目录，里面有一个 protoc的可执行文件，我们将其添加到环境变量中的Path中去，这样，我们就可以在终端中运行protoc命令了。\n验证安装 我们添加完环境变量后，打开一个新的终端，我们输入以下命令来验证一下安装是否成功：\n\u0026gt;protoc --version libprotoc 3.20.0 编写自己的protobuf协议 我想要实现一个协议，可以传输多媒体数据，且后端是用go写的。\n首先创建一个项目，名为 learnProto，表示这是我用来学习protobuf的练手项目。然后创建一个proto文件夹，用来存放我们的proto文件。\n// learnProto/proto/media.proto syntax = \u0026#34;proto3\u0026#34;; message Picture { string name = 1; // 图片的名称 enum PicTypes { PNG = 0; JPG = 1; JPEG = 2; BMQ = 3; } PicTypes type = 2; // 图片类型 bytes data = 3; // 图片数据 } 首先我们需要在文件的开始位置，写上 syntax = \u0026quot;proto3\u0026quot;，说明这个proto是用的proto3版本。\n上述proto文件中，我们定义了一个消息的结构：图片\n这个消息的结构有3个字段：\n名称：使用的是string类型 类型：我们自定义了一个枚举类型，用来表示允许进行传输的图片格式 数据：图片的数据应该是字节类型的 这样，我们就写好了自己的proto协议，我们将要使用这个协议来进行图片文件的传输。\n编译协议 **重要：**我们需要使用go来实现后端，然后使用编写的proto来实现数据的传输，这样，我们就需要给proto文件加上两行声明：\n第一行：告诉protoc编译器，这个proto文件编译成go文件之后，将要属于哪个包： package proto 第二行：告诉protoc编译器，这个proto文件当前的位置： option go_package=\u0026quot;learnProto/proto\u0026quot; proto文件改成了：\nsyntax = \u0026#34;proto3\u0026#34;; package proto; option go_package=\u0026#34;learnProto/proto\u0026#34;; // ... 省略 目前，我们的项目结构是长这样的：\nlearnProto/ proto/ media.proto go.mod 接下来我们使用protoc进行编译：\nprotoc --proto_path=. --go_out=. --go_opt=paths=source_relative proto/media.proto 命令运行之后，将会在 proto 文件夹下生成一个 media.pb.go 的文件，这个，就是我们通过 protoc 将 proto文件编译成的go文件。\n使用protobuf 编写go服务端 // learnProto/server/server_main.go package main import ( bf \u0026#34;google.golang.org/protobuf/proto\u0026#34; \u0026#34;learnProto/proto\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; ) func main() { l, _ := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8099\u0026#34;) for { conn, _ := l.Accept() log.Println(\u0026#34;Received on connection.\u0026#34;) // read all from connection _data := make([]byte, 1024) if n, err := conn.Read(_data); err != nil { log.Fatalf(\u0026#34;Read error: %s\u0026#34;, err) } else { log.Printf(\u0026#34;%d bytes read\\n\u0026#34;, n) } message := \u0026amp;proto.Picture{} bf.Unmarshal(_data, message) log.Printf(\u0026#34;data: %v\u0026#34;, message) } } 服务端的代码简单，我们定义一个TCP服务端，监听8099端口，从里面获取数据，通过proto将其转化成为对应的结构体。\n由于是简单的使用proto，我们暂时不考虑里面传输的数据长度，直接构建一个1024长度的byte切片，从连接里面读取数据。然后使用 bf.Unmarshal 将里面的字节码数据反序列化为 proto.Picture结构体\n后续可以优化这块，比如约定开始传送的是一个长度为 HEADER_SIZE 的数据，里面声明了我们本次传输的内容是图片还是视频，然后接下来的数据有多大，等。\n编写go客户端 // learnProto/client/client_main.go package main import ( bf \u0026#34;google.golang.org/protobuf/proto\u0026#34; \u0026#34;learnProto/proto\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;time\u0026#34; ) func main() { conn, _ := net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:8099\u0026#34;) message := \u0026amp;proto.Picture{ Name: \u0026#34;pandas\u0026#34;, Type: proto.Picture_JPG, Data: []byte{1, 2, 3, 4, 5, 6, 7}, } msg, _ := bf.Marshal(message) n, err := conn.Write(msg) if err != nil { log.Fatalf(\u0026#34;Error write to connectin: %s\u0026#34;, err) } log.Printf(\u0026#34;%d bytes wrote.\u0026#34;, n) time.Sleep(time.Second) } 客户端，我们使用 net.Dial 向服务端发起连接，然后构造一个假的 Picture消息，图片的名称为 pandas，图片的类型是 JPG，然后内容是随便给几个字节。\n然后，使用 bf.Marshal，将结构体数据序列化成为字节 []byte,通过tcp连接发送到客户端。\n随后休眠一秒，防止连接提前终端而导致服务端数据读取失败。\n运行 运行上述代码\n客户端输出：\n2022/04/14 23:42:00 19 bytes wrote. 服务端输出：\n2022/04/14 23:42:00 Received on connection. 2022/04/14 23:42:00 19 bytes read 2022/04/14 23:42:00 data: name:\u0026#34;pandas\u0026#34; type:JPG data:\u0026#34;\\x01\\x02\\x03\\x04\\x05\\x06\\x07\u0026#34; 可以看到，服务端收到了客户端发送的message，并且正确地将其序列化成为了 proto.Picture对象。\n改进 服务端可做改进： 考虑新增消息类型的时候，可以通过使用约定 HEADER 的方式，来确定接下来发送的消息属于什么类型，以及消息的长度等。\n接收到媒体信息的时候，将数据保存起来。\n使用 goroutine改造，支持并发处理多个请求。\n客户端可做改进： 图片的数据可以直接从文件或者数据库读取\n自动解析图片的名称、格式等\n","permalink":"https://zihuanling.github.io/posts/go-proto/","summary":"\u003ch1 id=\"google-protocol-buffers\"\u003eGoogle protocol buffers\u003c/h1\u003e\n\u003cp\u003e简称\u003ccode\u003eprotobuf\u003c/code\u003e，它是谷歌提供的跨语言，跨平台的，可扩展的用来将结构化的数据进行序列化的一种机制。就像\u003cstrong\u003eXML\u003c/strong\u003e，但是更加精简、快速和简单。我们可以先预先定义我们的协议（写到一个 \u003ccode\u003e.proto\u003c/code\u003e 文件里面），里面声明了我们需要怎么去将数据结构化，然后，我们可以使用这份proto，去生成我们指定的语言源代码文件，我们就能够很方便地从各种数据流中读取结构化数据，或者是写入结构化数据。并且是跨语言的。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eProtocol buffers are Google\u0026rsquo;s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"安装protoc编译器\"\u003e安装protoc编译器\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003e.proto\u003c/code\u003e文件只是我们定义的协议文件，一般来讲，编程语言是不能直接使用这份文件的（除非是自己针对proto写一个自定义的解析逻辑，但是没人会这么闲），我们想要的，应该是简单高效的开发，可以即写即用那种。\u003c/p\u003e\n\u003cp\u003e因此，我们使用特定的编程语言编写服务时，需要将我们定义的 \u003ccode\u003e.proto\u003c/code\u003e 文件翻译（编译）成为对应的语言文件，这时，google 给我们提供了 protoc。\u003c/p\u003e","title":"go 和 protobuf"},{"content":"安装 mkdir go-mongo go mod init go-mongo go get go.mongodb.org/mongo-driver/mongo 使用 数据库里面有一条这样的数据：\n{ \u0026#34;_id\u0026#34;: { \u0026#34;$oid\u0026#34;: \u0026#34;6252912ec4495f97bccf41aa\u0026#34; }, \u0026#34;title\u0026#34;: \u0026#34;My Mongo Post\u0026#34;, \u0026#34;create_time\u0026#34;: { \u0026#34;$date\u0026#34;: \u0026#34;2022-04-10T10:33:45.149Z\u0026#34; }, \u0026#34;viewer\u0026#34;: 201 } 连接数据库 // 关键代码 const MONGO_URI = \u0026#34;mongodb://localhost:27017/test\u0026#34; client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(MONGO_URI)) // 优雅关闭连接 defer client.Disconnect(context.TODO()) 数据查询 首先获取到对应的database以及collection\ncoll := client.Database(\u0026#34;test\u0026#34;).Collection(\u0026#34;post\u0026#34;) 然后查询：\ncoll := client.Database(\u0026#34;test\u0026#34;).Collection(\u0026#34;post\u0026#34;) var result bson.M\t// 需要一个bson.M 对象，用于存储查询回来的数据 // FindOne 接受2个参数，一个context，一个filter，filter 为 bson.D类型 err = coll.FindOne(context.TODO(), bson.D{}).Decode(\u0026amp;result) if err == mongo.ErrNoDocuments { fmt.Printf(\u0026#34;No document found.\\n\u0026#34;) return } else if err != nil { panic(err) } fmt.Printf(\u0026#34;Found document result: %s\\n\u0026#34;, result) 数据查询注意点：\n需要一个 bson.M 对象，用于存储返回的数据 Collection.FindOne 接受3个参数，ctx、filter和opts，返回的是一个 SingleResult 对象 ctx可以为nil，如果为nil的话，默认会创建一个 context.Backgroud的context filter为查询条件，为 bson.D 类型，上面代码默认为空，意味着查询任意一条数据。也可以指定特定的条件，如 bson.d{{\u0026quot;title\u0026quot;, \u0026quot;first post\u0026quot;}}，意味着查询 tile 为 first post 的文档。 opts：类型为 options.FindOneOptions 类型，查询的额外条件，比如限定返回的具体字段：\u0026amp;options.FindOneOptions{Projection: map[string]int{\u0026quot;_id\u0026quot;: 0}}，这个options禁止返回_id字段。 使用 SingleResult.Decode方法将返回的SingleResult反序列化为 bson.M 对象，SingleResult.Decode(\u0026amp;result) 上述查询输出：\nFound document result: map[_id:ObjectID(\u0026#34;6252912ec4495f97bccf41aa\u0026#34;) create_time:%!s(primitive.DateTime=1649548800000) title:My Mongo Post viewer:%!s(int32=201)] bson数据转struct 通过 SingleResult.Decode 转化而来的是一个bson.M对象，实际的应用场景中，这种格式的对象是很难用的，因此，我们需要将其Unmarshal一下，反序列化成为一个结构体。\n我们的post有几个字段：title、viewer、create_time，因此，我们定义一个 Post结构体，将这些字段映射到结构体里面：\ntype Post struct { Title string `bson:\u0026#34;title\u0026#34;` Viewer int32 `bson:\u0026#34;viewer\u0026#34;` CreateTime time.Time `bson:\u0026#34;create_Time\u0026#34;` } func (p Post) String() string { return fmt.Sprintf(\u0026#34;Title: %s\\nCreated at: %s\\nViewers: %d\\n\u0026#34;, p.Title, p.CreateTime, p.Viewer) } 同时，我们给这个结构体实现了 Stringer 接口，打印出特定的数据格式。\n接下来，我们将前面步骤取回的 result 转化成为 Post 结构体：\ndoc, err := bson.Marshal(result) if err != nil { fmt.Printf(\u0026#34;bson.Marshal error: %s\u0026#34;, err) return } err = bson.Unmarshal(doc, \u0026amp;post) if err != nil { fmt.Printf(\u0026#34;bson.Unmarshal error: %s\u0026#34;, err) return } fmt.Println(post) 一共是两个步骤：\n将bson.M对象使用bson.Marshal序列化成为一个bson字符串 使用 bson.Unmarshal 转化成struct结构体对象 重新运行上述代码，输出：\nTitle: My Mongo Post Created at: 2022-04-10 10:33:45.149 +0000 UTC Viewers: 201 可以看到，正常输出了内容，但是 Datetime 字段有些异常，primitive.Datetime 没能很好地转化成为 Time 对象。我们后续再研究下。\n数据插入 数据插入可以支持直接的结构体插入：\nfunc insert(coll *mongo.Collection) { rand.Seed(time.Now().Unix()) num := rand.Uint32() post := Post{ Title: \u0026#34;New post - \u0026#34; + strconv.Itoa(int(num)), Viewer: num, CreateTime: time.Now(), } r, err := coll.InsertOne(nil, \u0026amp;post) if err != nil { fmt.Printf(\u0026#34;Insert post error： %s\\n\u0026#34;, err) return } fmt.Printf(\u0026#34;Data inserted: %s， \\n%s\u0026#34;, r.InsertedID, post) } 运行后，数据库中多了一条数据：\n{ \u0026#34;_id\u0026#34;: { \u0026#34;$oid\u0026#34;: \u0026#34;6252b289dfed1f0d342af950\u0026#34; }, \u0026#34;title\u0026#34;: \u0026#34;New post - 2596996162\u0026#34;, \u0026#34;viewer\u0026#34;: 2596996162, \u0026#34;create_Time\u0026#34;: { \u0026#34;$date\u0026#34;: \u0026#34;2022-04-10T10:33:45.149Z\u0026#34; } } 可以看到数据是成功插入了的，但是时间 create_time 有点问题，跟实际时间不符，应该是时区的设置问题。\n数据删除 func delete(coll *mongo.Collection) { r, err := coll.DeleteOne(nil, bson.D{{\u0026#34;viewer\u0026#34;, 1170169558}}) if err != nil { fmt.Printf(\u0026#34;Delete error: %s\\n\u0026#34;, err) return } fmt.Printf(\u0026#34;Delete result: %d deleted\u0026#34;, r.DeletedCount) } 数据修改 func update(coll *mongo.Collection) { r, err := coll.UpdateOne( nil, bson.D{{\u0026#34;title\u0026#34;, \u0026#34;New post - 2596996162\u0026#34;}}, bson.D{{\u0026#34;$set\u0026#34;, bson.D{{\u0026#34;updated\u0026#34;, true}}}}, ) if err != nil { fmt.Printf(\u0026#34;update error: %s\\n\u0026#34;, err) return } fmt.Printf(\u0026#34;Update result: %d updated.\\n\u0026#34;, r.ModifiedCount) } ","permalink":"https://zihuanling.github.io/posts/go-mongo/","summary":"\u003ch1 id=\"安装\"\u003e安装\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir go-mongo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego mod init go-mongo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego get go.mongodb.org/mongo-driver/mongo\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"使用\"\u003e使用\u003c/h1\u003e\n\u003cp\u003e数据库里面有一条这样的数据：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;$oid\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;6252912ec4495f97bccf41aa\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;title\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;My Mongo Post\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;create_time\u0026#34;\u003c/span\u003e: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;$date\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2022-04-10T10:33:45.149Z\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;viewer\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e201\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"连接数据库\"\u003e连接数据库\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 关键代码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMONGO_URI\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mongodb://localhost:27017/test\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eclient\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emongo\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eConnect\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTODO\u003c/span\u003e(), \u003cspan style=\"color:#a6e22e\"\u003eoptions\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClient\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eApplyURI\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eMONGO_URI\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 优雅关闭连接\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eclient\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDisconnect\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTODO\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"数据查询\"\u003e数据查询\u003c/h2\u003e\n\u003cp\u003e首先获取到对应的\u003ccode\u003edatabase\u003c/code\u003e以及\u003ccode\u003ecollection\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ecoll\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eclient\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDatabase\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003eCollection\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;post\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后查询：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ecoll\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eclient\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDatabase\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003eCollection\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;post\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eM\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 需要一个bson.M 对象，用于存储查询回来的数据\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// FindOne 接受2个参数，一个context，一个filter，filter 为 bson.D类型\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ecoll\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFindOne\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTODO\u003c/span\u003e(), \u003cspan style=\"color:#a6e22e\"\u003ebson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eD\u003c/span\u003e{}).\u003cspan style=\"color:#a6e22e\"\u003eDecode\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emongo\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eErrNoDocuments\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;No document found.\\n\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    panic(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Found document result: %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e数据查询注意点：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e需要一个 \u003ccode\u003ebson.M\u003c/code\u003e 对象，用于存储返回的数据\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCollection.FindOne\u003c/code\u003e 接受3个参数，ctx、filter和opts，返回的是一个 \u003ccode\u003eSingleResult\u003c/code\u003e 对象\n\u003cul\u003e\n\u003cli\u003ectx可以为nil，如果为nil的话，默认会创建一个 \u003ccode\u003econtext.Backgroud\u003c/code\u003e的context\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efilter\u003c/code\u003e为查询条件，为 \u003ccode\u003ebson.D\u003c/code\u003e 类型，上面代码默认为空，意味着查询任意一条数据。也可以指定特定的条件，如 \u003ccode\u003ebson.d{{\u0026quot;title\u0026quot;, \u0026quot;first post\u0026quot;}}\u003c/code\u003e，意味着查询 tile 为 first post 的文档。\u003c/li\u003e\n\u003cli\u003eopts：类型为 \u003ccode\u003eoptions.FindOneOptions\u003c/code\u003e 类型，查询的额外条件，比如限定返回的具体字段：\u003ccode\u003e\u0026amp;options.FindOneOptions{Projection: map[string]int{\u0026quot;_id\u0026quot;: 0}}\u003c/code\u003e，这个options禁止返回\u003ccode\u003e_id\u003c/code\u003e字段。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e使用 \u003ccode\u003eSingleResult.Decode\u003c/code\u003e方法将返回的\u003ccode\u003eSingleResult\u003c/code\u003e反序列化为 \u003ccode\u003ebson.M\u003c/code\u003e 对象，\u003ccode\u003eSingleResult.Decode(\u0026amp;result)\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e上述查询输出：\u003c/p\u003e","title":"Go 使用mongodb"},{"content":"go 是天生支持并发的，我们只要 使用 go func() 就可以快速实现并发。在网络服务的处理中，实现并发可以大大提高服务的吞吐量，我们来研究一下。\n简单的TCP服务器 我们先来实现一个简单的tcp服务，监听8989端口，从连接中读取一段数据，这段数据表示的是当前请求的id，然后返回一段话。\n服务器 server/main.go\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; ) func main() { // 启动tcp连接，监听8989端口 l, err := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8989\u0026#34;) if err != nil { log.Fatalf(\u0026#34;Server listening error: %s\\n\u0026#34;, err) } for { conn, err := l.Accept() if err != nil { log.Printf(\u0026#34;Accept error: %s\\n\u0026#34;, err) continue } // 接收连接,调用handleConn处理当前连接 handleConn(conn) } } func handleConn(conn net.Conn) { defer conn.Close()\t// 优雅关闭连接 data := make([]byte, 4) conn.Read(data) log.Printf(\u0026#34;Recieved connection from %s, ID[%s]\\n\u0026#34;, conn.RemoteAddr(), data) // 向客户端返回一句 Hello World if n, err := conn.Write([]byte(fmt.Sprintf(\u0026#34;Hello world! -\u0026gt; ID[%s]\u0026#34;, data))); err != nil { log.Printf(\u0026#34;Write error: %s | %s\\n\u0026#34;, err, conn.RemoteAddr()) } else { log.Printf(\u0026#34;%d bytes wrote for ID[%s]\u0026#34;, n, data) } } 服务器简单，直接启动一个tcp服务，监听8989端口，然后接收到客户端连接的时候，从连接中获取当前请求的ID，然后向当前客户端返回一句 Hello World\n客户端 client/main.go\nfunc main() { reqTCP(2) } func reqTCP(id int) { conn, err := net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:8989\u0026#34;) if err != nil { log.Printf(\u0026#34;Dial tcp error: %s\\n\u0026#34;, err) return } // 写入当前请求ID if _, err := conn.Write([]byte(strconv.Itoa(id))); err != nil { log.Printf(\u0026#34;Conn write error: %s\\n\u0026#34;, err) return } data := make([]byte, 100) // 返回的内容应该不会超过100字节 if _, err := conn.Read(data); err != nil { log.Printf(\u0026#34;Conn read error: %s\\n\u0026#34;, err) return } log.Printf(\u0026#34;Recieved from server: %s\\n\u0026#34;, data) } 客户端要做的事情，就是向服务端拨号，然后发送一个请求ID，最后从服务端获取返回的数据，并打印出来。\n运行 运行 server/main.go\ngo run server/main.go 运行 client/main.go\ngo run client/main.go 客户端输出：\n2022/04/06 21:13:50 Recieved from server: Hello world! -\u0026gt; ID[2] 服务端输出：\n2022/04/06 21:13:50 Recieved connection from [::1]:64088, ID[2] 2022/04/06 21:13:50 24 bytes wrote for ID[2] 客户看到，一个简单的TCP服务器就实现了。\n并发的请求 一般情况下，服务器并不会仅处理一个请求，而是多个请求一起处理，这就要求我们的服务器有很好的并发处理能力。\n改动 client/main.go，让其进行并发请求：\nfunc main() { nReq := 10 // 10个并发请求 var wg sync.WaitGroup wg.Add(nReq) for i := 0; i \u0026lt; nReq; i++ { go func(n int) { defer wg.Done() reqTCP(n) }(i) } wg.Wait() } // 省略其他代码... 此时直接运行的话，可以看到输出：\n客户端输出： ---- 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[7] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[8] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[9] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[6] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[5] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[2] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[0] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[3] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[1] 2022/04/06 21:30:10 Recieved from server: Hello world! -\u0026gt; ID[4] 服务端输出： ---- 2022/04/06 21:30:10 Recieved connection from [::1]:56405, ID[7] 2022/04/06 21:30:10 24 bytes wrote for ID[7] 2022/04/06 21:30:10 Recieved connection from [::1]:56406, ID[8] 2022/04/06 21:30:10 24 bytes wrote for ID[8] 2022/04/06 21:30:10 Recieved connection from [::1]:56408, ID[9] 2022/04/06 21:30:10 24 bytes wrote for ID[9] 2022/04/06 21:30:10 Recieved connection from [::1]:56409, ID[6] 2022/04/06 21:30:10 24 bytes wrote for ID[6] 2022/04/06 21:30:10 Recieved connection from [::1]:56407, ID[5] 2022/04/06 21:30:10 24 bytes wrote for ID[5] 2022/04/06 21:30:10 Recieved connection from [::1]:56404, ID[2] 2022/04/06 21:30:10 24 bytes wrote for ID[2] 2022/04/06 21:30:10 Recieved connection from [::1]:56412, ID[0] 2022/04/06 21:30:10 24 bytes wrote for ID[0] 2022/04/06 21:30:10 Recieved connection from [::1]:56411, ID[3] 2022/04/06 21:30:10 24 bytes wrote for ID[3] 2022/04/06 21:30:10 Recieved connection from [::1]:56410, ID[1] 2022/04/06 21:30:10 24 bytes wrote for ID[1] 2022/04/06 21:30:10 Recieved connection from [::1]:56413, ID[4] 2022/04/06 21:30:10 24 bytes wrote for ID[4] 从输出可以看到，我们并发发起了10个请求，这些请求的时间间隔可能很小很小，可以看做是同时发起的，服务器接收到请求和返回数据的顺序和客户端打印的结果是一致的。但是由于程序处理太快，所有请求都在一瞬间处理完成了，不能体现出某些较耗时的操作情况下，客户端的等待情况。\n改造服务端，让其返回数据前 Sleep 一秒\nserver/main.go\nfunc handleConn(conn net.Conn) { defer conn.Close() // 优雅关闭连接 data := make([]byte, 4) conn.Read(data) log.Printf(\u0026#34;Recieved connection from %s, ID[%s]\\n\u0026#34;, conn.RemoteAddr(), data) time.Sleep(time.Second)\t// 返回前 Sleep 1秒 // 向客户端返回一句 Hello World if n, err := conn.Write([]byte(fmt.Sprintf(\u0026#34;Hello world! -\u0026gt; ID[%s]\u0026#34;, data))); err != nil { log.Printf(\u0026#34;Write error: %s | %s\\n\u0026#34;, err, conn.RemoteAddr()) } else { log.Printf(\u0026#34;%d bytes wrote for ID[%s]\u0026#34;, n, data) } } 重新运行，客户端输出：\n2022/04/06 21:36:21 Recieved from server: Hello world! -\u0026gt; ID[9] 2022/04/06 21:36:22 Recieved from server: Hello world! -\u0026gt; ID[2] 2022/04/06 21:36:23 Recieved from server: Hello world! -\u0026gt; ID[4] 2022/04/06 21:36:24 Recieved from server: Hello world! -\u0026gt; ID[5] 2022/04/06 21:36:25 Recieved from server: Hello world! -\u0026gt; ID[7] 2022/04/06 21:36:27 Recieved from server: Hello world! -\u0026gt; ID[8] 2022/04/06 21:36:28 Recieved from server: Hello world! -\u0026gt; ID[6] 2022/04/06 21:36:29 Recieved from server: Hello world! -\u0026gt; ID[0] 2022/04/06 21:36:30 Recieved from server: Hello world! -\u0026gt; ID[1] 2022/04/06 21:36:31 Recieved from server: Hello world! -\u0026gt; ID[3] 可以看到，客户端是每间隔1秒打印一条记录，说明此时服务端对请求的处理是串行的，一次只处理一个连接。\n并发的服务 接下来，我们对服务器进行改造，让其实现一次性处理多个连接\nserver/main.go\nfunc main() { // 启动tcp连接，监听8989端口 l, err := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8989\u0026#34;) if err != nil { log.Fatalf(\u0026#34;Server listening error: %s\\n\u0026#34;, err) } for { conn, err := l.Accept() if err != nil { log.Printf(\u0026#34;Accept error: %s\\n\u0026#34;, err) continue } // 接收连接,调用handleConn处理当前连接 // 使用go关键字，直接并发处理 go handleConn(conn) } } // 省略其他... 服务端的并发非常简单！直接使用 go handleConn(conn) 即可让程序对当前连接的处理进入并发。\n重新运行服务端、客户端\n客户端输出：\n2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[6] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[5] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[1] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[3] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[9] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[7] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[2] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[4] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[0] 2022/04/06 21:42:38 Recieved from server: Hello world! -\u0026gt; ID[8] 服务端输出：\n2022/04/06 21:42:37 Recieved connection from [::1]:56841, ID[3] 2022/04/06 21:42:37 Recieved connection from [::1]:56839, ID[7] 2022/04/06 21:42:37 Recieved connection from [::1]:56844, ID[1] 2022/04/06 21:42:37 Recieved connection from [::1]:56843, ID[9] 2022/04/06 21:42:37 Recieved connection from [::1]:56840, ID[4] 2022/04/06 21:42:37 Recieved connection from [::1]:56842, ID[2] 2022/04/06 21:42:37 Recieved connection from [::1]:56847, ID[5] 2022/04/06 21:42:37 Recieved connection from [::1]:56845, ID[0] 2022/04/06 21:42:37 Recieved connection from [::1]:56846, ID[8] 2022/04/06 21:42:37 Recieved connection from [::1]:56848, ID[6] 2022/04/06 21:42:38 24 bytes wrote for ID[1] 2022/04/06 21:42:38 24 bytes wrote for ID[6] 2022/04/06 21:42:38 24 bytes wrote for ID[0] 2022/04/06 21:42:38 24 bytes wrote for ID[7] 2022/04/06 21:42:38 24 bytes wrote for ID[2] 2022/04/06 21:42:38 24 bytes wrote for ID[4] 2022/04/06 21:42:38 24 bytes wrote for ID[9] 2022/04/06 21:42:38 24 bytes wrote for ID[5] 2022/04/06 21:42:38 24 bytes wrote for ID[3] 2022/04/06 21:42:38 24 bytes wrote for ID[8] 可以看到，服务端是在同一时间接收到了所有请求，而且也在同一时间进入到了 handleConn函数，进行处理，在1秒后，所有连接都返回了对应的内容。非常简单的实现了并发！\n并发的HTTP server/main.go\nfunc main() { // 启动tcp连接，监听8989端口 l, err := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8989\u0026#34;) if err != nil { log.Fatalf(\u0026#34;Server listening error: %s\\n\u0026#34;, err) } http.HandleFunc(\u0026#34;/hello\u0026#34;, httpHandler) http.Serve(l, nil) } func httpHandler(resp http.ResponseWriter, req *http.Request) { id := req.URL.Query().Get(\u0026#34;id\u0026#34;) log.Printf(\u0026#34;Request from %s, method[%s], ID[%s]\\n\u0026#34;, req.RemoteAddr, req.Method, id) time.Sleep(time.Second) fmt.Fprintf(resp, fmt.Sprintf(\u0026#34;Hello http -\u0026gt; ID[%s]\u0026#34;, id)) log.Printf(\u0026#34;Done ID[%s]\u0026#34;, id) } client/main.go\nfunc main() { nReq := 10 // 10个并发请求 var wg sync.WaitGroup wg.Add(nReq) for i := 0; i \u0026lt; nReq; i++ { go func(n int) { defer wg.Done() reqHTTP(n) }(i) } wg.Wait() } func reqHTTP(id int) { // 简单的http Get resp, err := http.Get(\u0026#34;http://localhost:8989/hello?id=\u0026#34; + strconv.Itoa(id)) if err != nil { log.Printf(\u0026#34;Http request error: %s\\n\u0026#34;, err) return } data := make([]byte, resp.ContentLength) resp.Body.Read(data) log.Printf(\u0026#34;Http response: %s\\n\u0026#34;, data) } 运行 服务端和客户端，输出：\n客户端： ---- 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[4] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[2] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[3] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[1] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[5] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[0] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[9] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[8] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[6] 2022/04/06 22:12:45 Http response: Hello http -\u0026gt; ID[7] 服务端： ---- 2022/04/06 22:12:44 Request from [::1]:59686, method[GET], ID[9] 2022/04/06 22:12:44 Request from [::1]:59684, method[GET], ID[1] 2022/04/06 22:12:44 Request from [::1]:59685, method[GET], ID[8] 2022/04/06 22:12:44 Request from [::1]:59680, method[GET], ID[6] 2022/04/06 22:12:44 Request from [::1]:59688, method[GET], ID[4] 2022/04/06 22:12:44 Request from [::1]:59683, method[GET], ID[2] 2022/04/06 22:12:44 Request from [::1]:59687, method[GET], ID[3] 2022/04/06 22:12:44 Request from [::1]:59681, method[GET], ID[0] 2022/04/06 22:12:44 Request from [::1]:59682, method[GET], ID[5] 2022/04/06 22:12:44 Request from [::1]:59689, method[GET], ID[7] 2022/04/06 22:12:45 Done ID[4] 2022/04/06 22:12:45 Done ID[2] 2022/04/06 22:12:45 Done ID[3] 2022/04/06 22:12:45 Done ID[1] 2022/04/06 22:12:45 Done ID[5] 2022/04/06 22:12:45 Done ID[0] 2022/04/06 22:12:45 Done ID[9] 2022/04/06 22:12:45 Done ID[8] 2022/04/06 22:12:45 Done ID[6] 2022/04/06 22:12:45 Done ID[7] 可以看到，go http是天然并发的。\n","permalink":"https://zihuanling.github.io/posts/go-concurrent-server/","summary":"\u003cp\u003ego 是天生支持并发的，我们只要 使用 \u003ccode\u003ego func()\u003c/code\u003e 就可以快速实现并发。在网络服务的处理中，实现并发可以大大提高服务的吞吐量，我们来研究一下。\u003c/p\u003e\n\u003ch2 id=\"简单的tcp服务器\"\u003e简单的TCP服务器\u003c/h2\u003e\n\u003cp\u003e我们先来实现一个简单的tcp服务，监听8989端口，从连接中读取一段数据，这段数据表示的是当前请求的id，然后返回一段话。\u003c/p\u003e\n\u003ch3 id=\"服务器\"\u003e服务器\u003c/h3\u003e\n\u003cp\u003eserver/main.go\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;log\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 启动tcp连接，监听8989端口\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003el\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enet\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eListen\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;tcp\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;:8989\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFatalf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Server listening error: %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003el\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAccept\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Accept error: %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 接收连接,调用handleConn处理当前连接\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003ehandleConn\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehandleConn\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enet\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eConn\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClose\u003c/span\u003e()\t\t\u003cspan style=\"color:#75715e\"\u003e// 优雅关闭连接\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make([]\u003cspan style=\"color:#66d9ef\"\u003ebyte\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRead\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Recieved connection from %s, ID[%s]\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRemoteAddr\u003c/span\u003e(), \u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 向客户端返回一句 Hello World\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWrite\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSprintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello world! -\u0026gt; ID[%s]\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e))); \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Write error: %s | %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003econn\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRemoteAddr\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%d bytes wrote for ID[%s]\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e服务器简单，直接启动一个tcp服务，监听8989端口，然后接收到客户端连接的时候，从连接中获取当前请求的ID，然后向当前客户端返回一句 \u003ccode\u003eHello World\u003c/code\u003e\u003c/p\u003e","title":"go：简简单单的并发网络连接"},{"content":"RPC RPC - Remote Procedure Call 称为远程过程调用，是分布式系统中，不同节点调用的方式（进程间通信），属于 C/S 模式。由客户端发起，调用服务端的方法进行通信，服务端将结果返回给客户端。\nRPC的两个核心：\n通信协议 序列化 序列化和反序列化是一种把传输内容编码和解码的方式，常见的编解码方式有 JSON、Protobuf 等。\n大多数 RPC 架构中，都有 Client、Client Stub、Server、Server Stub这四个组件，Client 和 Server 通过 Socket 进行通信。\nflowchart LR subgraph 服务端进程 s(Server) ss(Server Stub) s --调用--\u0026gt; ss ss --返回--\u0026gt; s end subgraph 客户端进程 c(Client) cs(Client Stub) c --调用--\u0026gt; cs cs --返回--\u0026gt; c end 客户端进程 ==网络消息==\u0026gt; 服务端进程 服务端进程 ==\u0026gt; 客户端进程 调用流程：\nclient 调用 client stub，同时把参数传给 client stub client stub将参数打包编码，然后通过系统调用发送到服务端进程 客户端本地系统发送信息到服务器 服务器系统将信息发送到 server stub server stub 将信息解码 server stub 调用真正的服务端程序 server server 处理后，通过同样的方式，将结果返回到 client go 实现简单的RPC服务 go内置了 net/rpc 包帮助我们实现rpc服务。\n接下来实现一个简单的 Greet rpc 服务：\n定义服务 rpc/server/greet_service.go\npackage server type GreetService struct {} type GreetServiceArgs struct { Name string } func (g *GreetService) Greet(args GreetServiceArgs, reply *string) error { log.Printf(\u0026#34;Greeting to [%s]\u0026#34;, args.Name) s := fmt.Sprintf(\u0026#34;Hello, %s, you are using go rpc, have a good day!\u0026#34;, args.Name) *reply = s return nil } 上述代码做了以下几件事：\n定义 GreetService，表示远程服务对象 GreetServiceArgs表示参数，里面有个 Name成员 Greet方法实现了向服务调用者打招呼的功能 注册服务 直接通过 RegisterName 方法注册\nrpc/server_main.go\npackage main import ( \u0026#34;let_go/rpc/server\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;net/rpc\u0026#34; ) func main() { if err := rpc.RegisterName(\u0026#34;GreetService\u0026#34;, new(server.GreetService)); err != nil { log.Fatalf(\u0026#34;Register rpc service error: %s\u0026#34;, err) } l, e := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:1234\u0026#34;) if e != nil { log.Fatalf(\u0026#34;Server error: %s\u0026#34;, e) } rpc.Accept(l) } 直接通过 rpc.RegisterName 注册了一个服务对象 GreetService 通过 net.Listen 建立一个TCP连接，监听 1234 端口 然后通过 rcp.Accept函数，在该tcp连接的基础上提供rcp服务 规则 想要把一个对象注册为RPC服务，可以让客户端远程访问，则该对象的方法需要满足以下条件：\n方法的类型是可导出的（公开的） 方法本身也是可以导出的 方法必须有两个参数，且参数类型是可导出或者内建的 方法必须返回一个 error 类型 该方法的格式：\nfunc (t *T) MethodName(argType T1, replyType *T2) error 这里的 T1、T2 都是可以被 encoding/gob序列化的\n第一个参数 argType 是调用者（客户端）提供的 第二个参数 replyType 是返回给调用者结果，必须是指针类型 客户端调用 rpc/client_main.go\npackage main import ( \u0026#34;let_go/rpc/server\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/rpc\u0026#34; ) func main(){ client, err := rpc.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:1234\u0026#34;) if err != nil { log.Fatalf(\u0026#34;RPC dial error: %s\u0026#34;, err) } args := server.GreetServiceArgs{Name: \u0026#34;Mike\u0026#34;} var reply string err = client.Call(\u0026#34;GreetService.Greet\u0026#34;, args, \u0026amp;reply) if err != nil { log.Fatalf(\u0026#34;RPC call error: %s\u0026#34;, err) } log.Printf(\u0026#34;RPC reply: [%s]\u0026#34;, reply) } 通过 rpc.Dial 建立跟 rpc 服务端的连接，填写正确的IP和端口 构造参数 args 和返回值 reply 使用 rpc.Call 调用远程rpc方法，传入3个参数，第一个是服务的方法，新式为 ServiceName.Method，我们在前面注册了一个 GreetService，这个service有一个方法 Greet，因此我们通过客户端调用这个方法是 GreetService.Greet，后面两个参数分别为方法的参数值和返回值。 返回值必须是一个指针 运行服务端，然后运行客户端，输出结果如下：\n服务端：\n2022/03/30 14:43:17 Greeting to [Mike] 客户端：\n2022/03/30 14:43:17 RPC reply: [Hello, Mike, you are using go rpc, have a good day!] 基于 HTTP 的RPC go rpc 已经支持了http协议进行调用，我们可以简单对之前的代码进行修改：\nrpc/server_main.go\nfunc main() { if err := rpc.RegisterName(\u0026#34;GreetService\u0026#34;, new(server.GreetService)); err != nil { log.Fatalf(\u0026#34;Register rpc service error: %s\u0026#34;, err) } rpc.HandleHTTP()\t// 新增一行 l, e := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:1234\u0026#34;) if e != nil { log.Fatalf(\u0026#34;Server error: %s\u0026#34;, e) } //rpc.Accept(l) http.Serve(l, nil)\t// 修改为 http 服务 } 客户端的调用更加简单，直接将 rpc.Dail 改为 rpc.DailHTTP 即可\nrpc/client_main.go\nfunc main(){ //client, err := rpc.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:1234\u0026#34;) client, err := rpc.DialHTTP(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:1234\u0026#34;) // ... } 重新运行代码，输出结果是一样的。\n使用基于http的rpc之后，我们可以在浏览器中输入连接 http://localhost:1234/debug/rpc 即可查看我们定义的rpc服务\n返回注册的RPC服务、方法签名、被调用次数等信息\nJSON RPC跨平台通信 上述实现的rpc是通过 encoding/gob 方法编解码的，而在跨平台调用的时候，其他语言对其进行解码会比较困难。\n基于TCP的 JSON RPC 选择通用的编码是实现跨语言RPC服务的核心，比如JSON编码，go语言为我们提供了支持json的rpc包：net/rpc/jsonrpc\n对前面的服务端代码进行改造：\nrpc/server_main.go\nfunc main() { if err := rpc.RegisterName(\u0026#34;GreetService\u0026#34;, new(server.GreetService)); err != nil { log.Fatalf(\u0026#34;Register rpc service error: %s\u0026#34;, err) } l, e := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:1234\u0026#34;) if e != nil { log.Fatalf(\u0026#34;Server error: %s\u0026#34;, e) } for { conn, err := l.Accept() if err != nil { log.Fatalf(\u0026#34;Tcp accept error: %s\u0026#34;, err) return } go jsonrpc.ServeConn(conn) } } 只是简单的通过 l.Accept 获取当前连接，然后将连接交给 jsonrpc.ServeConn 进行处理\n客户端调用：\nfunc main(){ // rpc.Dial --\u0026gt; jsonrpc.Dial client, err := jsonrpc.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:1234\u0026#34;)\t// ... } 直接使用 jsonrpc.Dial 替换 rpc.Dial即可\n基于HTTP的JSON RPC 服务端改造\nrpc/server_main.go\nfunc main() { if err := rpc.RegisterName(\u0026#34;GreetService\u0026#34;, new(server.GreetService)); err != nil { log.Fatalf(\u0026#34;Register rpc service error: %s\u0026#34;, err) } // 实现核心，使用 http.HandleFunc 注册一个处理rpc的handler http.HandleFunc(rpc.DefaultRPCPath, func(rw http.ResponseWriter, r *http.Request){ conn, _, err := rw.(http.Hijacker).Hijack() if err != nil { log.Printf(\u0026#34;Error hijack from addr[%s], err: %s\u0026#34;, r.RemoteAddr, err) return } fmt.Fprintf(conn, \u0026#34;HTTP/1.0 200 Connected to JSON RPC\\n\\n\u0026#34;) jsonrpc.ServeConn(conn) }) l, e := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:1234\u0026#34;) if e != nil { log.Fatalf(\u0026#34;Server error: %s\u0026#34;, e) } http.Serve(l, nil) } 服务器代码，通过 http.HandleFunc注册一个rpc的路径处理函数，然后通过 http.Hijacker.Hijack()劫持连接，再交给 jsonrpc处理，如同基于tcp的jsonrpc实现一般。\n客户端代码调整\nrpc/client_main.go\nfunc main(){ client, err := DialHTTP(\u0026#34;tcp\u0026#34;, \u0026#34;localhost:1234\u0026#34;) if err != nil { log.Fatalf(\u0026#34;RPC dial error: %s\u0026#34;, err) } args := server.GreetServiceArgs{Name: \u0026#34;Mike\u0026#34;} var reply string err = client.Call(\u0026#34;GreetService.Greet\u0026#34;, args, \u0026amp;reply) if err != nil { log.Fatalf(\u0026#34;RPC call error: %s\u0026#34;, err) } log.Printf(\u0026#34;RPC reply: [%s]\u0026#34;, reply) } func DialHTTP(network, address string) (*rpc.Client, error) { return DialHTTPPath(network, address, rpc.DefaultRPCPath) } // 从 rpc.DialHTTPPath 改造而来 func DialHTTPPath(network, address, path string) (*rpc.Client, error) { var err error // 建立TCP连接 conn, err := net.Dial(network, address) if err != nil { return nil, err } // 写入请求头 fmt.Fprintf(conn, \u0026#34;GET %s HTTP/1.0\\n\\n\u0026#34;, path) // 读取http响应体 resp, err := http.ReadResponse(bufio.NewReader(conn), \u0026amp;http.Request{Method: \u0026#34;GET\u0026#34;}) if err == nil{ // 如果服务端返回了 \u0026#34;200 Connected to JSON RPC\u0026#34; 说明连接成功 if resp.Status == \u0026#34;200 Connected to JSON RPC\u0026#34; { // 直接从当前连接创建 jsonRpcClient 即可 return jsonrpc.NewClient(conn), nil } return nil, errors.New(\u0026#34;Http response error, status=\u0026#34; + resp.Status) } conn.Close() return nil, \u0026amp;net.OpError{ Op: \u0026#34;dial-http\u0026#34;, Net: network + \u0026#34; \u0026#34; + address, Addr: nil, Err: err, } } 说是基于 http 的 rpc调用，实际上可以称作是披着http外皮原生jsonrpc调用，所用到http的地方，不过是请求了一个http地址，获取正确的连接，然后就是正常的jsonrpc调用的通信流程。\n上述改动，通过修改 rpc.DialHTTPPath方法，请求服务端地址，获取一个rpc连接，然后实现jsonrpc通信。\n跨平台，使用python调用基于http的jsonrpc服务 只要符合jsonrpc通信规范，其他语言可以调用go的rpc服务，以python为例，调用上述基于http的jsonrpc服务，上代码：\ntest_go_jsonrpc.py\nimport json, socket # 建立tcp连接 s = socket.create_connection((\u0026#34;localhost\u0026#34;, 1234)) # 发送http请求，成功后才能发起jsonrpc通信 req_body = \u0026#39;\u0026#39;\u0026#39;GET /_goRPC_ HTTP/1.0\\n\\n\u0026#39;\u0026#39;\u0026#39; s.sendall(req_body.encode()) print((s.recv(4096)).decode()) # rpc调用参数，id用以区分返回内容，另外包含： # method参数：需要调用的方法名称 # params参数：参数列表，服务端定义的是一个结构体，这里需要是一个可以让go unmarshal为对应的参数结构体的json对象 rpc_params = dict( id=2, method=\u0026#34;GreetService.Greet\u0026#34;, params=[{\u0026#34;Name\u0026#34;: \u0026#34;python\u0026#34;}], ) s.sendall(json.dumps(rpc_params).encode()) print((s.recv(4096)).decode()) 运行上述python代码，可以看到返回：\n\u0026gt;python test_go_jsonrpc.py HTTP/1.0 200 Connected to JSON RPC {\u0026#34;id\u0026#34;:2,\u0026#34;result\u0026#34;:\u0026#34;Hello, python, you are using go rpc, have a good day!\u0026#34;,\u0026#34;error\u0026#34;:null} 首先是http请求，返回了正确的header： HTTP/1.0 200 Connected to JSON RPC，说明接下来可以进行jsonrpc通信了\n然后是jsonrpc通信的返回结果，使用json解码后，得到一个json字符串，其中有三个字段：\nid： 我们发起请求参数里面的id result：rpc调用的返回结果 error：调用rpc产生的错误 ","permalink":"https://zihuanling.github.io/posts/go-rpc-server/","summary":"\u003ch2 id=\"rpc\"\u003eRPC\u003c/h2\u003e\n\u003cp\u003eRPC  - Remote Procedure Call  称为远程过程调用，是分布式系统中，不同节点调用的方式（进程间通信），属于 C/S 模式。由客户端发起，调用服务端的方法进行通信，服务端将结果返回给客户端。\u003c/p\u003e\n\u003cp\u003eRPC的两个核心：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e通信协议\u003c/li\u003e\n\u003cli\u003e序列化\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e序列化\u003c/strong\u003e和\u003cstrong\u003e反序列化\u003c/strong\u003e是一种把传输内容编码和解码的方式，常见的编解码方式有 JSON、Protobuf 等。\u003c/p\u003e\n\u003cp\u003e大多数 RPC 架构中，都有 \u003cstrong\u003eClient、Client Stub、Server、Server Stub\u003c/strong\u003e这四个组件，Client 和 Server 通过 \u003cstrong\u003eSocket\u003c/strong\u003e 进行通信。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-mermaid\" data-lang=\"mermaid\"\u003eflowchart LR\nsubgraph 服务端进程\ns(Server)\nss(Server Stub)\ns --调用--\u0026gt; ss\nss --返回--\u0026gt; s\nend\nsubgraph 客户端进程\nc(Client)\ncs(Client Stub)\nc --调用--\u0026gt; cs\ncs --返回--\u0026gt; c\nend\n客户端进程 ==网络消息==\u0026gt; 服务端进程\n服务端进程 ==\u0026gt; 客户端进程\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e调用流程：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eclient 调用 client stub，同时把参数传给 client stub\u003c/li\u003e\n\u003cli\u003eclient stub将参数打包编码，然后通过系统调用发送到服务端进程\u003c/li\u003e\n\u003cli\u003e客户端本地系统发送信息到服务器\u003c/li\u003e\n\u003cli\u003e服务器系统将信息发送到 server stub\u003c/li\u003e\n\u003cli\u003eserver stub 将信息解码\u003c/li\u003e\n\u003cli\u003eserver stub 调用真正的服务端程序 server\u003c/li\u003e\n\u003cli\u003eserver 处理后，通过同样的方式，将结果返回到 client\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"go-实现简单的rpc服务\"\u003ego 实现简单的RPC服务\u003c/h2\u003e\n\u003cp\u003ego内置了 \u003ccode\u003enet/rpc\u003c/code\u003e 包帮助我们实现rpc服务。\u003c/p\u003e","title":"Go：简单的跨平台rpc服务"},{"content":"简易 http 服务 使用 go 自带的 http 模块实现一个简单的 http 服务器，对请求者说一句 Hello, go http!\nfunc main(){ http.HandleFunc(\u0026#34;/hello\u0026#34;, sayHello) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } func sayHello(resp http.ResponseWriter, req *http.Request) { log.Println(\u0026#34;In say hello\u0026#34;) resp.WriteHeader(http.StatusOK) resp.Write([]byte(\u0026#34;Hello, go http!\u0026#34;)) } 运行上述代码，在终端使用 curl 访问该地址，返回正常：\n\u0026gt;curl http://localhost:8080/hello Hello, go http! 且程序使用 log 打印了一句： 2022/03/27 15:32:48 In say hello\n但是，我们的http方法有很多， GET/POST/PUT/PATCH/DELETE 等，我们使用任意一个方法，访问上述地址，都能得到 Hello, go http! 这句返回，如 POST:\n\u0026gt;curl -X POST http://localhost:8080/hello Hello, go http! http.Request 这个结构体中，包含了一个 Method 字段，让我们可以根据不同的方法，决定不同的处理方式，我们将 sayHello 方法改造如下：\nfunc sayHello(resp http.ResponseWriter, req *http.Request) { log.Println(\u0026#34;In say hello\u0026#34;) switch req.Method { case \u0026#34;GET\u0026#34;: fmt.Fprintln(resp, \u0026#34;GET: Hello, go http!\u0026#34;) case \u0026#34;POST\u0026#34;: fmt.Fprintln(resp, \u0026#34;POST: Hello, go http!\u0026#34;) default: resp.WriteHeader(http.StatusNotFound) fmt.Fprintln(resp, \u0026#34;Not found\u0026#34;) } } 请求输出：\n\u0026gt;curl http://localhost:8080/hello GET: Hello, go http! \u0026gt;curl -X POST http://localhost:8080/hello POST: Hello, go http! \u0026gt;curl -X PUT http://localhost:8080/hello Not found 基于 gin 框架的 RESTFUL API 服务 虽然go自带的net/http包可以方便的创建HTTP服务，但是其包含以下不足：\n不能单独地对请求方法（POST/GET等）注册特定的处理函数 不支持 path 变量参数 不能自动对 path 进行校准 性能一般 扩展性不足 。。。 gin是 github 上开源的一个 go web 框架，它是一个包，我们可以通过 go.mod 进行引入\n接下来，我们使用gin框架实现一个简单的用户查询系统\n引入 gin框架 安装命令：\ngo get github.com/gin-gonic/gin 引入：\nimport \u0026#34;github.com/gin-gonic/gin\u0026#34; 使用gin框架 使用gin框架，我们可以简单的这样写：\nfunc main(){ engine := gin.Default() engine.Run(\u0026#34;:8080\u0026#34;) } gin.Default()方法会返回一个 gin Engine 实例，我们调用这个 实例的 Run方法，传入监听的地址，就可以实现一个简单的gin服务。\n处理具体路径的请求，我们可以使用 engine.Handle方法\n定义 User 结构体： type User struct { ID uint\t`json:\u0026#34;id\u0026#34;` Age uint\t`json:\u0026#34;age\u0026#34;` Name string\t`json:\u0026#34;name\u0026#34;` } // 初始用户列表 var userList []User = []User{ {1, 19, \u0026#34;Mike\u0026#34;}, {2, 20, \u0026#34;Jack\u0026#34;} } 上述代码，我们定义了一个用户User的基本接口，包含字段 ID, Age, Name， 然后定义了 json struct tag，让gin返回的json字段小写。\n获取用户列表 我们接下来实现一个 GET 方法，这个方法请求路径 /users，返回所有的用户列表：\n// 获取用户列表 func getAllUsers(c *gin.Context) { log.Println(\u0026#34;Getting user list\u0026#34;) c.JSON(http.StatusOK, userList) } 非常简单，getAllUsers 是一个Handler， 接收一个 *gin.Contest类型的参数，在这个Handler里面，我们直接将用户列表userList返回，使用 c.JSON方法，这个方法接收两个参数：\n第一个是 http状态码 第二个是要返回的对象 这个方法会自动将返回对象序列化成为Json字符串\n完成了一个Handler，我们接下来在gin中注册这个Handler\nmain.go\nfunc main(){ engine := gin.Default() engine.Handle(http.MethodGet, \u0026#34;/users\u0026#34;, getAllUsers) engine.Run(\u0026#34;:8080\u0026#34;) } 运行上述程序，然后在终端测试：\n\u0026gt;curl http://localhost:8080/users [{\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;},{\u0026#34;id\u0026#34;:2,\u0026#34;age\u0026#34;:20,\u0026#34;name\u0026#34;:\u0026#34;Jack\u0026#34;}] 可以看到，正常返回了用户列表\n查询具体用户 // 查询用户 func queryUser(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) log.Printf(\u0026#34;Querying user with id = %s\u0026#34;, id) for _, u := range userList { if strings.EqualFold(id, strconv.Itoa(int(u.ID))) { log.Printf(\u0026#34;Found user: %v\u0026#34;, u) c.JSON(http.StatusOK, u) return } } log.Printf(\u0026#34;User %s not found\u0026#34;, id) c.JSON(http.StatusNotFound, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;user not found.\u0026#34;, }) } 我们实现了一个 queryUser handler，这个handler处理的方法是 GET，请求的api是 /users/:id 里面的 id 是路径参数，在gin中，我们通过 c.Param() 方法，获取路径参数，这个方法返回的是一个 string类型的变量，我们用定义的 ID字段是 uint 类型的，因此，我们需要使用 strconv.Itoa 对其进行一个转换，然后通过对比查找对应的用户。\n找到对应的用户之后，我们通过 c.JSON 返回用户，否则，我们返回一个 404 Not Found\n里面用到的 gin.H 本质是一个 map[string]interface{} 类型，用来方便返回一些信息\n在 main中注册新的Handler：\nfunc main(){ //... engine.Handle(http.MethodGet, \u0026#34;/users/:id\u0026#34;, queryUser) //... } 运行程序后，测试：\n\u0026gt;curl http://localhost:8080/users/1 {\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;}\t# 查询到id为1的用户 \u0026gt;curl http://localhost:8080/users/3\t# id为3的用户不存在 {\u0026#34;message\u0026#34;:\u0026#34;user not found.\u0026#34;} 可以看到，在用户存在或不存在的情况下，都可以正常返回\n创建新用户 // 创建用户 func createUser(c *gin.Context) { var name string var age uint if name_, ok := c.GetPostForm(\u0026#34;name\u0026#34;); !ok { msg := \u0026#34;[name] field missed.\u0026#34; log.Println(msg) c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: msg}) return } else { name = name_ } if age_, ok := c.GetPostForm(\u0026#34;age\u0026#34;); !ok { msg := \u0026#34;[age] field missed.\u0026#34; log.Println(msg) c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: msg}) return } else { _age, err := strconv.Atoi(age_) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Invalid age: \u0026#34; + age_}) return } age = uint(_age) } // find last ID var lastID uint = userList[len(userList) - 1].ID // create user newUser := User{ID: lastID+1, Age: age, Name: name} userList = append(userList, newUser) c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok, new user id : \u0026#34; + strconv.Itoa(int(newUser.ID))}) log.Printf(\u0026#34;New user created, id = %d\u0026#34;, newUser.ID) } 创建用户，我们通过 POST方法，请求 /users ，传入的参数有两个字段： name，age。\ngin框架中，我们通过 c.GetPostForm 方法，获取POST方法里面的内容，返回 string, bool，我们可以通过返回的flag，判断用户是否传了对应的值，以实现特定的逻辑。当然，我们也可以通过 c.DefaultPostForm, 在获取不到指定值的情况下，返回一个空字符串。\n然后通过取到最后一个用户的ID，生成下一个用户的ID，实现自增。（其实这样有很大的缺陷，比如我们删除了最后一个用户之后，新建的用户ID可能会跟刚刚删除的用户ID一样，导致冲突。）\n注册Handler：\nfunc main(){ //... engine.Handle(http.MethodPost, \u0026#34;/users\u0026#34;, createUser) //... } 运行、测试：\n\u0026gt;curl -X POST http://localhost:8080/users {\u0026#34;message\u0026#34;:\u0026#34;[name] field missed.\u0026#34;}\t# 错误：不带name参数 \u0026gt;curl -X POST http://localhost:8080/users -d \u0026#34;name=Jack\u0026#34; {\u0026#34;message\u0026#34;:\u0026#34;[age] field missed.\u0026#34;}\t# 错误：不带age参数 \u0026gt;curl -X POST http://localhost:8080/users -d \u0026#34;name=Jack\u0026#34; -d \u0026#34;age=aa\u0026#34; {\u0026#34;message\u0026#34;:\u0026#34;Invalid age: aa\u0026#34;}\t# 错误的 age 参数 \u0026gt;curl -X POST http://localhost:8080/users -d \u0026#34;name=Jack\u0026#34; -d \u0026#34;age=29\u0026#34; {\u0026#34;message\u0026#34;:\u0026#34;ok, new user id : 3\u0026#34;}\t# 创建成功，新用户ID 为 3 \u0026gt;curl http://localhost:8080/users\t# 查看用户列表，新用户成功创建 [{\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;},{\u0026#34;id\u0026#34;:2,\u0026#34;age\u0026#34;:20,\u0026#34;name\u0026#34;:\u0026#34;Jack\u0026#34;},{\u0026#34;id\u0026#34;:3,\u0026#34;age\u0026#34;:29,\u0026#34;name\u0026#34;:\u0026#34;Jack\u0026#34;}] 修改用户 // 修改用户 func modifyUser(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) var age int name := c.DefaultPostForm(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) _age := c.DefaultPostForm(\u0026#34;age\u0026#34;, \u0026#34;\u0026#34;) // 两个必传参数 if name == \u0026#34;\u0026#34; || _age == \u0026#34;\u0026#34; { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: \u0026#34;[name] and [age] is required.\u0026#34;}) return } // string to int age, err := strconv.Atoi(_age) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Invalid age: \u0026#34; + _age}) return } log.Printf(\u0026#34;Modify user with id = %s\u0026#34;, id) // 查找用户进行修改 for idx, u := range userList { if strings.EqualFold(id, strconv.Itoa(int(u.ID))) { log.Printf(\u0026#34;Found user: %v\u0026#34;, u) u.Name = name u.Age = uint(age) userList[idx] = u log.Printf(\u0026#34;User modified to: %v\u0026#34;, u) c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok\u0026#34;}) return } } log.Printf(\u0026#34;User %s not found\u0026#34;, id) c.JSON(http.StatusNotFound, gin.H{\u0026#34;message\u0026#34;: \u0026#34;user not found.\u0026#34;}) } 逻辑简单，直接从post方法传过来的数据中，获取name和age字段，然后查找对应的用户，对其进行修改，当用户不存在的情况下，返回 404 Not Found\n注册 Handler：\nfunc main(){ //... engine.Handle(http.MethodPut, \u0026#34;/users/:id\u0026#34;, modifyUser) //... } 运行，测试：\n\u0026gt;curl http://localhost:8080/users\t# 当前的用户列表 [{\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;},{\u0026#34;id\u0026#34;:2,\u0026#34;age\u0026#34;:20,\u0026#34;name\u0026#34;:\u0026#34;Jack\u0026#34;}] \u0026gt;curl -X PUT http://localhost:8080/users/4 -d \u0026#34;name=Amy\u0026#34;\t# 参数缺失 {\u0026#34;message\u0026#34;:\u0026#34;[name] and [age] is required.\u0026#34;} \u0026gt;curl -X PUT http://localhost:8080/users/4 -d \u0026#34;name=Amy\u0026#34; -d \u0026#34;age=10\u0026#34;\t# id=4的用户不存在 {\u0026#34;message\u0026#34;:\u0026#34;user not found.\u0026#34;} \u0026gt;curl -X PUT http://localhost:8080/users/2 -d \u0026#34;name=Amy\u0026#34; -d \u0026#34;age=10\u0026#34;\t# 修改成功 {\u0026#34;message\u0026#34;:\u0026#34;ok\u0026#34;} \u0026gt;curl http://localhost:8080/users [{\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;},{\u0026#34;id\u0026#34;:2,\u0026#34;age\u0026#34;:10,\u0026#34;name\u0026#34;:\u0026#34;Amy\u0026#34;}]\t# 修改成功后的用户列表 可以看到，表现符合预期。\n删除用户 // 删除用户 func deleteUser(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) log.Printf(\u0026#34;Querying user with id = %s\u0026#34;, id) for idx, u := range userList { if strings.EqualFold(id, strconv.Itoa(int(u.ID))) { log.Printf(\u0026#34;Deleting user: %v\u0026#34;, u) for ; idx \u0026lt; len(userList) - 1; idx++ { userList[idx] = userList[idx+1] } userList = userList[:len(userList) - 1] c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok\u0026#34;}) return } } log.Printf(\u0026#34;User %s not found\u0026#34;, id) c.JSON(http.StatusNotFound, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;user not found.\u0026#34;, }) } 这里删除用户的逻辑也是很简单的：从传入的用户id里面找到当前用户的位置，然后从数组里面将该用户删除掉即可。\n这里使用的是数据覆盖，实际业务逻辑中，我们可以使用Mongo、MySQL等数据引擎，实现CRUD操作。\nfunc main(){ //... engine.Handle(http.MethodDelete, \u0026#34;/users/:id\u0026#34;, deleteUser) //... } 运行、调试：\n\u0026gt;curl http://localhost:8080/users [{\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;},{\u0026#34;id\u0026#34;:2,\u0026#34;age\u0026#34;:20,\u0026#34;name\u0026#34;:\u0026#34;Jack\u0026#34;}]\t# 当前的所有用户 \u0026gt;curl -X DELETE http://localhost:8080/users/2\t# 删除第二个用户 {\u0026#34;message\u0026#34;:\u0026#34;ok\u0026#34;} \u0026gt;curl http://localhost:8080/users [{\u0026#34;id\u0026#34;:1,\u0026#34;age\u0026#34;:19,\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;}]\t# 剩下一个用户 附代码：\npackage main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;strings\u0026#34; ) func main(){ engine := gin.Default() engine.Handle(http.MethodGet, \u0026#34;/users\u0026#34;, getAllUsers) engine.Handle(http.MethodPost, \u0026#34;/users\u0026#34;, createUser) engine.Handle(http.MethodGet, \u0026#34;/users/:id\u0026#34;, queryUser) engine.Handle(http.MethodPut, \u0026#34;/users/:id\u0026#34;, modifyUser) engine.Handle(http.MethodDelete, \u0026#34;/users/:id\u0026#34;, deleteUser) engine.Run(\u0026#34;:8080\u0026#34;) } type User struct { ID uint\t`json:\u0026#34;id\u0026#34;` Age uint\t`json:\u0026#34;age\u0026#34;` Name string\t`json:\u0026#34;name\u0026#34;` } // 初始用户列表 var userList []User = []User{ {1, 19, \u0026#34;Mike\u0026#34;}, {2, 20, \u0026#34;Jack\u0026#34;} } // 获取用户列表 func getAllUsers(c *gin.Context) { log.Println(\u0026#34;Getting user list\u0026#34;) c.JSON(http.StatusOK, userList) } // 查询用户 func queryUser(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) log.Printf(\u0026#34;Querying user with id = %s\u0026#34;, id) for _, u := range userList { if strings.EqualFold(id, strconv.Itoa(int(u.ID))) { log.Printf(\u0026#34;Found user: %v\u0026#34;, u) c.JSON(http.StatusOK, u) return } } log.Printf(\u0026#34;User %s not found\u0026#34;, id) c.JSON(http.StatusNotFound, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;user not found.\u0026#34;, }) } // 创建用户 func createUser(c *gin.Context) { var name string var age uint if name_, ok := c.GetPostForm(\u0026#34;name\u0026#34;); !ok { msg := \u0026#34;[name] field missed.\u0026#34; log.Println(msg) c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: msg}) return } else { name = name_ } if age_, ok := c.GetPostForm(\u0026#34;age\u0026#34;); !ok { msg := \u0026#34;[age] field missed.\u0026#34; log.Println(msg) c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: msg}) return } else { _age, err := strconv.Atoi(age_) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Invalid age: \u0026#34; + age_}) return } age = uint(_age) } // find last ID var lastID uint = userList[len(userList) - 1].ID // create user newUser := User{ID: lastID+1, Age: age, Name: name} userList = append(userList, newUser) c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok, new user id : \u0026#34; + strconv.Itoa(int(newUser.ID))}) log.Printf(\u0026#34;New user created, id = %d\u0026#34;, newUser.ID) } // 修改用户 func modifyUser(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) var age int name := c.DefaultPostForm(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) _age := c.DefaultPostForm(\u0026#34;age\u0026#34;, \u0026#34;\u0026#34;) if name == \u0026#34;\u0026#34; || _age == \u0026#34;\u0026#34; { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: \u0026#34;[name] and [age] is required.\u0026#34;}) return } // string to int age, err := strconv.Atoi(_age) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Invalid age: \u0026#34; + _age}) return } log.Printf(\u0026#34;Modify user with id = %s\u0026#34;, id) for idx, u := range userList { if strings.EqualFold(id, strconv.Itoa(int(u.ID))) { log.Printf(\u0026#34;Found user: %v\u0026#34;, u) u.Name = name u.Age = uint(age) userList[idx] = u log.Printf(\u0026#34;User modified to: %v\u0026#34;, u) c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok\u0026#34;}) return } } log.Printf(\u0026#34;User %s not found\u0026#34;, id) c.JSON(http.StatusNotFound, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;user not found.\u0026#34;, }) } // 删除用户 func deleteUser(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) log.Printf(\u0026#34;Querying user with id = %s\u0026#34;, id) for idx, u := range userList { if strings.EqualFold(id, strconv.Itoa(int(u.ID))) { log.Printf(\u0026#34;Deleting user: %v\u0026#34;, u) for ; idx \u0026lt; len(userList) - 1; idx++ { userList[idx] = userList[idx+1] } userList = userList[:len(userList) - 1] c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;ok\u0026#34;}) return } } log.Printf(\u0026#34;User %s not found\u0026#34;, id) c.JSON(http.StatusNotFound, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;user not found.\u0026#34;, }) } ","permalink":"https://zihuanling.github.io/posts/go-http-server/","summary":"\u003ch2 id=\"简易-http-服务\"\u003e简易 http 服务\u003c/h2\u003e\n\u003cp\u003e使用 go 自带的 http 模块实现一个简单的 http 服务器，对请求者说一句 \u003ccode\u003eHello, go http!\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eHandleFunc\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/hello\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003esayHello\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eListenAndServe\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;:8080\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esayHello\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResponseWriter\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRequest\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;In say hello\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWriteHeader\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eStatusOK\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWrite\u003c/span\u003e([]byte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello, go http!\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e运行上述代码，在终端使用 curl 访问该地址，返回正常：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e\u0026gt;curl http://localhost:8080/hello\nHello, go http!\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e且程序使用 \u003ccode\u003elog\u003c/code\u003e 打印了一句： \u003ccode\u003e2022/03/27 15:32:48 In say hello\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e但是，我们的http方法有很多， GET/POST/PUT/PATCH/DELETE 等，我们使用任意一个方法，访问上述地址，都能得到 \u003ccode\u003eHello, go http!\u003c/code\u003e 这句返回，如 \u003ccode\u003ePOST\u003c/code\u003e:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e\u0026gt;curl -X POST http://localhost:8080/hello\nHello, go http!\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003ehttp.Request\u003c/code\u003e 这个结构体中，包含了一个 \u003ccode\u003eMethod\u003c/code\u003e 字段，让我们可以根据不同的方法，决定不同的处理方式，我们将 \u003ccode\u003esayHello\u003c/code\u003e 方法改造如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esayHello\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResponseWriter\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRequest\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;In say hello\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eswitch\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMethod\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFprintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GET: Hello, go http!\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;POST\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFprintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;POST: Hello, go http!\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWriteHeader\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eStatusNotFound\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFprintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eresp\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Not found\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e请求输出：\u003c/p\u003e","title":"Go：简单的http服务以及Restful Api"},{"content":"对我们的代码进行检查，有助于提高代码质量，确保代码更加符合规范。\n使用 golangci-lint 进行代码检查 go语言代码分析的工具有很多，如 golint、gofmt、misspell等，我们一般使用 集成工具 golangci-lint，而不是单独使用他们。\n安装 go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2 查看版本 golangci-lint version 输出：\ngolangci-lint has version v1.32.2 ... 运行检查： golangci-lint run quality/ 这里的 quality是一个包，里面有一个 main.go 文件，内容如下：\nquality/main.go\npackage main import \u0026#34;os\u0026#34; func main(){ os.Mkdir(\u0026#34;/tmp\u0026#34;, 0666) } 运行检查后，输出如下：\nquality\\main.go:6:10: Error return value of `os.Mkdir` is not checked (errcheck) os.Mkdir(\u0026#34;/tmp\u0026#34;, 0666) 因为 os.Mkdir 返回一个 error，但是我们没有处理这个 error，因此提示了这个错误。\ngolangci-lint 配置 golangci-lint是一个集成工具，里面有很多linters，我们可以用 golangci-lint linters 查看有哪些linters，以及他们的作用、启用情况，默认启用的linters如下：\ndeadcode - 死代码检查 errcheck - 返回错误是否使用检查 gosimple - 检查代码是否可以简化 govet - 代码可疑检查，比如格式化字符串和类型不一致 ineffassign - 检查是否有未使用的代码 staticcheck - 静态分析检查 structcheck - 查找未使用的结构体字段 typecheck - 类型检查 unused - 未使用代码检查 varcheck - 未使用的全局变量和常量检查 我们可以通过一个 .golangci.yml 来对 golangci-lint 进行自定义的配置：\n.golangci.yml\n# 假设我们的代码检查仅启用 unused linters: disable-all: true enable: - unused 配置 golangci-lint 使用固定版本，在 .golangci.yml 里面添加：\nservice: golangci-lint-version: 1.32.2 # 使用固定版本的golangci-lint 针对每个启用的 linter 使用单独的配置，如设置拼写检测语言为US：\nlinters-settings: misspell: locale: US 更多配置参考： 官方文档\n性能优化 写正确的代码是性能优化的前提\n性能优化的目的是让程序更好、更快地运行，但这不是必要的。\n堆分配还是栈 go语言的内存空间分为两部分：栈内存和堆内存\n栈内存：由编译器自动分配和释放，开发者无法控制。一般存储函数中的局部变量、参数等，函数创建的时候，这些内存会被自动创建；函数返回的时候，这些内存会被自动释放。 栈内存：生命周期比栈内存要长，如果函数返回的值还会在其他地方使用，那么这个值就会被编译器自动分配到堆上。相比栈内存来说，不能自动被编译器释放，只能通过垃圾回收器才能释放，所以效率相对低。 逃逸分析 在 main.go 写入以下代码：\nfunc newString() *string { s := new(string) *s = \u0026#34;mike\u0026#34; return s } 查看逃逸分析：\ngo build -gcflags=\u0026#34;-m -l\u0026#34; ./quality/main.go # -m 打印出逃逸分析信息 # -l 禁止内联，方便更好观察逃逸 输出：\nquality\\main.go:4:10: new(string) escapes to heap 上述结果说明：指针作为函数返回值的时候，一定会发生逃逸\n修改函数，让其返回 string 而不是 *string\nfunc newString() string { s := new(string) *s = \u0026#34;Mike\u0026#34; return *s } 再次运行逃逸分析，输出：\nquality\\main.go:4:10: new(string) does not escape 可以看到没有发生逃逸了，因为我们没有在函数中返回指针。\n并不是说不使用指针作为函数返回就不会发生逃逸，被已经逃逸的指针变量引用的变量，也会发生逃逸\n如：\nfmt.Println(\u0026#34;Hello world.\u0026#34;) \u0026gt;go build -gcflags=\u0026#34;-m -l\u0026#34; ./quality/main.go # let_go/quality quality\\main.go:6:13: ... argument does not escape quality\\main.go:6:14: \u0026#34;Hello world.\u0026#34; escapes to heap 引用代码如下：\nfunc (p *pp) printArg(arg interface{}, verb rune) { p.arg = arg //省略其他无关代码 } 被 slice、map、chan 三种类型引用的指针也会发生逃逸：\nm := map[int]*string{} s := \u0026#34;Mike\u0026#34; m[0] = \u0026amp;s 逃逸分析结果：\nquality\\main.go:5:2: moved to heap: s quality\\main.go:4:22: map[int]*string{} does not escape 可以看到被变量 m 引用的 变量s 逃逸到了 堆上。\n优化技巧 尽可能避免逃逸，因为栈内存效率更高，小对象传参时，array比slice效果要更好 对于频繁的内存申请操作，我们应该学会重用内存，比如 sync.Pool 选用合适的算法达到高性能的目的，如 空间换时间 其他：\n尽可能避免使用锁、并发加锁的范围要尽可能小、使用 StringBuilder做string和 []byte 之间的转换、defer嵌套不要太多，等。\ngo 性能剖析工具： pprof ","permalink":"https://zihuanling.github.io/posts/go-17-quarlity-and-optimization/","summary":"\u003cp\u003e对我们的代码进行检查，有助于提高代码质量，确保代码更加符合规范。\u003c/p\u003e\n\u003ch2 id=\"使用-golangci-lint-进行代码检查\"\u003e使用 golangci-lint 进行代码检查\u003c/h2\u003e\n\u003cp\u003ego语言代码分析的工具有很多，如 \u003ccode\u003egolint、gofmt、misspell\u003c/code\u003e等，我们一般使用 \u003cstrong\u003e集成工具\u003c/strong\u003e \u003ccode\u003egolangci-lint\u003c/code\u003e，而不是单独使用他们。\u003c/p\u003e\n\u003ch3 id=\"安装\"\u003e安装\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.32.2\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"查看版本\"\u003e查看版本\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egolangci-lint version\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003egolangci-lint has version v1.32.2 ...\n\u003c/code\u003e\u003c/pre\u003e\u003ch3 id=\"运行检查\"\u003e运行检查：\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egolangci-lint run quality/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里的 \u003ccode\u003equality\u003c/code\u003e是一个包，里面有一个 \u003ccode\u003emain.go\u003c/code\u003e 文件，内容如下：\u003c/p\u003e\n\u003cp\u003equality/main.go\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;os\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMkdir\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/tmp\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0666\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e运行检查后，输出如下：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003equality\\main.go:6:10: Error return value of `os.Mkdir` is not checked (errcheck)\n        os.Mkdir(\u0026#34;/tmp\u0026#34;, 0666)\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e因为 \u003ccode\u003eos.Mkdir\u003c/code\u003e 返回一个 error，但是我们没有处理这个 error，因此提示了这个错误。\u003c/p\u003e\n\u003ch3 id=\"golangci-lint-配置\"\u003egolangci-lint 配置\u003c/h3\u003e\n\u003cp\u003egolangci-lint是一个集成工具，里面有很多linters，我们可以用 \u003ccode\u003egolangci-lint linters\u003c/code\u003e 查看有哪些linters，以及他们的作用、启用情况，默认启用的linters如下：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003edeadcode - 死代码检查\nerrcheck - 返回错误是否使用检查\ngosimple - 检查代码是否可以简化\ngovet - 代码可疑检查，比如格式化字符串和类型不一致\nineffassign - 检查是否有未使用的代码\nstaticcheck - 静态分析检查\nstructcheck - 查找未使用的结构体字段\ntypecheck - 类型检查\nunused - 未使用代码检查\nvarcheck - 未使用的全局变量和常量检查\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e我们可以通过一个 \u003ccode\u003e.golangci.yml\u003c/code\u003e 来对 golangci-lint 进行自定义的配置：\u003c/p\u003e","title":"17.go: 代码检查和性能优化"},{"content":"单元测试 单元测试就是对单元进行测试（听起来是一句废话），单元可以是一个函数、一个模块等，我们最小的单元是一个函数。\n以斐波那契数列为例，实现一个测试用例\ntest/main.go\npackage main // 斐波那契数列 func Fibonacci(n int) int { if n \u0026lt;= 0 { return 0 } else if n == 1 { return 1 } return Fibonacci(n-1) + Fibonacci(n-2) } 我们在 main.go 里面写了一个 Fibonacci函数，用于计算对应的斐波那契值。\n我们接下来写一个测试用例，测试的文件名应该是以 _test.go 结尾的，前面的名称最好是需要测试的文件名称，比如要测试 main.go，则测试文件命名为 main_test.go，而在 测试文件里面，需要一个以 Test开头的函数，后面接需要测试的函数名称，如 TestFibonacci，这个函数接受一个 *Testing.T指针，且不返回任何值\ntest/main_test.go\nfunc TestFibonacci(t *testing.T) { result := map[int]int{ 1: 1, 2: 1, 3: 2, 4: 3, 5: 5, 6: 8, 7: 13, 8: 21, } for n, expect := range result { got := Fibonacci(n) if expect != got{ t.Fatalf(\u0026#34;Test Fibonacci failed: expect %d, got %d\u0026#34;, expect, got) } else { fmt.Printf(\u0026#34;ok, Fibonacci(%d) = %d\\n\u0026#34;, n, got) } } } 在代码所在目录下，运行测试用例（所有）\ngo test -v . 输出：\n=== RUN TestFibonacci ok, Fibonacci(5) = 5 ok, Fibonacci(6) = 8 ok, Fibonacci(7) = 13 ok, Fibonacci(8) = 21 ok, Fibonacci(1) = 1 ok, Fibonacci(2) = 1 ok, Fibonacci(3) = 2 ok, Fibonacci(4) = 3 --- PASS: TestFibonacci (0.00s) PASS ok let_go/test 1.434s 单元测试覆盖率 通过一个 flag --coverprofile 来获得一个单元测试覆盖率文件：\ngo test -v --coverprofile=test.cover . 输出最后的内容是：\nPASS coverage: 100.0% of statements ok let_go/test 18.222s coverage: 100.0% of statements 可以看到，输出了覆盖率数据，我们的测试用例的覆盖率是 100%。\n当前路径下，生成了一个 test.cover文件，我们可以使用 go tool 从其中得到一个html的覆盖率测试报告：\ngo tool cover -html=test.cover -o=test-cover.html 打开生成的 test-cover.html文件，我们就可以看到对应的覆盖率情况。\n基准测试 基准测试用于评估代码的性能。\n测试文件的命名跟单元测试是一样的，都是 _test.go结尾，不同的是基准测试的函数名是 Benchmark开头，后接被测试函数名，如 BenchmarkFibonacci，同时接收一个 *testing.B指针参数。\nmain_test.go\nfunc BenchmarkFibonacci(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { Fibonacci(10) } } b.N是框架提供的，表示循环运行次数。\n运行测试用例，同样是使用 go test，不过要加上 -bench参数，后接 . 表示所有，也可以接具体的函数名称。\ngo test -bench=. . # go test -bench=Fibonacci . 输出：\ngoos: windows goarch: amd64 pkg: let_go/test cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz BenchmarkFibonacci-8 3076749 354.4 ns/op PASS ok let_go/test 2.838s BenchmarkFibonacci-8：这里的 -8 表示运行基准测试对应的 GOMAXPROCS 的值。\n3076749：表示一共for循环了 3076749次\n354.4 ns/op：表示每次循环耗时 354.4 ns\n基准测试默认是 1秒，因此上述结果是1秒内运行了3076749次，每次耗时354.4 ns。我们同样可以指定运行时长，使用 -benchtime 参数：\ngo test -bench=. -benchtime=3s . 计时方法 基准测试之前，我们可能需要准备数据，我们的基准测试应该把这部分时间排除在外，这是，我们可以使用 ResetTimer方法：\nfunc BenchmarkFibonacci(b *testing.B) { n := 10\t// 前期准备 b.ResetTimer() for i := 0; i \u0026lt; b.N; i++ { Fibonacci(n) } } 此外，还有 StartTimer和 StopTimer，可以灵活控制计时时间。\n内存统计 通过 ReportAllocs 方法开启内存统计：\nfunc BenchmarkFibonacci(b *testing.B) { n := 10 b.ReportAllocs()\t// 报告内存统计 b.ResetTimer()\t// 重置计时器 for i := 0; i \u0026lt; b.N; i++ { Fibonacci(n) } } 输出：\nBenchmarkFibonacci-8 3320989 374.9 ns/op 0 B/op 0 allocs/op PASS ok let_go/test 21.393s 发现多了 0 B/op 0 allocs/op 这段输出，前者表示每次分配了多少字节的内存，后者表示每次操作分配内存的次数。\n除了 b.ReportAllocs外，还可以在命令行中使用 -benchmem参数，可以达到同样的效果。\n并发基准测试 主要用以测试 在多个goroutine 下的代码性能，使用 runParallel 方法，传入一个函数作为参数：\nfunc BenchmarkFibonacci(b *testing.B) { n := 10 b.RunParallel(func(pb *testing.PB){ for pb.Next() { Fibonacci(n) } }) } 运行：\ngo test -bench=. . 输出：\nBenchmarkFibonacci-8 13952709 114.2 ns/op 可以看到，并行测试的情况下，函数被运行了 13952709 次。\n优化 可以看到上述基准测试的内存统计中，并没有发生内存申请，也就是说，内存并不是影响函数性能的原因。\n下面我们使用缓存来优化 Fibonacci函数\nmain.go\n// 斐波那契数列 func Fibonacci(n int) int { if n \u0026lt;= 0 { return 0 } else if n == 1 { return 1 } v, ok := cache[n] if !ok { v = Fibonacci(n-1) + Fibonacci(n-2) cache[n] = v } return v } 再次运行基准测试，输出：\nBenchmarkFibonacci-8 261475790 4.585 ns/op 可以看到，每次循环的时间从 114.2 ns -\u0026gt; 4.585 ns，性能提高了约25倍。\n","permalink":"https://zihuanling.github.io/posts/go-16-test-1/","summary":"\u003ch1 id=\"单元测试\"\u003e单元测试\u003c/h1\u003e\n\u003cp\u003e单元测试就是对单元进行测试（听起来是一句废话），单元可以是一个函数、一个模块等，我们最小的单元是一个函数。\u003c/p\u003e\n\u003cp\u003e以\u003cstrong\u003e斐波那契数列\u003c/strong\u003e为例，实现一个测试用例\u003c/p\u003e\n\u003cp\u003etest/main.go\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 斐波那契数列\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFibonacci\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFibonacci\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFibonacci\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e我们在 main.go 里面写了一个 \u003cstrong\u003eFibonacci\u003c/strong\u003e函数，用于计算对应的斐波那契值。\u003c/p\u003e\n\u003cp\u003e我们接下来写一个测试用例，测试的文件名应该是以 \u003ccode\u003e_test.go\u003c/code\u003e 结尾的，前面的名称最好是需要测试的文件名称，比如要测试 \u003ccode\u003emain.go\u003c/code\u003e，则测试文件命名为 \u003ccode\u003emain_test.go\u003c/code\u003e，而在 测试文件里面，需要一个以 \u003ccode\u003eTest\u003c/code\u003e开头的函数，后面接需要测试的函数名称，如 \u003ccode\u003eTestFibonacci\u003c/code\u003e，这个函数接受一个 \u003ccode\u003e*Testing.T\u003c/code\u003e指针，且不返回任何值\u003c/p\u003e\n\u003cp\u003etest/main_test.go\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTestFibonacci\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003et\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etesting\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eT\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e]\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e7\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e13\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e21\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eexpect\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003egot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFibonacci\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eexpect\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egot\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003et\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFatalf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Test Fibonacci failed: expect %d, got %d\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eexpect\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003egot\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ok, Fibonacci(%d) = %d\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003egot\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在代码所在目录下，运行测试用例（所有）\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego test -v .\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e","title":"16.go: 单元测试和基准测试"},{"content":"数组 数组由两部分组成：数组的大小和数组内部的元素类型。\n// 伪代码表示 array { len item type } 看以下两个数组的定义：\na1 := [1]string{\u0026#34;mike\u0026#34;} a2 := [2]string{\u0026#34;mike\u0026#34;} 上述定义的两个变量，a1的类型为 [1]string，a2的类型为 [2]string，所以说，数组的大小也属于数组类型的一部分。\n数组的两个限制 **一旦一个数组被声明，它的大小和内部的类型就不能改变，**我们将不能随意向其中追加任意多的元素。\n另外，当我们使用数组存储大量数据，然后将数组作为函数的参数进行传值时，由于函数之间是值传递的，因此，数组的拷贝将会耗费巨大的内存。\nslice 切片 我们可以将切片理解为动态的数组。\n切片是对数组的封装，它的底层是一个数组存储了所有的元素，但是它可以动态地添加元素，容量不足时可以自动扩容。\n动态扩容 使用内置的append方法，向切片中追加元素，返回一个新的切片。\n同时，当容量不足的时候，append会自动对切片进行扩容。\nfunc main(){ ss := []string{\u0026#34;mike\u0026#34;} // 定义切片ss fmt.Printf(\u0026#34;slice before append: %s, length=%d, cap=%d\\n\u0026#34;, ss, len(ss), cap(ss)) ss = append(ss, \u0026#34;lucy\u0026#34;, \u0026#34;john\u0026#34;) // append 追加元素 fmt.Printf(\u0026#34;slice after append: %s, length=%d, cap=%d\\n\u0026#34;, ss, len(ss), cap(ss)) } 输出：\nslice before append: [mike], length=1, cap=1 slice after append: [mike lucy john], length=3, cap=3 数据结构 切片在go语言中是一个数据结构：\ntype SliceHeader struct { Data uintptr\t// 指向存储切片元素的数组 Len int\t// 切片长度 Cap int\t// 切片容量 } 示例证明：\nfunc main(){ arr := [4]string{\u0026#34;mike\u0026#34;, \u0026#34;lucy\u0026#34;, \u0026#34;john\u0026#34;, \u0026#34;trump\u0026#34;} s1 := arr[0:1] s2 := arr[:] s3 := arr[1:] s4 := s2[1:] s5 := append(s1, \u0026#34;amy\u0026#34;) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s1)).Data) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s2)).Data) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s3)).Data) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s4)).Data) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s5)).Data) fmt.Println( unsafe.Sizeof(arr), unsafe.Sizeof(s1), unsafe.Sizeof(s2), unsafe.Sizeof(s3), unsafe.Sizeof(s4), unsafe.Sizeof(s5), ) } 输出：\n824634801872 824634801872 824634801888 824634801888 824634801872 64 24 24 24 24 24 上述代码中，我们定义了一个数组arr，然后用不同的方法创建了5个数组切片 s1~s5，我们用 unsafe.Pointer 获取到 SliceHeader切片里面指向的Data，可以发现：\ns1 和 s2 Data指向的内存是一样的，因为他们都是从 arr切片下来的，且都是从头部开始切 s2 和 s3 Data指向的内存是不一样的，因为 s3是从arr第2个元素开始切的，它的 Data的初始位置是arr的第二个元素 s4 的Data指向，和s3的是相同的，虽然s4是从s2第1个元素切出来的，但是s2的Data开始地址跟arr一致，因此效果等同于从arr的第二个元素开始切，因此Data指向跟s3一致 s5 是使用append(s1, \u0026quot;amy\u0026quot;)得到，但是返回了新的切片之后，其Data指向的地址仍然和s1是相同的 最后，打印出arr和各个slice的Size，可以看到，数组占用的Size是较大的，而无论slice里面的数据有多少，其都只占用 24字节 大小的内存，符合其 SliceHeader 的定义 高效的原因 一方面，从集合类型的方面考虑，数组、切片和map都是集合类型，他们都可以存放元素，但是数组和切片是连续的内存操作，通过索引就可以快速地找到元素存储的地址，因此取值和赋值要更加高效。\n另一方面，使用切片之所以高效，是因为我们在函数中进行参数传递的时候，传递的只是一份24个字节的SliceHeader数据，实际访问数据时，使用的是同一个底层数组，因此避免了耗费大量内存去拷贝数据，提高了效率。\n需要注意的 使用切片作为函数传值虽然高效，但由于使用的底层数据是相同的，修改切片里面的数据时，其他切片的数据也可能会被修改，一个例子：\n// 打印切片信息 func info(s []string) { head := (*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s)) addr := (*string)(unsafe.Pointer(head.Data)) fmt.Printf(\u0026#34;data: %v, address: %v, len: %d, cap: %d \\n\u0026#34;, head.Data, addr, len(s), cap(s)) for i, _ := range s { fmt.Printf(\u0026#34;%p %v\\n\u0026#34;, \u0026amp;s[i], s[i]) } fmt.Println(\u0026#34;======\u0026#34;) } func main(){ arr := [2]string{\u0026#34;mike\u0026#34;, \u0026#34;lucy\u0026#34;} s1 := arr[0:1] s2 := append(s1, \u0026#34;amy\u0026#34;) // 往切片s1追加元素，返回新切片给s2 s1[0] = \u0026#34;Lisa\u0026#34;\t// 修改切片s1第一个元素为 Lisa info(s1) info(s2) info(arr[:]) } 输出：\ndata: 824634229888, address: 0xc00007c480, len: 1, cap: 3 0xc00007c480 Lisa ====== data: 824634229888, address: 0xc00007c480, len: 2, cap: 3 0xc00007c480 Lisa 0xc00007c490 amy ====== data: 824634229888, address: 0xc00007c480, len: 3, cap: 3 0xc00007c480 Lisa 0xc00007c490 amy 0xc00007c4a0 trump ====== 从上面的输出可以看到，我们从原始数组arr切出了一个切片s1，s1里面只有一个元素，但是，在打印信息的时候，显示它的容量 cap=3，等于原始数组的长度。\n我们往s1里面追加了一个元素 amy，返回一个新的切片s2，然后，将s1的第一个元素改成Lisa，随后的输出结果可以看到，原始数组arr、切片s1、s2的第一个元素都变成了Lisa，而我们往s1追加元素的时候，修改的是原数组的第二个元素， lucy -\u0026gt; amy。且三者的各个数据的地址都是一样的，说明切片和原始数组共用一个底层数组。\n但是，有意思的是，我们使用append往切片中追加元素，当追加的元素超过了当前的slice的容量时，返回的切片指向的就是底层数组就是一块新的内存了。\n我们将上述代码中的 append 语句改成：\ns2 := append(s1, \u0026#34;amy\u0026#34;, \u0026#34;panda\u0026#34;, \u0026#34;python\u0026#34;, \u0026#34;java\u0026#34;) // 往切片s1追加元素，返回新切片给s2 输出：\ndata: 824634229888, address: 0xc00007c480, len: 1, cap: 3 0xc00007c480 Lisa ====== data: 824634048608, address: 0xc000050060, len: 5, cap: 6 0xc000050060 mike 0xc000050070 amy 0xc000050080 panda 0xc000050090 python 0xc0000500a0 java ====== data: 824634229888, address: 0xc00007c480, len: 3, cap: 3 0xc00007c480 Lisa 0xc00007c490 lucy 0xc00007c4a0 trump ====== 可以看到有4个变化：\ns2的容量 cap 跟其他两个不同了，自动扩容到了 5 s1和arr的对应元素地址是一样的，说明两者用的底层相同，s2的元素地址跟其他两个不同，说明新申请了一块内存空间来存储s2切片内容 修改s1的第一个元素，没有影响到s2的数据 往s1中追加多个元素，没有影响到原始数组arr的数据 **所以，多个切片使用到同一个底层数组的情况下，应该考虑到数据之间的冲突问题。**go提供一个 copy 内部函数，让我们可以实现切片拷贝，修改拷贝数据，不会影响到原始数据。\n[]byte和string 转换 一般情况下，我们字符串string和[]byte可以这样转换：\nvar s string = \u0026#34;Hello world.\u0026#34; // string -\u0026gt; []byte b := []byte(s) // []byte -\u0026gt; string bs := string(b) slice类型有SliceHeader， 同样的，string类型也有 StringHeader，它的结构体如下：\ntype StringHeader struct { Data uintptr Len int } 但是跟 SliceHeader 的区别是，StringHeader少了一个Cap字段用以存储容量。\n回到string 和 []byte 转换的例子，go语言是通过分配内存然后在复制内容的方式，去实现[]byte和string的互相转换的。我们用 SliceHeader、StringHeader看下转换后的 Data指向地址\nfunc main(){ var s string = \u0026#34;Hello world.\u0026#34; // string -\u0026gt; []byte b := []byte(s) // []byte -\u0026gt; string bs := string(b) fmt.Printf(\u0026#34;addr of s: %v\\n\u0026#34;, (*reflect.StringHeader)(unsafe.Pointer(\u0026amp;s)).Data) fmt.Printf(\u0026#34;addr of b: %v\\n\u0026#34;, (*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;b)).Data) fmt.Printf(\u0026#34;addr of bs: %v\\n\u0026#34;, (*reflect.StringHeader)(unsafe.Pointer(\u0026amp;bs)).Data) } 输出：\naddr of s: 10646786 addr of b: 824634580712 addr of bs: 824634580680 由此可见，直接的 string \u0026lt;-\u0026gt; []byte互转是通过拷贝原始值来实现的。\n转换优化 StringHeader 比 SliceHeader 少了一个Cap字段，我们可以通过 unsafe.Pointer 将 SliceHeader 直接转换成 StringHeader，但是返回来却是行不通的，我们还需要手动补充上一个 Cap字段。\n为了在string 和 []byte 互转的时候节省内存，实现零值拷贝，我们在转换的时候，使用对应的Header结构体 + unsafe.Pointer进行转换。\n将上述例子改变如下：\nfunc main(){ var s string = \u0026#34;Hello world.\u0026#34; // string -\u0026gt; []byte sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;s)) sliceHeader.Cap = sliceHeader.Len b := *(*[]byte)(unsafe.Pointer(sliceHeader)) // []byte -\u0026gt; string stringHeader := (*reflect.StringHeader)(unsafe.Pointer(\u0026amp;b)) bs := *(*string)(unsafe.Pointer(stringHeader)) fmt.Printf(\u0026#34;%s, addr of s: %v\\n\u0026#34;, s, (*reflect.StringHeader)(unsafe.Pointer(\u0026amp;s)).Data) fmt.Printf(\u0026#34;%s, addr of b: %v\\n\u0026#34;, b, (*reflect.SliceHeader)(unsafe.Pointer(\u0026amp;b)).Data) fmt.Printf(\u0026#34;%s, addr of bs: %v\\n\u0026#34;, bs, (*reflect.StringHeader)(unsafe.Pointer(\u0026amp;bs)).Data) } 输出：\nHello world., addr of s: 9860354 Hello world., addr of b: 9860354 Hello world., addr of bs: 9860354 可以看到，修改之后，对应的Header中Data指向的是同一个地址。\n需要注意的是，通过 unsafe.Pointer将stirng转换为 []byte的时候，不可以通过索引对 []byte修改，否则会导致程序崩溃，因为go语言中，string内存是只读的。\ngo语言标准库中， strings.Builder 也使用了零值拷贝提升新能：\n// String returns the accumulated string. func (b *Builder) String() string { return *(*string)(unsafe.Pointer(\u0026amp;b.buf)) } ","permalink":"https://zihuanling.github.io/posts/go-15-depth-slice-header/","summary":"\u003ch2 id=\"数组\"\u003e数组\u003c/h2\u003e\n\u003cp\u003e数组由两部分组成：\u003cstrong\u003e数组的大小和数组内部的元素类型\u003c/strong\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 伪代码表示\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003earray\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003elen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eitem\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e看以下两个数组的定义：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ea1\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e]\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ea2\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e]\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述定义的两个变量，a1的类型为 \u003ccode\u003e[1]string\u003c/code\u003e，a2的类型为 \u003ccode\u003e[2]string\u003c/code\u003e，所以说，数组的大小也属于数组类型的一部分。\u003c/p\u003e\n\u003ch3 id=\"数组的两个限制\"\u003e数组的两个限制\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e**一旦一个数组被声明，它的大小和内部的类型就不能改变，**我们将不能随意向其中追加任意多的元素。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e另外，当我们使用数组存储大量数据，然后将数组作为函数的参数进行传值时，由于函数之间是\u003cstrong\u003e值传递\u003c/strong\u003e的，因此，数组的拷贝将会\u003cstrong\u003e耗费巨大的内存\u003c/strong\u003e。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"slice-切片\"\u003eslice 切片\u003c/h2\u003e\n\u003cp\u003e我们可以将切片理解为\u003cstrong\u003e动态的数组\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e切片是对数组的封装，它的底层是一个数组存储了所有的元素，但是它可以动态地添加元素，容量不足时可以自动扩容。\u003c/strong\u003e\u003c/p\u003e\n\u003ch3 id=\"动态扩容\"\u003e动态扩容\u003c/h3\u003e\n\u003cp\u003e使用内置的\u003ccode\u003eappend\u003c/code\u003e方法，向切片中追加元素，\u003cstrong\u003e返回一个新的切片\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e同时，当容量不足的时候，\u003ccode\u003eappend\u003c/code\u003e\u003cstrong\u003e会自动对切片进行扩容。\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e} \u003cspan style=\"color:#75715e\"\u003e// 定义切片ss\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;slice before append: %s, length=%d, cap=%d\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e, len(\u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e), cap(\u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e))    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e = append(\u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lucy\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;john\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#75715e\"\u003e// append 追加元素\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;slice after append: %s, length=%d, cap=%d\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e, len(\u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e), cap(\u003cspan style=\"color:#a6e22e\"\u003ess\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eslice before append: [mike], length=1, cap=1\nslice after append: [mike lucy john], length=3, cap=3\n\u003c/code\u003e\u003c/pre\u003e\u003ch3 id=\"数据结构\"\u003e数据结构\u003c/h3\u003e\n\u003cp\u003e切片在go语言中是一个数据结构：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSliceHeader\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eData\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euintptr\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 指向存储切片元素的数组\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eLen\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\t\t\t\u003cspan style=\"color:#75715e\"\u003e// 切片长度\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eCap\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\t\t\t\u003cspan style=\"color:#75715e\"\u003e// 切片容量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e示例证明：\u003c/p\u003e","title":"15.go深入：SliceHeader，高效的slice"},{"content":"unsafe是不安全的，可以绕过go的安全检查机制，直接对内存进行读写，非必要不使用。\n指针类型转换 go是一门强类型的静态语言，意味着一旦定义了，类型就不能改变，且类型检查在运行前已经完成。\n出于安全考虑，go不允许两个指针类型进行转换，比如 *int 不能转换成 *float。\ni := 10 ip := \u0026amp;i var fp *float64 = (*float64)(ip)\t// 尝试将 *int 转换成 *float64 在编译的时候，会报错： cannot convert ip (type * int) to type * float64，提示不可转换\nunsafe.Pointer unsafe.Pointer表示任意类型的指针，上述例子中，我们使用 unsafe.Pointer进行中转，实现指针类型转换\ni := 10 ip := \u0026amp;i var fp *float64 = (*float64)(unsafe.Pointer(ip))\t// 尝试将 *int 转换成 *float64 *fp = *fp * 3 fmt.Println(i,*fp, ip, fp) 输出：\n30 1.5e-322 0xc0000aa058 0xc0000aa058 可以看到，通过转化后的指针修改指向内存的值，原始值i也变成了30，ip和fp指向的是同一块内存地址。需要注意的是，试图通过 *fp 打印出原来内存的数据时，却出现了一个异常值，说明指针可以通过 unsafe.Pointer 进行转化，但转化后的指针不一定可以访问到原始内存的值。\nunsafe.Pointer源码定义：\n// ArbitraryType is here for the purposes of documentation // only and is not actually part of the unsafe package. // It represents the type of an arbitrary Go expression. type ArbitraryType int type Pointer *ArbitraryType ArbitraryType可以表示任意类型，在源码中只是文档需要。unsafe.Pointer是 *ArbitraryType，表示通用型指针，可以表示任何内存地址。\nuintptr 指针类型 因为 unsafe.Pointer 不能进行运行，如 + 运行操作，因此有 uintptr，它足够大，可以表示任何指针，也可以对指针进行偏移计算，访问特定的内存，实现真正的内存级操作。\nuintptr定义：\n// uintptr is an integer type that is large enough // to hold the bit pattern of any pointer. type uintptr uintptr 使用 uintptr修改结构体内部字段例子：\nfunc main() { p := new(person) // p.Name是第一个字段，因此不需要偏移 pName := (*string)(unsafe.Pointer(p)) *pName = \u0026#34;Mike\u0026#34; // p.Age不是第一个字段，需要使用 uintptr 和 unsafe.Offsetof 进行偏移计算 u := uintptr(unsafe.Pointer(p)) + unsafe.Offsetof(p.Age) // 计算过偏移的 uintptr 需要通过 unsafe.Pointer 再次转化成 *int（或者*uint） pAge := (*int)(unsafe.Pointer(u)) fmt.Println(\u0026#34;address of uintptr of age is : \u0026#34;, u, pAge, \u0026amp;p.Age) *pAge = 20 fmt.Println(*p) } 输出：\naddress of uintptr of age is : 824633737352 0xc000004088 0xc000004088 {Mike 20} 可以看到，计算偏移后， PAge 和 p.Age 指向的地址是一样的，对应的值也修改成功了。\n上述操作步骤：\n先使用 new 函数声明一个 *person 类型的指针变量 p。 然后把 *person 类型的指针变量 p 通过 unsafe.Pointer，转换为 *string 类型的指针变量 pName。 因为 person 这个结构体的第一个字段就是 string 类型的 Name，所以 pName 这个指针就指向 Name 字段（偏移为 0），对 pName 进行修改其实就是修改字段 Name 的值。 因为 Age 字段不是 person 的第一个字段，要修改它必须要进行指针偏移运算。所以需要先把指针变量 p 通过 unsafe.Pointer 转换为 uintptr，这样才能进行地址运算。既然要进行指针偏移，那么要偏移多少呢？这个偏移量可以通过函数 unsafe.Offsetof 计算出来，该函数返回的是一个 uintptr 类型的偏移量，有了这个偏移量就可以通过 + 号运算符获得正确的 Age 字段的内存地址了，也就是通过 unsafe.Pointer 转换后的 *int 类型的指针变量 pAge。 然后需要注意的是，如果要进行指针运算，要先通过 unsafe.Pointer 转换为 uintptr 类型的指针。指针运算完毕后，还要通过 unsafe.Pointer 转换为真实的指针类型（比如示例中的 *int 类型），这样可以对这块内存进行赋值或取值操作。 有了指向字段 Age 的指针变量 pAge，就可以对其进行赋值操作，修改字段 Age 的值了。 上述代码效果等价于：\nfunc main() { p := new(person) p.Name = \u0026#34;Mike\u0026#34; p.Age = 20 } 指针转换规则 flowchart LR *T \u0026lt;--转换--\u0026gt; unsafe.Pointer \u0026lt;--转换--\u0026gt; uintptr unsafe.Pointer主要用于指针类型的转换，uintptr主要用于偏移量定位不同的内存位置。\n总结 unsafe包里的功能虽然不安全，但很实用，如指针运算、类型转换等，有助于我们提高性能。\n","permalink":"https://zihuanling.github.io/posts/go-14-depth-unsafe/","summary":"\u003cp\u003e\u003ccode\u003eunsafe\u003c/code\u003e是不安全的，可以绕过go的安全检查机制，直接对内存进行读写，非必要不使用。\u003c/p\u003e\n\u003ch2 id=\"指针类型转换\"\u003e指针类型转换\u003c/h2\u003e\n\u003cp\u003ego是一门\u003cstrong\u003e强类型的静态语言\u003c/strong\u003e，意味着一旦定义了，类型就不能改变，且类型检查在运行前已经完成。\u003c/p\u003e\n\u003cp\u003e出于安全考虑，go不允许两个指针类型进行转换，比如 \u003ccode\u003e*int\u003c/code\u003e 不能转换成 \u003ccode\u003e*float\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eip\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efloat64\u003c/span\u003e = (\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efloat64\u003c/span\u003e)(\u003cspan style=\"color:#a6e22e\"\u003eip\u003c/span\u003e)\t\u003cspan style=\"color:#75715e\"\u003e// 尝试将 *int 转换成 *float64\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在编译的时候，会报错： \u003ccode\u003ecannot convert ip (type * int) to type * float64\u003c/code\u003e，提示不可转换\u003c/p\u003e\n\u003ch3 id=\"unsafepointer\"\u003eunsafe.Pointer\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eunsafe.Pointer\u003c/code\u003e表示\u003cstrong\u003e任意类型的指针\u003c/strong\u003e，上述例子中，我们使用 \u003ccode\u003eunsafe.Pointer\u003c/code\u003e进行中转，实现指针类型转换\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eip\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efloat64\u003c/span\u003e = (\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efloat64\u003c/span\u003e)(\u003cspan style=\"color:#a6e22e\"\u003eunsafe\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePointer\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eip\u003c/span\u003e))\t\u003cspan style=\"color:#75715e\"\u003e// 尝试将 *int 转换成 *float64\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003efp\u003c/span\u003e = \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003efp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003efp\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eip\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003efp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e30 1.5e-322 0xc0000aa058 0xc0000aa058\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e可以看到，通过转化后的指针修改指向内存的值，原始值\u003cstrong\u003ei\u003c/strong\u003e也变成了30，ip和fp指向的是同一块内存地址。需要注意的是，试图通过 \u003ccode\u003e*fp\u003c/code\u003e 打印出原来内存的数据时，却出现了一个\u003cstrong\u003e异常值\u003c/strong\u003e，说明\u003cstrong\u003e指针可以通过 \u003ccode\u003eunsafe.Pointer\u003c/code\u003e 进行转化，但转化后的指针不一定可以访问到原始内存的值\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eunsafe.Pointer\u003c/code\u003e源码定义：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// ArbitraryType is here for the purposes of documentation\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// only and is not actually part of the unsafe package. \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// It represents the type of an arbitrary Go expression.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eArbitraryType\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePointer\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eArbitraryType\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eArbitraryType\u003c/code\u003e可以表示任意类型，在源码中只是文档需要。\u003ccode\u003eunsafe.Pointer\u003c/code\u003e是 \u003ccode\u003e*ArbitraryType\u003c/code\u003e，表示\u003cstrong\u003e通用型指针\u003c/strong\u003e，可以表示任何内存地址。\u003c/p\u003e","title":"14.go深入：非类型安全unsafe"},{"content":"啥是反射 go语言中，反射为我们提供了一种可以在运行时操作任意类型对象的能力，比如，查看一个接口变量的具体类型、看一个结构体有多少字段、修改某个字段的值等。\n比如 fmt.Println：\nfunc Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) } 函数定义中有一个可变参数 a ...interface{}，我们在调用的时候，可以传1个到多个参数进去。\nreflect.Value 和 reflect.Type go语言的反射定义中，任何接口都有两个部分组成：接口的具体类型，以及具体类型对应的值。如 var i in = 3，由于 interface{}可以表示任何类型，因此i可以转化为 interface{}，将其当做一个接口，此时它在go反射中就表示成 \u0026lt;Value, Type\u0026gt;，其中Value为3，Type为int。\ngo反射中，标准库为我们提供了两种类型 reflect.Value和 reflect.Type分别表示变量的值和类型，并且可以用函数 reflect.ValueOf和 reflect.TypeOf分别获取任意对象 Value和Type。\nfunc main(){ var i int = 3 iv := reflect.ValueOf(i) it := reflect.TypeOf(i) fmt.Println(iv, it) } reflect.Value 结构体定义 reflect.Value 可以通过 reflect.ValueOf获得，其结构体定义如下\ntype Value struct { typ *rtype ptr unsafe.Pointer flag } 其里面的变量都是私有的，意味着我们只能使用它的方法，它的常用方法有：\n//针对具体类型的系列方法 //以下是用于获取对应的值 Bool Bytes Complex Float Int String Uint CanSet //是否可以修改对应的值 // 以下是用于修改对应的值 Set SetBool SetBytes SetComplex SetFloat SetInt SetString Elem //获取指针指向的值，一般用于修改对应的值 //以下Field系列方法用于获取struct类型中的字段 Field FieldByIndex FieldByName FieldByNameFunc Interface //获取对应的原始类型 IsNil //值是否为nil IsZero //值是否是零值 Kind //获取对应的类型类别，比如Array、Slice、Map等 //获取对应的方法 Method MethodByName NumField //获取struct类型中字段的数量 NumMethod//类型上方法集的数量 Type//获取对应的reflect.Type 可以总结为3类：\n用户获取和修改对应的值\n和struct类型的字段有关\n和类型上的方法集有关，用于获取对应的方法\n获取原始类型 我们使用 reflect.ValueOf 将任意类型的对象转为 reflect.Value，也可以通过 reflect.Interface进行逆转换\nfunc main(){ var i int = 3 // int to reflect.Value iv := reflect.ValueOf(i) // reflect.Value to int i1 := iv.Interface().(int) fmt.Println(iv, i1) } 修改对应的值 已经定义的变量可以通过反射，在运行时修改，如下所示：\nfunc main() { i := 3 ipv := reflect.ValueOf(\u0026amp;i) ipv.Elem().SetInt(4) fmt.Println(i) } 需要注意的是， reflect.ValueOf 返回的是一个值的拷贝，因此，我们想要修改原始值，就需要传入指针变量。\n因为传入的是指针变量，所以需要调用 Elem()方法，找到这个指针指向的值，然后才能调用 Set... 方法进行修改。\n所以，使用运行时修改变量值的关键点在于：\n传递指针（可寻址），通过 Elem方法获取指向的值，才可以保证值可以被修改。reflect.Value为我们提供了 CanSet方法，判断是否可修改该变量。\n修改struct结构体中的值，也是同理：\n传递strcut结构体指针，获取 reflect.ValueOf 通过 Elem方法获取指针指向的值 通过 Field方法获取需要修改的字段 通过 Set系列方法，修改成对应的值 type person struct { Name string Age uint } func main(){ p := person{Name: \u0026#34;mike\u0026#34;, Age: 10} fmt.Println(\u0026#34;Original person is : \u0026#34;, p) ppv := reflect.ValueOf(\u0026amp;p) ppv.Elem().FieldByName(\u0026#34;Age\u0026#34;).SetUint(20)\t// set age to 20 // ppv.Elem().Field(1).SetUint(20)\t// 也可 fmt.Println(\u0026#34;person changed: \u0026#34;, p) } 输出：\nOriginal person is : {mike 10} person changed: {mike 20} 小总结，通过反射修改一个值的规则：\n可被寻址，即需要向 reflect.ValueOf 传递一个指针作为参数 如果想修改struct结构体的字段值，则对应的字段必须是可导出的，即该字段的首字母是大写的 需要使用 Elem 方法获得指针指向的值，才能通过 Set 系列方法进行修改 获取对应的底层类型 可以使用 Kind 方法获取对应的底层类型：\nfunc main(){ p := person{\u0026#34;Mike\u0026#34;, 20} ppv := reflect.ValueOf(\u0026amp;p) pv := reflect.ValueOf(p) fmt.Println(ppv.Kind(), pv.Kind())\t// ptr, struct } Kind返回的值可以为：\ntype Kind uint const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer ) reflect.Type 我们使用 reflect.TypeOf 获取一个 reflect.Type。\nreflect.Value 用于于值有关的操作中，而如果是和变量类型本身有关的操作，则最好使用reflectType，如：获取结构体对应的字段名称或方法。\n接口定义 reflect.Value是一个结构体，而 reflect.Type是一个接口，大部分常用方法同 reflect.Value是相同的，如下：\ntype Type interface { Implements(u Type) bool AssignableTo(u Type) bool ConvertibleTo(u Type) bool Comparable() bool // 同 reflect.Value功能相同 Kind() Kind Method(int) Method MethodByName(string) (Method, bool) NumMethod() int Elem() Type Field(i int) StructField FieldByIndex(index []int) StructField FieldByName(name string) (StructField, bool) FieldByNameFunc(match func(string) bool) (StructField, bool) NumField() int } 特有的方法：\nImplements：用于判断是否实现了该接口 AssignableTo：用于判断是否可以赋值给类型u，即使用=进行赋值 ConvertibleTo：用于判断是否可以转换为类型u Comparable：用于判断该类型是否可比较，使用关系运算符进行比较 遍历结构体的字段和方法 // 实现String方法 func (p person) String() string { return fmt.Sprintf(\u0026#34;This person name is %s, age = %d\u0026#34;, p.Name, p.Age) } func main(){ p := person{\u0026#34;Mike\u0026#34;, 20} pt := reflect.TypeOf(p) // 遍历字段 for i := 0; i \u0026lt; pt.NumField(); i++ { fmt.Printf(\u0026#34;Field %d: %s\\n\u0026#34;, i+1, pt.Field(i).Name) } // 遍历方法 for i := 0; i \u0026lt; pt.NumMethod(); i++ { fmt.Printf(\u0026#34;Method %d: %s\\n\u0026#34;, i+1, pt.Method(i).Name) } } 输出：\nField 1: Name Field 2: Age Method 1: String 是否实现某接口 下面检查person是否实现 fmt.Stringer 和 io.Writer 接口：\nfunc main(){ p := person{\u0026#34;Mike\u0026#34;, 20} pt := reflect.TypeOf(p) stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() writerType := reflect.TypeOf((*io.Writer)(nil)).Elem() fmt.Println(\u0026#34;person implement stringer: \u0026#34;, pt.Implements(stringerType)) fmt.Println(\u0026#34;person implement writer: \u0026#34;, pt.Implements(writerType)) } 输出：\nperson implement stringer: true person implement writer: false 由于 fmt.Stringer 是一个接口，而传入 reflect.TypeOf 里面的必须是一个值，因此需要转化一下，传入一个空接口指针 (*fmt.Stringer)(nil)，然后取其对应的 Elem 进行判断。\n这样的做法很少，我们一般使用断言进行判断，而不是反射，以下写法更简单：\nfunc main(){ p := person{\u0026#34;Mike\u0026#34;, 20} pt := reflect.TypeOf(p) _, ok := pt.(fmt.Stringer) fmt.Println(\u0026#34;Stringer implemented by person: \u0026#34;, ok) } 通过反射调用方法 type person struct { Name string `json:\u0026#34;name\u0026#34;` Age uint\t`json:\u0026#34;age\u0026#34;` } func (p person) Greet(who string) { fmt.Printf(\u0026#34;Hello! %s is greeting to %s.\\n\u0026#34;, who, p.Name) } func main() { p := person{Name: \u0026#34;Mike\u0026#34;, Age: 10} pv := reflect.ValueOf(p) method := pv.MethodByName(\u0026#34;Greet\u0026#34;) // call method args := []reflect.Value{ reflect.ValueOf(\u0026#34;Amy\u0026#34;), } // 相当于 p.Greet(\u0026#34;Amy\u0026#34;) method.Call(args) /* // 同样可以这样写 pt := reflect.TypeOf(p) method, ok := pt.MethodByName(\u0026#34;Greet\u0026#34;) if !ok { fmt.Println(\u0026#34;Method [Greet] not exist\u0026#34;) } else { // call method args := []reflect.Value{ reflect.ValueOf(p), reflect.ValueOf(\u0026#34;Amy\u0026#34;), } // 相当于 Greet(p, \u0026#34;Amy\u0026#34;) method.Func.Call(args) } */ } 输出：\nHello! Amy is greeting to Mike. 字符串和结构体互换 字符串和结构体互转，最多是Json和Struct互相转换，这样的转换相当于python里面将字典序列化成为json字符串或者反序列化。\nJson和Struct互转 go语言提供了一个json包，可以让我们实现json字符串和struct结构体的互转：\nfunc main() { p := person{Name: \u0026#34;Mike\u0026#34;, Age: 10} // struct 转换成字符串 pJson, err := json.Marshal(p) if err == nil { fmt.Printf(\u0026#34;pJson string : %s\\n\u0026#34;, pJson) } // Json 字符串转化为struct结构体 jsonString := \u0026#34;{\\\u0026#34;Name\\\u0026#34;: \\\u0026#34;Jack\\\u0026#34;, \\\u0026#34;Age\\\u0026#34;: 20}\u0026#34; if err := json.Unmarshal([]byte(jsonString), \u0026amp;p); err == nil { fmt.Printf(\u0026#34;Unmarshaled struct: %s\\n\u0026#34;, p) } else { fmt.Printf(\u0026#34;Error unmarshal: %e\\n\u0026#34;, err) } } 输出：\npJson string : {\u0026#34;Name\u0026#34;:\u0026#34;Mike\u0026#34;,\u0026#34;Age\u0026#34;:10} Unmarshaled struct: This person name is Jack, age = 20 通过 json.Marshal 将struct转化成 字符串（返回的是 []byte） 通过 json.Unmarshal 将json字符串（[]byte）转化成 strcut结构体 需要注意的是，如果 person 的定义中，含有私有成员变量（小写开头），那么在json序列化和反序列化的过程中，将不会解析/赋值该字段。\nStruct Tag Struct Tag是struct结构体字段的标签，用其辅助完成一些额外的操作，如果 json和struct 互转，使用tag让json化的字段变成小写：\ntype person struct { Name string `json:\u0026#34;name\u0026#34;` Age uint\t`json:\u0026#34;age\u0026#34;` } func main() { p := person{Name: \u0026#34;Mike\u0026#34;, Age: 10} // struct 转换成字符串 pJson, err := json.Marshal(p) if err == nil { fmt.Printf(\u0026#34;pJson string : %s\\n\u0026#34;, pJson) } // Json 字符串转化为struct结构体 jsonString := \u0026#34;{\\\u0026#34;name\\\u0026#34;: \\\u0026#34;Jack\\\u0026#34;, \\\u0026#34;age\\\u0026#34;: 20}\u0026#34; if err := json.Unmarshal([]byte(jsonString), \u0026amp;p); err == nil { fmt.Printf(\u0026#34;Unmarshaled struct: %s\\n\u0026#34;, p) } } 输出：\npJson string : {\u0026#34;name\u0026#34;:\u0026#34;Mike\u0026#34;,\u0026#34;age\u0026#34;:10} Unmarshaled struct: This person name is Jack, age = 20 需要注意的是，json.Unmarshal传入的字符串，如果存在 \u0026quot;{\\\u0026quot;age\\\u0026quot;: 10, \\\u0026quot;Age\\\u0026quot;: 20}\u0026quot; 两个字段，那么在反序列化之后，得到的结构体对应的值是最后一个，即20。\n我们通过反射获取结构体的tag，通过 Field方法返回一个StructField，然后取Tag.Get 获取对应的tag\nfunc main() { p := person{Name: \u0026#34;Mike\u0026#34;, Age: 10} pt := reflect.TypeOf(p) for i := 0; i \u0026lt; pt.NumField(); i++ { f := pt.Field(i) fmt.Println(f.Tag.Get(\u0026#34;json\u0026#34;)) } } 同一个结构体可以定义多个tag：\ntype person struct { Name string `json:\u0026#34;name\u0026#34; bson:\u0026#34;b_name\u0026#34;` Age uint `json:\u0026#34;age\u0026#34; bson:\u0026#34;b_age\u0026#34;` } 实现Struct转Json func StructToJson(i interface{}) string { builder := strings.Builder{} builder.WriteString(\u0026#34;{\u0026#34;) iv := reflect.ValueOf(i) it := reflect.TypeOf(i) numFields := it.NumField() for i := 0; i \u0026lt; numFields; i++ { f := it.Field(i) jTag := f.Tag.Get(\u0026#34;json\u0026#34;) builder.WriteString(\u0026#34;\\\u0026#34;\u0026#34; + jTag + \u0026#34;\\\u0026#34;:\u0026#34;) builder.WriteString(fmt.Sprintf(\u0026#34;\\\u0026#34;%v\\\u0026#34;\u0026#34;, iv.Field(i))) if i \u0026lt; numFields - 1 { builder.WriteString(\u0026#34;,\u0026#34;) } } builder.WriteString(\u0026#34;}\u0026#34;) return fmt.Sprintf(\u0026#34;%s\u0026#34;, builder.String()) } func main() { p := person{Name: \u0026#34;Mike\u0026#34;, Age: 10} s := StructToJson(p) fmt.Println(s) } 输出：\n{\u0026#34;name\u0026#34;:Mike,\u0026#34;age\u0026#34;:10} 反射定律 反射是计算机语言中程序检视自身结构的一种方法，灵活、强大，可以绕过编译器的很多静态检查，过多使用会造成混乱。\n任何接口值 interface{}都可以反射出反射对象，即 reflect.Value和reflect.Type，通过函数 reflect.ValueOf和reflect.TypeOf获得 反射对象也可以还原为 interface{}变量，即定律1 的可逆性，通过 reflect.Value的 Interface方法获得 要修改反射的对象，该值必须可设置（传入指针） 总结 在反射中，获取变量的值、修改变量的值等，优先使用 reflect.Value；获取结构体内的字段、类型拥有的方法集等，优先使用 reflect.Type\n","permalink":"https://zihuanling.github.io/posts/go-13-depth-reflect/","summary":"\u003ch2 id=\"啥是反射\"\u003e啥是反射\u003c/h2\u003e\n\u003cp\u003ego语言中，反射为我们提供了一种可以在运行时操作任意类型对象的能力，比如，查看一个接口变量的具体类型、看一个结构体有多少字段、修改某个字段的值等。\u003c/p\u003e\n\u003cp\u003e比如 \u003ccode\u003efmt.Println\u003c/code\u003e：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003einterface\u003c/span\u003e{}) (\u003cspan style=\"color:#a6e22e\"\u003en\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eFprintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eStdout\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e函数定义中有一个可变参数 \u003ccode\u003ea ...interface{}\u003c/code\u003e，我们在调用的时候，可以传1个到多个参数进去。\u003c/p\u003e\n\u003ch2 id=\"reflectvalue-和-reflecttype\"\u003ereflect.Value 和 reflect.Type\u003c/h2\u003e\n\u003cp\u003ego语言的反射定义中，任何接口都有两个部分组成：\u003cstrong\u003e接口的具体类型，以及具体类型对应的值\u003c/strong\u003e。如 \u003ccode\u003evar i in = 3\u003c/code\u003e，由于 \u003ccode\u003einterface{}\u003c/code\u003e可以表示任何类型，因此\u003cstrong\u003ei\u003c/strong\u003e可以转化为 \u003ccode\u003einterface{}\u003c/code\u003e，将其当做一个接口，此时它在go反射中就表示成 \u003ccode\u003e\u0026lt;Value, Type\u0026gt;\u003c/code\u003e，其中Value为3，Type为int。\u003c/p\u003e\n\u003cp\u003ego反射中，标准库为我们提供了两种类型 \u003ccode\u003ereflect.Value\u003c/code\u003e和 \u003ccode\u003ereflect.Type\u003c/code\u003e分别表示变量的值和类型，并且可以用函数 \u003ccode\u003ereflect.ValueOf\u003c/code\u003e和 \u003ccode\u003ereflect.TypeOf\u003c/code\u003e分别获取\u003cstrong\u003e任意对象\u003c/strong\u003e Value和Type。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eiv\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereflect\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eValueOf\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eit\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereflect\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTypeOf\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eiv\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eit\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"reflectvalue\"\u003ereflect.Value\u003c/h3\u003e\n\u003ch4 id=\"结构体定义\"\u003e结构体定义\u003c/h4\u003e\n\u003cp\u003ereflect.Value 可以通过 \u003ccode\u003ereflect.ValueOf\u003c/code\u003e获得，其结构体定义如下\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eValue\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etyp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ertype\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eptr\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eunsafe\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePointer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eflag\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e其里面的变量都是私有的，意味着我们只能使用它的方法，它的常用方法有：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//针对具体类型的系列方法\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//以下是用于获取对应的值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eBool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eBytes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eComplex\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eFloat\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eUint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eCanSet\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//是否可以修改对应的值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 以下是用于修改对应的值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSet\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eSetBool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eSetBytes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eSetComplex\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eSetFloat\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eSetInt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eSetString\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eElem\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//获取指针指向的值，一般用于修改对应的值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//以下Field系列方法用于获取struct类型中的字段\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eField\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eFieldByIndex\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eFieldByName\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eFieldByNameFunc\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eInterface\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//获取对应的原始类型\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eIsNil\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//值是否为nil\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eIsZero\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//值是否是零值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eKind\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//获取对应的类型类别，比如Array、Slice、Map等\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//获取对应的方法\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eMethodByName\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eNumField\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e//获取struct类型中字段的数量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eNumMethod\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e//类型上方法集的数量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eType\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e//获取对应的reflect.Type\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以总结为3类：\u003c/p\u003e","title":"13.go深入：reflect 运行时反射"},{"content":"Go语言程序所管理的虚拟内存空间会被分为两部分：堆内存和栈内存。栈内存主要由Go语言来管理，开发者无法干涉太多，堆内存则有开发者进行分配。\n变量 一个数据类型，在声明之后，会被赋值给一个变量，变量存储了程序所需的数据。\n变量的声明 单纯的声明变量，可以使用var关键字，如：\nvar s string\t// 声明一个[字符串]变量，初始值为零值\u0026#34;\u0026#34; var sp *string\t// 声明一个[字符串指针]变量，初始值为 nil 变量的初始化 有3种方法\n声明的时候直接初始化： var name string = \u0026quot;mike\u0026quot; 声明之后再进行赋值初始化： name = \u0026quot;mike\u0026quot;,此前 name 变量已经声明 直接使用 := 进行初始化： name := \u0026quot;mike\u0026quot; 值变量和指针变量初始化的区别 我们使用值初始化的时候，可以简单的这样写：\nvar name string name = \u0026#34;mike\u0026#34; 但是，当我们使用指针初始化的时候：\nvar nameP *string // 声明一个字符串指针 *nameP = \u0026#34;mike\u0026#34;\t// 给nameP指向的地址赋值初始化 这时候，由于 nameP 指向的是一个空地址 nil，我们对这个空地址进行赋值初始化的时候，会报以下错误：\npanic: runtime error: invalid memory address or nil pointer dereference 显而易见，我们无法对一个空地址赋值。\n因此，**如果要对一个变量赋值，这个变量必须有对应的分配好的内存，这样才可以对这块内存操作，完成赋值的目的。**不止是赋值操作，针对指针变量，如果没有分配内存，对其进行取值时一样会报 nil异常，因为没有可以操作的内存。\nnew函数 声明指针变量的时候，是默认没有分配内存的，因此，我们可以使用 new 函数来进行内存的分配：\nvar nameP *string nameP = new(string) // 分配一块string类型的内存 *nameP := \u0026#34;mike\u0026#34; fmt.Println(*nameP) 这样，我们的nameP变量指向的就不是一个 nil 空地址了，而是指向了一块具体的字符串的内存。因此可以对其进行赋值操作。\nnew函数源码 // The new built-in function allocates memory. The first argument is a type, // not a value, and the value returned is a pointer to a newly // allocated zero value of that type. func new(Type) *Type 所以 new就是根据传入的类型，申请一块内存，然后返回指向这块内存的指针，该块内存初始化的数据就是该类型的零值。\nmake函数 make函数是map、slice、chan的工厂函数，可以同时用于三种类型的初始化。\n总结 new函数只用于分配内存，并且把内存清零，也就是返回一个指向对应类型的零值的指针。new函数一般用于需要显式地返回指针的情况，不常用。\nmake函数只用于slice、chan、map三种内置类型的创建和初始化，因为这三种类型的结构比较复杂，如 slice 要提前初始化好内部元素的类型，slice的长度和容量等，这样才能更好地使用它们。\n","permalink":"https://zihuanling.github.io/posts/go-12-depth-new-and-make/","summary":"\u003cp\u003eGo语言程序所管理的虚拟内存空间会被分为两部分：堆内存和栈内存。栈内存主要由Go语言来管理，开发者无法干涉太多，堆内存则有开发者进行分配。\u003c/p\u003e\n\u003ch2 id=\"变量\"\u003e变量\u003c/h2\u003e\n\u003cp\u003e一个数据类型，在声明之后，会被赋值给一个变量，变量存储了程序所需的数据。\u003c/p\u003e\n\u003ch3 id=\"变量的声明\"\u003e变量的声明\u003c/h3\u003e\n\u003cp\u003e单纯的声明变量，可以使用\u003ccode\u003evar\u003c/code\u003e关键字，如：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 声明一个[字符串]变量，初始值为零值\u0026#34;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 声明一个[字符串指针]变量，初始值为 nil\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"变量的初始化\"\u003e变量的初始化\u003c/h3\u003e\n\u003cp\u003e有3种方法\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e声明的时候直接初始化： \u003ccode\u003evar name string = \u0026quot;mike\u0026quot;\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e声明之后再进行赋值初始化： \u003ccode\u003ename = \u0026quot;mike\u0026quot;\u003c/code\u003e,此前 \u003ccode\u003ename\u003c/code\u003e 变量已经声明\u003c/li\u003e\n\u003cli\u003e直接使用 \u003ccode\u003e:=\u003c/code\u003e 进行初始化： \u003ccode\u003ename := \u0026quot;mike\u0026quot;\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"值变量和指针变量初始化的区别\"\u003e值变量和指针变量初始化的区别\u003c/h3\u003e\n\u003cp\u003e我们使用值初始化的时候，可以简单的这样写：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e但是，当我们使用指针初始化的时候：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enameP\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e   \u003cspan style=\"color:#75715e\"\u003e// 声明一个字符串指针\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003enameP\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 给nameP指向的地址赋值初始化\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这时候，由于 \u003ccode\u003enameP\u003c/code\u003e 指向的是一个空地址 \u003ccode\u003enil\u003c/code\u003e，我们对这个空地址进行赋值初始化的时候，会报以下错误：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epanic: runtime error: invalid memory address or nil pointer dereference\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e显而易见，我们无法对一个空地址赋值。\u003c/p\u003e\n\u003cp\u003e因此，**如果要对一个变量赋值，这个变量必须有对应的分配好的内存，这样才可以对这块内存操作，完成赋值的目的。**不止是赋值操作，针对指针变量，如果没有分配内存，对其进行取值时一样会报 \u003ccode\u003enil异常\u003c/code\u003e，因为没有可以操作的内存。\u003c/p\u003e\n\u003ch2 id=\"new函数\"\u003enew函数\u003c/h2\u003e\n\u003cp\u003e声明指针变量的时候，是默认没有分配内存的，因此，我们可以使用 \u003ccode\u003enew\u003c/code\u003e 函数来进行内存的分配：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enameP\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003enameP\u003c/span\u003e = new(\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e) \t\u003cspan style=\"color:#75715e\"\u003e// 分配一块string类型的内存\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003enameP\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003enameP\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样，我们的nameP变量指向的就不是一个 \u003ccode\u003enil\u003c/code\u003e 空地址了，而是指向了一块具体的字符串的内存。因此可以对其进行赋值操作。\u003c/p\u003e","title":"12.go深入：new、make和内存分配"},{"content":"值类型 假设我们有一个这样的结构体：\ntype person struct { name string age uint } 然后我们试图定义一个函数，修改这个结构体实例的值：\nfunc modify(argP person){ argP.name = \u0026#34;Nobody\u0026#34; argP.age = 10 } // 我们实际运行一下这个函数 func main(){ p := person{name: \u0026#34;mike\u0026#34;, age: 19} fmt.Println(p) modify(p) fmt.Println(p) } 上述的例子中，我们期望在经过了modify函数修改后，打印出来的结果是 {Nobody, 10}，然而实际上，输出的仍然是初始化的值： {mike, 19}\n为啥会这样呢，因为上面定义的变量 p 是值类型，而通过modify函数传入的变量值argP只是原始值变量p的一份值拷贝，而不是原来的数据本身。\n其实，我们只要在modify函数内部和外部打印出变量p和argP的地址，就可以发现他们的不同之处。\nfunc modify(argP person){ fmt.Printf(\u0026#34;address of arg p: %p\\n\u0026#34;, \u0026amp;argP) // ... } func main(){ // ... fmt.Printf(\u0026#34;address of p: %p\\n\u0026#34;, \u0026amp;p) modify(p) // ... } 输出为：\naddress of p: 0xc0002905a0 address of arg p: 0xc0000d01b0 可以看到，在函数内部和外部，变量的地址是不一样的。\n实际上，在go语言中，函数的传参都是值传递，也就是原来数据的一份拷贝，而不是数据本身。以上述的modify函数来说，参数argP就是原始变量p的一份值拷贝。除了struct之外，还有浮点型、整形、字符串、布尔、数组等，这些都是值类型\n指针类型 回到最初的期望，我们想要修改 person 的值，只需要修改modify函数的参数为指针类型（*person），然后在对应的函数体中修改原始数据的值：\nfunc modify(argP *person) { fmt.Printf(\u0026#34;address of arg p: %p\\n\u0026#34;, argP) argP.name = \u0026#34;Nobody\u0026#34; argP.age = 10 } 这样，就Ok了。\n看到输出：\naddress of p: 0xc000290450 {Mike 19} address of arg p: 0xc000290450 {Nobody 10} 引用类型 引用类型，包括 map 跟 chan\nmap 同样是上述的例子，我们不试用自定义结构体 person，而是使用map来达到修改的目的：\nfunc main(){ m := make(map[string]int) m[\u0026#34;age\u0026#34;] = 19 fmt.println(m) modify(m) fmt.Println(m) } func modify(p map[string]int){ p[\u0026#34;age\u0026#34;] = 10 } 可以看到输出如下：\nmap[age:19] map[age:10] 同样达到了我们修改值这个期望，因为 map 是一个引用类型，在go语言中，无论是通过字面量还是make函数创建的map，go语言编译器都会自动帮我们转化成为对 runtime.makemap 的调用，rumtime.makemap的定义如下：\n// makemap implements Go map creation for make(map[k]v, hint). func makemap(t *maptype, hint int, h *hmap) *hmap{ //省略无关代码 } 可以看到，这个函数最终返回的是 *hmap 类型，而他本质上就是一个指针。因此，我们才可以通过在函数内修改map的值，达到修改原始数据的目的。\n需要注意的是，map在这里被理解为引用类型，但是它的本质上就是一个指针。\nchan 作为goroutine中的通信桥梁，channel也是可以被理解为引用类型，本质上也是一个指针\n同map一样，go语言也会帮我们自动调用 runtime.makechan函数，创建一个channal：\nfunc makechan(t *chantype, size int64) *hchan { //省略无关代码 } 可以看到，返回的是一个 *hchan，本质上也是一个指针，同map一样。\n严格来说，GO语言没有引用类型，将map、chan成为引用类型是为了便于理解。此外，go语言中的函数、接口、slice切片、指针类型等，也可以成为引用类型。\n类型的零值 在go中，定义变量要么通过声明，要么通过make和new函数，不一样的是make和new函数属于显式声明并初始化，如果我们声明的变量没有显式声明初始化，则该变量的默认值就是对应类型的零值：\n类型 零值 数值类型（int、float等） 0 bool false string \u0026ldquo;\u0026quot;（空字符串） struct 内部字段的零值 slice、map、指针、函数、chan、interface nil 总结 在go语言中，函数的参数传递只有值传递，而且传递的实参都是原始数据的一份拷贝。\n如果拷贝的内容都是值类型的，那么在函数中就无法修改原始数据 如果拷贝内容是指针，则可以在函数中修改原始数据 ","permalink":"https://zihuanling.github.io/posts/go-11-depth/","summary":"\u003ch2 id=\"值类型\"\u003e值类型\u003c/h2\u003e\n\u003cp\u003e假设我们有一个这样的结构体：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后我们试图定义一个函数，修改这个结构体实例的值：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emodify\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Nobody\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 我们实际运行一下这个函数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e{\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mike\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e19\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emodify\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述的例子中，我们期望在经过了\u003ccode\u003emodify\u003c/code\u003e函数修改后，打印出来的结果是 \u003ccode\u003e{Nobody, 10}\u003c/code\u003e，然而实际上，输出的仍然是初始化的值： \u003ccode\u003e{mike, 19}\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e为啥会这样呢，因为上面定义的变量 \u003ccode\u003ep\u003c/code\u003e 是\u003cstrong\u003e值类型\u003c/strong\u003e，而通过\u003ccode\u003emodify\u003c/code\u003e函数传入的变量值\u003ccode\u003eargP\u003c/code\u003e只是\u003cstrong\u003e原始值变量p的一份值拷贝\u003c/strong\u003e，而不是原来的数据本身。\u003c/p\u003e\n\u003cp\u003e其实，我们只要在\u003ccode\u003emodify\u003c/code\u003e函数内部和外部打印出变量p和argP的地址，就可以发现他们的不同之处。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emodify\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;address of arg p: %p\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// ... \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;address of p: %p\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emodify\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出为：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eaddress of p: 0xc0002905a0\naddress of arg p: 0xc0000d01b0\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e可以看到，在函数内部和外部，变量的地址是不一样的。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e实际上，在go语言中，函数的传参都是值传递，也就是原来数据的一份拷贝，而不是数据本身。以上述的\u003ccode\u003emodify\u003c/code\u003e函数来说，参数\u003ccode\u003eargP\u003c/code\u003e就是原始变量\u003ccode\u003ep\u003c/code\u003e的一份值拷贝。除了struct之外，还有浮点型、整形、字符串、布尔、数组等，这些都是值类型\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"指针类型\"\u003e指针类型\u003c/h2\u003e\n\u003cp\u003e回到最初的期望，我们想要修改 person 的值，只需要修改\u003ccode\u003emodify\u003c/code\u003e函数的参数为指针类型（*person），然后在对应的函数体中修改原始数据的值：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emodify\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;address of arg p: %p\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Nobody\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eargP\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样，就Ok了。\u003c/p\u003e","title":"11. go深入：值、指针以及引用类型"},{"content":"由于我的博客文章里面用到了mermaid绘制流程图，从Jekyll迁移到Hugo之后，使用的PaperMod主题是默认不支持mermaid渲染的，搜了很多帖子，看到有很多解决办法。\n比如有定义 /layouts/shortcodes/mermaid.html 的，然后在代码里面，需要用到mermaid的地方（文章里面）这样写：\n{{ \u0026lt;mermaid\u0026gt; }} ... 这里写mermaid内容 {{ \u0026lt;/mermaid\u0026gt; }} 但是这样的写法不是我想要的，我想要的是保留原来的写法，能够在渲染成html的时候，动态渲染 .language-mermaid 这个类的元素为 mermaid代码块。\n查了一下PaperMod这个主题的代码，发现预留了 extend_head.html 这个扩展html头，太棒了！我们就可以在不改变原有主题代码的情况下，增添自己的head内容！\n首先，我们新建一个文件： layouts/partials/extend_head.html，然后在里面写上mermaid的初始化代码：\n\u0026lt;!-- 使用cdn加载文件 --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; // 自定义mermaid配置 const config = { startOnLoad:true, theme: \u0026#39;forest\u0026#39;, themeVariables: { lineColor: \u0026#34;#fafafa\u0026#34; // 由于paperMod的代码块背景是黑色的，这里将线条设置为白色 }, flowchart: { useMaxWidth:false, htmlLabels:true } }; mermaid.initialize(config); // 需要注意的是，要将初始化代码放到 window.onload 回调函数里面才有用 // 否则会因为在html元素加载前进行初始化，找不到元素而失效 window.onload = () =\u0026gt; { window.mermaid.init(undefined, document.querySelectorAll(\u0026#39;.language-mermaid\u0026#39;)); } \u0026lt;/script\u0026gt; Ok！现在，我的文章就可以支持使用mermaid画图了！\ngraph LR Hello --\u0026gt; Mermaid --\u0026gt; Yes ","permalink":"https://zihuanling.github.io/posts/hugo-papermod-mermaid/","summary":"\u003cp\u003e由于我的博客文章里面用到了mermaid绘制流程图，从Jekyll迁移到Hugo之后，使用的\u003cstrong\u003ePaperMod\u003c/strong\u003e主题是默认不支持mermaid渲染的，搜了很多帖子，看到有很多解决办法。\u003c/p\u003e\n\u003cp\u003e比如有定义 \u003ccode\u003e/layouts/shortcodes/mermaid.html\u003c/code\u003e 的，然后在代码里面，需要用到mermaid的地方（文章里面）这样写：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ \u0026lt;\u003cspan style=\"color:#f92672\"\u003emermaid\u003c/span\u003e\u0026gt; }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e... 这里写mermaid内容\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ \u0026lt;/\u003cspan style=\"color:#f92672\"\u003emermaid\u003c/span\u003e\u0026gt; }}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e但是这样的写法不是我想要的，我想要的是保留原来的写法，能够在渲染成html的时候，动态渲染 .language-mermaid 这个类的元素为 mermaid代码块。\u003c/p\u003e\n\u003cp\u003e查了一下PaperMod这个主题的代码，发现预留了 \u003ccode\u003eextend_head.html\u003c/code\u003e 这个扩展html头，太棒了！我们就可以在不改变原有主题代码的情况下，增添自己的\u003ccode\u003ehead\u003c/code\u003e内容！\u003c/p\u003e\n\u003cp\u003e首先，我们新建一个文件： \u003ccode\u003elayouts/partials/extend_head.html\u003c/code\u003e，然后在里面写上mermaid的初始化代码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u0026lt;!-- 使用cdn加载文件 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esrc\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\u0026#34;\u003c/span\u003e\u0026gt;\u0026lt;/\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 自定义mermaid配置\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econfig\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003estartOnLoad\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etheme\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;forest\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ethemeVariables\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003elineColor\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#fafafa\u0026#34;\u003c/span\u003e    \u003cspan style=\"color:#75715e\"\u003e// 由于paperMod的代码块背景是黑色的，这里将线条设置为白色\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eflowchart\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003euseMaxWidth\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ehtmlLabels\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emermaid\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003einitialize\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econfig\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 需要注意的是，要将初始化代码放到 window.onload 回调函数里面才有用\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 否则会因为在html元素加载前进行初始化，找不到元素而失效\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003ewindow.\u003cspan style=\"color:#a6e22e\"\u003eonload\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e () =\u0026gt; {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    window.\u003cspan style=\"color:#a6e22e\"\u003emermaid\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003einit\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eundefined\u003c/span\u003e, document.\u003cspan style=\"color:#a6e22e\"\u003equerySelectorAll\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.language-mermaid\u0026#39;\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;/\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOk！现在，我的文章就可以支持使用mermaid画图了！\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-mermaid\" data-lang=\"mermaid\"\u003egraph LR\nHello --\u0026gt; Mermaid --\u0026gt; Yes\n\u003c/code\u003e\u003c/pre\u003e","title":"让Hugo Papermod主题支持 Mermaid"},{"content":"在 Jinja2 模板中，我们经常需要判断一个变量是否存在，里面的值是否为空等等。\n检查变量是否存在，或者是否被定义 {% if variable is defined %} variable is defined {% else %} variable is not defined {% endif %} 检查数据的长度是否为空 对于列表类型的变量，我们可能需要知道这个列表是否为空的\n{% if variable | length %} variable is not empty {% else %} variable is empty {% endif %} 需要注意的是，如果这个变量为非列表类型，模板渲染的时候会报错\n检查变量值是否为True {% if variable is sameas true %} variable is true {% else %} variable is not true {% endif %} 同样，我们也可以用关键字and来实现多个判断 判断变量存在且不为空（列表型变量） {% if variable is defined and variable | length %} variable is defined and is not empty {% else %} variable is not defined or empty {% endif %} 判断变量存在且为true（布尔型变量） {% if variable is defined and is sameas true %} variable is defined and is true {% else %} variable is not defined or not true {% endif %} ","permalink":"https://zihuanling.github.io/posts/2022-03-09-jinja-templates-variable-judgement/","summary":"\u003cp\u003e在 Jinja2 模板中，我们经常需要判断一个变量是否存在，里面的值是否为空等等。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch3 id=\"检查变量是否存在或者是否被定义\"\u003e检查变量是否存在，或者是否被定义\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jinja\" data-lang=\"jinja\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e variable \u003cspan style=\"color:#66d9ef\"\u003eis\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edefined\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is defined\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is not defined\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eendif\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"检查数据的长度是否为空\"\u003e检查数据的长度是否为空\u003c/h3\u003e\n\u003cp\u003e对于列表类型的变量，我们可能需要知道这个列表是否为空的\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jinja\" data-lang=\"jinja\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e variable \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elength\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is not empty\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is empty\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eendif\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e需要注意的是，如果这个变量为非列表类型，模板渲染的时候会报错\u003c/p\u003e\n\u003ch3 id=\"检查变量值是否为true\"\u003e检查变量值是否为True\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jinja\" data-lang=\"jinja\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e variable \u003cspan style=\"color:#66d9ef\"\u003eis\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esameas\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is not true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eendif\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"同样我们也可以用关键字and来实现多个判断\"\u003e同样，我们也可以用关键字\u003ccode\u003eand\u003c/code\u003e来实现多个判断\u003c/h3\u003e\n\u003ch4 id=\"判断变量存在且不为空列表型变量\"\u003e判断变量存在且不为空（列表型变量）\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jinja\" data-lang=\"jinja\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e variable \u003cspan style=\"color:#66d9ef\"\u003eis\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edefined\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eand\u003c/span\u003e variable \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elength\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is defined and is not empty\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is not defined or empty\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eendif\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"判断变量存在且为true布尔型变量\"\u003e判断变量存在且为true（布尔型变量）\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jinja\" data-lang=\"jinja\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e variable \u003cspan style=\"color:#66d9ef\"\u003eis\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edefined\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eand\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eis\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esameas\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is defined and is true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    variable is not defined or not true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e{%\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eendif\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e%}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"Jinja2: 判断变量是否为空、存在等"},{"content":"什么是指针 可以简单将指针理解为内存地址。\n指针是一种数据类型，用来存储一个内存地址，该地址指向存储在该内存中的对象，可以是整型、字符串、或者是我们自定义的任意结构体类型。\n也可以理解为：指针就是一本书上的目录上面的页码，这个页码指向具体的内容。\n指针的声明和定义 以字符串指针为例\n// 声明一个字符串指针变量 var sp *string // 直接通过 \u0026amp; 操作符获取一个字符串的地址 s := \u0026#34;Hello world.\u0026#34; sp := \u0026amp;s // 还可以使用 new 函数，传入一个类型作为参数，用以返回该类型的指针 sp := new(string) 指针的操作 针对指针的操作，有两种：获取或者修改指针指向的值\n获取指针指向的值 使用 *指针变量 获取指针指向的值，如 *sp 获取sp指针指向的内存地址的值。\n修改指针指向的值 修改跟获取值也是类似的，使用 *指针变量 = 值 来修改，如 *sp = \u0026quot;new value\u0026quot;，这样，就把 sp指针指向的内存地址值修改为了 new value\n注意 另外，通过 var sp *string 定义的sp指针，初始值是nil，表示它没有指向任何一块内存地址，我们不能够对它进行取值和赋值操作，否则，会提示：\ninvalid memory address or nil pointer dereference\n要解决这个问题，只需要使用 new 函数给该地址分配一块内存即可：\nvar sp *string = new(string)\n还有就是，不能获取常量的指针。\n指针参数 当我们需要在函数中通过形参改变实参的值是，需要使用指针类型的参数\n举个栗子，我们有个 increase 函数，每次调用的时候，我们就让传入的参数加一\n无效写法：\nfunc increase(num int){ num++ } 上述修改的只是形参传入的值，外部的实参没有变化\n有效写法：\nfunc increase(num *int){ *num++ } 指针接收者 一点复习\n如果接收者类型是 map、slice、channel 这类引用类型，不使用指针 如果需要修改接收者，需要使用指针 如果接收者是比较大的类型，可以考虑使用指针，因为内存拷贝廉价，所以效率更高。 指针的两大好处 可以修改指向数据的值 在变量赋值、参数传值的时候可以节省内存 使用建议 不要多map、slice、channel这类引用类型使用指针 如果需要修改方法接收者内部的数据或者状态时，需要使用指针 如果需要修改参数的值或者内部数据时，也需要使用指针类型的参数 如果是比较大的结构体，每次参数传递或者调用方法都要内存拷贝，内存占用多，这时候可以考虑使用指针 像int、bool这样的小数据类型没有必要使用指针 如果需要并发安全，则尽可能地不要使用指针，使用指针一定要保证并发安全，比如加锁之类的 指针最好不要嵌套，也就是不要使用一个指向指针的指针，这会让代码变得复杂。 指向接口的指针是否实现了该接口？ 答案是：没有\n看个例子：\n// 定义一个结构体 address，其实现了 fmt.Stringer 接口 type address stuct { city string province string } func (addr address) String() string { return fmt.Sprintf(\u0026#34;Address is %s/%s\u0026#34;, addr.province, addr.city) } // 然后，我们定义一个调用 Stringer 的打印函数 func printer(s fmt.Stringer) { fmt.Println(\u0026#34;string value is : \u0026#34;, s.String()) } 一般情况下，我们知道，值接收者实现了Stringer接口，那么它的值类型和指针类型也实现了该接口。\n因此，我们可以这样调用：\nfunc main(){ addr := address{city: \u0026#34;Shenzhen\u0026#34;, province: \u0026#34;GuangDong\u0026#34;} // 验证值类型接收者 printer(addr) // 验证指针类型接收者 printer(\u0026amp;addr) } 这些都是没有问题的，我们接下来验证下指向接口的指针是否实现了该接口\nfunc main(){ // 声明一个 Stringer 接口 si，因为 address实现了 Stringer接口，因此可以直接赋值给si var si fmt.Stringer := address{city: \u0026#34;Shenzhen\u0026#34;, province: \u0026#34;GuangDong\u0026#34;} // 调用 printer 打印 si，没有问题 printer(si) // 来一个指向si的指针sip（指向接口的指针） sip := \u0026amp;si // 看看sip是否实现了Stringer接口 printer(sip) } 最后，printer(sip) 这句会编译不通过，提示 Cannot use '\u0026amp;si' (type *fmt.Stringer) as type fmt.Stringer\n所以结论是：虽然指向具体类型的指针可以实现一个接口，但是指向接口的指针永远不可能实现该接口\n","permalink":"https://zihuanling.github.io/posts/2022-03-06-go-10-depth/","summary":"\u003ch2 id=\"什么是指针\"\u003e什么是指针\u003c/h2\u003e\n\u003cp\u003e可以简单将指针理解为内存地址。\u003c/p\u003e\n\u003cp\u003e指针是一种数据类型，用来存储一个内存地址，该地址\u003cstrong\u003e指向\u003c/strong\u003e存储在该内存中的对象，可以是整型、字符串、或者是我们自定义的任意结构体类型。\u003c/p\u003e\n\u003cp\u003e也可以理解为：指针就是一本书上的目录上面的页码，这个页码指向具体的内容。\u003c/p\u003e\n\u003ch2 id=\"指针的声明和定义\"\u003e指针的声明和定义\u003c/h2\u003e\n\u003cp\u003e以字符串指针为例\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e// 声明一个字符串指针变量\nvar sp *string\n// 直接通过 \u0026amp; 操作符获取一个字符串的地址\ns := \u0026#34;Hello world.\u0026#34;\nsp := \u0026amp;s\n// 还可以使用 new 函数，传入一个类型作为参数，用以返回该类型的指针\nsp := new(string)\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"指针的操作\"\u003e指针的操作\u003c/h2\u003e\n\u003cp\u003e针对指针的操作，有两种：获取或者修改指针指向的值\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e获取指针指向的值\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e使用 \u003ccode\u003e*指针变量\u003c/code\u003e 获取指针指向的值，如 \u003ccode\u003e*sp\u003c/code\u003e 获取sp指针指向的内存地址的值。\u003c/p\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003e修改指针指向的值\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e修改跟获取值也是类似的，使用 \u003ccode\u003e*指针变量 = 值\u003c/code\u003e 来修改，如 \u003ccode\u003e*sp = \u0026quot;new value\u0026quot;\u003c/code\u003e，这样，就把 sp指针指向的内存地址值修改为了 \u003cstrong\u003enew value\u003c/strong\u003e\u003c/p\u003e\n\u003ch4 id=\"注意\"\u003e注意\u003c/h4\u003e\n\u003cp\u003e另外，通过 \u003ccode\u003evar sp *string\u003c/code\u003e 定义的sp指针，初始值是nil，表示它没有指向任何一块内存地址，我们不能够对它进行取值和赋值操作，否则，会提示：\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003einvalid memory address or nil pointer dereference\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e要解决这个问题，只需要使用 \u003cstrong\u003enew\u003c/strong\u003e 函数给该地址分配一块内存即可：\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003evar sp *string = new(string)\u003c/code\u003e\u003c/p\u003e","title":"10. go深入：Go 中的指针"},{"content":"for select 模式 这是一种常见的并发模式，我们一般使用 for {} 死循环，然后里面加select配合channel，获取协程的终止信号，来控制协程的退出\nfor { select { case \u0026lt;- done: // received stop signal, stopping. return default: // processing ... } } 类似的还有 for range select模式，主要就是使用 for ... range 遍历某个数据数组，将数据发送到channel里面：\nfor _, value := range []int{} { select { case \u0026lt;- done: // 接受到终止信号 return case resultCh \u0026lt;- value: // do nothing but put value to rsult channel } } select timeout 模式 select timeout模式，核心在于使用 time.After(t time.Duration) 返回一个定时的channel，使用select配合这个channel控制协程的退出。\n来看一个网络访问超时的demo：\nfunc main(){ result := make(chan string) go func(){ // simulate internet visiting... time.Sleep(5 * time.Second) result \u0026lt;- \u0026#34;Done, internet visited.\u0026#34; }() select { case v := \u0026lt;-result: fmt.Println(\u0026#34;result from internet: \u0026#34;, v) case \u0026lt;- time.After(3 * time.Second): fmt.Println(\u0026#34;Timeout!\u0026#34;) } } pipeline 模式 pipeline流水线模式，顾名思义，就是整一个生产流程中有多个工序，后面的工序依赖前面一道工序的结果。\n或者说，前面工序的输出，就是后面工序的输入。\ngraph LR 生产:::point --\u0026gt; 工序1 --\u0026gt; 工序2 --\u0026gt; 工序3 --\u0026gt; 成品:::point classDef point fill:#f88; 我们以酒席的上菜流程来看，我们将其作为一个流水线，简化为3个步骤：\n洗菜 炒菜 端菜上桌 洗菜:\n// n表示需要处理的菜品数量 // 返回一个channel，用户传递洗好的菜 func wash(n int) \u0026lt;-chan string { washedCh := make(chan string) go func(){ defer close(washedCh) for i := 1; i \u0026lt;= n; i++ { washedCh \u0026lt;- fmt.Sprintf(\u0026#34;洗好的菜%d\u0026#34;, i) } }() return washedCh } 炒菜：\n// 炒菜，传入channel，里面是洗好的菜 // 返回一个channel，里面是炒好的菜 func fire(washed \u0026lt;-chan string) \u0026lt;-chan string { firedCh := make(chan string) go func() { defer close(firedCh) for w := range washed { firedCh \u0026lt;- fmt.Sprintf(\u0026#34;炒好的【%s】\u0026#34;, w) } }() return firedCh } 上菜：\n// 传菜，传入channel，里面是炒好的菜，我们负责将菜端到各桌 func deliver(fired \u0026lt;-chan string) { for food := range fired { fmt.Printf(\u0026#34;已上桌： %s\\n\u0026#34;, food) } } 完整的酒席流程：\n三个工序（步骤）我们已经定义好了，接下来将其组装成为一个完整的流程：\nfunc main() { washed := wash(10) fired := fire(washed) deliver(fired) } 流程输出记录如下：\n已上桌： 炒好的【洗好的菜1】 已上桌： 炒好的【洗好的菜2】 已上桌： 炒好的【洗好的菜3】 已上桌： 炒好的【洗好的菜4】 已上桌： 炒好的【洗好的菜5】 已上桌： 炒好的【洗好的菜6】 已上桌： 炒好的【洗好的菜7】 已上桌： 炒好的【洗好的菜8】 已上桌： 炒好的【洗好的菜9】 已上桌： 炒好的【洗好的菜10】 扇出扇入模式 原来的模式是这样的：\ngraph LR 洗菜 --\u0026gt; 厨师炒菜 --\u0026gt; 端菜上桌 每道步骤都只有一个人，但是，洗菜是比较快的（假设不是很麻烦的菜，随便洗洗那种），炒菜却需要较长的时间，厨师需要配菜、掌控火候、调味装盘等。\n所以，上述流程的性能瓶颈便是炒菜\n在厨师炒菜的过程中，服务员需要等待菜炒好了才能端菜，这段时间便会空闲下来，没有充足的利用到资源，造成了浪费。\n我们使用扇出扇入模式对上述流程进行改造：\ngraph LR subgraph 扇出 洗菜 end 洗菜 --chan--\u0026gt; 厨师1炒菜 --chan--\u0026gt; 合并 洗菜 --chan--\u0026gt; 厨师2炒菜 --chan--\u0026gt; 合并 洗菜 --chan--\u0026gt; 厨师3炒菜 --chan--\u0026gt; 合并 subgraph 扇入 合并 end 合并 --chan--\u0026gt; 端菜上桌 主要改造内容就是：调用多个厨师来炒菜，然后将炒好的菜汇合在一起，服务员进行端菜。\n首先，我们对炒菜函数进行改造，主要是加入一个参数cooker，用于区分当前菜品是哪个厨师炒的：\n// 炒菜，传入channel，里面是洗好的菜, cooker 表示厨师的名字 // 返回一个channel，里面是炒好的菜 func fire(washed \u0026lt;-chan string, cooker string) \u0026lt;-chan string { firedCh := make(chan string) go func() { defer close(firedCh) for w := range washed { firedCh \u0026lt;- fmt.Sprintf(\u0026#34;%s炒好的【%s】\u0026#34;, cooker, w) } }() return firedCh } 然后实现菜品汇合部分的代码：\n// 将炒好的菜汇合在一起，提供给服务员端菜 func merge(fireds ...\u0026lt;-chan string) \u0026lt;-chan string { // 所有的菜品都将汇合到 merged channel 里面 merged := make(chan string) var wg sync.WaitGroup go func() { wg.Add(len(fireds)) defer close(merged) for _, fired := range fireds { go func(foods \u0026lt;-chan string) { defer wg.Done() for food := range foods { merged \u0026lt;- food } }(fired) } wg.Wait() }() return merged } 最后！我们改造下main函数，让多个厨师炒菜，然后实现 菜品汇合 -\u0026gt; 端菜\nfunc main() { washed := wash(10) // 3个厨师炒菜 fired1 := fire(washed, \u0026#34;cooker1\u0026#34;) fired2 := fire(washed, \u0026#34;cooker2\u0026#34;) fired3 := fire(washed, \u0026#34;cooker3\u0026#34;) // 汇合菜品 merged := merge(fired1, fired2, fired3) // 端菜 deliver(merged) } 流程输出记录：\n已上桌： cooker3炒好的【洗好的菜3】 已上桌： cooker3炒好的【洗好的菜4】 已上桌： cooker1炒好的【洗好的菜1】 已上桌： cooker1炒好的【洗好的菜7】 已上桌： cooker3炒好的【洗好的菜5】 已上桌： cooker1炒好的【洗好的菜8】 已上桌： cooker3炒好的【洗好的菜10】 已上桌： cooker1炒好的【洗好的菜9】 已上桌： cooker2炒好的【洗好的菜2】 已上桌： cooker2炒好的【洗好的菜6】 可以看到，各个菜品的上菜时间是无序的，也就是说，哪道菜先做好就先上，大大提高了上菜效率，不会让客人久等了！\nFutures 模式 流水线 Pipeline模式中的各个步骤都是相互依赖的，只有上一个工序完成，下一个工序才能开始。\nFutures模式：实际需求中，很多任务是独立的，比如说煲汤、煮饭和炒菜，我们可以同时煲汤、煮饭，不需要立刻获取到煮好的汤和饭，可以先去做其他的事情，比如炒菜，然后在未来的某个时候，去获取煲汤和煮饭的结果，这就是未来模式（Futrues模式）\n以上述的煲汤、煮饭、炒菜为例，代码实现一下：\nfunc main() { fmt.Println(\u0026#34;开始做饭\u0026#34;) riceCh := rice() soupCh := soup() fmt.Println(\u0026#34;去做其他事情。。。\u0026#34;) fmt.Println(\u0026#34;开始炒菜吧。。。\u0026#34;) time.Sleep(2 * time.Second) fmt.Println(\u0026#34;菜炒好了！\u0026#34;) r := \u0026lt;-riceCh s := \u0026lt;-soupCh fmt.Printf(\u0026#34;吃饭！ 菜、%s、喝 %s\\n\u0026#34;, r, s) } // 煮饭 func rice() \u0026lt;-chan string { fmt.Println(\u0026#34;开始煮饭啦\u0026#34;) ch := make(chan string) go func() { time.Sleep(3 * time.Second)\t// 煮饭耗时 ch \u0026lt;- \u0026#34;煮好的饭\u0026#34; fmt.Println(\u0026#34;饭煮好了！\u0026#34;) }() return ch } // 煲汤 func soup() \u0026lt;-chan string { fmt.Println(\u0026#34;开始煲汤啦！\u0026#34;) ch := make(chan string) go func() { time.Sleep(4 * time.Second)\t// 煲汤耗时 ch \u0026lt;- \u0026#34;煲好的汤\u0026#34; fmt.Println(\u0026#34;汤煲好了\u0026#34;) }() return ch } Futures 模式下的协程和普通协程最大的区别是可以返回结果，而这个结果会在未来的某个时间点使用。所以在未来获取这个结果的操作必须是一个阻塞的操作，要一直等到获取结果为止。\n如果大任务可以拆解为一个个独立并发执行的小任务，并且可以通过这些小任务的结果得出最终大任务的结果，就可以使用 Futures 模式\n最后 并发模式和设计模式很相似，都是对现实场景的抽象封装，以便提供一个统一的解决方案。但和设计模式不同的是，并发模式更专注于异步和并发。\n","permalink":"https://zihuanling.github.io/posts/2022-03-04-go-9-concurrency/","summary":"\u003ch2 id=\"for-select-模式\"\u003efor select 模式\u003c/h2\u003e\n\u003cp\u003e这是一种常见的并发模式，我们一般使用 \u003ccode\u003efor {} \u003c/code\u003e 死循环，然后里面加\u003ccode\u003eselect\u003c/code\u003e配合\u003ccode\u003echannel\u003c/code\u003e，获取协程的终止信号，来控制协程的退出\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edone\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// received stop signal, stopping.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t    \u003cspan style=\"color:#75715e\"\u003e// processing ... \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e类似的还有 \u003ccode\u003efor range select\u003c/code\u003e模式，主要就是使用 \u003ccode\u003efor ... range\u003c/code\u003e 遍历某个数据数组，将数据发送到channel里面：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e{} {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edone\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 接受到终止信号\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eresultCh\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// do nothing but put value to rsult channel\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"select-timeout-模式\"\u003eselect timeout 模式\u003c/h2\u003e\n\u003cp\u003eselect timeout模式，核心在于使用 \u003ccode\u003etime.After(t time.Duration)\u003c/code\u003e 返回一个定时的channel，使用select配合这个channel控制协程的退出。\u003c/p\u003e\n\u003cp\u003e来看一个网络访问超时的demo：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// simulate internet visiting...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e        \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Done, internet visited.\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ev\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \t    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;result from internet: \u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ev\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAfter\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t        \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Timeout!\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"pipeline-模式\"\u003epipeline 模式\u003c/h2\u003e\n\u003cp\u003epipeline流水线模式，顾名思义，就是整一个生产流程中有多个工序，后面的工序依赖前面一道工序的结果。\u003c/p\u003e","title":"9. go并发：Go 中的并发模式"},{"content":"目前，github page 会将 markdown里面的 mermaid 块渲染成为一个 div.language-mermaid 的 html 代码块，但是，mermaid-js仅支持渲染 div.mermaid 的html代码块，因此，我们需要做一点处理。\n我们只需要在文章模板的末尾，添加如下转化代码即可：\n文件：_layouts/post.html {%- if content contains \u0026#39;mermaid\u0026#39; -%} \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; const config = { startOnLoad:true, theme: \u0026#39;forest\u0026#39;, flowchart: { useMaxWidth:false, htmlLabels:true } }; mermaid.initialize(config); window.mermaid.init(undefined, document.querySelectorAll(\u0026#39;.language-mermaid\u0026#39;)); \u0026lt;/script\u0026gt; {% endif %} 上述代码手动将 div.language-mermaid 添加到 mermaid引擎的渲染中。\n引用 在文章里面指定文章layout，即可引用，并且渲染mermaid！\n--- layout: post --- 其他方法 当然也可以在 _config.yml 配置kramdown, 让其可以将 mermaid 块渲染成 div.mermaid 这样的html的代码块，但是实现起来有点复杂。\n参考： Issue https://github.com/gettalong/kramdown/issues/637\n","permalink":"https://zihuanling.github.io/posts/2022-03-04-support-mermaid-on-github-page/","summary":"\u003cp\u003e目前，github page 会将 markdown里面的 \u003ccode\u003emermaid\u003c/code\u003e 块渲染成为一个 \u003ccode\u003ediv.language-mermaid\u003c/code\u003e 的 html 代码块，但是，mermaid-js仅支持渲染 \u003ccode\u003ediv.mermaid\u003c/code\u003e 的html代码块，因此，我们需要做一点处理。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003cp\u003e我们只需要在文章模板的末尾，添加如下转化代码即可：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e文件：_layouts/post.html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{%- if content contains \u0026#39;mermaid\u0026#39; -%}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esrc\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\u0026#34;\u003c/span\u003e\u0026gt;\u0026lt;/\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econfig\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003estartOnLoad\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etheme\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;forest\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eflowchart\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003euseMaxWidth\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ehtmlLabels\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emermaid\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003einitialize\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econfig\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewindow.\u003cspan style=\"color:#a6e22e\"\u003emermaid\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003einit\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eundefined\u003c/span\u003e, document.\u003cspan style=\"color:#a6e22e\"\u003equerySelectorAll\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.language-mermaid\u0026#39;\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;/\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{% endif %}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述代码手动将 \u003ccode\u003ediv.language-mermaid\u003c/code\u003e 添加到 mermaid引擎的渲染中。\u003c/p\u003e\n\u003ch4 id=\"引用\"\u003e引用\u003c/h4\u003e\n\u003cp\u003e在文章里面指定文章layout，即可引用，并且渲染mermaid！\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elayout: post\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"其他方法\"\u003e其他方法\u003c/h4\u003e\n\u003cp\u003e当然也可以在 \u003ccode\u003e_config.yml\u003c/code\u003e 配置kramdown, 让其可以将 \u003ccode\u003emermaid\u003c/code\u003e 块渲染成 \u003ccode\u003ediv.mermaid\u003c/code\u003e 这样的html的代码块，但是实现起来有点复杂。\u003c/p\u003e\n\u003cp\u003e参考： \u003ca href=\"https://github.com/gettalong/kramdown/issues/637\"\u003eIssue https://github.com/gettalong/kramdown/issues/637\u003c/a\u003e\u003c/p\u003e","title":"让github page支持mermaid语法"},{"content":"协程如何退出 一般来说，我们执行协程，需要等到协程执行完毕，才能够退出。但是，当我们想要让协程提前退出，就需要一种机制，去控制协程的退出。\n以下例子使用select + channel的方式，控制协程的退出\nfunc main() { ch := make(chan bool) var wg sync.WaitGroup go func() { defer wg.Done() // 开启looper协程 looper(ch) }() wg.Add(1) // 5秒后发送中断信号 time.Sleep(time.Second * 5) fmt.Println(\u0026#34;Signal to goroutine exit...\u0026#34;) ch \u0026lt;- true fmt.Println(\u0026#34;Signal sent.\u0026#34;) wg.Wait() // 等待协程完全退出 fmt.Println(\u0026#34;Exit.\u0026#34;) } func looper(ch \u0026lt;-chan bool){ for { select { case \u0026lt;-ch: fmt.Println(\u0026#34;go break signal\u0026#34;) return default: fmt.Println(\u0026#34;looper is running...\u0026#34;) time.Sleep(time.Second * 1) } } } 上述例子通过一个ch channel 发送信号，让协程收到信号后终止循环。\n使用Context控制协程退出 上述，我们可以通过select+ channel 的方式进行控制协程的退出，但是那是单个协程的退出，当我们想要控制多个协程的退出时，也可以用定义多个channel的方法，但是这会让代码变得十分复杂。\n而Context，可以让我们跟踪到每个协程，从而更好地进行限制和控制。\n使用 Context 改造上述的代码：\nfunc main() { var wg sync.WaitGroup ctx, stop := context.WithCancel(context.Background()) go func() { defer wg.Done() looper(ctx) }() wg.Add(1) time.Sleep(time.Second * 5) fmt.Println(\u0026#34;Signal to goroutine exit...\u0026#34;) stop()\t// 向context发送停止指令 fmt.Println(\u0026#34;Signal sent.\u0026#34;) wg.Wait() fmt.Println(\u0026#34;Exit.\u0026#34;) } func looper(ctx context.Context){ for { select { case \u0026lt;-ctx.Done(): fmt.Println(\u0026#34;go break signal\u0026#34;) return default: fmt.Println(\u0026#34;looper is running...\u0026#34;) time.Sleep(time.Second * 1) } } } 上述代码实现的功能和使用select + channel也是一样的，主要改动为：\nlooper的ch参数改为了ctx context.Context 使用 case \u0026lt;- ctx.Done() 来判断是否接受到终止信号 使用 context.WithCancel(context.Backgroud())来生成一个可以取消的Context，返回两个内容： context和 stop函数，stop函数用于向context发送终止指令。context.Backgroud()用于生成一个空的Context， 一般作为整个Context树的根节点 原来的发送停止指令为向chchannel发送true，作为一个停止的指令，而现在通过调用 stop()来向ctx发送终止信号。 什么是Context Context是一个接口，是并发安全的，具备手动、定时、超时发出取消信号、传值等功能，主要用于控制多个协程之间的协作，尤其是取消操作。一旦取消指令下达，那么被Context跟踪的这些协程都会收到取消信号，就可以做清理和退出操作\nContext的代码定义： type Context interface { Deadline() (deadline time.Time, ok bool) Done() \u0026lt;-chan struct{} Err() error Value(key interface{}) interface{} } Deadline方法可以获取设置的截止时间，第一个返回的值deadline是截止时间，到了这个时间，Context就会自动发起取消请求，第二个返回值ok表示这个Context是否设置了截止时间。 Done方法返回一个只读的channel，类型为struct{}。协程中，如果该方法返回的chan可以被读取，意味着Context已经发起了终止信号，我们的协程收到这个信号之后，就可以做协程的清理和退出操作了 Err方法返回取消的错误原因，是什么原因导致Contxt被取消。 Value方法可以获取该Context上绑定的值，是一个键值对，需要通过key来获取 最常用的是Done方法，我们用它来判断当前context是否被取消。\nContext树 我们不需要自己实现Context接口，因为Golang提供了多个函数帮助我们生成不同的Context，通过这些函数，我们可以生成一颗Context树，将不同的Context关联起来，这样，当父Context发出取消信号的时候，子Context也会发出该信号，以便控制不同层级的协程退出。\n功能上分，有四种实现好的Context：\n空Context：context.Backgroud()，不可取消，没有截止时间，主要用于Context的根节点\n可取消的Context：context.WithCancel(parent context.Context)，用于发送取消信号，当取消时，子Context也会取消\n可以定时取消的Context：\ncontext.WithDeadline(parant context.Context, d time.Time)， 设置deadline，在某个时间取消 context.WithTimeout(parant context.Context, timeout time.Duration)， 设置多长时间后自动取消 值Context：context.WithValue(parant context.Context, key, val interface{})，用于存储key-value键值对\n上述前三种都是可取消的Context，值Context则用于存储key-value键值对\n使用Context取消多个协程 以上述的looper循环例子为例，使用Context同时取消多个协程\nfunc main() { var wg sync.WaitGroup wg.Add(4) ctx, stop := context.WithCancel(context.Background()) for i := 0;i \u0026lt; 3; i++ { // 启动3个协程，使用父进程ctx控制这些进程的取消 go func(id int) { defer wg.Done() looper(ctx, id) }(i) } // 创建一个子Context，当父Context取消时，subCtx也会发出取消信号 subCtx, _ := context.WithCancel(ctx) go func() { defer wg.Done() looper(subCtx, 3) fmt.Println(\u0026#34;looper 3 exist because parent context exit.\u0026#34;) }() time.Sleep(time.Second * 5) fmt.Println(\u0026#34;Signal to goroutine exit...\u0026#34;) stop()\t// 向context发送停止指令 fmt.Println(\u0026#34;Signal sent.\u0026#34;) wg.Wait() fmt.Println(\u0026#34;Exit.\u0026#34;) } func looper(ctx context.Context, id int){ for { select { case \u0026lt;-ctx.Done(): fmt.Printf(\u0026#34;looper %d: go break signal\\n\u0026#34;, id) return default: fmt.Printf(\u0026#34;looper %d is running...\\n\u0026#34;, id) time.Sleep(time.Second * 1) } } } Context 之间的取消关系图：\ngraph LR Ctx1 --\u0026gt; Ctx2:::canceled Ctx2:::canceled -.cancel.-\u0026gt; Ctx3:::canceled Ctx2:::canceled -.cancel.-\u0026gt; Ctx4:::canceled Ctx1 --\u0026gt; Ctx5 --\u0026gt; Ctx6 classDef canceled fill:#f9a; 可以看到，当节点Ctx2取消时，子节点Ctx4、Ctx5也一同被取消了。如果Ctx4、Ctx5还有更多的子节点的话，也会一并取消。\nContext传值 我们改造上述代码：\nfunc main() { var wg sync.WaitGroup wg.Add(1) // 创建一个可取消的parent Context ctx, stop := context.WithCancel(context.Background()) // 创建一个ValueContext，key为userId, value为1 valCtx := context.WithValue(ctx, \u0026#34;userId\u0026#34;, 1) go func() { defer wg.Done() getUser(valCtx) }() time.Sleep(time.Second * 2) fmt.Println(\u0026#34;Signal to goroutine exit...\u0026#34;) stop()\t// 向context发送停止指令 fmt.Println(\u0026#34;Signal sent.\u0026#34;) wg.Wait() fmt.Println(\u0026#34;Exit.\u0026#34;) } func getUser(ctx context.Context){ for { select { case \u0026lt;-ctx.Done(): fmt.Printf(\u0026#34;gorouting [getUser]: go break signal\\n\u0026#34;) return default: value := ctx.Value(\u0026#34;userId\u0026#34;) fmt.Printf(\u0026#34;gorouting [getUser], userId=%v\\n\u0026#34;, value) time.Sleep(time.Second * 1) } } } 可以看到，我们通过context.WithValue创建一个含值的Context，然后到goroutine中，在goroutine中通过ctx.Value(\u0026quot;userId\u0026quot;)将我们传入的值获取出来。\n需要注意的是，context.WithValue是没有返回cancelFunc的，也就是说，我们不能够通过值Context主动告诉协程去终止，而是需要创建一个可取消的父Context，通过这个父Context发送取消信号。\nContext使用原则 Context不要放在结构体中，要以参数的方式传递 Context作为函数参数的时候，要作为第一个参数 要使用context.Backgroud()函数生成根节点Context，也就是顶层Context Context传值要传递必须传递的值，而且要尽可能少，不要什么都传 Context多协程安全，可以在多个协程中放心使用 总结： Context 通过 With 系列函数生成 Context 树，把相关的 Context 关联起来，这样就可以统一进行控制。一声令下，关联的 Context 都会发出取消信号，使用这些 Context 的协程就可以收到取消信号，然后清理退出。我们在定义函数的时候，如果想让外部给函数发取消信号，就可以为这个函数增加一个 Context 参数，让外部的调用者可以通过 Context 进行控制，比如下载一个文件超时退出的需求。\n","permalink":"https://zihuanling.github.io/posts/2022-02-27-go-8-concurrency/","summary":"\u003ch2 id=\"协程如何退出\"\u003e协程如何退出\u003c/h2\u003e\n\u003cp\u003e一般来说，我们执行协程，需要等到协程执行完毕，才能够退出。但是，当我们想要让协程提前退出，就需要一种机制，去控制协程的退出。\u003c/p\u003e\n\u003cp\u003e以下例子使用\u003ccode\u003eselect + channel\u003c/code\u003e的方式，控制协程的退出\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ebool\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esync\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWaitGroup\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 开启looper协程\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003elooper\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAdd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 5秒后发送中断信号\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Signal to goroutine exit...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Signal sent.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWait\u003c/span\u003e()  \u003cspan style=\"color:#75715e\"\u003e// 等待协程完全退出\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Exit.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elooper\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ebool\u003c/span\u003e){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;go break signal\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;looper is running...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述例子通过一个\u003ccode\u003ech\u003c/code\u003e channel 发送信号，让协程收到信号后终止循环。\u003c/p\u003e\n\u003ch2 id=\"使用context控制协程退出\"\u003e使用Context控制协程退出\u003c/h2\u003e\n\u003cp\u003e上述，我们可以通过select+ channel 的方式进行控制协程的退出，但是那是单个协程的退出，当我们想要控制多个协程的退出时，也可以用定义多个channel的方法，但是这会让代码变得十分复杂。\u003c/p\u003e\n\u003cp\u003e而Context，可以让我们跟踪到每个协程，从而更好地进行限制和控制。\u003c/p\u003e\n\u003cp\u003e使用 Context 改造上述的代码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esync\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWaitGroup\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estop\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithCancel\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBackground\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003elooper\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAdd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Signal to goroutine exit...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estop\u003c/span\u003e()\t\u003cspan style=\"color:#75715e\"\u003e// 向context发送停止指令\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Signal sent.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWait\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Exit.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elooper\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;go break signal\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;looper is running...\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述代码实现的功能和使用\u003ccode\u003eselect + channel\u003c/code\u003e也是一样的，主要改动为：\u003c/p\u003e","title":"8. go并发：Context-多线程并发控制神器"},{"content":"在某些场景中，我们写的bash脚本需要获取命令行中指定的某些参数，用以判断某些条件，或者设置某些变量等。\ngetopts可以让我们通过指定 -a 1 的方式指定对应的参数名称和参数值。\n一个更详细的小教程，可以看这里： getopts_tutorial 使用 可以使用 while getopts \u0026quot;:a:p:\u0026quot; opt; do... 的方式，将参数名称读取到 opt 变量中，然后，在循环体中，使用 $OPTARG 获取到具体的参数值。\n一个小栗子，用于快捷创建新的 gihub pages 博客模板：\n#!/usr/bin/env bash # 用于快捷创建新博客 # 命令： ./new.sh -t 标签1,标签2 -n 文章标题 -l layout -c 1 layout=\u0026#34;post\u0026#34; title=\u0026#34;new-post\u0026#34; now=$(date +%Y-%m-%d) can_comment=\u0026#34;false\u0026#34; author=\u0026#34;ZihuanLing\u0026#34; while getopts \u0026#34;:l:t:c:n:\u0026#34; opt; do case $opt in l) layout=\u0026#34;$OPTARG\u0026#34; ;; t) tags=\u0026#34;[$OPTARG]\u0026#34; ;; c) can_comment=\u0026#34;true\u0026#34; ;; n) title=$(echo \u0026#34;$OPTARG\u0026#34; | sed \u0026#34;s/ /\\-/g\u0026#34;) ;; \\?) echo \u0026#34;Invalid option -$OPTARG\u0026#34; \u0026gt;\u0026amp;2 exit 1 ;; esac done if [ -z $tags ]; then tags=\u0026#34;[]\u0026#34; fi filename=\u0026#34;_posts/${now}-${title}.md\u0026#34; read -r -d \u0026#39;\u0026#39; content \u0026lt;\u0026lt; EOM --- layout: ${layout} title: \u0026#34;${title}\u0026#34; date: ${now} tags: ${tags} comments: ${can_comment} author: ${author} --- \u0026lt;!-- more --\u0026gt; EOM echo \u0026#34;$filename\u0026#34; echo \u0026#34;$content\u0026#34; echo \u0026#34;$content\u0026#34; \u0026gt; $filename ","permalink":"https://zihuanling.github.io/posts/2022-02-24-try-linux-getopts/","summary":"\u003cp\u003e在某些场景中，我们写的bash脚本需要获取命令行中指定的某些参数，用以判断某些条件，或者设置某些变量等。\u003c/p\u003e\n\u003cp\u003egetopts可以让我们通过指定 \u003ccode\u003e-a 1\u003c/code\u003e 的方式指定对应的参数名称和参数值。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch3 id=\"一个更详细的小教程可以看这里-getopts_tutorialhttpswikibash-hackersorghowtogetopts_tutorial\"\u003e一个更详细的小教程，可以看这里： \u003ca href=\"https://wiki.bash-hackers.org/howto/getopts_tutorial\"\u003egetopts_tutorial\u003c/a\u003e\u003c/h3\u003e\n\u003ch3 id=\"使用\"\u003e使用\u003c/h3\u003e\n\u003cp\u003e可以使用 \u003ccode\u003ewhile getopts \u0026quot;:a:p:\u0026quot; opt; do...\u003c/code\u003e 的方式，将参数名称读取到 \u003ccode\u003eopt\u003c/code\u003e 变量中，然后，在循环体中，使用 \u003ccode\u003e$OPTARG\u003c/code\u003e 获取到具体的参数值。\u003c/p\u003e\n\u003cp\u003e一个小栗子，用于快捷创建新的 gihub pages 博客模板：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#!/usr/bin/env bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 用于快捷创建新博客\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 命令： ./new.sh -t 标签1,标签2 -n 文章标题 -l layout -c 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elayout\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;post\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etitle\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;new-post\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enow\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003edate +%Y-%m-%d\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecan_comment\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;false\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eauthor\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ZihuanLing\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e getopts \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;:l:t:c:n:\u0026#34;\u003c/span\u003e opt; \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e $opt in \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    l\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      layout\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$OPTARG\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    t\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      tags\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;[\u003c/span\u003e$OPTARG\u003cspan style=\"color:#e6db74\"\u003e]\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    c\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      can_comment\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    n\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      title\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$OPTARG\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e | sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s/ /\\-/g\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#ae81ff\"\u003e\\?\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Invalid option -\u003c/span\u003e$OPTARG\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    exit \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eesac\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e -z $tags \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  tags\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;[]\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efilename\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_posts/\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003enow\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003etitle\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e.md\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eread -r -d \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e content \u003cspan style=\"color:#e6db74\"\u003e\u0026lt;\u0026lt; EOM\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003elayout: ${layout}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003etitle: \u0026#34;${title}\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edate: ${now}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003etags: ${tags}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ecomments: ${can_comment}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eauthor: ${author}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026lt;!-- more --\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eEOM\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$filename\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$content\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$content\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u0026gt; $filename\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"使用 getopts 获取命令行中的参数"},{"content":"从 lemonchan.github.io fork了这个博客项目，感觉太棒了，设计很简洁，我需要的功能基本上都有。\n感谢！\n","permalink":"https://zihuanling.github.io/posts/2022-02-23-first-github-page-blog/","summary":"\u003cp\u003e从 \u003ca href=\"https://github.com/lemonchann/lemonchann.github.io\"\u003elemonchan.github.io\u003c/a\u003e fork了这个博客项目，感觉太棒了，设计很简洁，我需要的功能基本上都有。\u003c/p\u003e\n\u003cp\u003e感谢！\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e","title":"第一篇GitHub Page博客"},{"content":"go在并发的时候，可能会出现多个协程同时访问一个资源的时候，这就出现了资源竞争。也可能出现协程还在运行，但是主程序却退出了的情况，这是缺少控制导致的。\n用sync包，可以方便的控制资源的访问，也可以方便实现阻塞等待，让协程执行完毕再退出程序，或者执行下一步。\n资源竞争 在同一个goroutine中，如果分配的内存没有被其他的goroutine访问，只在该goroutine中使用，则不存在资源竞争问题\n如果同一块内存被多个goroutine同时访问，就会产生不知道谁先访问，也无法预料最后结果的情况，这就是资源竞争：\n// 共享的资源 var sum = 0 func main(){ for i := 0; i \u0026lt; 100; i++ { go add(10) } // goroutine 不会阻塞下面的代码，此处Sleep一下，防止main goroutine直接退出 // 而导致未完成的goroutine也被终止 time.Sleep(time.Second * 2) fmt.Println(\u0026#34;Sum is:\u0026#34;, sum) } func add(i int) { sum += i } 上述例子中，sum变量为共享的资源，程序运行的过程中会发生资源竞争。\n使用 go build、go run、go test 这些 Go 语言工具链提供的命令时，添加 -race 标识可以帮你检查 Go 语言代码是否存在资源竞争\n同步原语 sync.Mutex 互斥锁，同一个时刻，只有一个goroutine能执行某段代码，其他协程需要等待该协程执行完毕后才能继续执行。\nvar ( sum int mutex sync.Mutex ) func add(i int) { mutex.Lock() // defer mutex.Unlock()\t// 使用defer，关闭更加优雅 sum += i mutex.Unlock() } sync.RWMutex 读写锁\n有几种情况：\n写的时候不能同时读，这个时候读取的话可能读到脏数据 读的时候不能同时写，也可能产生不可预料的结果 读的时候可以同时读，因为数据不会改变。 // 共享的资源 var ( sum = 0 mutex sync.RWMutex ) func main(){ for i := 0; i \u0026lt; 100; i++ { go add(10) } for i := 0; i \u0026lt; 10; i++ { go fmt.Println(\u0026#34;sum is:\u0026#34;, readSum()) } time.Sleep(time.Second) } func readSum() int { // 读取的时候使用RLock（读锁） mutex.RLock() defer mutex.RUnlock() d := sum return d } func add(i int) { mutex.Lock() defer mutex.Unlock() sum += i } sync.WaitGroup 上述代码中，我们用到了 time.Sleep 来防止goroutine执行完毕之前，主程序退出。\n由于不知道多个协程执行完毕需要多少时间，因此设置了一个固定的值，调用Sleep\n我们可以使用sync.WaitGroup来监听协程的运行情况：\nfunc main(){ wg := sync.WaitGroup wg.Add(110) // 监听 110 个goroutine for i := 0; i \u0026lt; 100; i++ { go func(){ defer wg.Done()\t// wg计数器减一 add(10) }() } for i := 0; i \u0026lt; 10; i++ { go func(){ defer wg.Done() fmt.Println(\u0026#34;sum is : \u0026#34;, readSum()) }() } // 一直等待（阻塞），直到计数器为0 wg.Wait() } sync.WaitGroup 的使用步骤:\n声明一个WaitGroup，wg := sync.WaitGroup,然后调用wg.Add方法，设置需要跟踪的协程数量 在每个协程执行完毕时调用 wg.Done，让计数器值减一，告诉sync.WaitGroup，该协程执行完毕 在需要阻塞的地方，调用wg.Wait方法，知道计数器值为0 sync.Once func main() { var once sync.Once var wg sync.WaitGroup wg.Add(10) onceBody := func() { fmt.Println(\u0026#34;only once.\u0026#34;) } //启动10个协程执行once.Do(onceBody) for i := 0; i \u0026lt; 10; i++ { go func() { defer wg.Done() //把要执行的函数(方法)作为参数传给once.Do方法即可 once.Do(onceBody) }() } wg.Wait() }\tsync.Once 适用于创建某个对象的单例、只加载一次的资源等只执行一次的场景\nsync.Cond sync.Cond 可以一声令下让所有协程都开始执行，关键点在于协程开始的时候是等待的，要等待 sync.Cond 唤醒才能执行\nsync.Cond 可以用于控制协程的阻塞和唤醒。\nfunc main() { cond := sync.NewCond(\u0026amp;sync.Mutex{}) var wg sync.WaitGroup wg.Add(11) // 10 个人赛跑 for i := 1; i \u0026lt;= 10; i++ { go func(num int) { defer wg.Done() fmt.Printf(\u0026#34;%-2d is ready.\\n\u0026#34;, num) cond.L.Lock() defer cond.L.Unlock() cond.Wait()\t// 进入等待，直到 cond.Signal() 或者 cond.Broadcast() fmt.Printf(\u0026#34;%-2d is running...\\n\u0026#34;, num) }(i) } time.Sleep(time.Second) // 1 个裁判发号施令 go func() { defer wg.Done() fmt.Println(\u0026#34;judgement is ready.\u0026#34;) fmt.Println(\u0026#34;Run!\u0026#34;) cond.Broadcast() // Broadcast 让所有的协程开始运行 }() wg.Wait() } sync.Cond 的三个方法：\nWait：阻塞当前协程，知道被其他协程调用Broadcast或者Signal方法唤醒，使用的时候需要加锁，直接使用sync.Cond中的锁即可（L字段） Signal：唤醒一个等待时间最长的协程 Broadcast：唤醒所有等待的协程 在调用 cond.Signal 或者 cond.Broadcast 之前，要确保目标协程处于Wait阻塞状态，否则会有死锁问题。\nsync.Map 方法：\nStore：存储一堆key-value值 Load：根据key获取对应的value值，且可判断key是否存在 LoadOrStore：如果key对应的value存在，则返回value；否则，存储对应的value Delete：删除一个key-value键值对 Range：循环迭代sync.Map，效果与for-range一样 func main() { var mp sync.Map var wg sync.WaitGroup wg.Add(10) for i := 0; i \u0026lt; 10; i++ { go func(id int) { defer wg.Done() if _, loaded := mp.LoadOrStore(\u0026#34;initialized\u0026#34;, id); !loaded { fmt.Println(id, \u0026#34;mapper not initialized, init now.\u0026#34;) return } value, loaded := mp.LoadOrStore(id, id) if loaded { fmt.Println(id, \u0026#34;Loaded value \u0026#34;, value) if value != id { fmt.Println(id, \u0026#34;Insert from other goroutine, delete it\u0026#34;) mp.Delete(id) } } else { fmt.Println(id, \u0026#34;Store value \u0026#34;, id) } mp.Store(id + 1, id) }(i) } wg.Wait() wg.Add(1) go func() { defer wg.Done() mp.Range(func(key, value interface{}) bool { fmt.Printf(\u0026#34;range for k = %v, value = %v\\n\u0026#34;, key, value) return true }) }() wg.Wait() } ","permalink":"https://zihuanling.github.io/posts/2022-02-19-go-7-concurrency/","summary":"\u003cp\u003ego在并发的时候，可能会出现多个协程同时访问一个资源的时候，这就出现了资源竞争。也可能出现协程还在运行，但是主程序却退出了的情况，这是缺少控制导致的。\u003c/p\u003e\n\u003cp\u003e用sync包，可以方便的控制资源的访问，也可以方便实现阻塞等待，让协程执行完毕再退出程序，或者执行下一步。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"资源竞争\"\u003e资源竞争\u003c/h2\u003e\n\u003cp\u003e在同一个goroutine中，如果分配的内存没有被其他的goroutine访问，只在该goroutine中使用，则不存在资源竞争问题\u003c/p\u003e\n\u003cp\u003e如果同一块内存被多个goroutine同时访问，就会产生不知道谁先访问，也无法预料最后结果的情况，这就是资源竞争：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 共享的资源\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e// goroutine 不会阻塞下面的代码，此处Sleep一下，防止main goroutine直接退出\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#75715e\"\u003e// 而导致未完成的goroutine也被终止\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Sum is:\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述例子中，sum变量为共享的资源，程序运行的过程中会发生资源竞争。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e使用 go build、go run、go test 这些 Go 语言工具链提供的命令时，添加 -race 标识可以帮你检查 Go 语言代码是否存在资源竞争\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"同步原语\"\u003e同步原语\u003c/h2\u003e\n\u003ch3 id=\"syncmutex\"\u003esync.Mutex\u003c/h3\u003e\n\u003cp\u003e互斥锁，同一个时刻，只有一个goroutine能执行某段代码，其他协程需要等待该协程执行完毕后才能继续执行。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003emutex\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esync\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMutex\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003emutex\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLock\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e// defer mutex.Unlock()\t\t// 使用defer，关闭更加优雅\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003emutex\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnlock\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"syncrwmutex\"\u003esync.RWMutex\u003c/h3\u003e\n\u003cp\u003e读写锁\u003c/p\u003e","title":"7. go并发：同步原语，用sync包控制并发"},{"content":"并发，就是让程序在同一时刻做多件事情。\ngo语言天生自带并发属性，使得并发编程十！分！方！便！，我们只需要 go 函数名() 即可！\\(^o^)/\n进程和线程 进程 程序启动时，系统会为其创建一个进程\n线程 是进程的执行空间，一个进程可以包含多个线程，线程被操作系统调度执行 一个程序启动，对应的进程会被创建，同时也会创建一个线程（主线程），主线程结束，整个程序也就退出了。 我们可以从主线程创建其他的子线程，这就是多线程并发\n协程 Goroutine goroutine比线程更加轻盈，被Go runtime调度。 启动协程： go function()\n// 这里启动了两个goroutine， 一个是用go关键字触发的，另一个是 main goroutine（主线程） func main(){ go fmt.Println(\u0026#34;Hello goroutine.\u0026#34;) fmt.Println(\u0026#34;Main goroutine.\u0026#34;) time.Sleep(time.Second) } Channel 多个goroutine之间，使用 channel进行通信\n声明一个channel // 直接使用 make 创建一个channel，接受的数据类型是string ch := make(chan string) // 一个channel的操作只有两种： // - 发送，向chan中发送值： chan\u0026lt;- // - 接受，从chan中获取值： \u0026lt;-chan demo\nfunc main(){ ch := make(chan string) go func(){ fmt.Println(\u0026#34;Message in goroutine\u0026#34;) ch \u0026lt;- \u0026#34;goroutine finished.\u0026#34;\t// send message to channel. }() fmt.Println(\u0026#34;Main goroutine.\u0026#34;) v := \u0026lt;-ch\t// receive message from channel fmt.Println(\u0026#34;Message from channel: \u0026#34;, v) } 在上面的示例中，我们在新启动的 goroutine 中向 chan 类型的变量 ch 发送值；在 main goroutine 中，从变量 ch 接收值；如果 ch 中没有值，则阻塞等待到 ch 中有值可以接收为止。\n通过 make 创建的 chan 中没有值，而 main goroutine 又想从 chan 中获取值，获取不到就一直等待，等到另一个 goroutine 向 chan 发送值为止。\nchannel 有点像在两个 goroutine 之间架设的管道，一个 goroutine 可以往这个管道里发送数据，另外一个可以从这个管道里取数据，有点类似于我们说的队列。\n无缓冲channel 上述的channel就是一个无缓冲的channel，容量为0，无法存储数据，只能传输。 它的发送和接受操作是同时的，可以称为同步channel\n有缓冲channel 类似一个可阻塞的队列，内部的元素先进先出，通过make第二个参数指定channel容量大小：\nch := make(chan int, 5) // 创建一个容量为5的channel 上述创建一个容量为5的channel，可以存放最多5个int类型的元素，其具备以下特点：\n有缓冲channel内部有一个缓冲队列 发送操作是向队尾插入元素，如果队列已满，则阻塞等待，直到另一个goroutine执行接收操作，释放channel的空间 接收操作是从队头获取一个元素，如果队列已空，阻塞等待，直到有goroutine插入新的元素 关闭channel 使用内置的close函数关闭：\nclose(ch) 如果一个channel被关闭了，就不能向里面发送数据了，继续发送会引发panic异常。\n但是可以接受已关闭channel里面的数据，无数据则接受的是元素类型的零值。\n单向channel 只能发送不能接收，或者只能接收不能发送的channel，成为单向channel\nonlySend := make(chan\u0026lt;- int)\t// 单发 onlyReceive := make(\u0026lt;-chan int)\t// 单收 这样的channel一般在函数或者方法的参数声明中使用：\nfunc counter(sendCh chan\u0026lt;- int){ // 只能往sendCh中发送数据 // num := \u0026lt;-sendCh // 不能从单发channel中接受数据，编译会不通过 } select + channel 示例 // 结构示例 select { case i1 = \u0026lt;-ch1: // todo case ch2 \u0026lt;- i2: // todo default: // default process } 整体结构与switch很像，有case和default，但是select的case是一个个可以操作的channel\n// select 下载例子 func main() { firstCh := make(chan string) secondCh := make(chan string) thirdCh := make(chan string) go func() { firstCh \u0026lt;- downloadFile(\u0026#34;firstCh\u0026#34;) }() go func() { secondCh \u0026lt;- downloadFile(\u0026#34;secondCh\u0026#34;) }() go func() { thirdCh \u0026lt;- downloadFile(\u0026#34;thirdCh\u0026#34;) }() // select 多路复用，哪个channel最先获取到值， // 就说明当前channel下载好了 select { case filePath := \u0026lt;-firstCh: fmt.Println(filePath) case filePath := \u0026lt;-secondCh: fmt.Println(filePath) case filePath := \u0026lt;-thirdCh: fmt.Println(filePath) } } func downloadFile(filename string) string { time.Sleep(time.Second) return \u0026#34;filepath:\u0026#34; + filename } 如果这些 case 中有一个可以执行，select 语句会选择该 case 执行，如果同时有多个 case 可以被执行，则随机选择一个，这样每个 case 都有平等的被执行的机会。如果一个 select 没有任何 case，那么它会一直等待下去。\n在 Go 语言中，提倡通过通信来共享内存，而不是通过共享内存来通信，其实就是提倡通过 channel 发送接收消息的方式进行数据传递，而不是通过修改同一个变量。所以在数据流动、传递的场景中要优先使用 channel，它是并发安全的，性能也不错。(因为channel内部带有同步互斥锁)\n","permalink":"https://zihuanling.github.io/posts/2022-02-18-go-6-concurrency/","summary":"\u003cp\u003e并发，就是让程序在同一时刻做多件事情。\u003c/p\u003e\n\u003cp\u003ego语言天生自带并发属性，使得并发编程\u003cstrong\u003e十！分！方！便！\u003c/strong\u003e，我们只需要 \u003ccode\u003ego 函数名()\u003c/code\u003e 即可！\u003ccode\u003e\\(^o^)/\u003c/code\u003e\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"进程和线程\"\u003e进程和线程\u003c/h2\u003e\n\u003ch3 id=\"进程\"\u003e进程\u003c/h3\u003e\n\u003cp\u003e程序启动时，系统会为其创建一个进程\u003c/p\u003e\n\u003ch3 id=\"线程\"\u003e线程\u003c/h3\u003e\n\u003cp\u003e是进程的执行空间，一个进程可以包含多个线程，线程被操作系统调度执行\n一个程序启动，对应的进程会被创建，同时也会创建一个线程（主线程），主线程结束，整个程序也就退出了。\n我们可以从主线程创建其他的子线程，这就是多线程并发\u003c/p\u003e\n\u003ch3 id=\"协程-goroutine\"\u003e协程 Goroutine\u003c/h3\u003e\n\u003cp\u003egoroutine比线程更加轻盈，被Go runtime调度。\n启动协程： \u003ccode\u003ego function()\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 这里启动了两个goroutine， 一个是用go关键字触发的，另一个是 main goroutine（主线程）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello goroutine.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Main goroutine.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSleep\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"channel\"\u003eChannel\u003c/h2\u003e\n\u003cp\u003e多个goroutine之间，使用 channel进行通信\u003c/p\u003e\n\u003ch3 id=\"声明一个channel\"\u003e声明一个channel\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 直接使用 make 创建一个channel，接受的数据类型是string\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 一个channel的操作只有两种：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// - 发送，向chan中发送值： chan\u0026lt;-\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// - 接受，从chan中获取值： \u0026lt;-chan\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003edemo\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Message in goroutine\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;goroutine finished.\u0026#34;\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// send message to channel.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  }()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Main goroutine.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ev\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ech\u003c/span\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// receive message from channel\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Message from channel: \u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ev\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e在上面的示例中，我们在新启动的 goroutine 中向 chan 类型的变量 ch 发送值；在 main goroutine 中，从变量 ch 接收值；如果 ch 中没有值，则\u003cstrong\u003e阻塞\u003c/strong\u003e等待到 ch 中有值可以接收为止。\u003c/p\u003e","title":"6. go并发：Goroutines和Channels的声明和使用"},{"content":"正确处理错误，可以让程序更加稳定。error是一个错误接口，一般来说，error是不会影响到整个程序运行的，我们可以自行处理。 而panic是会导致程序直接崩溃退出的，我们也可以用 go自带的recover()来恢复panic，在程序崩溃前进行资源的释放工作。\n错误 error接口 error接口只有一个Error方法，返回具体的错误信息（string）\ntype error interface { Error() string } 一个字符串转整数的例子：\nfunc main(){ // try to conver alpha \u0026#34;a\u0026#34; to integer i, err := strconv.Atoi(\u0026#34;a\u0026#34;) if err != nil { fmt.Println(err) } else { fmt.Println(i) } } 尝试将字符 a 转换成为 整数，显然是不成功的，因此返回了err\n一般而言，error 接口用于当方法或者函数执行遇到错误时进行返回，而且是第二个返回值。通过这种方式，可以让调用者自己根据错误信息决定如何进行下一步处理。\nerror 工厂函数 除了可以使用其他函数，自己定义的函数也可以返回错误信息给调用者\n自定义error 自定义error就是先自定义一个新的类型，然后实现error接口\n// 自定义一个结构体，携带错误码和具体的错误信息 type commonError struct { errorCode int\t// 错误码 errorMsg string // 错误信息 } // 实现error接口 func (ce *commonError) Error() string { return ce.errorMsg } error 断言 func add(a, b int) int, error { if a \u0026lt; 0 || b \u0026lt; 0 { return 0, \u0026amp;commonError{ errorCode: 1, errorMsg: \u0026#34;a and b must \u0026gt;= 0\u0026#34; } } return a + b, nil } func main(){ sum, err := add(-1, 2) // 错误类型断言 if cm, ok := err.(*commonError); ok { fmt.Printf(\u0026#34;Error code = %d, error message = %s\\n\u0026#34;, cm.errorCode, cm.errorMsg) } else { fmt.Printf(\u0026#34;Sum = %d\\n\u0026#34;, sum) } } 错误嵌套 Error Wrapping 需求，将一个错误嵌套到另一个错误中：\n比如调用一个函数，返回了一个错误信息 error，在不想丢失这个 error 的情况下，又想添加一些额外信息返回新的 error。这时候，我们首先想到的应该是自定义一个 struct，如下面的代码所示：\ntype MyError struct { err error\t// 指向原有的error msg string\t// 当前error的错误信息 } // 上述就是错误的嵌套 // MyError 实现error接口，返回当前错误信息，和原有错误信息 func (me *MyError) Error() string { return fmt.Sprintf(\u0026#34;Wrapped error: %s -\u0026gt; %s\u0026#34;, me.err.Error(), me.msg) } func main(){ newError := MyError{err, \u0026#34;New error message.\u0026#34;} } 上述实现方式比较繁琐，从Go 1.13开始，go标准库新增了Error Wrapping功能，我们可以使用 fmt.Errorf 加 %w 的方式，实现错误的嵌套\ne := errors.New(\u0026#34;Original Error\u0026#34;) w := fmt.Errorf(\u0026#34;Wrapped Error: %w\u0026#34;, e) fmt.Println(w) errors.Unwrap 函数 通过 errors.Unwrap 解开嵌套的错误\nerrors.Unwrap(w) // w 是一个嵌套的错误，会返回嵌套前的原始错误 errors.Unwrap(e)\t// e 是一个原始的错误，会返回一个nil errors.Is 函数 使用 Error Wrapping 之后，原来使用 == 判断是否同一个错误的方式失效了\n使用 errors.Is：\nfunc Is(err, target error) bool 如果err和target 是同一个，返回true 如果err是一个wrapping error，且target也包含在这个嵌套的error链中的话，也返回true errors.As 函数 errors.As是嵌套error的断言\nvar cm *commonError if errors.As(err,\u0026amp;cm){ fmt.Println(\u0026#34;错误代码为:\u0026#34;,cm.errorCode,\u0026#34;，错误信息为：\u0026#34;,cm.errorMsg) } else { fmt.Println(sum) } Deffered 函数 defer 函数用于保证打开的文件一定会被关闭，功能有点类似于 python 的 with\ndefer 的执行关系是先调用后执行\ndefer func() {fmt.Println(\u0026#34;A\u0026#34;)}() defer func() {fmt.Println(\u0026#34;B\u0026#34;)}() defer func() {fmt.Println(\u0026#34;C\u0026#34;)}() // 最后输出C、B、A defer 有一个调用栈，越早定义越靠近栈的底部，越晚定义月靠近栈的顶部，执行这些defer语句的时候，会先从栈顶弹出一个defer然后执行它。\nPanic 异常 go语言是一门静态的强类型语言，很多问题都尽可能地在编译是捕获，但是有一些只能在运行时检查，比如数组越界访问、不相同的类型强制转换等，这类运行时问题都会引起panic异常。\npanic异常直接将运行中的程序中断\npanic可以接受 interface{}类型的参数（空接口），任何类型的值都可以传到panic\nfunc panic(v interface{}) panic 异常是一种非常严重的情况，会让程序中断运行，使程序崩溃，所以如果是不影响程序运行的错误，不要使用 panic，使用普通错误 error 即可\n使用Recover捕获panic异常 通常情况下，不需要对panic进行处理，直接让其中断程序即可\n但当我们需要在程序崩溃前做一些资源释放处理时，这时就得从panic恢复，然后进行处理\n我们可以使用go语言内置的recover函数恢复panic异常，只有defer修饰的函数才能在程序崩溃时执行，所以：\nrecover函数要结合defer关键字使用才能生效\nfunc connectMysql(ip, username, password string) { if ip == \u0026#34;\u0026#34; { panic(\u0026#34;Invalid ip, expect not empty string.\u0026#34;) } else { fmt.Printf(\u0026#34;Connecting to ip[] with %s@%s\\n\u0026#34;, ip, username, password) } } func main(){ defer func(){ if p := recover(); p != nil { fmt.Printf(\u0026#34;go panic: %v\u0026#34;, p) } }() connectMysql(\u0026#34;\u0026#34;, \u0026#34;root\u0026#34;, \u0026#34;root\u0026#34;) } ","permalink":"https://zihuanling.github.io/posts/2022-02-17-go-5-basic/","summary":"\u003cp\u003e正确处理错误，可以让程序更加稳定。error是一个错误接口，一般来说，error是不会影响到整个程序运行的，我们可以自行处理。\n而panic是会导致程序直接崩溃退出的，我们也可以用 go自带的\u003ccode\u003erecover()\u003c/code\u003e来恢复panic，在程序崩溃前进行资源的释放工作。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"错误\"\u003e错误\u003c/h2\u003e\n\u003ch3 id=\"error接口\"\u003eerror接口\u003c/h3\u003e\n\u003cp\u003eerror接口只有一个Error方法，返回具体的错误信息（string）\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003einterface\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e() \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e一个字符串转整数的例子：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// try to conver alpha \u0026#34;a\u0026#34; to integer\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estrconv\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAtoi\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e尝试将字符 a 转换成为 整数，显然是不成功的，因此返回了err\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e一般而言，error 接口用于当方法或者函数执行遇到错误时进行返回，而且是第二个返回值。通过这种方式，可以让调用者自己根据错误信息决定如何进行下一步处理。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"error-工厂函数\"\u003eerror 工厂函数\u003c/h3\u003e\n\u003cp\u003e除了可以使用其他函数，自己定义的函数也可以返回错误信息给调用者\u003c/p\u003e\n\u003ch3 id=\"自定义error\"\u003e自定义error\u003c/h3\u003e\n\u003cp\u003e自定义error就是先自定义一个新的类型，然后实现error接口\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 自定义一个结构体，携带错误码和具体的错误信息\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecommonError\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eerrorCode\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 错误码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#a6e22e\"\u003eerrorMsg\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// 错误信息\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 实现error接口\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003ece\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ecommonError\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e() \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ece\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eerrorMsg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"error-断言\"\u003eerror 断言\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ecommonError\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eerrorCode\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eerrorMsg\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a and b must \u0026gt;= 0\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 错误类型断言\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecm\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eok\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e.(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ecommonError\u003c/span\u003e); \u003cspan style=\"color:#a6e22e\"\u003eok\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Error code = %d, error message = %s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ecm\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eerrorCode\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ecm\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eerrorMsg\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Sum = %d\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"错误嵌套-error-wrapping\"\u003e错误嵌套 Error Wrapping\u003c/h3\u003e\n\u003cp\u003e需求，将一个错误嵌套到另一个错误中：\u003c/p\u003e","title":"5. go基础：通过 error/deffer/panic 处理错误"},{"content":"函数和方法，大大提高了我们的代码复用率，也提高了代码的可读性。 go语言的函数和方法的定义十分简单，直接使用 func 关键字即可。\n不同于python、C++等有类的概念，而这些类中，又有自己的方法，或者父类的方法。go语言中，我们可以定义一个数据结构，然后给这个数据结构实现自己的方法，同样可以实现【类】的效果，通过组合，同一个结构体中，也可以使用或者覆写其他结构体的方法。\n函数 函数的组成： 关键字 func 函数名 funcName 参数 params 返回值（返回值类型） 函数体 func funcName(params) result { // body } // 示例1：普通的函数定义 func sum(a int, b int) int { return a + b } // 示例2：参数表的类型合并，(a int, b int) -\u0026gt; (a, b int) func sum2(a, b int) int { return a + b } // 示例3：多值返回，用 （） 包裹起来 func sum3(a, b int) (int, error) { if a \u0026lt; 0 || b \u0026lt; 0 { return 0, errors.New(\u0026#34;invalid params\u0026#34;) } return a + b, nil } // 示例4：命名返回参数 // 这里命名的返回值是 res 和 err // 我们可以在函数体中直接使用这两个变量 func sum4(a, b int) (res int, err error) { if a \u0026lt; 0 || b \u0026lt; 0 { // 可以直接返回 return 0, errors.New(\u0026#34;invalid params\u0026#34;) } // 也可以给参数赋值，然后return res = a + b err = nil return } 可变参数 // 这里的可变参数类型是一个切片 func sum5(params ...int) int { res := 0 for _, num := range params { res += num } return res } 需要注意的是，当函数中有普通参数，也有可变参数的时候，那么可变参数一定是放在参数表的最后的。\n包级函数 函数名称的首字母小写代表私有函数，只有在同一个包中才能调用 函数名称的首字母大写代表公有函数，可以在不同的包中被调用 任何一个函数都会从属于一个包 匿名函数和闭包 匿名函数就是没有名称的函数，可以赋值给一个变量\nsum6 := func(a, b int) int { return a + b } fmt.Println(sum6(1, 2)) 闭包：在函数中再定义函数（匿名函数），称为内部函数，这个内部函数可以使用外部函数的变量，这种定义方式称为闭包\nfunc closure() func() int { // 这个闭包函数返回一个函数A，这个函数A的返回值类型是 int [func() int] i := 0 return func() int { i++\t// 这里使用的i是外部函数的变量 i return i } } func main(){ cl := closure() fmt.Println(cl())\t// 输出 1 fmt.Println(cl())\t// 输出 2 fmt.Println(cl())\t// 输出 3 } 方法 方法不同于函数，方法需要一个接收者，而函数不需要\n这个接收者是一个类型，这样方法就和这个类型绑定在一起了，称为这个类型的方法\n// 定义一个Age类型 type Age uint func (age Age) String() { fmt.Println(\u0026#34;age is \u0026#34;, age) } 上述的 String() 就是Age的方法，类型Age是String()的接收者（接收者写在方法名前面，使用小括号()包裹）\n调用方法，可以使用点操作符\nage := Age(20) age.String() 值类型接收者和指针类型接收者 对指针类型接收者的修改是有效的 对值类型接收者的修改是无效的 // 指针类型接收者方法 func (age *Age) Modify(){ *age = Age(30) } age := Age(20) age.String() // age is 20 age.Modify() // 使用一个值类型变量调用指针类型接收者方法，Go会自动帮我们取指针调用 // （\u0026amp;age).Modify() 跟上述语句是一样的 // 使用指针类型变量调用值类型接收者方法，go会帮我们【解引用调用】 // (\u0026amp;age).String() 同 age.String() age.String() // age is 30 简单地理解为：\n值接收者使用的是值的副本来调用方法，而指针接收者使用实际的值来调用方法。\n方法是否可以赋值给一个变量 可以\ntype Age uint // 值类型接收者方法 func (age Age) String() { fmt.Println(\u0026#34;age is \u0026#34;, age) } // 指针类型接收者方法 func (age *Age) Incr() { *age++ } age := Age(10) // 值类型接收者方法赋值给变量 sm := Age.String sm(age) // 调用 // 指针类型接收者方法赋值给变量 im := (*Age).Incr im(\u0026amp;age)\t// 调用 sm(age) 不管方法是否有参数，通过方法表达式调用，第一个参数必须是接收者，然后才是方法自身的参数。\n","permalink":"https://zihuanling.github.io/posts/2022-02-15-go-3-basic/","summary":"\u003cp\u003e函数和方法，大大提高了我们的代码复用率，也提高了代码的可读性。\ngo语言的函数和方法的定义十分简单，直接使用 \u003ccode\u003efunc\u003c/code\u003e 关键字即可。\u003c/p\u003e\n\u003cp\u003e不同于python、C++等有\u003cstrong\u003e类\u003c/strong\u003e的概念，而这些类中，又有自己的方法，或者父类的方法。go语言中，我们可以定义一个数据结构，然后给这个数据结构实现自己的方法，同样可以实现【\u003cstrong\u003e类\u003c/strong\u003e】的效果，通过\u003cstrong\u003e组合\u003c/strong\u003e，同一个结构体中，也可以\u003cstrong\u003e使用或者覆写\u003c/strong\u003e其他结构体的方法。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"函数\"\u003e函数\u003c/h2\u003e\n\u003ch3 id=\"函数的组成\"\u003e函数的组成：\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e关键字 func\u003c/li\u003e\n\u003cli\u003e函数名 funcName\u003c/li\u003e\n\u003cli\u003e参数 params\u003c/li\u003e\n\u003cli\u003e返回值（返回值类型）\u003c/li\u003e\n\u003cli\u003e函数体\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efuncName\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// body\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 示例1：普通的函数定义\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esum\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 示例2：参数表的类型合并，(a int, b int) -\u0026gt; (a, b int)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esum2\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 示例3：多值返回，用 （） 包裹起来\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esum3\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) (\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerrors\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;invalid params\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 示例4：命名返回参数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 这里命名的返回值是 res 和 err\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 我们可以在函数体中直接使用这两个变量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esum4\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) (\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 可以直接返回\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerrors\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;invalid params\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 也可以给参数赋值，然后return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"可变参数\"\u003e可变参数\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 这里的可变参数类型是一个切片\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esum5\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e需要注意的是，当函数中有普通参数，也有可变参数的时候，那么可变参数一定是放在参数表的最后的。\u003c/p\u003e","title":"3. go基础：函数和方法"},{"content":"接口是一种聚合类型，结构体是和调用方的一种约定，有点抽象类的意思。:)\n结构体 结构体定义 结构体是一种聚合类型，里面可以包含任意类型的值，这些值就是我们定义的结构体成员，也称为字段\n在go语言中，要定义一个结构体，需要使用 type+struct 关键字组合。\n// 定义一个代表【人】的结构体 type person struct { name string\t// 名称 age uint\t// 年龄 } 结构体的成员字段并不是必需的，也可以一个字段都没有，这种结构体成为空结构体。\ntype s struct {} 结构体声明使用 // 使用var声明一个person变量，未初始化，里面的值为各自变量的零值 var p person // 可以使用结构体字面量初始化的方式 p2 := person{\u0026#34;Mike\u0026#34;, 10} // 第一个值为 name，第二个值为age，与结构体字段定义顺序有关 // 可以指定字段名初始化，不按定义顺序 p3 := person{age: 10, name: \u0026#34;Mike\u0026#34;} 字段结构体 type address struct { province string city string } // 结构体的字段可以是任意类型，也可以是自定义的结构体 type person struct { name string age uint addr address } // 初始化 p := person{ name: \u0026#34;Mike\u0026#34;, age: 10, addr: address{ province: \u0026#34;Guandong\u0026#34;, city: \u0026#34;Maoming\u0026#34;, }, } fmt.Println(p.addr.province) 接口 接口的定义 接口是和调用方的一种约定，是一个高度抽象的类型，不需要和具体的实现细节绑定在一起。 接口要做的就是定义好约定，告诉调用方，自己可以做什么，但是不需要知道它的内部是如何实现的 我们通过 type + interface关键字定义一个接口\ntype Stringer interface { String() string } 上述我们定义了一个接口 Stringer，这个接口有个方法 String() string\nStringer是Go SDK的一个接口，属于fmt包\n接口的实现 接口的实现者必须是一个具体的类型\nfunc (p person) String() string { return fmt.Sprintf(\u0026#34;name = %s, age = %d\u0026#34;, p.name, p.age) } 这里 person 实现了 Stringer接口的 String() 方法 我们接下来实现可以打印Stringer接口方法的函数：\nfunc printString(s fmt.Stringer){ fmt.Println(s.String()) } 我们可以调用 printString(p)来打印person的内容，因为 person实现了 fmt.Stringer 这个接口 同样，我们让 address 也实现Stringer接口\nfunc (addr address) String() string { return fmt.Sprintf(\u0026#34;Addr province = %s, city = %s\u0026#34;, addr.province, addr.city) } 然后同样可以调用 printString 来输出： printString(p.addr)\n这就是面向接口的好处，只要定义和调用双方满足约定，就可以使用，而不用管具体实现。接口的实现者也可以更好的升级重构，而不会有任何影响，因为接口约定没有变。\n值接收者和指针接收者 调用printString(\u0026amp;p)，可以发现以值类型接收者实现接口的时候，不管是类型本身，还是该类型的指针类型，都实现了改接口\n但是，我们现在将接收者改为指针类型：\nfunc (p *person) String() string { return fmt.Sprintf(\u0026#34;name: %s, age: %d\u0026#34;, p.name, p.age) } 然后调用 printString(p)，将会提示编译不通过，因为person没有实现Stringer接口。 说明：以指针类型接收者实现接口的时候，只有对应的指针类型才被认为实现了该接口\n接口实现规则\n方法接收者 实现的接口类型 (p person) person 和 *person (p *person) *person 当值类型作为接收者时，person 类型和*person类型都实现了该接口。 当指针类型作为接收者时，只有*person类型实现了该接口。 工厂函数 工厂函数一般用于创建自定义的结构体，便于使用者调用\nfunc NewPerson(name string) *person { return \u0026amp;person{name: name} } p1 := NewPerson(\u0026#34;Mike\u0026#34;) 以 errors.New 这个 Go 语言自带的工厂函数为例，演示如何通过工厂函数创建一个接口，并隐藏其内部实现：\n// 工厂函数，返回一个error接口，具体实现是*errorString func New(text string) error { return \u0026amp;errorString{text} } // 结构体，内部一个字段s，存储错误信息 type errorString struct { s string } // 实现error接口 func (e *errorString) Error() string { return e.s } 继承和组合 在 Go 语言中没有继承的概念，所以结构、接口之间也没有父子关系，Go 语言提倡的是组合，利用组合达到代码复用的目的，这也更灵活。\n以io标准包自带的接口为例：\ntype Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // ReadWriter 是 Reader和Writer的组合 type ReadWriter interface { Reader Writer } ReadWriter 接口就是 Reader 和 Writer 的组合，组合后，ReadWriter 接口具有 Reader 和 Writer 中的所有方法，这样新接口 ReadWriter 就不用定义自己的方法了，组合 Reader 和 Writer 的就可以了。\n接口可以组合，结构体也可以组合：\ntype address struct { province string city string } type person struct { name string age uint address } // 初始化 p := person{ name: \u0026#34;mike\u0026#34;, age: 10, address: address{ province: \u0026#34;Guangdong\u0026#34;, city: \u0026#34;Maoming\u0026#34;, }, } // 像使用自己的字段一样使用address的字段 fmt.Println(\u0026#34;my address is \u0026#34;, p.province, p.city) 类型组合后，外部类型不仅可以使用内部类型的字段，也可以使用内部类型的方法，就像使用自己的方法一样。如果外部类型定义了和内部类型同样的方法，那么外部类型的会覆盖内部类型，这就是方法的覆写。\ntype address struct { province string city string } type person struct { name string age uint address } func (addr *address) detail() { fmt.Printf(\u0026#34;Address detail: province = %s, city = %s\\n\u0026#34;, addr.province, addr.city) } func (addr *address) who(){ fmt.Println(\u0026#34;I am address.\u0026#34;) } func (p *person) who() { fmt.Println(\u0026#34;I am person\u0026#34;) } p := person{ name: \u0026#34;mike\u0026#34;, age: 10, address: address{ province: \u0026#34;Guangdong\u0026#34;, city: \u0026#34;Maoming\u0026#34;, }, } p.who()\t// 输出 I am person, 覆写了address.who 方法 p.address.who()\t// 可以调用 address.who，因为方法覆写不会影响到内部的方法实现 p.detail()\t// 输出 Address detail: province = Guangdong, city = Maoming， person没有实现该方法，因此直接调用address的detail方法 p.address.detail()// 输出 Address detail: province = Guangdong, city = Maoming 类型断言 类型断言用来判断一个接口的值是否实现改接口的的某个具体类型\n\u0026lt;接口类型变量\u0026gt;.(断言类型)\n如：\nvar s fmt.Stringer s = p // 断言接口的值 s 是否为一个 person if _, ok := s.(person); ok { fmt.Println(\u0026#34;person yes\u0026#34;) }else { fmt.Println(\u0026#34;person no\u0026#34;) } 类型断言会返回两个值 value, ok, 如果类型断言成功，value将会是一个断言对象的实例，如上述代码，断言成功后会返回一个person实例，ok=true，否则，ok=false\n结构体是对现实世界的描述，接口是对某一类行为的规范和抽象。通过它们，我们可以实现代码的抽象和复用，同时可以面向接口编程，把具体实现细节隐藏起来，让写出来的代码更灵活，适应能力也更强。\n","permalink":"https://zihuanling.github.io/posts/2022-02-16-go-4-basic/","summary":"\u003cp\u003e接口是一种\u003cstrong\u003e聚合类型\u003c/strong\u003e，结构体是和调用方的\u003cstrong\u003e一种约定\u003c/strong\u003e，有点\u003cstrong\u003e抽象类\u003c/strong\u003e的意思。:)\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"结构体\"\u003e结构体\u003c/h2\u003e\n\u003ch3 id=\"结构体定义\"\u003e结构体定义\u003c/h3\u003e\n\u003cp\u003e结构体是一种\u003cstrong\u003e聚合类型\u003c/strong\u003e，里面可以包含任意类型的值，这些值就是我们定义的结构体成员，也称为\u003cstrong\u003e字段\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在go语言中，要定义一个结构体，需要使用 \u003cstrong\u003etype+struct\u003c/strong\u003e 关键字组合。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 定义一个代表【人】的结构体\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 名称\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 年龄\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e结构体的成员字段并不是必需的，也可以一个字段都没有，这种结构体成为空结构体。\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"结构体声明使用\"\u003e结构体声明使用\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 使用var声明一个person变量，未初始化，里面的值为各自变量的零值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 可以使用结构体字面量初始化的方式\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ep2\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Mike\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e} \u003cspan style=\"color:#75715e\"\u003e// 第一个值为 name，第二个值为age，与结构体字段定义顺序有关\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 可以指定字段名初始化，不按定义顺序\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ep3\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e{\u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Mike\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"字段结构体\"\u003e字段结构体\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eaddress\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eprovince\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ecity\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 结构体的字段可以是任意类型，也可以是自定义的结构体\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eaddr\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eaddress\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 初始化\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Mike\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eaddr\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eaddress\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eprovince\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Guandong\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ecity\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Maoming\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ep\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eaddr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eprovince\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"接口\"\u003e接口\u003c/h2\u003e\n\u003ch3 id=\"接口的定义\"\u003e接口的定义\u003c/h3\u003e\n\u003cp\u003e接口是和调用方的\u003cstrong\u003e一种约定\u003c/strong\u003e，是一个\u003cstrong\u003e高度抽象\u003c/strong\u003e的类型，不需要和具体的实现细节绑定在一起。\n接口要做的就是定义好约定，告诉调用方，自己可以做什么，但是不需要知道它的内部是如何实现的\n我们通过 \u003ccode\u003etype + interface\u003c/code\u003e关键字定义一个接口\u003c/p\u003e","title":"4. go基础：结构体和接口"},{"content":"数组，大概是每个变成语言都有基础数据结构。\nGo的切片类型（slice）提供了一种方便和有效的方法来处理类型化数据序列（数组）。slice类似于其他语言中的数组，但有一些不同寻常的属性。\nmap可以理解为一个字典。\n数组声明 可以使用 [长度]类型{} 来定义，比如 [2]string{}定义一个长度为2的字符串数组\n这里的长度必须是一个常量，如果要使用变量去动态生成数组的话，需要使用make，如\nmake([]string, size) 这里的size是一个变变量，生成size长度的字符串数组\n另外需要注意的是, [1]string 和 [2]string 不是用一种类型，长度也是数组类型的一部分\n定义数组时可以省略数组的长度，此时会根据大括号{}里面的元素个数推到出数组的长度：\narr := []string{\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;d\u0026#34;, \u0026#34;e\u0026#34;} 这里会自动生成长度为5的字符串数组，适用于所有元素都被初始化的数据\n但是如果只有特定元素被初始化的数组，就不合适了：\narr := [5]string{1: \u0026#34;a\u0026#34;, 3: \u0026#34;c\u0026#34;} 上述表示数组初始化索引为1的值为a，索引为3的值为c，其他未初始化的为空值（字符串的空值为\u0026quot;\u0026quot;）\n如果我们不指定 [5]string，那么go就会根据我们定义的最大索引值3 来确定数组的长度为4\n数组循环 使用传统的数组循环： for i := 0; i \u0026lt; len(arr); i++ { fmt.Println(arr[i]) } 使用 for range 数组遍历，写法更加简洁 for index, value := range arr { fmt.Printf(\u0026#34;index = %v, value = %v\\n\u0026#34;, index, value) } 切片 Slice 切片的区间是左闭右开，如arr[2:5]，选取的索引取数是[2,3,4] 切片的底层是（原）数组，将切片内容修改之后，原数组也会改变\narr := []int{1,2,3,4,5} sliced := arr[1:3] // sliced = {2,3} sliced[0] = 0 // 切片修改后，原数组内容有会改变 // arr = {1, 0, 3, 4, 5} 使用make声明切片 声明一个元素类型为string的切片，长度为5：slice1 := make([]string, 5) 还可以传入一个容量参数：slice2 := make([]string, 4, 8) 这里声明了一个长度为4，容量为8的字符串数组 长度是切片内元素的个数，容量是切片的空间\n上面的示例说明，Go 语言在内存上划分了一块容量为 8 的内容空间（容量为 8），但是只有 4 个内存空间才有元素（长度为 4），其他的内存空间处于空闲状态，当通过 append 函数往切片中追加元素的时候，会追加到空闲的内存上，当切片的长度要超过容量的时候，会进行扩容。\n除了make，同样可以通过字面量的方式声明： slice1 := []string{\u0026quot;a\u0026quot;, \u0026quot;b\u0026quot;, \u0026quot;c\u0026quot;}\nAppend 通过内置的append函数对一个切片进行元素追加，返回新的切片\n//追加一个元素 slice2:=append(slice1,\u0026#34;f\u0026#34;) //多加多个元素 slice2:=append(slice1,\u0026#34;f\u0026#34;,\u0026#34;g\u0026#34;) //追加另一个切片 slice2:=append(slice1,slice...) 切片元素循环 可以使用for range，同array一样\nMap映射 map是无序的K-V键值对集合，结构为 map[k]v，k为Key，v为Value key的类型必须支持 ==运算符，以便判断其是或否存在，并保证Key的唯一\nMap的声明初始化 使用内置的make函数进行初始化\n// 创建的map，key类型为string，value类型为int nameAgeMap := make(map[string]int) 赋值： nameAgeMap[\u0026quot;Mike\u0026quot;] = 10\nMap获取和删除 map的操作和数组、切片差不多，都是通过[]操作符，不过切片中的[]是索引，而map的[]是key\nGo 语言的 map 可以获取不存在的 K-V 键值对，如果 Key 不存在，返回的 Value 是该类型的零值，比如 int 的零值就是 0。所以很多时候，我们需要先判断 map 中的 Key 是否存在。\n如何判断map中是否存在某个值呢 map 的 []操作会返回两个值\n对应的Value 标记该Key是否存在，存在则该标记为true mapper := map[string]int{\u0026#34;Mike\u0026#34;: 10, \u0026#34;Lisa\u0026#34;: 11} value, tag := mapper[\u0026#34;Mike\u0026#34;] fmt.Println(value, tag)\t// tag 为true value, tag := mapper[\u0026#34;John\u0026#34;] fmt.Println(value, tag)\t// tag 为false 使用delete删除map中的值 delete(mapper, \u0026quot;Mike\u0026quot;)\n遍历map 使用 for range 十分方便，返回 map的key和value\nmapper := map[string]int{\u0026#34;Mike\u0026#34;: 10, \u0026#34;Lisa\u0026#34;: 11} for key, value := range mapper { fmt.Println(key, value) } 需要注意的是 map 的遍历是无序的，也就是说你每次遍历，键值对的顺序可能会不一样。如果想按顺序遍历，可以先获取所有的 Key，并对 Key 排序，然后根据排序好的 Key 获取对应的 Value\nmap的大小 和数组切片不一样的是，map是没有容量的，只有长度，获取长度只需要使用len函数即可： fmt.Println(len(mapper))\nstring 和 []byte 字符串string也是一个不可变的字节序列，可以直接转为字节切片 []byte\ns := \u0026#34;你好golang\u0026#34; bs := []byte(s) fmt.Println(bs) // 同样可以使用[] 操作符来获取指定索引的字节值： fmt.Println(s[0], s[11]) // 在go语言中，utf8编码下，一个汉字对应三个字节，因此，s虽然只有8个字符，但是长度却是12。 如果我们想把汉字当成一个长度计算，可以使用utf8.RuneCountInString函数\n","permalink":"https://zihuanling.github.io/posts/2022-02-10-go-2-basic/","summary":"\u003cp\u003e数组，大概是每个变成语言都有基础数据结构。\u003c/p\u003e\n\u003cp\u003eGo的切片类型（slice）提供了一种方便和有效的方法来处理类型化数据序列（数组）。slice类似于其他语言中的数组，但有一些不同寻常的属性。\u003c/p\u003e\n\u003cp\u003emap可以理解为一个字典。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"数组声明\"\u003e数组声明\u003c/h2\u003e\n\u003cp\u003e可以使用 \u003ccode\u003e[长度]类型{}\u003c/code\u003e 来定义，比如  \u003ccode\u003e[2]string{}\u003c/code\u003e定义一个长度为2的字符串数组\u003c/p\u003e\n\u003cp\u003e这里的长度必须是一个常量，如果要使用变量去动态生成数组的话，需要使用make，如\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003emake([]string, size)\u003c/code\u003e 这里的\u003cstrong\u003esize\u003c/strong\u003e是一个变变量，生成size长度的字符串数组\u003c/p\u003e\n\u003cp\u003e另外需要注意的是, \u003ccode\u003e[1]string\u003c/code\u003e 和 \u003ccode\u003e[2]string\u003c/code\u003e  不是用一种类型，\u003cstrong\u003e长度也是数组类型的一部分\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e定义数组时可以省略数组的长度，此时会根据大括号\u003ccode\u003e{}\u003c/code\u003e里面的元素个数推到出数组的长度：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;d\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;e\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里会自动生成\u003cstrong\u003e长度为5\u003c/strong\u003e的字符串数组，适用于所有元素都被初始化的数据\u003c/p\u003e\n\u003cp\u003e但是如果只有特定元素被初始化的数组，就不合适了：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e]\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述表示数组初始化索引为1的值为a，索引为3的值为c，其他未初始化的为空值（字符串的空值为\u0026quot;\u0026quot;）\u003c/p\u003e\n\u003cp\u003e如果我们不指定 \u003ccode\u003e[5]string\u003c/code\u003e，那么go就会根据我们定义的最大索引值\u003cstrong\u003e3\u003c/strong\u003e 来确定数组的长度为\u003cstrong\u003e4\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"数组循环\"\u003e数组循环\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e使用传统的数组循环：\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u0026lt; len(\u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e); \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e[\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e使用 \u003ccode\u003efor range\u003c/code\u003e 数组遍历，写法更加简洁\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eindex\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;index = %v, value = %v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eindex\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"切片-slice\"\u003e切片 Slice\u003c/h2\u003e\n\u003cp\u003e切片的区间是\u003cstrong\u003e左闭右开\u003c/strong\u003e，如arr[2:5]，选取的索引取数是[2,3,4]\n切片的底层是（原）数组，将切片内容修改之后，原数组也会改变\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e{\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003esliced\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// sliced = {2,3}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003esliced\u003c/span\u003e[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] = \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 切片修改后，原数组内容有会改变\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// arr = {1, 0, 3, 4, 5}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"使用make声明切片\"\u003e使用make声明切片\u003c/h3\u003e\n\u003cp\u003e声明一个元素类型为string的切片，长度为5：\u003ccode\u003eslice1 := make([]string, 5)\u003c/code\u003e\n还可以传入一个容量参数：\u003ccode\u003eslice2 := make([]string, 4, 8)\u003c/code\u003e 这里声明了一个长度为4，容量为8的字符串数组\n长度是切片内\u003cstrong\u003e元素的个数\u003c/strong\u003e，容量是切片的\u003cstrong\u003e空间\u003c/strong\u003e\u003c/p\u003e","title":"2. go基础：使用array/slice/map"},{"content":"go最近很火啊，自带高并发，内置网络模块强大，作为后端工程师一枚，自然也要学起来！\ngo 的基础部分跟我们之前学过的大部分语言是都是十分相似的，上手十分简单。\n第一个go程序 第一个程序大都是hello嘛，向新世界打个招呼 :)\nimport fmt func main(){ fmt.Println(\u0026#34;hello golang!\u0026#34;) } 数据类型以及变量声明的方式 变量声明的方式 // var 变量名 类型 = 表达式 var num int = 1 // 也可以声明变量，但是不赋值 var num int\t// 这样的默认值就是int的默认值 0 // 可以直接赋值并且声明类型 num := 1 // 可以实现多值定义 a, b, c := 1, 2, \u0026#34;hello\u0026#34; var ( a = 1 b = 2 c = \u0026#34;hello\u0026#34; ) 常量声明 常量声明，我们可以直接使用 const 关键字，不需要指定数据的类型，通常，常量我们用大写进行命名\n我们指定变量的值，会自定将变量转为对应的类型\nconst MAX_SIZE = 10 基础类型 整型 有符号：int， int8， int16， int32， int64 无符号：uint，uint8，uint16，uint32，uint64 浮点数：float，float32， float64 布尔型：bool 字符串：string 指针 常量：使用const声明 const name = \u0026quot;Mike\u0026quot; 字符串转换 字符串转数字： strconv.Atoi(\u0026quot;123\u0026quot;) 数字转字符串：strconv.Itoa(123) 数字类型的强制转换：\nint -\u0026gt; float: i2f = float(i) float -\u0026gt; int: f2i = int(f) 3. 控制结构：if/for/switch if // 单纯的if if you == \u0026#34;mike\u0026#34; { fmt.Println(\u0026#34;Yes, I am mike.\u0026#34;) } // if + else if you == \u0026#34;mike\u0026#34; { fmt.Println(\u0026#34;Yes, I am mike.\u0026#34;) } else { fmt.Println(\u0026#34;No, my name is \u0026#34;, you) } // if + else if + else if you == \u0026#34;mike\u0026#34; { fmt.Println(\u0026#34;Yes, I am mike.\u0026#34;) } else if you == \u0026#34;lisa\u0026#34; { fmt.Println(\u0026#34;Ok, I am lisa.\u0026#34;) } else { fmt.Println(\u0026#34;No, my name is \u0026#34;, you) } switch switch 自带break 可以使用fallthrough，让程序执行到下一个条件 switch you { case \u0026#34;mike\u0026#34;: // yes I am mike case \u0026#34;lisa\u0026#34;: // yes I am lisa case \u0026#34;john\u0026#34;: // I am john, fall through to default fallthrough default: // nothing match } for // 简单for循环 for i := 0; i \u0026lt; 100; i++ { // do something... } // for 实现while循环 for { // do something // then break } // 或者 for i \u0026lt; 100 { // do something // increase i // i++ } // 使用for range 遍历数组或者其他可迭代对象 nums := []int{1,2,3} for index, num := range nums { // print(idx, num) } ","permalink":"https://zihuanling.github.io/posts/2022-01-07-go-1-basic/","summary":"\u003cp\u003ego最近很火啊，自带高并发，内置网络模块强大，作为后端工程师一枚，自然也要学起来！\u003c/p\u003e\n\u003cp\u003ego 的基础部分跟我们之前学过的大部分语言是都是十分相似的，上手十分简单。\u003c/p\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch1 id=\"第一个go程序\"\u003e第一个go程序\u003c/h1\u003e\n\u003cp\u003e第一个程序大都是hello嘛，向新世界打个招呼 :)\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hello golang!\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"数据类型以及变量声明的方式\"\u003e数据类型以及变量声明的方式\u003c/h1\u003e\n\u003ch2 id=\"变量声明的方式\"\u003e变量声明的方式\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// var 变量名 类型 = 表达式\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 也可以声明变量，但是不赋值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// 这样的默认值就是int的默认值 0 \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 可以直接赋值并且声明类型\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 可以实现多值定义\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hello\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eb\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hello\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"常量声明\"\u003e常量声明\u003c/h2\u003e\n\u003cp\u003e常量声明，我们可以直接使用 \u003ccode\u003econst\u003c/code\u003e 关键字，不需要指定数据的类型，通常，常量我们用大写进行命名\u003c/p\u003e\n\u003cp\u003e我们指定变量的值，会自定将变量转为对应的类型\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMAX_SIZE\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"基础类型\"\u003e基础类型\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e整型\n\u003cul\u003e\n\u003cli\u003e有符号：int， int8， int16， int32， int64\u003c/li\u003e\n\u003cli\u003e无符号：uint，uint8，uint16，uint32，uint64\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e浮点数：float，float32， float64\u003c/li\u003e\n\u003cli\u003e布尔型：bool\u003c/li\u003e\n\u003cli\u003e字符串：string\u003c/li\u003e\n\u003cli\u003e指针\u003c/li\u003e\n\u003cli\u003e常量：使用const声明 \u003ccode\u003econst name = \u0026quot;Mike\u0026quot;\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"字符串转换\"\u003e字符串转换\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e字符串转数字： \u003ccode\u003estrconv.Atoi(\u0026quot;123\u0026quot;)\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e数字转字符串：\u003ccode\u003estrconv.Itoa(123)\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e数字类型的强制转换：\u003c/p\u003e","title":"1. go基础：数据类型，以及控制结构"},{"content":"searchHidden: true\n","permalink":"https://zihuanling.github.io/leetcode/","summary":"\u003cp\u003esearchHidden: true\u003c/p\u003e","title":"Leetcode"}]