Ham*_*FzM 1 arrays ip network-programming go slice
我正在 Golang 中开发一个网络应用程序。我有一个 IP 地址片段。每次收到请求时,我都会用来net.LookupIP(host)
查找返回net.IP
. 比较这些的最佳方法是什么?
顺便说一句,在 Python 中我们有一个set
数据结构,这使得上面的问题很容易解决,但是 Go 呢?
Go 中没有内置Set
类型,但你可以优雅地使用 amap[Type]bool
作为集合,例如:
// Create a set with 2 values in it: [1, 2]\nm := map[int]bool{1: true, 2: true}\n\n// Test an element:\nfmt.Println(m[1]) // true\nfmt.Println(m[3]) // false\n\n// Set an element:\nm[3] = true\nfmt.Println(m[3]) // true\n\n// Delete an element:\ndelete(m, 1)\nfmt.Println(m[1]) // false\n
Run Code Online (Sandbox Code Playgroud)\n\n注意:我们利用了这样一个事实:如果键不在映射中,则对映射进行索引会导致值类型的零值false
(在 的情况下)bool
,正确地表明该元素不在映射(集合)中。
在Go Playground上尝试一下。
\n\n注意#2:有一些技巧可以使将映射作为集合处理的代码更短,您可以在这个答案中检查它们:检查值是否在列表中。
\n\nnet.IP
在集合中使用现在我们只需要一个表示 a 的类型net.IP
,它可以用作映射中的键类型(有关映射键类型的构成,请参阅此问题:How can I Prevent a type being used as a map key?)。
不幸的是net.IP
它本身不符合条件,因为它是一个切片:
type IP []byte\n
Run Code Online (Sandbox Code Playgroud)\n\n而且切片没有可比性。有关详细信息,请参阅此问题:Hash with key as an array type以及:Why have arrays in Go?
\n\n一个简单的方法是将其转换为规范string
值,我们就完成了。为此,我们可以简单地将 IP 的字节转换为十六进制string
。但是 IPv4 地址可能会呈现为 IPv6,因此我们应该首先将其转换为 IPv6:
func Key(ip net.IP) string {\n return hex.EncodeToString(ip.To16())\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n注意:IP 地址的字节可能不是有效的 UTF-8 编码string
(这是 Gostring
在内存中存储 s 的方式),但string
Go 中的值表示任意字节序列,因此以下方法也可以工作,更简单且更高效:
func Key(ip net.IP) string {\n return string(ip.To16()) // Simple []byte => string conversion\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n我们可以使用这样的IP字符串作为密钥。使用 IP 填充您的地图以进行检查:
\n\n// Populate forbidden IPs:\nforbIPs := map[string]bool{\n Key(ip1): true,\n Key(ip2): true,\n}\n\n// Now check a single IP:\nipToCheck := ...\nif forbIPs[Key(ipToCheck)] {\n fmt.Println("Forbidden!")\n} else {\n fmt.Println("Allowed.")\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n如果您有多个 IP 要检查(由 返回net.LookupIP()
),则这是一个for
循环:
ips, err := net.LookupIP(host)\n// Check err\nfor _, ip := range ips {\n if forbIPs[Key(ip)] {\n // FORBIDDEN!\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n请注意,\xe2\x80\x93 如上所述,\xe2\x80\x93 切片不可比较,但数组可以。所以我们也可以使用数组作为键。它可能是这样的:
\n\nfunc Key(ip net.IP) (a [16]byte) {\n copy(a[:], ip)\n return\n}\n\n// And the IP set:\nforbIPs := map[[16]byte]bool{\n // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n或者,我们可以简单地将禁止的 IP 存储在一个切片中[]net.IP
,并对其进行排序。如果它是排序的,我们可以使用二分搜索来查找其中的IP(标准库sort.Search()
)。
O(log2(n))
是的,与O(1)
上面的(哈希)映射解决方案的复杂性相比,二分搜索具有复杂性。但这种替代方案还有另一个优点:
枚举各个 IP 并不总是可行的。有时(通常)列出 IP 范围更容易。第一个解决方案对于处理 IP 范围不可行,但此解决方案可能是:您也可以及时找到覆盖 IP 地址的范围O(log2(n))
。