IP归属地在线查询平台

一、项目介绍

1、背景

根据IP得到位置,加标签

进行大数据分析,比如淘宝推荐等提供优质数据

             www.ip.cn 等 查询IP

2、需求

     

IP 分析 归属地信息 , 查找在毫秒内完成

IP地址库,公网都是开放的

IANA : 国际组织,负责公网IP维护分发

3、技术栈

Eclipse ,JavaSE中面向对象、IO流、二分法算法、Base64编码、工具类封装

4、目标

 

通过开发IP地址归属地查询平台,我们需要对JavaSE综合技术有所提升,增强实战能力。学习完该项目我们应该具备如下能力:

1 面向对象程序设计

2 工具类封装与使用写法

3 文件IO流

4 字符串处理

5 二分法查找

6 IP地址的不同形式的使用

二、主要思路

       

1 程序中读取内容

2 解析IP字符串,进行结构化处理

3 封装工具类

4 接口API

          入参 : IP

           出参 : 归属地

应用开发类项目

C/S结构,需要有特定的客户端,比如QQ,微信,eclipse

Web开发类项目

B/S结构为主.通过网页形式访问的在线系统,比如各类官网,各类管理系统等

中小型项目研发标准流程

1 需求概述-需求描述:说清楚你为什么做这个项目

根据IP获取归属地

2 需求分析 :

根据需求概述,用技术角度考虑一下,是否可行

三方面 : 1 输入 , 2  输出 , 3 必备物料(地址库)

输入 : 给定一个任意的合法IP地址

输出 : 返回IP地址对应的地址库

3 开发步骤

1 读取IP地址库

2 解析地址库的信息,进行结构化处理

3 将对象保存到list中

4 进行二分法查找,提高效率

5 对外提供访问的接口

6 测试

4 细节开发与风险控制

5 BUG修复,调优,标准化

6 正式上线

7 项目总结-项目复盘

  •  三、代码开发

  1.  无脑读取文件

 

2. 文本文件读取工具类

抽象工具类

1 通过编码,实现输入与输出

2 抽象输入与输出,形成方法入参和出参

3 工具类代码实现并测试

2.1  工具类编码

 2.2  工具类测试

3.结构化 

结构化 : 当我们知道第一个数据的格式的时候,那么第二个的数据格式就已经确定了,有规律可循,方便操作

根据非结构化数据,找到对应的规则,并创建对应的实体类进行封装,转换为结构化数

4. 抽象实体类并保存数据 

4.1 实体类

 

4.2  拆分数据为三列

 

5. 封装业务类DataProcessManager

5.1  封装数据集合

// 1 文件路径

                  String ipLibrayPath = "ip_location_relation.txt";

                  String encoding = "UTF-8";

                  // 保存数据对象

                  List<IPAndLocationPojo> ipAndLocationPojos = new ArrayList<IPAndLocationPojo>();

                  try {

                          List<String> lineList = FileOperatorUtil.getLineList(ipLibrayPath,

                                            encoding);

                          for (String string : lineList) {

                                   // 判断是否是空行

                                   if (string==null || string.trim().equals("")) {

                                            continue;

                                   }

                                   // 分割为数组

                                   String[] columnArray = string.split("        ");

                                   // 获取起始IP

                                   String startIP = columnArray[0];

                                   // 获取结束IP

                                   String endIP = columnArray[1];

                                   // 获取归属地

                                   String location = columnArray[2];

                                   // 封装到对象中

                                   IPAndLocationPojo ipAndLocationPojo = new IPAndLocationPojo(startIP, endIP, location);

                                   // 添加到集合中

                                   ipAndLocationPojos.add(ipAndLocationPojo);

                          }

                          // 遍历测试

                          for (IPAndLocationPojo ip : ipAndLocationPojos) {

                                   System.out.println(ip);

                          }

                  } catch (IOException e) {

                          e.printStackTrace();

                  }

5.2 封装为管理类

public class DataProcessManager {

         /**

          * 结构化数据集合

          *

          * @param filePath

          * @param encoding

          * @return

          * @throws IOException

          */

         public static List<IPAndLocationPojo> getPojoList(String filePath,

                          String encoding) throws IOException {

                  // 保存数据对象

                  List<IPAndLocationPojo> ipAndLocationPojos = new ArrayList<IPAndLocationPojo>();

                  List<String> lineList = FileOperatorUtil

                                   .getLineList(filePath, encoding);

                  for (String string : lineList) {

                          // 判断是否是空行

                          if (string == null || string.trim().equals("")) {

                                   continue;

                          }

                          // 分割为数组

                          String[] columnArray = string.split("        ");

                          // 获取起始IP

                          String startIP = columnArray[0];

                          // 获取结束IP

                          String endIP = columnArray[1];

                          // 获取归属地

                          String location = columnArray[2];

                          // 封装到对象中

                          IPAndLocationPojo ipAndLocationPojo = new IPAndLocationPojo(

                                            startIP, endIP, location);

                          // 添加到集合中

                          ipAndLocationPojos.add(ipAndLocationPojo);

                  }

                  return ipAndLocationPojos;

         }

}

5.3 测试封装类的方法

6.结构化集合转换为数组,toArray 

public class TestListToArray_01 {

         public static void main(String[] args) {

                  List<String> list = new ArrayList<String>();

                  list.add("a");

                  list.add("b");

                  list.add("c");

                  String[] strs = new String[list.size()];

                  list.toArray(strs);

                  for (String string : strs) {

                          System.out.println(string);

                  }

                  // 结构化数据集合转数组

                  // 1 文件路径

                  String ipLibrayPath = "ip_location_relation.txt";

                  String encoding = "UTF-8";

                  // 保存数据对象

                  try {

                          List<IPAndLocationPojo> ipAndLocationPojos = DataProcessManager

                                            .getPojoList(ipLibrayPath, encoding);

                          IPAndLocationPojo[] ipAndLocationPojoArray = new IPAndLocationPojo[ipAndLocationPojos

                                            .size()];

                          ipAndLocationPojos.toArray(ipAndLocationPojoArray);

                          for (IPAndLocationPojo ipAndLocationPojo : ipAndLocationPojoArray) {

                                   System.out.println(ipAndLocationPojo);

                          }

                  } catch (IOException e) {

                          e.printStackTrace();

                  }

         }

}

7.对象数组进行排序

7.1 实现排序

1 自定义实现

冒泡排序

选择排序

2 工具类

Collections

Arrays

一般不需要自己写,使用工具即可

7.2  排序注意事项

被排序的对象必须具备可比性

1 实现Comparable接口

2 实现 Comparator接口

public class TestArraySort_01 {

         public static void main(String[] args) {

                  // 基本类型

                  int[] intArray = { 10, 6, 8, 9, 3, 15 };

                  Arrays.sort(intArray);

                  for (int i : intArray) {

                          System.out.println(i);

                  }

                  // 引用类型 按照年龄升序

                  User[] users = { new User(18, "张三1"), new User(19, "张三2"),

                                   new User(15, "张三3"), new User(17, "张三4") };

                  Arrays.sort(users);

                  for (User user : users) {

                          System.out.println(user);

                  }

         }

}

class User implements Comparable<User> {

         private int age;

         private String name;

         public int getAge() {

                  return age;

         }

         public void setAge(int age) {

                  this.age = age;

         }

         public String getName() {

                  return name;

         }

         public void setName(String name) {

                  this.name = name;

         }

         public User(int age, String name) {

                  super();

                  this.age = age;

                  this.name = name;

         }

         public User() {

                  super();

         }

         @Override

         public String toString() {

                  return "User [age=" + age + ", name=" + name + "]";

         }

         @Override

         public int compareTo(User o) {

                  // 返回大于0 往后放

                  // 返回小于0 往前放

                  // 返回0 相等

                  return this.age - o.age;

         }

}

7.3  业务问题

上面已经通过测试,解决了技术问题,下面就是业务问题

我们比较肯定是IP,我们的IP地址没有办法使用字符串进行比较,因为字符串比较的ASCII码进行比较的

比如 1.1.6.2 和 1.1.125.1

看上去 肯定是 125大于 6  但是按ASCII码进行比较的话 是 6 大于 125

所以 我们需要把IP进行转换

public class IPUtil {

         public static void main(String[] args) {

                  String ip = "126.56.78.59";

                  long ipLong = ipToLong(ip);

                  System.out.println(ipLong);

                  System.out.println(longToIP(ipLong));

         }

         /**

          * 将127.0.0.1形式的IP地址转换成十进制整数,这里没有进行任何错误处理

          * 通过左移位操作(<<)给每一段的数字加权,第一段的权为2的24次方,第二段的权为2的16次方,第三段的权为2的8次方,最后一段的权为1

          */

         public static long ipToLong(String ipaddress) {

                  long[] ip = new long[4];

                  // 先找到IP地址字符串中.的位置

                  int position1 = ipaddress.indexOf(".");

                  int position2 = ipaddress.indexOf(".", position1 + 1);

                  int position3 = ipaddress.indexOf(".", position2 + 1);

                  // 将每个.之间的字符串转换成整型

                  ip[0] = Long.parseLong(ipaddress.substring(0, position1));

                  ip[1] = Long.parseLong(ipaddress.substring(position1 + 1, position2));

                  ip[2] = Long.parseLong(ipaddress.substring(position2 + 1, position3));

                  ip[3] = Long.parseLong(ipaddress.substring(position3 + 1));

                  return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];

         }

         /**

          * 将十进制整数形式转换成127.0.0.1形式的ip地址 将整数值进行右移位操作(>>>),右移24位,右移时高位补0,得到的数字即为第一段IP。

          * 通过与操作符(&)将整数值的高8位设为0,再右移16位,得到的数字即为第二段IP。

          * 通过与操作符吧整数值的高16位设为0,再右移8位,得到的数字即为第三段IP。 通过与操作符吧整数值的高24位设为0,得到的数字即为第四段IP。

          */

         public static String longToIP(long ipaddress) {

                  StringBuffer sb = new StringBuffer("");

                  // 直接右移24位

                  sb.append(String.valueOf((ipaddress >>> 24)));

                  sb.append(".");

                  // 将高8位置0,然后右移16位

                  sb.append(String.valueOf((ipaddress & 0x00FFFFFF) >>> 16));

                  sb.append(".");

                  // 将高16位置0,然后右移8位

                  sb.append(String.valueOf((ipaddress & 0x0000FFFF) >>> 8));

                  sb.append(".");

                  // 将高24位置0

                  sb.append(String.valueOf((ipaddress & 0x000000FF)));

                  return sb.toString();

         }

}

7.4   实体类中衍生两个字段

上面可以把IP地址转换为long类型,方便进行排序比较

那么我们有起始IP和结束IP , 另外转换之后的长整型的值,也是需要保存起来的,和对应的IP字段需要一一对应

所以需要在对应的IP实体类中,再添加两个成员变量,分别保存转换之后的long值

 通过构造方法对long类赋值

 完整的实体类

public class IPAndLocationPojo implements Comparable<IPAndLocationPojo> {

         // 衍生字段,用于保存IP对应的long值

         private long startIPLong;

         private long endIPLong;

         /**

          * 起始IP

          */

         private String startIP;

         /**

          * 结束IP

          */

         private String endIP;

         /**

          * 归属地

          */

         private String location;

         @Override

         public int compareTo(IPAndLocationPojo o) {

                  long status = this.startIPLong - o.startIPLong;

                  // 不能强制转换 , 如果两个值相差 2147483647的话,转换为int之后 得到负数

                  // return (int) (this.startIPLong - o.startIPLong);

                  return status > 0 ? 1 : 0;

         }

         public String getStartIP() {

                  return startIP;

         }

         public long getStartIPLong() {

                  return startIPLong;

         }

         public void setStartIPLong(long startIPLong) {

                  this.startIPLong = startIPLong;

         }

         public long getEndIPLong() {

                  return endIPLong;

         }

         public void setEndIPLong(long endIPLong) {

                  this.endIPLong = endIPLong;

         }

         public void setStartIP(String startIP) {

                  this.startIP = startIP;

         }

         public String getEndIP() {

                  return endIP;

         }

         public void setEndIP(String endIP) {

                  this.endIP = endIP;

         }

         public String getLocation() {

                  return location;

         }

         public void setLocation(String location) {

                  this.location = location;

         }

         public IPAndLocationPojo(String startIP, String endIP, String location) {

                  super();

                  this.startIP = startIP;

                  this.endIP = endIP;

                  this.location = location;

                  // 对长整型赋值

                  this.startIPLong = IPUtil.ipToLong(startIP);

                  this.endIPLong = IPUtil.ipToLong(endIP);

         }

         public IPAndLocationPojo() {

                  super();

         }

         @Override

         public String toString() {

                  return "IPAndLocationPojo [startIP=" + startIP + ", endIP=" + endIP

                                   + ", location=" + location + "]";

         }

}

7.5  测试pojo排序

上面通过两个衍生字段已经让实体类拥有了排序功能,但是还没有进行排序

 7.6 封装排序方法

 7.7 测试

 8.二分法查询

上面已经把结构化数据进行排序,下一步就是二分法查询

// 1 确定起始和结束位置及中间位置

                  // 2 如果目标数据小于中间数据,起始位置不变,结束位置为 中间位置-1 , 重新生成中间位置

                  // 3 如果目标数据大于中间数据,结束位置不变,起始位置为 中间位置+1 , 重新生成中间位置

                  // 4 如果目标数据等于中间数据, 终止,中间位置则是对应的下标

                  // 5 如果起始位置 大于 结束位置 说明不存在

8.1  基本类型

public class TestBinaraySearch_01 {

         public static void main(String[] args) {

                  test1();

         }

         public static void test1() {

                  // 1 确定起始和结束位置及中间位置

                  // 2 如果目标数据小于中间数据,起始位置不变,结束位置为 中间位置-1 , 重新生成中间位置

                  // 3 如果目标数据大于中间数据,结束位置不变,起始位置为 中间位置+1 , 重新生成中间位置

                  // 4 如果目标数据等于中间数据, 终止,中间位置则是对应的下标

                  // 5 如果起始位置 大于 结束位置 说明不存在

                  int[] arr = { 1, 2, 3, 4, 7, 8, 9, 11, 15, 16, 18, 21, 26, 33, 55 };

                  int target = 19;

                  int startIndex = 0;

                  int endIndex = arr.length - 1;

                  int m = (startIndex + endIndex) / 2;

                  while (startIndex <= endIndex) {

                          if (target == arr[m]) {

                                   System.out.println(target + " 在 " + m + " 位上");

                                   return;

                           }

                          if (target < arr[m]) {

                                   endIndex = m - 1;

                          } else {

                                   startIndex = m + 1;

                          }

                          m = (startIndex + endIndex) / 2;

                  }

                  System.out.println(target+" 不存在");

         }

}

8.2  复杂类型

 9.IP地址对象二分法

9.1  编码实现

public static void test3() throws IOException {

                  // 1 文件路径

                  String ipLibrayPath = "ip_location_relation.txt";

                  String encoding = "UTF-8";

                  // 保存数据对象

                  List<IPAndLocationPojo> ipAndLocationPojos = DataProcessManager

                                   .getPojoList(ipLibrayPath, encoding);

                  // 转数组并排序

                  IPAndLocationPojo[] ipAndLocationPojoArray = DataProcessManager

                                   .convertListToArraySort(ipAndLocationPojos);

                  // 目标数据是IP

                  String targetIP = "1226.44.58.127";

                  // 把IP转换为long

                  long targetIPLong = IPUtil.ipToLong(targetIP);

                  int startIndex = 0;

                  int endIndex = ipAndLocationPojoArray.length - 1;

                  int m = (startIndex + endIndex) / 2;

                  /**

                   * 如果 小于 起始IP  找前面

                   *

                   * 如果 大于 起始IP 找后面

                   *

                   * 如果 大于等于起始IP且 小于等于 结束IP 则说明找到了

                   */

                  while (startIndex <= endIndex) {

                          if (targetIPLong >= ipAndLocationPojoArray[m].getStartIPLong() && targetIPLong <= ipAndLocationPojoArray[m].getEndIPLong()) {

                                   System.out.println(targetIP + " 在 " + ipAndLocationPojoArray[m].getLocation());

                                   return;

                          }

                          if (targetIPLong < ipAndLocationPojoArray[m].getStartIPLong()) {

                                   endIndex = m - 1;

                          } else {

                                   startIndex = m + 1;

                          }

                          m = (startIndex + endIndex) / 2;

                  }

                  System.out.println(targetIP + " 不存在");

         }

9.2  封装

public class DataProcessManager {

         /**

          * 二分法查找,入参是IP和数组,出参是对应的索引,找不到返回-1

          *

          * @param ipAndLocationPojoArray

          * @param targetIP

          * @return

          * @throws IOException

          */

         public static int binaraySeach(IPAndLocationPojo[] ipAndLocationPojoArray,

                          String targetIP) throws IOException {

                  // 把IP转换为long

                  long targetIPLong = IPUtil.ipToLong(targetIP);

                  int startIndex = 0;

                  int endIndex = ipAndLocationPojoArray.length - 1;

                  int m = (startIndex + endIndex) / 2;

                  /**

                   * 如果 小于 起始IP 找前面

                   *

                   * 如果 大于 起始IP 找后面

                   *

                   * 如果 大于等于起始IP且 小于等于 结束IP 则说明找到了

                   */

                  while (startIndex <= endIndex) {

                          if (targetIPLong >= ipAndLocationPojoArray[m].getStartIPLong()

                                           && targetIPLong <= ipAndLocationPojoArray[m].getEndIPLong()) {

                                   return m;

                          }

                          if (targetIPLong < ipAndLocationPojoArray[m].getStartIPLong()) {

                                   endIndex = m - 1;

                          } else {

                                   startIndex = m + 1;

                          }

                          m = (startIndex + endIndex) / 2;

                  }

                  return -1;

         }

9.3 测试

 10  工具类封装

10.1 编码

上面已经把二分法完成,已经可以实现功能了,测试代码就相当于客户端

但是客户端现在需要知道的数据还有点多,比如IP地址库文件名,比如字符编码等

这些都是客户无感的,客户只关心 IP和归属地,我把IP给你,你把归属地给我,就行了

所以此时 我们需要对外提供一个方法,只有入参和出参

public class DataProcessManager {

         /**

          * 对外提供的接口,入参是IP,出参是归属地

          *

          * @param ip

          * @return

          */

         public static String getLocation(String ip) {

                  // 1 文件路径

                  String ipLibrayPath = "ip_location_relation.txt";

                  String encoding = "UTF-8";

                  // 保存数据对象

                  List<IPAndLocationPojo> ipAndLocationPojos = null;

                  IPAndLocationPojo[] ipAndLocationPojoArray = null;

                  try {

                          // 获取数据

                          ipAndLocationPojos = DataProcessManager.getPojoList(ipLibrayPath,

                                            encoding);

                          // 转数组并排序

                          ipAndLocationPojoArray = DataProcessManager

                                            .convertListToArraySort(ipAndLocationPojos);

                  } catch (IOException e) {

                          e.printStackTrace();

                  }

                  // 二分法查找

                  int index = DataProcessManager.binaraySeach(ipAndLocationPojoArray, ip);

                  // 判断是否找到

                  if (index == -1) {

                          return null;

                  } else {

                          return ipAndLocationPojoArray[index].getLocation();

                  }

         }

10.2 测试

 10.3  优化

现在getLocation方法,每次调用都会去读取IP地址库,并且转换为结构化数据,然后进行排序

这样效率很低,我们只需要让以上初始化工作在程序生命周期中只执行一次就可以

静态语句块 : 加载阶段执行,最先执行,且只执行一次

静态变量 : 静态变量的值再整个程序生命周期周有效

11. 入口类

项目代码标准化之后,肯定要准备一个程序入口

 

 

 12  打包

1 普通jar包 : 不能独立运行,一般用于让其他程序引入调用的

2 可执行jar包 : 一般不会被引入

 

 

 

 运行jar包

 

把jar包和地址库放在同一目录下

然后CMD 进入该目录

执行命令 java   -jar  xxx.jar 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值