🌟 问题分析
• 数据规模:几十万条 IP 地址(如 100 万条)。
• 查询需求:高效判断某个 IP 是否在 IP 库中。
• 存储方式:考虑 内存占用 和 查询速度。
• IP 地址格式:
• IPv4:x.x.x.x,范围 0.0.0.0 ~ 255.255.255.255(32-bit 整数)。
• IPv6:较复杂,主要讨论 IPv4。
🚀 解决方案
✅ 方案 1:哈希表(HashSet / Bloom Filter)
🔹 方法 1.1:使用 HashSet(字典)
• 思路:
-
先将 IP 地址转换为整数 存入 HashSet(Python set)。
-
查询 IP 时,直接 O(1) 时间复杂度 查找。
• IP 转换方式:
from ipaddress import ip_address
def ip_to_int(ip):
return int(ip_address(ip))
ip_set = set()
ip_set.add(ip_to_int("192.168.1.1")) # 添加 IP
# 查询某个 IP 是否在库中
def is_ip_in_db(ip):
return ip_to_int(ip) in ip_set
print(is_ip_in_db("192.168.1.1")) # True
print(is_ip_in_db("8.8.8.8")) # False
• 优点:
• 查询 O(1) 时间复杂度,非常快。
• 简单易实现,适用于 数据量不大的情况(如 <100 万条)。
• 缺点:
• 占用内存较大,100 万个 IPv4 地址大约 20~30MB。
🔹 方法 1.2:使用布隆过滤器(Bloom Filter)
• 适用场景:
• 节省内存,但允许 极少量误判。
• 查询速度 O(1) ,适用于 超大规模数据。
• Python 示例(使用 pybloom 库):
from pybloom_live import BloomFilter
from ipaddress import ip_address
bf = BloomFilter(capacity=1000000, error_rate=0.001)
def ip_to_int(ip):
return int(ip_address(ip))
# 添加 IP
bf.add(ip_to_int("192.168.1.1"))
# 查询 IP 是否存在
print(ip_to_int("192.168.1.1") in bf) # True
print(ip_to_int("8.8.8.8") in bf) # False (极小概率误判)
• 优点:
• 大幅节省内存(100 万个 IP 仅需几 MB)。
• 查询速度 O(1) 。
• 缺点:
• 允许误判(0.001 的误判率意味着 1000 个查询可能有 1 个误判)。
• 不能删除元素(标准 Bloom Filter 只能插入,不支持删除)。
✅ 方案 2:前缀树(Trie / Radix Tree)
• 适用场景:
• 存储 IP 段(如 192.168.0.0/16) ,或者当 IP 数据很多 时节省空间。
• 适用于黑名单匹配、防火墙规则匹配。
• 思路:
-
构建前缀树(Trie) ,每个 IP 存储成二进制形式。
-
查询时,从根节点开始匹配 O(log n) 时间复杂度。
• 示例代码:
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class IPTrie:
def __init__(self):
self.root = TrieNode()
def insert(self, ip):
node = self.root
for bit in f"{ip:032b}": # 转换为 32-bit 二进制字符串
if bit not in node.children:
node.children[bit] = TrieNode()
node = node.children[bit]
node.is_end = True
def search(self, ip):
node = self.root
for bit in f"{ip:032b}":
if bit not in node.children:
return False
node = node.children[bit]
return node.is_end
trie = IPTrie()
trie.insert(ip_to_int("192.168.1.1"))
print(trie.search(ip_to_int("192.168.1.1"))) # True
print(trie.search(ip_to_int("8.8.8.8"))) # False
• 优点:
• 查询速度 O(log n) 。
• 节省存储空间(合并前缀减少冗余)。
• 缺点:
• 实现较复杂。
• 构建 Trie 需要时间。
✅ 方案 3:位图(BitSet)
• 适用场景:
• IP 数据量巨大(千万级别) ,但范围有限(0 ~ 4.2 亿)。
• 适用于 IP 黑名单、IP 访问限制。
• 思路:
-
使用 位数组(BitSet) ,每个 IP 占 1 bit。
-
查询时 O(1) 读取 bit,内存占用极小(4.2 亿 IP 仅需 50MB)。
• 示例代码:
import numpy as np
N = 2**32 # IPv4 地址范围 (0 ~ 4.2亿)
bitset = np.zeros(N // 8, dtype=np.uint8) # 约 50MB 内存
def add_ip(ip):
index = ip_to_int(ip)
bitset[index // 8] |= (1 << (index % 8))
def check_ip(ip):
index = ip_to_int(ip)
return (bitset[index // 8] & (1 << (index % 8))) != 0
add_ip("192.168.1.1")
print(check_ip("192.168.1.1")) # True
print(check_ip("8.8.8.8")) # False
• 优点:
• 超高效查询(O(1)) 。
• 超低内存占用(4.2 亿 IP 仅需 50MB) 。
• 缺点:
• 只能存 IPv4,IPv6 需要更大空间。
• 不能存储 IP 段(适合单个 IP 存储)。
🎯 方案对比
💡 结论
• 百万级 IP ✅ HashSet(查询快)
• 亿级 IP ✅ 布隆过滤器(省内存)
• IP 段匹配 ✅ Trie(存储前缀)
• 大规模单个 IP ✅ BitSet(存 4.2 亿 IP 仅 50MB)
🔥 结论:一般选择 Bloom Filter 或 BitSet,大规模查询更高效! 🚀