1.使用序列化
在程序第一次运行的时候,将保存ip分段的文件进行序列化存储,从而使得程序在多个生命周期中的查询时间大幅度降低,除去第一次进行序列化的时间较长外,其他生命周期中程序会进行判断,若存在指定的序列化文件,程序会直接反序列化该文件,从而使程序运行效率大幅提升。
1.1 程序的入口(客户端)
package com.controller;
import java.util.Scanner;
import com.manager.DataProcessManager;
//程序入口
public class SystemController {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入IP : ");
String ip = scanner.nextLine();
long startTime = System.currentTimeMillis();
String location = DataProcessManager.getLocation(ip);
long endTime = System.currentTimeMillis();
System.out.println(location + " : " + (endTime - startTime));
}
}
}
1.2 管理类
package com.manager;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.pojo.IPAndLocationPojo;
import com.util.FileOperatorUtil;
import com.util.IPUtil;
import com.util.RegexUtil;
import com.util.SerDeUtil;
import com.util.StaticValue;
//该类为项目中管理类
public class DataProcessManager {
private static IPAndLocationPojo[] ipLocationPojoArr = null;
static {
// 判断序列化文件是否存在
File file = new File(StaticValue.serde_obj_filepath);
boolean isInit = file.exists();
// 存在就反序列化获取
if (isInit) {
try {
Object object = SerDeUtil.getObj(StaticValue.serde_obj_filepath, StaticValue.cacheByteArrayLength);
ipLocationPojoArr = (IPAndLocationPojo[]) object;
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
} else {
// 不存在就序列化写出
// 获取数据
List<IPAndLocationPojo> ipAndLocationPojos = null;
try {
ipAndLocationPojos = DataProcessManager.getPojoList(StaticValue.ipLibrartPath, StaticValue.encoding);
} catch (IOException e) {
e.printStackTrace();
}
// 转换为数组
ipLocationPojoArr = DataProcessManager.convertListToArrayAndSort(ipAndLocationPojos);
try {
SerDeUtil.saveObject(ipLocationPojoArr, StaticValue.serde_obj_filepath,
StaticValue.cacheByteArrayLength);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 对外提供的接口,入参IP出参地址
public static String getLocation(String ip) {
if (RegexUtil.isValidIP(ip)) {
int index = DataProcessManager.binaraySearch(ipLocationPojoArr, ip);
return index == -1 ? "未找到" : ipLocationPojoArr[index].getLocation();
} else {
return "IP地址格式不正确,请重新输入";
}
}
// 业务类对应的二分法
public static int binaraySearch(IPAndLocationPojo[] ipLocationPojoArr, String ip) {
// 转换为long类型
long ipLong = IPUtil.ipToLong(ip);
int startIndex = 0;
int endIndex = ipLocationPojoArr.length - 1;
int m = (endIndex + startIndex) / 2;
while (startIndex <= endIndex) {
// 大于等于起始 小于等于结束 说明找到了
// 小于起始 在前半截
// 大于结束 在后半截
if (ipLong > ipLocationPojoArr[m].getEndIPLong()) {
startIndex = m + 1;
} else if (ipLong < ipLocationPojoArr[m].getStartIPLong()) {
endIndex = m - 1;
} else {
return m;
}
m = (startIndex + endIndex) / 2;
}
return -1;
}
// 将对象集合转换为对象数组并排序
public static IPAndLocationPojo[] convertListToArrayAndSort(List<IPAndLocationPojo> pojoList) {
// 转换为数组
IPAndLocationPojo[] ipLocationPojoArr = new IPAndLocationPojo[pojoList.size()];
pojoList.toArray(ipLocationPojoArr);
// 排序
Arrays.sort(ipLocationPojoArr);
return ipLocationPojoArr;
}
// 把读取到的List<String> 转换为 List<IPAndLocationPojo>
public static List<IPAndLocationPojo> getPojoList(String ipLibrartPath, String encoding) throws IOException {
List<String> lineList = FileOperatorUtil.getLineList(ipLibrartPath, encoding);
List<IPAndLocationPojo> ipAndLocationPojos = new ArrayList<IPAndLocationPojo>();
// 遍历 获取每一行
for (String string : lineList) {
if (string == null || string.trim().equals("")) {
continue;
}
// 以\t分割,得到三个列
String[] columnArray = string.split("\t");
// 创建结构化对象
IPAndLocationPojo ipAndLocationPojo = new IPAndLocationPojo(columnArray[0], columnArray[1], columnArray[2]);
// 保存到集合中
ipAndLocationPojos.add(ipAndLocationPojo);
}
return ipAndLocationPojos;
}
}
1.3 结构化实体类
package com.pojo;
import java.io.Serializable;
import com.util.IPUtil;
//结构化实体类
public class IPAndLocationPojo implements Serializable, Comparable<IPAndLocationPojo> {
private static final long serialVersionUID = 1L;
@Override
public String toString() {
return "IPAndLocationPojo [startIP=" + startIP + ", endIP=" + endIP + ", location=" + location + "]";
}
// 起始IP
private transient String startIP;
// 衍生字段
private long startIPLong;
private long endIPLong;
// 结束IP
private transient String endIP;
// 归属地
private String location;
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 String getStartIP() {
return startIP;
}
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 int compareTo(IPAndLocationPojo o) {
long result = startIPLong - o.startIPLong;
if (result > 0) {
return 1;
} else if (result < 0) {
return -1;
} else {
return 0;
}
}
}
1.4 工具类
1. 4.1
package com.util;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
//读取文件
public class FileOperatorUtil {
// 读取文件,并返回List
public static List<String> getLineList(String txtFilePath, String encoding) throws IOException {
List<String> lineLine = new ArrayList<String>();
// 字节流
FileInputStream fis = new FileInputStream(txtFilePath);
// 转换为字符流
Reader reader = new InputStreamReader(fis, encoding);
// 封装缓冲流
BufferedReader br = new BufferedReader(reader);
String temp = null;
while ((temp = br.readLine()) != null) {
lineLine.add(temp);
}
// 关闭资源
br.close();
return lineLine;
}
}
1.4.2
package com.util;
import java.util.Scanner;
//IP和long类型之间的转换
public class IPUtil {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String ip = scanner.nextLine();
}
public static Long ipToLong(String ipStr) {
long ipLong = 0;
if (ipStr != null && ipStr.length() > 0) {
// 将ip(点分十进制的形式 a.b.c.d) 地址按.分割
String[] ipSplit = ipStr.split("\\.");
try {
if (ipSplit.length != 4) {
throw new Exception("IP Format Error");
}
for (int i = 0; i < ipSplit.length; i++) {
int temp = Integer.parseInt(ipSplit[i]);
ipLong += temp * (1L << (ipSplit.length - i - 1) * 8);
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
}
return ipLong;
}
// 将long型IP地址转换回点分十进制表示的字符串类型
public static String longToIp(long ipLong) {
StringBuffer ipStr = new StringBuffer();
try {
if (ipLong < 0) {
throw new Exception("Can not to IP...");
}
// 最高8位,直接右移24位
ipStr.append((ipLong >>> 24));
ipStr.append(".");
// 将高8位设置0,然后右移16位
ipStr.append(((ipLong & 0x00FFFFFF) >>> 16));// 获得高8位,6个f对应的是24个1,最高8位设置空为0,之后右移16位将前面多余的16位去掉,以下类推即可
ipStr.append(".");
// 将高16位设置0,然后右移8位
ipStr.append((ipLong & 0x0000FFFF) >>> 8); // 前16位
// 设置0,移除低8位,16个1,高16位设置为0
ipStr.append(".");
// 将高24位设置0
ipStr.append(ipLong & 0x000000FF); // 前24位 设置0,留低8位,8个1,高24位设置为0
} catch (Exception e) {
e.printStackTrace();
}
return ipStr.toString();
}
}
1.4.3
package com.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexUtil {
private static String regexIP = "((25[0-5]|2[0-4]\\d|[1]{1}\\d{1}\\d{1}|[1-9]{1}\\d{1}|\\d{1})($|(?!\\.$)\\.)){4}";
// 校验IP
public static boolean isValidIP(String ip) {
return isValid(regexIP, ip);
}
// 格式验证
public static boolean isValid(String regex, String input) {
Pattern pattern = Pattern.compile(regex);
// 创建匹配器
Matcher matcher = pattern.matcher(input);
return matcher.matches();
}
// 数据提取
public static String getMatchContent(String regex, String input, int groupIndex) {
Pattern pattern = Pattern.compile(regex);
// 创建匹配器
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
return matcher.group(groupIndex);
}
return null;
}
public static String getMatchContent(String regex, String input) {
return getMatchContent(regex, input, 0);
}
}
1.4.4
package com.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerDeUtil {
// 对象写出,引入缓冲流
public static void saveObject(Object obj, String objFilePath, int cacheByyeArrayLength) throws IOException {
FileOutputStream fos = new FileOutputStream(objFilePath);
// 字节数组缓冲流
ByteArrayOutputStream baos = new ByteArrayOutputStream(cacheByyeArrayLength);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] bytes = baos.toByteArray();
fos.write(bytes);
oos.close();
fos.close();
}
// 获取,引入缓冲流
public static Object getObj(String objFilePath, int cacheByyeArrayLength)
throws ClassNotFoundException, IOException {
FileInputStream fis = new FileInputStream(objFilePath);
byte[] bytes = new byte[cacheByyeArrayLength];
fis.read(bytes);
fis.close();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = ois.readObject();
ois.close();
return o;
}
// 写入
public static void saveObject(Object obj, String objFilePath) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(objFilePath));
oos.writeObject(obj);
oos.close();
}
// 读取
public static Object getObj(String objFilePath) throws ClassNotFoundException, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(objFilePath));
Object o = ois.readObject();
ois.close();
return o;
}
}
1.4.5
package com.util;
public class StaticValue {
// 地址库文件
public static String ipLibrartPath = "ip_location_relation.txt";
// 字符编码
public static String encoding = "utf-8";
// 文件地址
// public static String serde_obj_filepath = "ipLibObj.data";
public static String serde_obj_filepath = "D:/1";
// 根据文件大小,计算长度
public static int cacheByteArrayLength = 25 * 1024 * 1024;
}
2. 使用数据库
将保存ip分段的文件导入到MySQL数据库中,之后的查询直接使用SQL语句进行查询
2.1 添加
public class InsertIP {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = DBUtil.getConnection();
String sql=("insert into ip (startIP,endIP,location) values(?,?,?)");
stmt=conn.prepareStatement(sql);
int i=0;
for (IPAndLocationPojo ipAndLocationPojo : ipLocationPojoArr) {
stmt.setLong(1, ipAndLocationPojo.getStartIPLong());
stmt.setLong(2, ipAndLocationPojo.getEndIPLong());
stmt.setString(3, ipAndLocationPojo.getLocation());
stmt.executeUpdate();
}
System.out.println("执行成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
2.2 查询
package com.IPSearch;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;
import com.util.DBUtil;
import com.util.IPUtil;
public class Search_IP {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String ip = scanner.nextLine();
long ip1 = IPUtil.ipToLong(ip);
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
// 3 创建语句传输对象
String sql = "select location from ip where ? >= startIP and ? <= endIP";
stmt = conn.prepareStatement(sql);
stmt.setLong(1, ip1);
stmt.setLong(2, ip1);
// 4 执行并获取结果
rs = stmt.executeQuery();
// 遍历获取数据
while (rs.next()) {
System.out.print(rs.getString("location") + " ");
System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(rs);
DBUtil.close(stmt);
DBUtil.close(conn);
}
}
}
2.3 工具类DBUtil
package com.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
public static Connection getConnection() throws SQLException, ClassNotFoundException {
// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 链接对象
// 导的所有包都是 java.sql的
// jdbc:mysql://IP:端口/数据库 用户名 密码
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/work_11_11", "root", "root");
}
// AutoCloseable 是 Connection,Statement,ResultSet的父类
//利用多态性,来什么都可以接收
public static void close(AutoCloseable obj) {
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}