<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go on prometheus</title><link>https://new.halfrost.com/zh/categories/go/</link><description>Recent content in Go on prometheus</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><copyright>halfrost</copyright><lastBuildDate>Wed, 10 Jan 2018 09:19:00 +0000</lastBuildDate><atom:link href="https://new.halfrost.com/zh/categories/go/index.xml" rel="self" type="application/rss+xml"/><item><title>Google S2 是如何解决空间覆盖最优解问题的?</title><link>https://new.halfrost.com/zh/go_s2_regioncoverer/</link><pubDate>Wed, 10 Jan 2018 09:19:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_regioncoverer/</guid><description>前言 这篇不出意外就是 Google S2 整个系列的最终篇了。这篇里面会把 regionCoverer 算法都讲解清楚。至于 Google S2 库里面还有很多其他的小算法，代码同样也很值得阅读和学习，这里也就不一一展开了，有兴趣的读者可以把整个库都读一遍。
在写这篇文章的同时，发现了库的作者的一些新的 commit ，比如 2017年12月4号的 commit f9610db2b871b54b17d36d4da6a4d6a2aab6018d，这次提交的改动更改了 README，别看只改了文档，其实里面内容很多。
-For an analogous library in C++, see -https://code.google.com/archive/p/s2-geometry-library/, and in Java, see -https://github.com/google/s2-geometry-library-java ------------------------------------------------------------------------------------ +For an analogous library in C++, see https://github.com/google/s2geometry, in +Java, see https://github.com/google/s2-geometry-library-java, and Python, see +https://github.com/google/s2geometry/tree/master/src/python 可以看到他们把原来存在 Google 官方私有代码仓库里面的代码放到了 github。之前都只能在代码归档里面查看 C++ 代码，现在直接可以在 github 上查看了。方便了很多。
+More details about S2 in general are available on the S2 Geometry Website +[s2geometry.</description></item><item><title>四叉树上如何求希尔伯特曲线的邻居 ？</title><link>https://new.halfrost.com/zh/go_s2_hilbert_neighbor/</link><pubDate>Wed, 15 Nov 2017 03:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_hilbert_neighbor/</guid><description>关于邻居的定义，相邻即为邻居，那么邻居分为2种，边相邻和点相邻。边相邻的有4个方向，上下左右。点相邻的也有4个方向，即4个顶点相邻的。
如上图，绿色的区域是一颗四叉树表示的范围，四叉树上面有一个点，图中黄色区域标明的点。现在想求四叉树上黄色的点的希尔伯特曲线邻居。图中黑色的线就是一颗穿过四叉树的希尔伯特曲线。希尔伯特曲线的起点0在左上角的方格中，终点63在右上角的方格中。
红色的四个格子是黄色格子边相邻邻居，蓝色的四个格子是黄色格子的顶点相邻的邻居，所以黄色格子的邻居为8个格子，分别表示的点是8，9，54，11，53，30，31，32 。可以看出来这些邻居在表示的点上面并不是相邻的。
那么怎么求四叉树上任意一点的希尔伯特曲线邻居呢？
一. 边邻居 边邻居最直接的想法就是 先拿到中心点的坐标 (i，j) ，然后通过坐标系的关系，拿到与它边相邻的 Cell 的坐标 (i + 1，j) ， (i - 1，j) ， (i，j - 1) ， (i，j + 1) 。
实际做法也是如此。不过这里涉及到需要转换的地方。这里需要把希尔伯特曲线上的点转换成坐标以后才能按照上面的思路来计算边邻居。
关于 CellID 的生成与数据结构，见笔者这篇《Google S2 中的 CellID 是如何生成的 ？》
按照上述的思路，实现出来的代码如下：
func (ci CellID) EdgeNeighbors() [4]CellID { level := ci.Level() size := sizeIJ(level) f, i, j, _ := ci.faceIJOrientation() return [4]CellID{ cellIDFromFaceIJWrap(f, i, j-size).Parent(level), cellIDFromFaceIJWrap(f, i+size, j).Parent(level), cellIDFromFaceIJWrap(f, i, j+size).Parent(level), cellIDFromFaceIJWrap(f, i-size, j).</description></item><item><title>Google S2 中的 CellID 是如何生成的 ？</title><link>https://new.halfrost.com/zh/go_s2_cellid/</link><pubDate>Thu, 02 Nov 2017 08:37:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_cellid/</guid><description>笔者在《高效的多维空间点索引算法 — Geohash 和 Google S2》文章中详细的分析了 Google S2 的算法实现思想。文章发出来以后，一部分读者对它的实现产生了好奇。本文算是对上篇文章的补充，将从代码实现的角度来看看 Google S2 的算法具体实现。建议先读完上篇文章里面的算法思想，再看本篇的代码实现会更好理解一些。
一. 什么是 Cell ？ Google S2 中定义了一个将单位球体分解成单元格层次结构的框架。每个 Cell 的单元格是由四个测地线限定的四边形。通过将立方体的六个面投影到单位球上来获得层级的顶层，通过递归地将每个单元细分为四个子层来获得较低层。例如，下面的图片显示了六个 face 中 Cell 的两个，其中一个已经细分了几次：
注意 Cell 边缘似乎是弯曲的，这是因为它们是球形测地线，即球体上的直线（类似于飞机飞行的路线）
层次结构中的每个单元格都有一个 level 级别，定义为单元格细分的次数（以面单元格开始）。细胞水平范围从 0 到 30。在 level - 30 的最小细胞被称为叶细胞，总共有6 * 4^30^个，每个在地球表面 1cm 左右。 （每个级别的单元格大小的细节可以在S2 Cell ID 数据结构找到）
S2 Level 对于空间索引和将区域逼近为单元集合非常有用。Cell 可用于表示点和区域：点通常表示为叶子节点，而区域表示为任何 Level 的 Cell 的集合。例如，下面是夏威夷近似的22个单元的集合：
二. S(lat,lng) -&amp;gt; f(x,y,z) 纬度 Latitude 的取值范围在 [-90°,90°] 之间。 经度 Longitude 的取值范围在 [-180°,180°] 之间。
第一步转换，将球面坐标转换成三维直角坐标
func makeCell() { latlng := s2.</description></item><item><title>神奇的德布鲁因序列</title><link>https://new.halfrost.com/zh/go_s2_de_bruijn/</link><pubDate>Fri, 27 Oct 2017 00:12:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_de_bruijn/</guid><description>数学中存在这样一个序列，它充满魔力，在实际工程中也有一部分的应用。今天就打算分享一下这个序列，它在 Google S2 中是如何使用的以及它在图论中，其他领域中的应用。这个序列就是德布鲁因序列 De Bruijn。
一. 从一个魔术开始说起 有这样一个扑克牌魔术。魔术师手上拿着一叠牌，给5个人(这里的人数只能少于等于32，原因稍后会解释)分别检查扑克牌，查看扑克牌的花色和点数是否都是不同的，即没有相同的牌。
检查完扑克牌，没有重复的牌以后，就可以给这5个人洗牌了。让这5个人任意的抽一叠牌从上面放到下面，即切牌。5个人轮流切完牌，牌的顺序已经全部变化了。
接着开始抽牌。魔术师让最后一个切牌的人抽走这叠牌最上面的一张，依次给每个人抽走最上面的一张。这时候抽走了5张牌。魔术师会说，“我已经看透了你们的心思，你们手上的牌我都知道了”。然后魔术师会让拿黑色牌的人站起来(这一步很关键！)。然后魔术师会依次说出所有人手上的牌。最后每个人翻出自己的牌，全部命中。全场欢呼。
二. 魔术原理揭秘 在整个魔术中，有三个地方比较关键。第一个是参与的人数只能少于等于32 。一副完整的扑克牌中，总共有54张牌，但是除去2张鬼牌(因为他们花色只有2种)，总共就52张牌。
在上述魔术中，所有的牌都用二进制进行编码，要想任意说出任意连续的5张牌，那么必须这副牌具有全排列的特性。即枚举所有种组合，并且每个组合都唯一代表了一组排列。
如果窗口大小为5，5张连续的扑克牌。二进制编码 2^5^ = 32 ，所以需要32张牌。如果窗口大小为6，6张连续的扑克牌，二进制编码 2^6^ = 64，需要64张扑克牌。总共牌只有52张，所以不可能到64张。所以32个人是上限了 。
第二个关键的地方是，只有让拿黑色的牌的人或者拿红色的牌的人站出来，魔术师才能知道这5个人拿到的连续5张扑克牌究竟是什么。其实魔术师说“我已经知道你们所有人拿到的是什么牌”的时候，他并不知道每个人拿到的是什么牌。
扑克牌除了点数以外，还有4种花色。现在需要32张牌，就是1-8号牌，每号牌都有4种花色。花色用2位二进制编码，1-8用3位二进制编码。于是5位二进制正好可以表示一张扑克牌所有信息。
如上图，00110 表示的就是梅花6 。11000 表示的是红桃8（因为没有 0 号牌，所以000就表示8）
第一步将扑克牌编码完成以后，第二步就需要找到一个序列，它必须满足以下的条件：由 2^n-1^个1和2^n-1^个0构成的序列或者圆排列，是否能存在在任意 n 个位置上0，1序列两两都不同。满足这个条件的序列也称为 n 阶完备二进圆排列。
这个魔术中我们需要找的是 5 阶完备二进圆排列。答案是存在这样一个满足条件的序列。这个序列也就是文章的主角，德布鲁因序列。
上述序列就是一个窗口大小为5的德布鲁因序列。任意连续的5个二进制相互之间都是两两不同的。所以给观众任意洗牌，不管怎么洗牌，只要最终挑出来是连续的5张，这5张的组合都在最终的结果之中。
将窗口大小为5的德布鲁因序列每5个二进制位都转换成扑克牌的编码，如下：
所以32张牌的初始顺序如下：
梅花8，梅花A，梅花2，梅花4，黑桃A，方片2，梅花5，黑桃3，方片6，黑桃4，红桃A，方片3，梅花7，黑桃7，红桃7，红桃6，红桃4，红桃8，方片A，梅花3，梅花6，黑桃5，红桃3，方片7，黑桃6，红桃5，红桃2，方片5，黑桃2，方片4，黑桃8，方片8。
上面这32张牌的初始顺序魔术师是要记在心里的。
将所有的排列组合列举出来，如上图。当魔术师让黑色或者红色的牌的人出列的时候，就能确定到具体是哪一种组合了。于是也就可以直接说出每个人手上拿的是什么牌了。
这个魔术中选取的德布鲁因序列也非常特殊，是可以通过一部分的递推得到。
这个特殊的序列，任意取出其中一个窗口，即5个连续的二进制，5个二进制的第一位和第三位，或者倒数第三位和倒数第五位相加，加法遵循二进制规则，即可得到这个窗口紧接着的下一位。
如上图的例子，假设当前窗口里面的五位是 00001，左数第一位加上第三位，或者右数第三位加上第五位，得到的是0，那么这个窗口紧接着的后一位就是0 ，即 000010 。再举一个例子，当前窗口里面是 11000 ，左数第一位加上第三位为1，所以紧接着的下一位是1，即 110001 。
最后一个关键的地方就在切牌的手法上。由于德布鲁因序列是一个循环的序列，为了维护这个序列，切牌只能把上面的牌切到下面去，不能乱切。只有上面的牌切到下面，因为循环的原因，这样才不能影响德布鲁因序列。
魔术能成功实施，必要条件就是要先记住32张牌的初始位置。然后切牌的时候暗示观众牌是洗过的。最后根据观众拿了黑色花色的牌(梅花和黑桃)举手，快速定位到5个连续的牌位于32张牌的哪个窗口，然后说出这5张牌的花色和点数。
三. 德布鲁因序列的定义和性质 1. 定义 德布鲁因序列(De Bruijn sequence)，记为B(k, n)，是 k 元素构成的循环序列。所有长度为 n 的 k 元素构成序列都在它的子序列（以环状形式）中，出现并且仅出现一次。</description></item><item><title>Google S2 中的四叉树求 LCA 最近公共祖先</title><link>https://new.halfrost.com/zh/go_s2_lowest_common_ancestor/</link><pubDate>Fri, 20 Oct 2017 03:18:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_s2_lowest_common_ancestor/</guid><description>一. 寻找父亲节点和孩子节点 首先需要回顾一下希尔伯特曲线的生成方式，具体代码见笔者上篇文章的分析，在这个分析中，有4个方向比较重要，接下来的分析需要，所以把这4个方向的图搬过来。
在举例之前还需要说明一点，有些网站提供的二进制转换，并没有标明有符号还是无符号的转换，这样就会导致使用者的一些误解。笔者开始并没有发现这个问题，导致掉入了这个坑，好一会才转过弯来。笔者在网上查询了很多在线转换计算器的工具，都发现了这个问题。比如常见的在线进制转换http://tool.oschina.net/hexconvert，随便找两个64位的二进制数，有符号的和无符号的分别转换成十进制，或者反过来转换，你会惊喜的发现，两次结果居然相同！例如你输入 3932700003016900608 和 3932700003016900600，你会发现转换成二进制以后结果都是 11011010010011110000011101000100000000000000000000000000000000。但是很明显这两个数不同。
假如 3932700003016900608 是无符号的，3932700003016900600 是有符号的，正确的结果应该如下：
// 3932700003016900608 11011010010011110000011101000100000000000000000000000000000000 // 3932700003016900600 11011010010011110000011101000011111111111111111111111111111000 差距明显很大。这种例子其实还有很多，随便再举出几组：无符号的 3932700011606835200 和有符号的 3932700011606835000；无符号的 3932700020196769792 和有符号的 3932700020196770000；无符号的 3932700028786704384 和有符号的 3932700028786704400……可以举的例子很多，这里就不再举了。
利用网上的这个工具，十进制转二进制是无符号的转换，二进制转十进制就会变成有符号的转换了。而 Google S2 默认是无符号的 CellID，所以用有符号的 CellID 会出现错误。所以转换中需要注意一下。笔者之前没有发现这一点的时候出现了一些问题，后来突然发现了这一点，茅塞顿开。
好了，进入正题。接下来直接看一个例子，笔者用例子来说明每个 Cell 之间的关系，以及如何查找父亲节点的。
假设有上图这4个连在一起的 Cell。先根据经纬度把 CellID 计算出来。
对应的4个 CellID 分别是：
由于前两位都是0，所以其实可以省略，但是为了读者看的更清晰，笔者还是补全了64位，前面2个0还是补上 // 3932700003016900608 右上 0011011010010011110000011101000100000000000000000000000000000000 // 3932700011606835200 左上 0011011010010011110000011101001100000000000000000000000000000000 // 3932700020196769792 左下 0011011010010011110000011101010100000000000000000000000000000000 // 3932700028786704384 右下 0011011010010011110000011101011100000000000000000000000000000000 在前篇文章里面我们也分析了 Cell 64位的结构，这里是4个 Level 14的 Cell，所以末尾有 64 - 3 - 1 - 14 * 2 = 32 个 0 。从末尾往前的第33位是一个1，第34位，第35位是我们重点需要关注的。可以看到分别是00，01，10，11 。正好是连续的4个二进制。</description></item><item><title>如何设计并实现一个线程安全的 Map ？(下篇)</title><link>https://new.halfrost.com/zh/go_map_chapter_two/</link><pubDate>Wed, 04 Oct 2017 20:58:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_map_chapter_two/</guid><description>在上篇中，我们已经讨论过如何去实现一个 Map 了，并且也讨论了诸多优化点。在下篇中，我们将继续讨论如何实现一个线程安全的 Map。说到线程安全，需要从概念开始说起。
线程安全就是如果你的代码块所在的进程中有多个线程在同时运行，而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的，而且其他的变量的值也和预期的是一样的，就是线程安全的。
如果代码块中包含了对共享数据的更新操作，那么这个代码块就可能是非线程安全的。但是如果代码块中类似操作都处于临界区之中，那么这个代码块就是线程安全的。
通常有以下两类避免竞争条件的方法来实现线程安全：
第一类 —— 避免共享状态 1.可重入 Re-entrancy 通常在线程安全的问题中，最常见的代码块就是函数。让函数具有线程安全的最有效的方式就是使其可重入。如果某个进程中所有线程都可以并发的对函数进行调用，并且无论他们调用该函数的实际执行情况怎么样，该函数都可以产生预期的结果，那么就可以说这个函数是可重入的。
如果一个函数把共享数据作为它的返回结果或者包含在它返回的结果中，那么该函数就肯定不是一个可重入的函数。任何内含了操作共享数据的代码的函数都是不可重入的函数。
为了实现线程安全的函数，把所有代码都置放于临界区中是可行的。但是互斥量的使用总会耗费一定的系统资源和时间，使用互斥量的过程总会存在各种博弈和权衡。所以请合理使用互斥量保护好那些涉及共享数据操作的代码。
注意：可重入只是线程安全的充分不必要条件，并不是充要条件。这个反例在下面会讲到。
2.线程本地存储
如果变量已经被本地化，所以每个线程都有自己的私有副本。这些变量通过子程序和其他代码边界保留它们的值，并且是线程安全的，因为这些变量都是每个线程本地存储的，即使访问它们的代码可能被另一个线程同时执行，依旧是线程安全的。
3.不可变量
对象一旦初始化以后就不能改变。这意味着只有只读数据被共享，这也实现了固有的线程安全性。可变（不是常量）操作可以通过为它们创建新对象，而不是修改现有对象的方式去实现。 Java，C＃和 Python 中的字符串的实现就使用了这种方法。
第二类 —— 线程同步 第一类方法都比较简单，通过代码改造就可以实现。但是如果遇到一定要进行线程中共享数据的情况，第一类方法就解决不了了。这时候就出现了第二类解决方案，利用线程同步的方法来解决线程安全问题。
今天就从线程同步开始说起。
一. 线程同步理论 在多线程的程序中，多以共享数据作为线程之间传递数据的手段。由于一个进程所拥有的相当一部分虚拟内存地址都可以被该进程中所有线程共享，所以这些共享数据大多是以内存空间作为载体的。如果两个线程同时读取同一块共享内存但获取到的数据却不同，那么程序很容易出现一些 bug。
为了保证共享数据一致性，最简单并且最彻底的方法就是使该数据成为一个不变量。当然这种绝对的方式在大多数情况下都是不可行的。比如函数中会用到一个计数器，记录函数被调用了几次，这个计数器肯定就不能被设为常量。那这种必须是变量的情况下，还要保证共享数据的一致性，这就引出了临界区的概念。
临界区的出现就是为了使该区域只能被串行的访问或者执行。临界区可以是某个资源，也可以是某段代码。保证临界区最有效的方式就是利用线程同步机制。
先介绍2种共享数据同步的方法。
1. 互斥量 在同一时刻，只允许一个线程处于临界区之内的约束称为互斥，每个线程在进入临界区之前，都必须先锁定某个对象，只有成功锁定对象的线程才能允许进入临界区，否则就会阻塞。这个对象称为互斥对象或者互斥量。
一般我们日常说的互斥锁就能达到这个目的。
互斥量可以有多个，它们所保护的临界区也可以有多个。先从简单的说起，一个互斥量和一个临界区。
(一) 一个互斥量和一个临界区 上图就是一个互斥量和一个临界区的例子。当线程1先进入临界区的时候，当前临界区处于未上锁的状态，于是它便先将临界区上锁。线程1获取到临界区里面的值。
这个时候线程2准备进入临界区，由于线程1把临界区上锁了，所以线程2进入临界区失败，线程2由就绪状态转成睡眠状态。线程1继续对临界区的共享数据进行写入操作。
当线程1完成所有的操作以后，线程1调用解锁操作。当临界区被解锁以后，会尝试唤醒正在睡眠的线程2。线程2被唤醒以后，由睡眠状态再次转换成就绪状态。线程2准备进入临界区，当临界区此处处于未上锁的状态，线程2便将临界区上锁。
经过 read、write 一系列操作以后，最终在离开临界区的时候会解锁。
线程在离开临界区的时候，一定要记得把对应的互斥量解锁。这样其他因临界区被上锁而导致睡眠的线程还有机会被唤醒。所以对同一个互斥变量的锁定和解锁必须成对的出现。既不可以对一个互斥变量进行重复的锁定，也不能对一个互斥变量进行多次的解锁。
如果对一个互斥变量锁定多次可能会导致临界区最终永远阻塞。可能有人会问了，对一个未锁定的互斥变成解锁多次会出现什么问题呢？
在 Go 1.8 之前，虽然对互斥变量解锁多次不会引起任何 goroutine 的阻塞，但是它可能引起一个运行时的恐慌。Go 1.8 之前的版本，是可以尝试恢复这个恐慌的，但是恢复以后，可能会导致一系列的问题，比如重复解锁操作的 goroutine 会永久的阻塞。所以 Go 1.8 版本以后此类运行时的恐慌就变成了不可恢复的了。所以对互斥变量反复解锁就会导致运行时操作，最终程序异常退出。
(二) 多个互斥量和一个临界区 在这种情况下，极容易产生线程死锁的情况。所以尽量不要让不同的互斥量所保护的临界区重叠。
上图这个例子中，一个临界区中存在2个互斥量：互斥量 A 和互斥量 B。</description></item><item><title>如何设计并实现一个线程安全的 Map ？(上篇)</title><link>https://new.halfrost.com/zh/go_map_chapter_one/</link><pubDate>Sun, 10 Sep 2017 01:50:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_map_chapter_one/</guid><description>Map 是一种很常见的数据结构，用于存储一些无序的键值对。在主流的编程语言中，默认就自带它的实现。C、C++ 中的 STL 就实现了 Map，JavaScript 中也有 Map，Java 中有 HashMap，Swift 和 Python 中有 Dictionary，Go 中有 Map，Objective-C 中有 NSDictionary、NSMutableDictionary。
上面这些 Map 都是线程安全的么？答案是否定的，并非全是线程安全的。那如何能实现一个线程安全的 Map 呢？想回答这个问题，需要先从如何实现一个 Map 说起。
一. 选用什么数据结构实现 Map ？ Map 是一个非常常用的数据结构，一个无序的 key/value 对的集合，其中 Map 所有的 key 都是不同的，然后通过给定的 key 可以在常数时间 O(1) 复杂度内查找、更新或删除对应的 value。
要想实现常数级的查找，应该用什么来实现呢？读者应该很快会想到哈希表。确实，Map 底层一般都是使用数组来实现，会借用哈希算法辅助。对于给定的 key，一般先进行 hash 操作，然后相对哈希表的长度取模，将 key 映射到指定的地方。
哈希算法有很多种，选哪一种更加高效呢？
1. 哈希函数 MD5 和 SHA1 可以说是目前应用最广泛的 Hash 算法，而它们都是以 MD4 为基础设计的。
MD4(RFC 1320) 是 MIT 的Ronald L. Rivest 在 1990 年设计的，MD 是 Message Digest（消息摘要） 的缩写。它适用在32位字长的处理器上用高速软件实现——它是基于 32位操作数的位操作来实现的。 MD5(RFC 1321) 是 Rivest 于1991年对 MD4 的改进版本。它对输入仍以512位分组，其输出是4个32位字的级联，与 MD4 相同。MD5 比 MD4 来得复杂，并且速度较之要慢一点，但更安全，在抗分析和抗差分方面表现更好。</description></item><item><title>深入解析 Go 中 Slice 底层实现</title><link>https://new.halfrost.com/zh/go_slice/</link><pubDate>Fri, 25 Aug 2017 07:23:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_slice/</guid><description>切片是 Go 中的一种基本的数据结构，使用这种结构可以用来管理数据集合。切片的设计想法是由动态数组概念而来，为了开发者可以更加方便的使一个数据结构可以自动增加和减少。但是切片本身并不是动态数据或者数组指针。切片常见的操作有 reslice、append、copy。与此同时，切片还具有可索引，可迭代的优秀特性。
一. 切片和数组 关于切片和数组怎么选择？接下来好好讨论讨论这个问题。
在 Go 中，与 C 数组变量隐式作为指针使用不同，Go 数组是值类型，赋值和函数传参操作都会复制整个数组数据。
func main() { arrayA := [2]int{100, 200} var arrayB [2]int arrayB = arrayA fmt.Printf(&amp;#34;arrayA : %p , %v\n&amp;#34;, &amp;amp;arrayA, arrayA) fmt.Printf(&amp;#34;arrayB : %p , %v\n&amp;#34;, &amp;amp;arrayB, arrayB) testArray(arrayA) } func testArray(x [2]int) { fmt.Printf(&amp;#34;func Array : %p , %v\n&amp;#34;, &amp;amp;x, x) } 打印结果：
arrayA : 0xc4200bebf0 , [100 200] arrayB : 0xc4200bec00 , [100 200] func Array : 0xc4200bec30 , [100 200] 可以看到，三个内存地址都不同，这也就验证了 Go 中数组赋值和函数传参都是值复制的。那这会导致什么问题呢？</description></item><item><title>高效的多维空间点索引算法 — Geohash 和 Google S2</title><link>https://new.halfrost.com/zh/go_spatial_search/</link><pubDate>Fri, 11 Aug 2017 21:52:32 +0000</pubDate><guid>https://new.halfrost.com/zh/go_spatial_search/</guid><description>引子 每天我们晚上加班回家，可能都会用到滴滴或者共享单车。打开 app 会看到如下的界面：
app 界面上会显示出自己附近一个范围内可用的出租车或者共享单车。假设地图上会显示以自己为圆心，5公里为半径，这个范围内的车。如何实现呢？最直观的想法就是去数据库里面查表，计算并查询车距离用户小于等于5公里的，筛选出来，把数据返回给客户端。
这种做法比较笨，一般也不会这么做。为什么呢？因为这种做法需要对整个表里面的每一项都计算一次相对距离。太耗时了。既然数据量太大，我们就需要分而治之。那么就会想到把地图分块。这样即使每一块里面的每条数据都计算一次相对距离，也比之前全表都计算一次要快很多。
我们也都知道，现在用的比较多的数据库 MySQL、PostgreSQL 都原生支持 B+ 树。这种数据结构能高效的查询。地图分块的过程其实就是一种添加索引的过程，如果能想到一个办法，把地图上的点添加一个合适的索引，并且能够排序，那么就可以利用类似二分查找的方法进行快速查询。
问题就来了，地图上的点是二维的，有经度和纬度，这如何索引呢？如果只针对其中的一个维度，经度或者纬度进行搜索，那搜出来一遍以后还要进行二次搜索。那要是更高维度呢？三维。可能有人会说可以设置维度的优先级，比如拼接一个联合键，那在三维空间中，x，y，z 谁的优先级高呢？设置优先级好像并不是很合理。
本篇文章就来介绍2种比较通用的空间点索引算法。
一. GeoHash 算法 1. Geohash 算法简介 Geohash 是一种地理编码，由 Gustavo Niemeyer 发明的。它是一种分级的数据结构，把空间划分为网格。Geohash 属于空间填充曲线中的 Z 阶曲线（Z-order curve）的实际应用。
何为 Z 阶曲线？
上图就是 Z 阶曲线。这个曲线比较简单，生成它也比较容易，只需要把每个 Z 首尾相连即可。
Z 阶曲线同样可以扩展到三维空间。只要 Z 形状足够小并且足够密，也能填满整个三维空间。
说到这里可能读者依旧一头雾水，不知道 Geohash 和 Z 曲线究竟有啥关系？其实 Geohash算法 的理论基础就是基于 Z 曲线的生成原理。继续说回 Geohash。
Geohash 能够提供任意精度的分段级别。一般分级从 1-12 级。
还记得引语里面提到的问题么？这里我们就可以用 Geohash 来解决这个问题。
我们可以利用 Geohash 的字符串长短来决定要划分区域的大小。这个对应关系可以参考上面表格里面 cell 的宽和高。一旦选定 cell 的宽和高，那么 Geohash 字符串的长度就确定下来了。这样我们就把地图分成了一个个的矩形区域了。
地图上虽然把区域划分好了，但是还有一个问题没有解决，那就是如何快速的查找一个点附近邻近的点和区域呢？</description></item><item><title>初探 Go 的编译命令执行过程</title><link>https://new.halfrost.com/zh/go_command/</link><pubDate>Fri, 04 Aug 2017 11:10:00 +0000</pubDate><guid>https://new.halfrost.com/zh/go_command/</guid><description>引言 Go 语言这两年在语言排行榜上的上升势头非常猛，Go 语言虽然是静态编译型语言，但是它却拥有脚本化的语法，支持多种编程范式(函数式和面向对象)。Go 语言最最吸引人的地方可能是其原生支持并发编程(语言层面原生支持和通过第三方库支持是有很大区别的)。Go 语言的对网络通信、并发和并行编程的支持度极高，从而可以更好地利用大量的分布式和多核的计算机。开发者可以通过 goroutine 这种轻量级线程的概念来实现这个目标，然后通过 channel 来实现各个 goroutine 之间的通信。他们实现了分段栈增长和 goroutine 在线程基础上多路复用技术的自动化。
2017年7月 TIOBE 语言排行榜 Go 首次进入前十。今天就让我们来探究探究 Go 的编译命令执行过程。
一. 理解 Go 的环境变量 1. GOROOT 该环境变量的值为 Go 语言的当前安装目录。
2. GOPATH 该环境变量的值为 Go 语言的工作区的集合（意味着可以有很多个）。工作区类似于工作目录。每个不同的目录之间用：分隔。
工作区是放置 Go 源码文件的目录。一般情况下，Go 源码文件都需要存放到工作区中。
工作区一般会包含3个子文件夹，自己手动新建以下三个目录：src 目录，pkg 目录，bin 目录。
/home/halfrost/gorepo ├── bin ├── pkg └── src 这里需要额外说的一点：关于 IDE 新建 Go 项目。IDE 在新建完 Go 的项目以后，会自动的执行 go get 命令去把相应的基础包拉过来， 在这个过程中会新建 bin、pkg、src 三个目录。不用 IDE 的同学，需要自己手动创建这三个目录。
上图是 Atom 的 go-plus 插件在一个新的项目打开的时候，自动 go get 的一些基础包。</description></item><item><title>Go 初学者成长之路</title><link>https://new.halfrost.com/zh/new_gopher/</link><pubDate>Sat, 22 Jul 2017 05:50:00 +0000</pubDate><guid>https://new.halfrost.com/zh/new_gopher/</guid><description>开源书籍 编译器 1. Vim党 Vim党当然是不需要 IDE 的，直接 Vim + Vim-go（或者 Emacs）
2. 文本编辑器 + 插件 目前最常用最火的文本编辑器有 VSCode、Sublime、Atom
他们都可以安装相应的插件，就可以支持 Go 的编码了。我暂时用的是 Atom + go-plus，界面还比较美，如下图：
3. IDE 目前用的比较多的 IDE 有：IntelliJ idea、Goland、LiteIDE。
学习网站 视频 这个也看个人吧，有些人不喜欢看文档，或者有时候文档看累了看会视频。下面这些视频本人看过开头的，觉得讲的还可以，不过后面的我没有继续看下去了，因为觉得看视频学习有点慢，我还是选择看刷书刷题啦~
社区 最后，多多练习，多多实践 Go，只要功夫深，铁杵磨成针！
（由于 Ghost 不支持 MarkDown 里面的表格语法，所以文章中的表格都是图片格式，链接都不能点，如果想看有链接的版本，请点下面这个 Github 库里面的文章，那篇是带可点链接的版本。）
GitHub Repo：Halfrost-Field
Follow: halfrost · GitHub
Source: https://halfrost.com/new_gopher/</description></item></channel></rss>