我的第一个 Go 工程需要处理一堆 JSON 测试固件并把 JSON 数据作为参数传给我们搭建的 API 处理。另一个团队为了给 API 提供语言无关的、可预期的输入和输出,创建了这些测试固件。 在强类型语言中,JSON 通常很难处理 —— JSON 类型有字符串、数字、字典和数组。如果你使用的语言是 javascript、python、ruby 或 PHP,那么 JSON 有一个很大的好处就是在解析和编码数据时你不需要考虑类型。 ```bash // in PHP $objec...
-
简化 Go 中对 JSON 的处理
-
让 Go 的错误处理更加强大
Go 所提供的默认的 `errors` 包有很多的不足。编写多层架构应用程序并使用 API 公开功能的时候,相比于单纯的 `string` 类型的值,更需要具有上下文信息的错误处理。意识到这个缺点后,我开始实现一个更强大,更优雅的 error 包。这是一个逐渐演化的过程,随着时间推移,我需要在这个包中引入更多的功能。 在此,我们会探讨我们如何使用一个 `CustomError` 数据类型为应用中带来更多的价值,并且使错误处理更强大。 首先需要明白的是,如果实现了 `Error()` ...
-
如何使用 atomic 包减少锁冲突
![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/1.png) ## 写在前面 > 本文基于 Golang 1.14 Go 提供了 channel 或 mutex 等内存同步机制,有助于解决不同的问题。在共享内存的情况下,mutex 可以保护内存不发生数据竞争(data rac...
-
Go 简单而强大的反向代理(Reverse Proxy)
在本文中,我们将了解反向代理,它的应用场景以及如何在 Golang 中实现它。 反向代理是位于 Web 服务器前面并将客户端(例如 Web 浏览器)的请求转发到 Web 服务器的服务器。它们让你可以控制来自客户端的请求和来自服务器的响应,然后我们可以利用这个特点,可以增加缓存、做一些提高网站的安全性措施等。 在我们深入了解有关反向代理之前,让我们快速看普通代理(也称为正向代理)和反向代理之间的区别。 在**正向代理**中,代理代表原始客户端从另一个网站检索数据。它位于客户端(浏...
-
如何使用 WebAssembly 在浏览器中编译代码
浏览器的功能日益强大,从最早在 [CERN](https://home.cern/science/computing/birth-web) 上分享文章,到今天运行 [Google Earth](https://earth.google.com/web) ,玩 [Unity 3D](https://blogs.unity3d.com/2018/08/15/webassembly-is-here/) 游戏,甚至用 [AutoCAD](https://www.autodesk.com/products...
-
从 JavaScript 到 Go 语言的排序算法
在计算机科学中,排序的意思是获取一个数组,然后重新对他们进行排列,使他们遵循指定的顺序,例如按字母顺序对字符串进行排序、按最小到最大的顺序对数字进行排序,或按结构中的一个字段对结构数组进行排序。您可以使用它(排序)来提高算法的工作效率,或按特定顺序显示数据(例如时间上的从最近到最远)。 对于 Go 中的排序,标准库提供了 sort 包,有意思的是,它使用了 Go 接口来定义对数据进行排序的规则。如果您使用过 JavaScript 的 Array.prototype.sort 方法,(那么,...
-
如何使用 Go 从 HTML 生成 PDF 报告
![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/how-to-generate-a-pdf-report-from-html-with-go/1.jpg) 作为一名开发人员,我有时需要为我的应用程序创建 PDF 报告。完全通过编程来创建它们可能很麻烦,并且每个库都有些不同。最后,让事物看起来像设计师想要的那样可能具有挑战性。如果我们能在不花大量时间的情况下让它看起来像设计,那不是很好吗?设计师和前端...
-
如何以及为什么在 Go 中编写枚举
一个**枚举**(enum,**enumerator** 的缩写),是一组命名的常量值。枚举是一个强大 的工具,让开发者可以创建复杂的常量集,而这些常量集有着有用的名称和简单且唯一的取值。 *在我们走远之前,我想提一下[我最近启动了 Go Mastery,一门动手的 Golang 课程](https://qvault.io/go-mastery-course/)。如果想要了解更多关于 Go 的信息,请尝试下该课程,现在让我们回到枚举上面。* ## 语法示例 在一个常量声明中,[...
-
如何在 Go 中组织项目结构
GCTT 译者注:在翻译这篇文章之前,我自己其实对 Bob 大叔的 Clean Architecture 也做过一些研究,在项目中实践之后,也确确实实体验到了分层的魅力。在层与层之间将依赖进行隔离,各个层只关注自己本身的逻辑,所以能让开发者只关注本层的业务逻辑,也更容易进行单元测试,无形中就提高了你代码的质量和可阅读性。我觉得如果你对自己的代码有追求,就一定要去学习一下 Clean Architecture。 当然另一方面,Clean Architecture 也不是银弹,在复杂的项目中确...
-
深入剖析 Golang Pprof 标签
Polar Signals 提供的持续分析工具可以和任何格式的 pprof 分析适配,Go 深度集成了 [pprof](https://github.com/google/pprof) 甚至支持了它的`标签`特性。 然而,自从我们发布了我们的 [持续分析产品](https://www.polarsignals.com/blog/posts/2021/02/09/announcing-polar-signals-continuous-profiler/) 之后,收到了很多工程师的反馈,发现许...
-
Go 服务中 HTTP 请求的生命周期
Go 语言对于编写 HTTP 服务来说是一个常见且非常合适的工具。这篇博文通过一个 Go 服务来探讨一个典型 HTTP 请求的路由,涉及路由,中间件以及比如并发之类的相关问题。 为了有具体的代码可以参考,让我们先从这段简单的服务代码开始(来自于 [https://gobyexample.com/http-servers](https://gobyexample.com/http-servers)) ```go package main import ( "fmt" "...
-
Go:用 kqueue 实现一个简单的 TCP Server
## 介绍 在 [非阻塞 I/O 超简明介绍](https://dev.to/frosnerd/explain-non-blocking-i-o-like-i-m-five-2a5f) 中,我们已经讨论过现代 Web 服务器可以处理大量并发请求,这得益于现代操作系统内核内置的事件通知机制。受 Linux epoll [ [文档](https://man7.org/linux/man-pages/man7/epoll.7.html) ]启发,FreeBSD 发明了 kqueue [ [论文]...
-
Go:程序如何恢复(recover)?
![由 Renee French 创作的原始 Go Gopher 作品,为“ Go 的旅程”创建插图。](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20201002-Go-How-Does-a-Program-Recover/1_4zRau44piN5HjUnTnJsMOw.png) 当程序无法适当处理错误时,比如无效的内存访问,Go 中的 panic 就会被触发。如果错误是意料之外,且没有其他方式处...
-
Go: Goroutine 泄漏检查器
![Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.](https://raw.githubusercontent.com/studygolang/gctt-images2/master/goroutine_leak_detector/header_img.png) 具有监控存活的 goroutine 数量功能的 APM (Appl...
-
Go 中优化字符串的比较操作
![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200912-Optimizing-String-Comparisons-in-Go/00.jpeg) 你想让你的 Go 程序运行得更快些吗?优化下 Go 程序中的字符串比较可以减少程序的响应时间,以及增强它的可扩展性。对比两个字符串来检查它们是否相等需要一定的处理能力,并不是所有的比较操作都是相同的。 在之前的一篇文章中,我们研究了 [Go...
-
Go:多错误管理
![由Renee French创作的原始Go Gopher制作的“ Go的旅程”插图。](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200901-Go-Multiple-Errors-Management/Illustration.png) Go 语言中的错误(error)管理总是能引起争论,同时,在关于使用 Go 语言的时候,开发者面对最大的挑战的[年度调查](https://blog.gola...
-
Facebook的 Go ORM:ent 移动到了 Linux 基金会
#### [点击查看原文](https://thenewstack.io/facebooks-golang-object-relational-mapper-moves-to-the-linux-foundation/) ![](https://cdn.thenewstack.io/media/2021/09/7e01e106-go-ent.png) "[Ent](https://entgo.io/)" 是最初由 Facebook 创建并于 2019 年开源的 Go 实体框架,现...
-
Go Module 教程第 5 部分:Vendoring
前四个教程: - [Go Module 教程第 1 部分:为什么和做什么](https://studygolang.com/articles/24580) - [Go Module 教程第 2 部分:项目、依赖和 gopls](https://studygolang.com/articles/35202) - [Go Module 教程第 3 部分:最小版本选择](https://studygolang.com/articles/35210) - [Go Module 教程第 4 部...
-
Go Module 教程第 4 部分:镜像、校验和以及 Athens
前三个教程: - [Go Module 教程第 1 部分:为什么和做什么](https://studygolang.com/articles/24580) - [Go Module 教程第 2 部分:项目、依赖和 gopls](https://studygolang.com/articles/35202) - [Go Module 教程第 3 部分:最小版本选择](https://studygolang.com/articles/35210) > 注意,该教程基于 Go1.13。...
-
Go Module 教程第 3 部分:最小版本选择
前两个教程: - [Go Module 教程第 1 部分:为什么和做什么](https://studygolang.com/articles/24580) - [Go Module 教程第 2 部分:项目、依赖和 gopls](https://studygolang.com/articles/35202) > 注意,该教材基于 1.13。最新版本可能会有所不同。 ## 引言 每个依赖管理解决方案都必须解决选择依赖版本的问题。目前存在的许多版本选择算法都试图识别任何依赖关...
-
Go Module 教程第 2 部分:项目、依赖和 gopls
## 引言 模块是集成到 Go 系统中,为依赖管理提供支持。这意味着模块几乎可以触及任何与源代码相关的内容,包括编辑器支持。为了向编辑器提供模块支持(以及其他原因) ,Go 团队构建了一个名为 [gopls](https://github.com/golang/tools/blob/master/gopls/doc/user.md) 的服务,它实现了语言服务器协议([LSP](https://microsoft.github.io/language-server-protocol/))。L...
-
Go: 内置函数优化
![由 Renee French 创作的原始 Go Gopher 作品,为“ Go 的旅程”创作的插图。](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200813-Go-Built-in-Functions-Optimizations/Illustration.png) ℹ️ 这篇文章基于 Go 1.13。 Go 语言提供内置函数来辅助开发者处理 channel,slice,或者 map。...
-
Go: 定时器的生命周期
![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200704-go-timers-life-cycle/图0.png) > 本篇文章基于 Go `1.14` `定时器` 对于在将来的某个时刻执行代码时非常有用。Go 内部在管理创建的定时器的同时,也会对其执行进行规划。后者可能有点棘手,因为 Go 调度器是一个协作式(`cooperative`)调度器,这意味着一个 goroutine 必须...
-
当在 Go 中使用微服务还不够时:介绍 DDD Lite
当我开始用 Go 工作时,社区并不看好类似 DDD(Domain-Driven Design 领域驱动设计)和清晰架构这样的技术。我很多次听到这样的声音:*“不要在 Golang 中用 Java!”,“我已经在 Java 中见过了,请别这样做!”*。 这些时候,我已经有了近 10 年的 PHP 和 Python 经验。我已经见过太多糟糕的事情了。我记得所有那些“八千行”(有着 8 千行以上代码的方法 😉)和没有人愿意维护的应用。我查看了这些丑陋怪物以前的 Git 历史,他们最初看起来是无害...
-
Go:对象文件&重定位
![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200701-Go:Object-File%26Relocations/1_HxAju6n33e9Y8AJwMuQL3w.png) **本文章基于 Go 1.14** 重定位是链接过程中的一个阶段,重定位是链接过程中为每个外部符号分配适当地址。由于每个包都是单独编译的,因此它们不知道来自其它包的函数或者变量在哪里。 让我们从一个需要重定位的简...
-
Go 语言 HTML 安全编码
> 免责声明:这不是一个官方的谷歌帖子或公告,本文只是我理解的一些可以用的理论方法。 谷歌信息安全小组谷歌发布了 [Go 语言 “safehtml” 包](https://github.com/google/safehtml) 。如果你有兴趣使你的应用程序能够自适应服务器端 XSS,那么你可能希望采用它来替代“html/template”。(将你的应用程序中的 HTML 类库)迁移到 safehtml 会非常简单,因为它只是原始 html/template 标准包的一个强化分支。如果你的应...
-
容器的艰难之旅:gocker —— Go 实现的迷你 Docker
容器很受欢迎,但是被误解了。 容器已成为应用程序在服务器上打包和运行的默认方式,最初是由 Docker 普及的。现在,Docker 本身被误解了。它是一个公司的名字和一条命令(更确切地说是一组命令),使你容易地管理容器(创建,运行,删除,连接网络)。但是容器本身是由一组操作系统原语创建的。在本文中,我们将关注 Linux 操作系统上的容器,就像 [Windows 上的容器](https://docs.microsoft.com/en-us/virtualization/windowscontai...
-
为什么说 Golang 是 DevOps 专业人士的第一首选?
Golang 是当今最受欢迎的编程语言之一,现在就让我们来看看它在 DevOps 空间中能够做什么? Golang,也称为 “Go”,是一种具备快速和高性能的编译型语言,这是被设计成为易于阅读和理解的原因。Go 是由 Rob Pike,Robert Griesemer 和 Ken Thompson 等人在 Google 时编写的,于 2009 年 11 月首次发布。 Golang 被设计成高度简洁和易于理解的语法。 这是 Golang 中经典的 “hello world” 示例...
-
unsafe 真就不安全吗?- part2
在[上篇文章](https://studygolang.com/articles/28433)中,我已经谈到了 unsafe 包的初衷和功能。但还有一件事情没有解释。 ## type pointer 此类型表示指向任意类型的指针,这意味着,unsafe.Pointer 可以转换为任何类型或 uintptr 的指针值。你可能会想: 有什么限制吗?没有,是的... 你可以转换 Pointer 为任何你想要的,但你必须处理可能的后果。为了减少可能出现的问题,你可以使用某些模式: > ...
-
Go:字符串以及转换优化
![由 Renee French 创作的原始 Go Gopher 作品,为“ Go 的旅程”创作的插图。](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200610-Go-String-and-Conversion-Optimization/Illustration.png) ℹ️ 这篇文章基于 Go 1.14。 在 Go 语言中,将 byte 数组转换为 string 时,随着转换后字符串...
-
Golang 逃逸分析简介
# Golang 逃逸分析简介 ![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200907-Go-Introduction-to-the-Escape-Analysis/0.png) > 本篇文章基于 Golang 1.13. `逃逸分析` 是 Golang 编译器中的一个阶段,它通过分析用户源码,决定哪些变量应该在堆栈上分配,哪些变量应该逃逸到堆中。 ## 静态分析 ...
-
鸭子类型 vs 结构化类型 vs 标称类型
Go 语言是哪一种? 编程语言具有类型概念 - 布尔类型,字符串,整型或者被称为类或者结构体的更加复杂的结构。根据如何将类型解析并赋值给各种构造(例如变量,表达式,函数,函数参数等),编程语言可以归类为鸭子类型,结构化类型或标称类型。 本质上,分类决定了对象如何被解析并推断为具体的类型。 ![](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200608-duck-typing-vs-st...
-
Go: stringer 命令,通过代码生成提高效率
![由 Renee French 创作的原始 Go Gopher 作品,为“ Go 的旅程”创作的插图。](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200605-Go-Stringer-Command-Efficiency-Through-Code-Generation/00.png) ℹ️ 这篇文章基于 Go 1.13。 `stringer` 命令的目标是自动生成满足 `fmt.Str...
-
如何用 Go 编写词法分析器
*词法分析器是所有现代编译器的第一阶段,但是如何编写呢?让我们用 Go 从头开始构建一个。* ![lexer](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200601-How-to-Write-a-Lexer-in-Go/how-to-write-a-lexer-in-go-featured.jpg) ## 什么是词法分析器? 词法分析器有时也称为扫描器,它读取源程序并将输入转换为标记...
-
在 BigCache 中存储任意类型(interface{})
这篇文章也发在我的个人 [博客](https://calebschoepp.com/blog) 最近在工作中,我的任务是向我们的一个 Golang 服务添加缓存。这个服务需要传入请求以提供用于身份验证的 API key。因此,对于每个请求,该服务都会额外查询数据库以验证 API key,尽管它通常是相同的 key。这很不好。实现缓存最终比我想象的要难得多。 经过调研和工程师之间详尽讨论之后,我们认为 [BigCache](https://github.com/allegro/bigc...
-
将 Go 作为脚本语言用
Go 作为一种可用于创建高性能网络和并发系统的编程语言,它的生态应用变得[越来越广泛](https://blog.golang.org/survey2019-results),同时,这也激发了开发人员使用 Go 作为脚本语言的兴趣。虽然目前 Go 还未准备好作为脚本语言 “开箱即用” 的特性,用来替代 Python 和 Bash ,但是我们只需要一点点准备工作就可以达到想要的目标。 [正如来自 Codelang 的 Elton Minetto 所说的那样](https://dev.to/c...
-
Go 函数选项模式
作为 Golang 开发者,遇到的许多问题之一就是尝试将函数的参数设置成可选项。这是一个十分常见的场景,您可以使用一些已经设置默认配置和开箱即用的对象,同时您也可以使用一些更为详细的配置。 对于许多编程语言来说,这很容易。在 C 语言家族中,您可以提供具有同一个函数但是不同参数的多个版本;在 PHP 之类的语言中,您可以为参数提供默认值,并在调用该方法时将其忽略。但是在 Golang 中,上述的做法都不可以使用。那么您如何创建具有一些其他配置的函数,用户可以根据他的需求(但是仅在需要时)指...
-
为什么选择 Golang?彻底解决争论
我们都经历过绿地(greenfield)项目初期的幸福感。对于每一个决定,你都有无限的选择余地,当你启动项目编码的时候,你首先要处理的问题是我应该选择哪种编程语言来开发我的项目? 我是应该使用一种新的有趣的语言还是应该坚持使用最流行的语言?幸运的是,在 baconce Technology,我们的专家对这些问题非常熟悉,因为我们处理过各种宽高比(shapes)和尺寸的客户端(屏幕),而且我们总是在考虑编程语言应该适合服务器端应用程序开发这一共同可能性的情况下做出选择。这就是为什么我们特别重...
-
GoLang AST 简介
## 写在前面 当你对GoLang AST感兴趣时,你会参考什么?文档还是源代码? 虽然阅读文档可以帮助你抽象地理解它,但你无法看到API之间的关系等等。 如果是阅读整个源代码,你会完全看懂,但你想看完整个代码我觉得您应该会很累。 因此,本着高效学习的原则,我写了此文,希望对您能有所帮助。 让我们轻松一点,通过AST来了解我们平时写的Go代码在内部是如何表示的。 本文不深入探讨如何解析源代码,先从AST建立后的描述开始。 > 如果您对代码如何转换为AST...
-
Go:使用 Ebiten 在 2D 视频游戏中进行图像渲染
![Ebiten](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200205-Go-Image-Rendering-in-2D-Video-Games-with-Ebiten/illustration.png) 插图由创作原始 Go Gopher 作品的 Renee French 为“ Go的旅程”创作。 *本文基于 Ebiten 1.10。* [Ebiten](https://ebiten...
-
Go 官方 2020 年开发者调查报告
2021 年 3 月 9 日,在 Go 官方博客发布了 Go 开发者 2020 年调查报告。一起来看看该报告的内容吧。 > 2020 年,一共有 9648 人参与投票,大约相当于 2019 年的投票人数。 说明:你可能会注意到有些问题的样本量比其他问题小 (“n =”)。这是因为有些问题是向所有人展示的,而另一些只是向随机的一部分受访者展示。 ## 01 报告摘要 - Go 的使用场景和企业都在扩大,76% 的受访者工作中使用 Go;66% 的人说 Go 对他们公司的成功...
-
Go: 在我们的 ETL 中使用扇出模式
![](https://raw.githubusercontent.com/studygolang/gctt-images/master/20190703-go-fan-out-pattern/cover.png) Go 语言在构建微服务、特别是有使用 gRPC 的应用中,非常地流行,其实在构建命令行程序时也是特别地好用。为了学习扇出模式,我会基于我们公司使用 ETL 的例子,来介绍这个模式。 ## ETL ETL(提取(Extract),转换(Transform),加载(Lo...
-
Go 中基于 IP 地址的 HTTP 请求限流
如果你在运行 HTTP 服务并且想对 endpoints 进行限速,你可以使用维护良好的工具,例如 [github.com/didip/tollbooth](https://github.com/didip/tollbooth)。但是如果你在构建一些非常简单的东西,自己实现并不困难。 我们可以使用已经存在的试验性的 Go 包 `x/time/rate`。 在本教程中,我们将创建一个基于用户 IP 地址进行速率限制的简单的中间件。 ## 「干净的」HTTP 服务 让我们从构建一...
-
【Golang】不要在生产环境使用 http.DefaultServerMux
# 【Golang】不要在生产环境使用 http.DefaultServerMux 我看到许多文章和帖子都显示了一种方便简单的方法来这样创建 go 的 Web 服务: ```golang package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Requ...
-
Go:死锁是如何触发的?
![illustration](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200711-Go-How-Are-Deadlocks-Triggered/illustration.png) 由创作原始 Go Gopher 作品的 Renee French 为“ Go 的旅程”创作的插图。 *本文基于 Go 1.14。* 死锁是当 Goroutine 被阻塞而无法解除阻塞时产生的一种状态。G...
-
为什么不使用 Kubernetes
![When to choose Kubernetes?](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200603-Why-Not-Use-Kubernetes/00.png) 很多团队都很兴奋地开始使用 Kubernetes。其中一些团队希望能充分利用它的弹性、灵活性、可移植性、可靠性以及其他的一些 Kubernetes 能原生地提供的优势。也有些团队只是热衷于技术,仅仅想使用下这个平台,来更好...
-
我是如何在 Go 中构建 Web 服务的
从用了近十年的 C# 转到 Go 是一个有趣的旅程。有时,我陶醉于 Go 的[简洁](https://www.youtube.com/watch?v=rFejpH_tAHM);也有些时候,当熟悉的 OOP (面向对象编程)[模式](https://en.wikipedia.org/wiki/Software_design_pattern)无法在 Go 代码中使用的时候会感到沮丧。幸运的是,我已经摸索出了一些写 HTTP 服务的模式,在我的团队中应用地很好。 当在公司项目上工作时,我倾向把可...
-
Golang 有多快?—— 对比 Go 和 Python 实现的 Eratosthenes 筛选法
![Photo by Max Duzij on Unsplash](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200523-How-Fast-Is-Golang/Photo.jpeg) 时间宝贵,所以为什么浪费时间等待程序运行?除非过去几年与世隔绝,否则不会错过 Go 的兴起。由谷歌工程师 Robert Griesemer,Rob Pike 和 Ken Thompson [创造的](https:/...
-
Go:使用 Delve 和 Core Dump 调试代码
![由 Renee French 创作的原始 Go Gopher 为“ Go Go 之旅”创建的插图。](https://raw.githubusercontent.com/studygolang/gctt-images2/master/20200523-Go-Debugging-with-Delve-and-Core-Dumps/Illustration.png) ℹ️ 这篇文章基于 Go Delve 1.4.1。 core dump 是一个包含着意外终止的程序其内存快照的文件。这个文...
-
Go:Goroutine 的切换过程实际上涉及了什么
![Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.](https://raw.githubusercontent.com/studygolang/gctt-images/master/go-what-does-a-goroutine/1.png) 本文基于 Go 1.13 版本。 Goroutine 很轻,它只需要 2Kb ...