Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 354|回复: 0

[Java基础知识]纯真IP库的结构分析及一个查询类(java)

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

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

    157.                   
    复制代码


      
      
       
       

         
       

         
       
      
    复制代码
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-10 03:25 , Processed in 0.356777 second(s), 34 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表