2022.02.12_Java学习总结_IP地址查询_2

一、 项目介绍

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. BUG优化

目前用户输入的IP地址.如果格式不正确,就会出现下标越界异常
所以我们需要添加校验 : 正则表达式

1.1 技术问题

加入正则表达式
Java类中两个核心类
PatternMatcher
Pattern 是正则表达式引擎
Matcher 是功能较强大的匹配器
三种匹配模式
Matches : 全词匹配
Find : 任意位置
lookingAt : 从前往后匹配

Find和group连用可以提取数据

public class TestRegex_01 {
	public static void main(String[] args) {
		// (?=(\b|\D))(((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{1,2})|(2[0-4]\d)|(25[0-5]))(?=(\b|\D))
		// [^0-9]
		// . 匹配任意字符
		// \ 转移符
		// + 大于等于1 
		// * 大于等于0
		// ? 0或1
		// {n} n次
		// {n,} 大于等于n次
		// {n,m} 大于等于n且小于等于m
		String regex = "\\d{11}";
		String input = "a13101354234a";
		// 创建引擎
		Pattern pattern = Pattern.compile(regex);
		// 创建匹配器
		Matcher matcher = pattern.matcher(input);
		// 匹配校验
		// 全词匹配
		// System.out.println(matcher.matches());
		// 从前往后匹配
		// System.out.println(matcher.lookingAt());
		// 任意位置
		System.out.println(matcher.find());
		
		regex = "(?=(\\b|\\D))(((\\d{1,2})|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))\\.){3}((\\d{1,2})|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))(?=(\\b|\\D))";
		input = "352.35.4.6";
		pattern = Pattern.compile(regex);
		matcher = pattern.matcher(input);
		System.out.println(matcher.matches());
	}
}


1.2 封装方法

	/**
	 * 格式校验 全词匹配
	 * 
	 * @param regex
	 * @param input
	 * @return
	 */
	public static boolean isValid(String regex, String input) {
		// 创建引擎对象
		Pattern pattern = Pattern.compile(regex);
		// 创建匹配器
		Matcher matcher = pattern.matcher(input);
		// 进行全词校验
		return matcher.matches();
	}
}

1.3 测试

public class TestRegex_02 {
	public static void main(String[] args) {
		String regex = "(?=(\\b|\\D))(((\\d{1,2})|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))\\.){3}((\\d{1,2})|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))(?=(\\b|\\D))";
		String input = "252.35.4.6";
		System.out.println(RegexUtil.isValid(regex, input));
	}

}

1.4 应用到业务当中

public class RegexUtil {
	/**
	 * 校验IP
	 * 
	 * @param input
	 * @return
	 */
	public static boolean isValidIP(String input) {
		String regex = "(?=(\\b|\\D))(((\\d{1,2})|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))\\.){3}((\\d{1,2})|(1\\d{1,2})|(2[0-4]\\d)|(25[0-5]))(?=(\\b|\\D))";
		return isValid(regex, input);
	}
对外提供的接口为DataProcessManager类中的getLocation方法,并不是入口类
所以需要在getLocation方法中加入正则校验

	/**
	 * 对外提供的接口,入参是IP,出参是归属地
	 * 
	 * @param ip
	 * @return
	 */
	public static String getLocation(String ip) {
		// 校验IP
		if (!RegexUtil.isValidIP(ip)) {
			return "请输入正确的IP地址";
		}

1.5 测试

在这里插入图片描述

尽管IP格式不正确,也不会像一开始一样,.终止程序生命周期了,使程序的鲁棒性得到增强

2. 性能调优

2.1 相关技术

2.1.1 硬件方面
内存,CPU,磁盘,网络 等 都可以实现调优
2.1.2 软件方面
2.1.2.1 直接调优
哪里慢就调哪里,不需要一些其他花哨的
主要指算法和内存层面的调优,一般难度较大,所以绝大部分都是非直接调优

2.1.2.2 迂回调优
通过一些设计和策略进行优化
2.1.2.3 迂回调优方向
缓存策略

在这里插入图片描述

通过时间断点可以看出,结构化耗时较多

一开始,同时校验两个IP的时候,需要读取数据两次,结构化数据两次,所以我们把读数据和结构化数据加入到静态语句块中,从而可以让第二次及以后进行查询的时候,不再进行结构化操作,而是使用结构好的数据,这样效率得到了提高
那么如果让每一次生命周期都使用的是结构化好的数据,那么效率应该还会有所提高

序列化和反序列化

2.2 首次调优

2.2.1 技术问题
序列化 : 把内存中的数据持久化存储到硬盘当中
反序列化 : 把存储到本地文件中的数据,加载到内存中

想要被序列化,该类必须实现Serlizable接口

class User implements Serializable {

	private static final long serialVersionUID = 1L;
	private String name;
	private int age;

	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
public class TestSerDe_01 {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		User user = new User("张三", 18);
		String filePath = "obj.data";
		// 1 序列化
		FileOutputStream fos = new FileOutputStream(filePath);
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(user);
		oos.flush();
		oos.close();
		// 2 反序列化
		FileInputStream fis = new FileInputStream(filePath);
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object obj = ois.readObject();
		System.out.println(obj);
		ois.close();
	}
}
2.2.2 封装工具类
/**
 * 序列化相关工具类
 * 
 * @author 天亮教育-帅气多汁你泽哥
 * @Date 2022年2月12日 下午2:12:48
 */
public class SerDeUtil {
	/**
	 * 序列化
	 * 
	 * @param obj
	 * @param filePath
	 * @throws IOException
	 */
	public static void saveObj(Object obj, String filePath) throws IOException {
		// 1 序列化
		FileOutputStream fos = new FileOutputStream(filePath);
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(obj);
		oos.flush();
		oos.close();
	}

	/**
	 * 反序列化
	 * 
	 * @param filePath
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public static Object getObj(String filePath) throws IOException,
			ClassNotFoundException {
		// 2 反序列化
		FileInputStream fis = new FileInputStream(filePath);
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object obj = ois.readObject();
		return obj;
	}
}

2.2.3 测试
public class TestSerDe_02 {
	public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		   User user = new User("李四", 18);
		   String filePath = "obj.data";
		// // 1 序列化
		   SerDeUtil.saveObj(user, filePath);
		// // 2 反序列化
		   Object obj = SerDeUtil.getObj(filePath);
		   System.out.println(obj);
	}
}
2.2.4 初始化优化
在初始化的时候,判断是否有序列化之后的文件
如果没有,就读取数据,结构化,并且序列化到本地
如果有序列化的文件,则不再读取文件结构化,而且进行反序列化直接获取排序之后的数据

所以 需要更改初始化的地方,也就是DataProcessManager中的静态代码块

注意 IPAndLocationPojo 需要实现 Serinalizable接口

在这里插入图片描述

public class DataProcessManager {

	private static IPAndLocationPojo[] ipAndLocationPojoArray = null;
	static {
		// 序列化之后的文件名
		String serdeObjFilePath = "ipLibObj.data";
		// 1 文件路径
		String ipLibrayPath = "ip_location_relation.txt";
		String encoding = "UTF-8";
		// 保存数据对象
		List<IPAndLocationPojo> ipAndLocationPojos = null;
		// 判断是否有序列化的文件
		File file = new File(serdeObjFilePath);
		if (file.exists()) {
			// TODO
			long startTime = System.currentTimeMillis();
			// 如果存在 就反序列化
			try {
				Object obj = SerDeUtil.getObj(serdeObjFilePath);
				ipAndLocationPojoArray = (IPAndLocationPojo[]) obj;
			} catch (ClassNotFoundException | IOException e) {
				e.printStackTrace();
			}
			long endTime = System.currentTimeMillis();
			System.out.println("反序列化,耗时 : " + (endTime - startTime));
		}else {
			// 不存在 就读取数据 并且结构化 排序 然后 进行序列化
			try {
				// 获取数据
				ipAndLocationPojos = DataProcessManager.getPojoList(ipLibrayPath,
						encoding);
				// 转数组并排序
				// TODO
				long startTime = System.currentTimeMillis();
				ipAndLocationPojoArray = DataProcessManager
						.convertListToArraySort(ipAndLocationPojos);
				// 序列化
				SerDeUtil.saveObj(ipAndLocationPojoArray, serdeObjFilePath);
				long endTime = System.currentTimeMillis();
				System.out.println("转数组并排序序列化,耗时 : " + (endTime - startTime));

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}


2.2.5 测试结果
引入序列化后
首次执行 : 7000
第二次执行 : 19000

通过测试 是因为 序列化和反序列化降低了效率,也就是读写效率低

2.3 IO调优

2.3.1 引入缓冲流
public class SerDeUtil {
	/**
	 * 序列化
	 * 
	 * @param obj
	 * @param filePath
	 * @throws IOException
	 */
	public static void saveObj(Object obj, String filePath,int cacheByteArrayLength) throws IOException {
		// 1 序列化
		FileOutputStream fos = new FileOutputStream(filePath);
		// 字节数组流
		ByteArrayOutputStream baos = new ByteArrayOutputStream(cacheByteArrayLength);
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		// 数据写出到字节数组中
		oos.writeObject(obj);
		// 转换为字节数组
		byte[] byteArray = baos.toByteArray();
		oos.flush();
		oos.close();
		
		fos.write(byteArray);
		fos.close();
	}

	/**
	 * 反序列化
	 * 
	 * @param filePath
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public static Object getObj(String filePath,int cacheByteArrayLength) throws IOException,
			ClassNotFoundException {
		// 2 反序列化
		FileInputStream fis = new FileInputStream(filePath);
		byte[] byteArray = new byte[cacheByteArrayLength];
		// 数据读取到字节数组中
		fis.read(byteArray);
		fis.close();
		// 字节数组缓冲流
		ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
		ObjectInputStream ois = new ObjectInputStream(bais);
		Object obj = ois.readObject();
		ois.close();
		return obj;
	}
}

2.3.2 调用处更改在这里插入图片描述

在这里插入图片描述

2.3.3 测试结果
首次执行 : 因为需要读取,结构化,排序,序列化 所以耗时2000
其次 每次生命周期执行 只需要进行反序列化即可 , 耗时 : 900

2.4 代码标准化

2.4.1 编写静态数据类型

在这里插入图片描述

public class StaticValue {
	// 序列化之后的文件名
	public static String serdeObjFilePath = "ipLibObj.data";
	// 地址库文件路径
	public static String ipLibrayPath = "ip_location_relation.txt";
	// 字符编码
	public static String encoding = "UTF-8";
	// 序列化文件大小
	public static int cacheByteArrayLength = 25 * 1024 * 1024;
}
2.4.2 更改调用处
public class DataProcessManager {

	private static IPAndLocationPojo[] ipAndLocationPojoArray = null;
	static {
		// 保存数据对象
		List<IPAndLocationPojo> ipAndLocationPojos = null;
		// 判断是否有序列化的文件
		File file = new File(StaticValue.serdeObjFilePath);
		if (file.exists()) {
			// TODO
			long startTime = System.currentTimeMillis();
			// 如果存在 就反序列化
			try {
				Object obj = SerDeUtil.getObj(StaticValue.serdeObjFilePath,
						StaticValue.cacheByteArrayLength);
				ipAndLocationPojoArray = (IPAndLocationPojo[]) obj;
			} catch (ClassNotFoundException | IOException e) {
				e.printStackTrace();
			}
			long endTime = System.currentTimeMillis();
			System.out.println("反序列化,耗时 : " + (endTime - startTime));
		} else {
			// 不存在 就读取数据 并且结构化 排序 然后 进行序列化
			try {
				// 获取数据
				ipAndLocationPojos = DataProcessManager.getPojoList(
						StaticValue.ipLibrayPath, StaticValue.encoding);
				// 转数组并排序
				// TODO
				long startTime = System.currentTimeMillis();
				ipAndLocationPojoArray = DataProcessManager
						.convertListToArraySort(ipAndLocationPojos);
				// 序列化
				SerDeUtil.saveObj(ipAndLocationPojoArray,
						StaticValue.serdeObjFilePath,
						StaticValue.cacheByteArrayLength);
				long endTime = System.currentTimeMillis();
				System.out.println("转数组并排序序列化,耗时 : " + (endTime - startTime));

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

2.5 再次优化

2.5.1 IO优化
1 没有优化
首次执行 : 1700-1900
后续执行 : 1700-1900
2 引入序列化
文件大小是 38.3M
首次执行 : 6000-7000
后续执行 : 19000-20000
3 引入缓冲流
文件大小是 38.3
首次执行 : 2000
后续执行 : 800-900

通过测试 是因为 序列化写 和 反序列化读 慢
此时已经引入了缓冲流,进行了优化,下一步就要尝试优化文件大小
文件小,速度就快
transient 修饰符 修饰的属性不能被序列化
因为我们从始至终都没有使用startIP和endIP 所以 可以使用transient进行修饰

在这里插入图片描述

更改之后 序列化的文件 大小为 24.6

在这里插入图片描述

更改定义的文件大小

在这里插入图片描述

首次执行 : 2000
其次 : 450左右

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值