开发者社区> 技术小阿哥> 正文

纯真IP库的结构分析及一个查询类

简介:
+关注继续查看

  个人网站上有个功能,记录访问者的IP及其归属地。最初我偷懒通过一个WebService来查询IP归属地,后来觉得通过这种方法响应时间长,资源耗费大,而且对那个WebSerice的依赖度太高,如果它挂了或者网络原因,经常要到超时才返回。所以,我打算直接从本地的纯真IP库里查询。

    纯真库的数据结构在http://lumaqq.linuxsir.org/article/qqwry_format_detail.html上讲的很详细了。简单地讲数据文件分成三个区域:
    1、文件头(8个字节,前4字节是指向索引区第一条记录,后4字节指向索引区最后一条记录)
    2、记录区(一个记录包含IP地址,国家记录,地区记录,后两个记录有可能是字符串,也可能是重定向,有多种重定向模式)
    3、索引区(一个索引定长7个字节,前4字节是IP地址(little-endian),后3字节指向对应记录区的位置,这里的位置指从文件头开始计算的偏移字节)

   虽然这个库结构工作的很好,效率也没有问题,但是我觉得设计的有点小复杂了。而且,如果记录区中有条记录A,是重定向到记录B中的,假如我删除了记录B,查询记录A的时候就会有问题。当然,可以在删除记录B的时候进行相应处理,只是有些麻烦。如果把文件结构改成如下,应该处理起来会更方便一些:
   1、文件头(与原库一样)
   2、字符串区
   3、索引区(4字节的IP地址,4字节的偏移值,4字节的偏移值)
所有字符串放在字符串区中,统一管理。索引区中放IP地址,国家记录的“指针”和区域记录的“指针”,所谓的“指针”是对应到字符串区中某条的字符串偏移值。

 

不过既然纯真IP库是这么设计的,我只好根据它的结构来进行相应的查询。

索引区的记录是从小到大排列的,可以用二分法来查询。
IP库中索引的IP地址,并不是连续的,举个例子,192.168.0.0的后一条记录并不是192.168.0.1,可能是192.169.0.0,也就是说,它存储的一个是IP段。所以要做一个类似于“四舍五入”的处理。好在大部分情况下,我们都只要舍掉就可以了,比如查询192.168.1.1应该匹配192.168.0.0而不是192.169.0.0。

 


  1. import java.io.*; 
  2.  
  3. public class IPSeeker 
  4.     protected RandomAccessFile ipDataFile; 
  5.     protected final int RECORD_LEN = 7
  6.     protected final int MODE_1 = 0x01//重定向国家记录,地区记录 
  7.     protected final int MODE_2 = 0x02//重定向国家记录,有地区记录 
  8.     protected final int MODE_3 = 0x03//default 
  9.     protected long indexBegin; 
  10.     protected long indexEnd; 
  11.          
  12.     public IPSeeker() throws Exception 
  13.     { 
  14.         //打开纯真IP库数据文件 
  15.         ipDataFile = new RandomAccessFile("qqwry.dat""r"); 
  16.          
  17.         indexBegin = readLong(40);             
  18.         indexEnd = readLong(44); 
  19.     } 
  20.      
  21.     public static void main(String[] args) throws Exception 
  22.     { 
  23.         IPSeeker seeker = new IPSeeker();//may throw Exception 
  24.         String result = seeker.search("111.2.13.4");//输入查询的IP地址 
  25.         System.out.println(result); 
  26.         seeker.close();//关闭,若不调用close,将在finalize关闭 
  27.         seeker = null
  28.     } 
  29.      
  30.      
  31.     @Override 
  32.     protected void finalize() throws Throwable 
  33.     { 
  34.         try 
  35.         { 
  36.             ipDataFile.close(); 
  37.         }  
  38.         catch (IOException e) 
  39.         { 
  40.              
  41.         } 
  42.         super.finalize(); 
  43.     } 
  44.      
  45.      
  46.     public void close() 
  47.     { 
  48.         try 
  49.         { 
  50.             ipDataFile.close(); 
  51.         }  
  52.         catch (IOException e) 
  53.         { 
  54.              
  55.         } 
  56.     } 
  57.  
  58.     public String search(String ipStr) throws Exception 
  59.     { 
  60.         //采用二分法查询 
  61.         long recordCount = (indexEnd - indexBegin) / 7 + 1
  62.          
  63.         long itemStart = 0
  64.         long itemEnd = recordCount - 1
  65.         long ip = IPSeeker.stringIP2Long(ipStr); 
  66.         long middle = 0
  67.         long midIP = 0
  68.         while(itemStart <= itemEnd) 
  69.         { 
  70.             middle = (itemStart +  itemEnd) / 2
  71.             midIP = readLong(4, indexBegin + middle * 7); 
  72.             //String temp = IPSeeker.long2StringIP(midIP); 
  73.             if(midIP == ip) 
  74.             { 
  75.                 break
  76.             } 
  77.             else if(midIP < ip) 
  78.             { 
  79.                 itemStart = middle + 1
  80.             } 
  81.             else//midIP > ip 
  82.             { 
  83.                 itemEnd = middle - 1
  84.             } 
  85.         } 
  86.          
  87.         //若无完全匹配结果,则向前匹配 
  88.         if(ip < midIP && middle > 0
  89.         { 
  90.             middle--; 
  91.         } 
  92.  
  93.         long item = readLong(3, indexBegin + middle * 7 + 4); 
  94.         String[] result = getInfo(item + 4);//取出信息 
  95.         return long2StringIP(readLong(4, indexBegin + middle * 7))+ ","//匹配到的IP地址(段) 
  96.                 + result[0] + "," //国家 
  97.                 + result[1];//地区 
  98.     } 
  99.      
  100.     //32位整型格式的IP地址(little-endian)转化到字符串格式的IP地址 
  101.     public static String long2StringIP(long ip) 
  102.     { 
  103.         long ip4 = ip >> 0 & 0x000000FF
  104.         long ip3 = ip >> 8 & 0x000000FF
  105.         long ip2 = ip >> 16 & 0x000000FF
  106.         long ip1 = ip >> 24 & 0x000000FF
  107.          
  108.         return String.valueOf(ip1) + "." + String.valueOf(ip2) + "." +  
  109.                 String.valueOf(ip3) + "." + String.valueOf(ip4); 
  110.     } 
  111.      
  112.     //字符串格式的IP地址转化到32位整型格式的IP地址(little-endian) 
  113.     public static Long stringIP2Long(String ipStr) throws Exception 
  114.     { 
  115.         String[] list = ipStr.split("\\."); 
  116.         if(list.length != 4
  117.         { 
  118.             throw new Exception("IP地址格式错误"); 
  119.         } 
  120.         long ip = Long.parseLong(list[0]) << 24 & 0xFF000000
  121.         ip += Long.parseLong(list[1]) << 16 & 0x00FF0000
  122.         ip += Long.parseLong(list[2]) << 8 & 0x0000FF00
  123.         ip += Long.parseLong(list[3]) << 0 & 0x000000FF
  124.         return ip; 
  125.     } 
  126.      
  127.     //读取一个n位的 
  128.     private long readLong(int nByte, long offset) throws Exception 
  129.     { 
  130.         ipDataFile.seek(offset); 
  131.          
  132.         long result = 0
  133.         if(nByte > 4 || nByte < 0
  134.             throw new Exception("nBit should be 0-4"); 
  135.          
  136.         for(int i = 0; i < nByte; i++) 
  137.         { 
  138.             result |= ((long)ipDataFile.readByte() << 8 * i) & (0xFFL << 8 * i); 
  139.         } 
  140.          
  141.         return result; 
  142.     } 
  143.      
  144.     private String[] getInfo(long itemStartPos) throws Exception 
  145.     { 
  146.         //result[0]放国家,result[1]放地区 
  147.         String[] result = new String[2]; 
  148.          
  149.         ipDataFile.seek(itemStartPos); 
  150.         int mode = (int)ipDataFile.readByte(); 
  151.         switch (mode) 
  152.         { 
  153.         case MODE_1: 
  154.         { 
  155.             long offset = itemStartPos + 1
  156.             long redirPos = readLong(3, offset); 
  157.             result = getInfo(redirPos); 
  158.         } 
  159.         break
  160.         case MODE_2: 
  161.         { 
  162.             long offset = itemStartPos + 1
  163.             long redirPos = readLong(3, offset); 
  164.             result = getInfo(redirPos); 
  165.             result[1] = getArea(offset + 3); 
  166.         } 
  167.         break
  168.         default://MODE_3 
  169.         { 
  170.             long offset = itemStartPos; 
  171.             int countryLen = getStrLength(offset); 
  172.             result[0] = getString(offset, countryLen); 
  173.              
  174.             offset = itemStartPos + countryLen + 1
  175.             result[1] = getArea(offset); 
  176.         } 
  177.         break
  178.         } 
  179.         return result; 
  180.     } 
  181.          
  182.     private String getArea(long offset) throws Exception 
  183.     { 
  184.         ipDataFile.seek(offset); 
  185.         int cityMode = (int)ipDataFile.readByte(); 
  186.         if(cityMode ==  MODE_2 || cityMode ==  MODE_1) 
  187.         { 
  188.             offset = readLong(3, offset + 1); 
  189.         } 
  190.          
  191.         int cityLen = getStrLength(offset);  
  192.         return getString(offset, cityLen);   
  193.     } 
  194.      
  195.     private int getStrLength(long pos) throws IOException 
  196.     { 
  197.         ipDataFile.seek(pos); 
  198.         long strEnd = pos - 1
  199.         while(ipDataFile.readByte() != (byte)0
  200.         { 
  201.             strEnd++; 
  202.         } 
  203.          
  204.         return (int) (strEnd - pos + 1); 
  205.     } 
  206.      
  207.     private String getString(long pos, int len) throws IOException 
  208.     { 
  209.         byte buf[] = new byte[len]; 
  210.          
  211.         ipDataFile.seek(pos); 
  212.         ipDataFile.read(buf); 
  213.         String s = new String(buf, "gbk"); 
  214.         return s; 
  215.     } 
  216.  

 

 

 本文转自 dogegg250 51CTO博客,原文链接:http://blog.51cto.com/jianshusoft/624327,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
7579 0
MySql数据库列表数据分页查询、全文检索API零代码实现
前面文档主要介绍了元数据配置,包括表单定义和表关系管理,以及表单数据的录入,本文主要介绍数据查询和分页在crudapi中的实现。数据查询主要是指按照输入条件检索出符合要求的数据列表,如果数据量大的情况下,需要考虑分页。
121 0
oracle,mysql,SqlServer三种数据库的分页查询
MySql: MySQL数据库实现分页比较简单,提供了 LIMIT函数。一般只需要直接写到sql语句后面就行了。LIMIT子 句可以用来限制由SELECT语句返回过来的数据数量,它有一个或两个参数,如果给出两个参数, 第一个参数指定返回的第一行在所有数据中的位置,从0开始(注意不是1),第二个参数指定最多返回行数。
1009 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
18057 0
SpringBoot2 整合 ClickHouse数据库,实现高性能数据查询分析
本文源码:GitHub·点这里 || GitEE·点这里 一、ClickHouse简介 1、基础简介 Yandex开源的数据分析的数据库,名字叫做ClickHouse,适合流式或批次入库的时序数据。
1759 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
13664 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
23590 0
bboss数据库标签系列一 分页列表详细信息标签预编译sql查询数据
本系列文章详细介绍bboss标签库的数据库标签具体使用方法,涉及到的功能包括: db查询(普通查询、预编译查询,分页查询),db新增、修改、删除、批处理操作(预编译)。 bboss数据库标签系列一 分页列表详细信息标签预编译sql查询数据 beaninfo标签,pager标签,list标签预编译sql获取数据功能相关属性和标签: sqlparamskey-指定将绑定变量参数存储在request 属性集中的变量名称,以便pager,beaninfo,list标签获取sql的绑定变量参数值。
807 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
13760 0
13692
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载