如何在 C# 中有效地索引和搜索 IPv4 地址范围
本文关键字:搜索 IPv4 地址 范围 索引 有效地 | 更新日期: 2023-09-27 18:34:46
我有大约 90000 个 IPv4 地址范围,每个范围都有关联的数据
例如
1.0.0.0 - 1.1.0.0 -> "foo"
2.0.0.0 - 10.0.0.0 -> "bar"
给定一个 IP 地址,我需要检索关联的数据。我怎样才能有效地做到这一点?
我想我可以通过将地址转换为单个整数来使事情变得更容易,但是最好使用什么数据结构来存储它以实现快速搜索?
澄清 - 我使用单个 IP 进行搜索,而不是范围(例如"192.168.0.1"(
谢谢
对单个数组中非重叠间隔的末端进行排序。用一个标志标记每个结束,指示它是间隔的开始还是结束,如下所示:
1.0.0.0 1.1.0.0 2.0.0.0 10.0.0.0
start end start end
现在使用目标地址运行二进制搜索,例如3.2.1.0
。插入点落在 2.0.0.0
上,标记为 start
。这意味着3.2.1.0
是间隔之一。
现在考虑搜索 1.2.3.4
.它的插入点落在1.1.0.0
上,标记为end
。由于1.2.3.4
不等于1.1.0.0
,我们知道1.2.3.4
不是区间之一。
搜索的开销是Log(N)
,其中N
是间隔数。
如果您喜欢冒险,请考虑实施interval tree
。但是,对于非重叠间隔,这可能不值得。
如果只需要支持 IPv4 地址而不支持 IPv6 地址,则可以将每个地址存储为UInt32
。这将使比较它们变得非常容易和有效。
如果 IP 范围不重叠,并且您可以将它们按列表排序,则可以使用二叉搜索的变体来快速查找范围。
查找 0 到 20 之间的数字是否包括以下范围:
2-5 -> "a"
6-7 -> "b"
9-11 -> "c"
12-12 -> "d"
15-18 -> "e"
19-19 -> "f"
在循环内循环 1,000,000 次,包括 1 次创建和初始化 RangeCollection:只需 3 秒!
剩下要做的就是准备一个 IEnumerable 的元组,其中第一个 int 是最小 IP int 值,第二个是最大值,而 TValue 是与该最小~最大范围关联的数据。
这是我的实现:
public class RangeCollection<TValue>
{
private readonly int[] _mins;
private readonly int[] _maxs;
private readonly TValue[] _values;
public RangeCollection(params Tuple<int, int, TValue>[] input)
: this((IEnumerable<Tuple<int, int, TValue>>)input)
{
}
public RangeCollection(IEnumerable<Tuple<int, int, TValue>> input)
{
var tuples = input.OrderBy(tuple => tuple.Item1).ToArray();
for (var i = 1; i < tuples.Length; i++)
{
if (tuples[i].Item1 <= tuples[i - 1].Item2)
{
throw new ArgumentException("Ranges should not overlap.");
}
}
this._mins = new int[tuples.Length];
this._maxs = new int[tuples.Length];
this._values = new TValue[tuples.Length];
for (var i = 0; i < tuples.Length; i++)
{
var tuple = tuples[i];
this._mins[i] = tuple.Item1;
this._maxs[i] = tuple.Item2;
this._values[i] = tuple.Item3;
}
}
public bool TryGetValue(int key, out TValue value)
{
if (this.Contains(key, out key))
{
value = this._values[key];
return true;
}
value = default(TValue);
return false;
}
public bool Contains(int key)
{
return this.Contains(key, out key);
}
private bool Contains(int key, out int index)
{
index = 0;
if (this._mins.Length == 0 || key < this._mins[0] || key > this._maxs[this._maxs.Length - 1])
{
return false;
}
var low = 0;
var high = this._mins.Length - 1;
while (high >= low)
{
index = (low + high) / 2;
var cmp = this._mins[index].CompareTo(key);
if (cmp == 0)
{
return true;
}
if (cmp == 1)
{
high = index - 1;
}
else
{
low = index + 1;
}
}
if (this._mins[index] > key)
{
index--;
}
else if (this._mins[index + 1] <= key)
{
index++;
}
return this._maxs[index] >= key;
}
}
用法:
var collection = new RangeCollection<string>(new Tuple<int, int, string>(2, 5, "a"),
new Tuple<int, int, string>(6, 7, "b"),
new Tuple<int, int, string>(9, 11, "c"),
new Tuple<int, int, string>(12, 12, "d"),
new Tuple<int, int, string>(15, 18, "e"),
new Tuple<int, int, string>(19, 19, "f"));
for (var i = 0; i <= 20; i++)
{
string val;
if (collection.TryGetValue(i, out val))
{
Debug.WriteLine("Contains {0}. Value: {1}", i, val);
}
else
{
Debug.WriteLine("Doesn't contain " + i);
}
}
/* Output:
Doesn't contain 0
Doesn't contain 1
Contains 2. Value: a
Contains 3. Value: a
Contains 4. Value: a
Contains 5. Value: a
Contains 6. Value: b
Contains 7. Value: b
Doesn't contain 8
Contains 9. Value: c
Contains 10. Value: c
Contains 11. Value: c
Contains 12. Value: d
Doesn't contain 13
Doesn't contain 14
Contains 15. Value: e
Contains 16. Value: e
Contains 17. Value: e
Contains 18. Value: e
Contains 19. Value: f
Doesn't contain 20
*/