我的文件是CIDR格式,如192.168.1.0/24
,它被转换为这两列结构
3232236030 3232235777
使用以下代码进行每个字符串IP地址转换:
String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);
Inet4Address a = (Inet4Address) InetAddress.getByName(utils.getInfo().getHighAddress());
long high = bytesToLong(a.getAddress());
Inet4Address b = (Inet4Address) InetAddress.getByName(utils.getInfo().getLowAddress());
long low = bytesToLong(b.getAddress());
private static long bytesToLong(byte[] address) {
long ipnum = 0;
for (int i = 0; i < 4; ++i) {
long y = address[i];
if (y < 0) {
y += 256;
}
ipnum += y << ((3 - i) * 8);
}
return ipnum;
}
考虑到(low high : 3232236030 3232235777)
的条目超过500万
此外,还会有交叉点,因此IP可以来自多个范围。只是第一个不仅仅是OK
数据是只读的
找到ipToBefiltered
所属范围的最快方法是什么?该结构将完全在内存中,因此无需数据库查找。
我找到了这个Peerblock项目(它有超过百万的下载,所以我认为它必须有一些快速的算法): http://code.google.com/p/peerblock/source/browse/trunk/src/pbfilter/filter_wfp.c
答案 0 :(得分:7)
当谈到它时,我只需要知道IP是否存在于任何5M范围内。
我会考虑一个 n-ary 树,其中n = 256,并使用虚线地址而不是转换后的整数。
顶级是256个对象的数组。 null
条目表示“否”没有包含地址的范围,因此假设您的示例192.168.1.0/24
数组[192]将包含一个对象,但数组[100]可能为空,因为没有范围是为任何100.xxx/n
存储对象包含(引用)另一个数组[256]和范围说明符,只会设置其中一个,因此192.0.0.0/8
最终会有一个范围说明符,表示该范围内的所有地址要过滤。这将允许192.255.0.0/10
之类的地址,其中地址的前10位是重要的1100 0000 11xx xxxx
- 否则你需要检查第二级数组中的下一个八位字节。
最初将重叠范围(如果有的话)合并到更大的范围内......例如3 .. 10
和7 .. 16
变为3 .. 16
...允许这样做,因为您不需要将给定的IP与 范围定义它的相关联。
这应该不超过8次比较。每个八位字节最初直接用作索引,然后是null的比较,终端节点的比较(是范围还是指向下一个树级别的指针)
如果每个 IP地址都在过滤范围内,最坏情况下内存消耗理论上是4 GB (256 ^ 4)
,但当然会合并到一个范围内,所以实际上只有1个范围宾语。更现实的最坏情况可能更像(256 ^ 3)
或16.7 MB。真实世界的使用可能会使每个级别的大多数数组[256]节点都为空。
这基本上类似于霍夫曼/前缀编码。一旦找到答案(范围),最短的不同前缀就会终止,因此通常会有< 4
的平均值进行比较。
答案 1 :(得分:1)
我会使用int的排序数组(基地址)和另一个相同大小的数组(结束地址)。这将使用5M * 8 = 40 MB。第一个IP是基础,第二个IP是范围中的最后一个地址。您需要删除交叉点。
要查找地址是否已过滤为二进制搜索O(日志N),如果不是完全匹配,请检查它是否小于(或等于)上限。
答案 2 :(得分:1)
我在Vuze (aka azureus)项目中发现了这个二进制斩波算法:
public IpRange isInRange(long address_long) {
checkRebuild();
if (mergedRanges.length == 0) {
return (null);
}
// assisted binary chop
int bottom = 0;
int top = mergedRanges.length - 1;
int current = -1;
while (top >= 0 && bottom < mergedRanges.length && bottom <= top) {
current = (bottom + top) / 2;
IpRange e = mergedRanges[current];
long this_start = e.getStartIpLong();
long this_end = e.getMergedEndLong();
if (address_long == this_start) {
break;
} else if (address_long > this_start) {
if (address_long <= this_end) {
break;
}
// lies to the right of this entry
bottom = current + 1;
} else if (address_long == this_end) {
break;
} else {
// < this_end
if (address_long >= this_start) {
break;
}
top = current - 1;
}
}
if (top >= 0 && bottom < mergedRanges.length && bottom <= top) {
IpRange e = mergedRanges[current];
if (address_long <= e.getEndIpLong()) {
return (e);
}
IpRange[] merged = e.getMergedEntries();
if (merged == null) {
//inconsistent merged details - no entries
return (null);
}
for (IpRange me : merged) {
if (me.getStartIpLong() <= address_long && me.getEndIpLong() >= address_long) {
return (me);
}
}
}
return (null);
}
似乎表现得非常好。如果您对事情了解得更快,请告诉我。
答案 3 :(得分:1)
如果您只有一个CIDR地址(或它们的列表),并且您想要检查某个ipAddress是否在该CIDR(或CIDR列表)的范围内,只需定义一组SubnetUtils对象。
除非您正在过滤非常大的N个地址,否则这是所有字符串比较,并且执行速度非常快。您不需要根据高/低位和所有复杂的Jazz构建二叉树。
String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);
//...
//for each subnet, create a SubnetUtils object
Set<SubnetUtils> subnets = getAllSubnets();
//...
使用Guava Predicate过滤不在您的子网范围内的ipAddresses:
Set<String> ipAddresses = getIpAddressesToFilter();
Set<String> ipAddressesInRange =
Sets.filter(ipAddresses, filterIpsBySubnet(subnets))
Predicate<String> filterIpsBySubnet(final Set<SubnetUtils> subnets){
return new Predicate<String>() {
@Override
public boolean apply(String ipAddress) {
for (SubnetUtils subnet : subnets) {
if (subnet.getInfo().isInRange(ipAddress)) {
return true;
}
}
return false;
}
};
}
现在,如果IP位于任何子网中,您就拥有了一个简单的过滤器,并且您不必构建一个必须进行单元测试的数据结构。如果这不够高效,那么请进行优化。不要过早地优化:)
答案 4 :(得分:0)
这是答案的开始,当我获得更多的空闲时间时,我会回来
<强>设定:强>