欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

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

发布时间:2025/6/15 编程问答 50 豆豆
生活随笔 收集整理的这篇文章主要介绍了 纯真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。

 

  • import java.io.*; 
  •  
  • public class IPSeeker 
  •     protected RandomAccessFile ipDataFile; 
  •     protected final int RECORD_LEN = 7
  •     protected final int MODE_1 = 0x01//重定向国家记录,地区记录 
  •     protected final int MODE_2 = 0x02//重定向国家记录,有地区记录 
  •     protected final int MODE_3 = 0x03//default 
  •     protected long indexBegin; 
  •     protected long indexEnd; 
  •          
  •     public IPSeeker() throws Exception 
  •     { 
  •         //打开纯真IP库数据文件 
  •         ipDataFile = new RandomAccessFile("qqwry.dat""r"); 
  •          
  •         indexBegin = readLong(40);             
  •         indexEnd = readLong(44); 
  •     } 
  •      
  •     public static void main(String[] args) throws Exception 
  •     { 
  •         IPSeeker seeker = new IPSeeker();//may throw Exception 
  •         String result = seeker.search("111.2.13.4");//输入查询的IP地址 
  •         System.out.println(result); 
  •         seeker.close();//关闭,若不调用close,将在finalize关闭 
  •         seeker = null
  •     } 
  •      
  •      
  •     @Override 
  •     protected void finalize() throws Throwable 
  •     { 
  •         try 
  •         { 
  •             ipDataFile.close(); 
  •         }  
  •         catch (IOException e) 
  •         { 
  •              
  •         } 
  •         super.finalize(); 
  •     } 
  •      
  •      
  •     public void close() 
  •     { 
  •         try 
  •         { 
  •             ipDataFile.close(); 
  •         }  
  •         catch (IOException e) 
  •         { 
  •              
  •         } 
  •     } 
  •  
  •     public String search(String ipStr) throws Exception 
  •     { 
  •         //采用二分法查询 
  •         long recordCount = (indexEnd - indexBegin) / 7 + 1
  •          
  •         long itemStart = 0
  •         long itemEnd = recordCount - 1
  •         long ip = IPSeeker.stringIP2Long(ipStr); 
  •         long middle = 0
  •         long midIP = 0
  •         while(itemStart <= itemEnd) 
  •         { 
  •             middle = (itemStart +  itemEnd) / 2
  •             midIP = readLong(4, indexBegin + middle * 7); 
  •             //String temp = IPSeeker.long2StringIP(midIP); 
  •             if(midIP == ip) 
  •             { 
  •                 break
  •             } 
  •             else if(midIP < ip) 
  •             { 
  •                 itemStart = middle + 1
  •             } 
  •             else//midIP > ip 
  •             { 
  •                 itemEnd = middle - 1
  •             } 
  •         } 
  •          
  •         //若无完全匹配结果,则向前匹配 
  •         if(ip < midIP && middle > 0
  •         { 
  •             middle--; 
  •         } 
  •  
  •         long item = readLong(3, indexBegin + middle * 7 + 4); 
  •         String[] result = getInfo(item + 4);//取出信息 
  •         return long2StringIP(readLong(4, indexBegin + middle * 7))+ ","//匹配到的IP地址(段) 
  •                 + result[0] + "," //国家 
  •                 + result[1];//地区 
  •     } 
  •      
  •     //32位整型格式的IP地址(little-endian)转化到字符串格式的IP地址 
  •     public static String long2StringIP(long ip) 
  •     { 
  •         long ip4 = ip >> 0 & 0x000000FF
  •         long ip3 = ip >> 8 & 0x000000FF
  •         long ip2 = ip >> 16 & 0x000000FF
  •         long ip1 = ip >> 24 & 0x000000FF
  •          
  •         return String.valueOf(ip1) + "." + String.valueOf(ip2) + "." +  
  •                 String.valueOf(ip3) + "." + String.valueOf(ip4); 
  •     } 
  •      
  •     //字符串格式的IP地址转化到32位整型格式的IP地址(little-endian) 
  •     public static Long stringIP2Long(String ipStr) throws Exception 
  •     { 
  •         String[] list = ipStr.split("\\."); 
  •         if(list.length != 4
  •         { 
  •             throw new Exception("IP地址格式错误"); 
  •         } 
  •         long ip = Long.parseLong(list[0]) << 24 & 0xFF000000
  •         ip += Long.parseLong(list[1]) << 16 & 0x00FF0000
  •         ip += Long.parseLong(list[2]) << 8 & 0x0000FF00
  •         ip += Long.parseLong(list[3]) << 0 & 0x000000FF
  •         return ip; 
  •     } 
  •      
  •     //读取一个n位的 
  •     private long readLong(int nByte, long offset) throws Exception 
  •     { 
  •         ipDataFile.seek(offset); 
  •          
  •         long result = 0
  •         if(nByte > 4 || nByte < 0
  •             throw new Exception("nBit should be 0-4"); 
  •          
  •         for(int i = 0; i < nByte; i++) 
  •         { 
  •             result |= ((long)ipDataFile.readByte() << 8 * i) & (0xFFL << 8 * i); 
  •         } 
  •          
  •         return result; 
  •     } 
  •      
  •     private String[] getInfo(long itemStartPos) throws Exception 
  •     { 
  •         //result[0]放国家,result[1]放地区 
  •         String[] result = new String[2]; 
  •          
  •         ipDataFile.seek(itemStartPos); 
  •         int mode = (int)ipDataFile.readByte(); 
  •         switch (mode) 
  •         { 
  •         case MODE_1: 
  •         { 
  •             long offset = itemStartPos + 1
  •             long redirPos = readLong(3, offset); 
  •             result = getInfo(redirPos); 
  •         } 
  •         break
  •         case MODE_2: 
  •         { 
  •             long offset = itemStartPos + 1
  •             long redirPos = readLong(3, offset); 
  •             result = getInfo(redirPos); 
  •             result[1] = getArea(offset + 3); 
  •         } 
  •         break
  •         default://MODE_3 
  •         { 
  •             long offset = itemStartPos; 
  •             int countryLen = getStrLength(offset); 
  •             result[0] = getString(offset, countryLen); 
  •              
  •             offset = itemStartPos + countryLen + 1
  •             result[1] = getArea(offset); 
  •         } 
  •         break
  •         } 
  •         return result; 
  •     } 
  •          
  •     private String getArea(long offset) throws Exception 
  •     { 
  •         ipDataFile.seek(offset); 
  •         int cityMode = (int)ipDataFile.readByte(); 
  •         if(cityMode ==  MODE_2 || cityMode ==  MODE_1) 
  •         { 
  •             offset = readLong(3, offset + 1); 
  •         } 
  •          
  •         int cityLen = getStrLength(offset);  
  •         return getString(offset, cityLen);   
  •     } 
  •      
  •     private int getStrLength(long pos) throws IOException 
  •     { 
  •         ipDataFile.seek(pos); 
  •         long strEnd = pos - 1
  •         while(ipDataFile.readByte() != (byte)0
  •         { 
  •             strEnd++; 
  •         } 
  •          
  •         return (int) (strEnd - pos + 1); 
  •     } 
  •      
  •     private String getString(long pos, int len) throws IOException 
  •     { 
  •         byte buf[] = new byte[len]; 
  •          
  •         ipDataFile.seek(pos); 
  •         ipDataFile.read(buf); 
  •         String s = new String(buf, "gbk"); 
  •         return s; 
  •     } 
  •  
  •  

     

     

    转载于:https://blog.51cto.com/jianshusoft/624327

    总结

    以上是生活随笔为你收集整理的纯真IP库的结构分析及一个查询类的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。