Java - ip2region - 使用篇

本篇主要介绍 ip2regionip2region 支持很多客户端,本次主要以Java来介绍

在进行系统开发时,我们一般会涉及到获取到用户的具体位置信息,一般有两个方法:

  • 根据GPS 定位的信息 (一般用于手机端)
  • 用户的 IP 地址解析

每个手机都不一定会打开 GPS,而且有时并不太需要太精确的位置(到城市这个级别即可),所以根据 IP 地址入手来分析用户位置是个不错的选择。

下面就介绍一个分析 IP 地址一个比较好的东西 ip2region

接着上篇 Java - ip2region - 基础篇(你知道ip2region吗?),这篇主要介绍使用与一些原理

ip2region使用

1、导包

使用首先导包,从1.8版本开始,ip2region开源了ip2region.db生成程序的java实现,下面涉及到的源码以2.5.6版本说明

<dependency><groupId>net.dreamlu</groupId><artifactId>mica-ip2region</artifactId><version>2.5.6</version>
</dependency>

2、简单使用

这里举个例子来简单使用:

  • 写了一个 ipaddress 接口,请求接口返回位置信息
  • ipaddress 接口 用了 getIP() 方法获取请求的ip字符串地址
  • 根据 memorySearch()(memory算法)获取位置信息,返回
@RestController
public class UserController {private static final String UNKNOWN = "unknown";@AutowiredIp2regionSearcher ip2regionSearcher; // 核心处理类,具体为什么可以直接 @Autowired,下面原理来说@GetMapping("/ipaddress")public ResponseEntity<Object> ipTest(HttpServletRequest request) throws IOException {String ip = getIp(request); // 获取到请求的ip地址IpInfo ipInfo = ip2regionSearcher.memorySearch(ip); // 根据ip地址获取到addressString address = UNKNOWN;assert ipInfo != null;if(ipInfo != null){address = ipInfo.getAddress();}return new ResponseEntity<>(address, HttpStatus.OK);}/*** 根据请求获取ip地址*/private static String getIp(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}String comma = ",";String localhost = "127.0.0.1";if (ip.contains(comma)) {ip = ip.split(",")[0];}if (localhost.equals(ip)) {// 获取本机真正的ip地址try {ip = InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {}}return ip;}
}

请求返回:

3、方法与原理介绍

总体介绍图:(这个包很少东西,而且通俗易懂)

说明:

  • ip2region.db:基础篇介绍的ip2region.db文件
  • Ip2regionConfiguration:配置类(可直接@Autowired的主要原因)
  • Ip2regionProperties:配置文件读取类
  • DBxxxx主要涉及与 ip2region.db文件相关的处理/配置类
    • DataBlock
      DbConfig
      DbMakerConfigException
      DBReader
      DbSearcher
  • IndexBlock:对应 ip2region.db 中的 indexBlock 的结构,起始ip 终止ip 数据信息(数据地址与数据长度)
  • Ip2regionSearcher / Ip2regionSearcherImpl:核心接口与实现类,里面的核心方法为主要业务处理方法
  • IpInfo:获取到的ip信息,格式为: 城市ip,国家,区域,省份,城市,运营商

方法介绍

Ip2regionSearcher / Ip2regionSearcherImpl:核心接口与实现类,里面的核心方法为主要业务处理方法

  • net.dreamlu.mica.ip2region.core.Ip2regionSearcher
package net.dreamlu.mica.ip2region.core;
import net.dreamlu.mica.ip2region.utils.IpInfoUtil;
import org.springframework.lang.Nullable;
import java.util.function.Function;
/*** ip 搜索器** @author dream.lu*/
public interface Ip2regionSearcher {/*** ip 位置 搜索(memory 算法)* @param ip ip* @return 位置*/@NullableIpInfo memorySearch(long ip);/*** ip 位置 搜索(memory 算法)** @param ip ip* @return 位置*/@NullableIpInfo memorySearch(String ip);/*** ip 位置 搜索(btree搜索 算法)** @param ip ip* @return 位置*/@NullableIpInfo btreeSearch(long ip);/*** ip 位置 搜索(btree搜索 算法)** @param ip ip* @return 位置*/@NullableIpInfo btreeSearch(String ip);/*** ip 位置 搜索(binary 搜索 算法)** @param ip ip* @return 位置*/@NullableIpInfo binarySearch(long ip);/*** ip 位置 搜索(binary 搜索 算法)** @param ip ip* @return 位置*/@NullableIpInfo binarySearch(String ip);/*** ip 位置 搜索** @param ptr ptr* @return 位置*/@NullableIpInfo getByIndexPtr(long ptr);/*** 读取 ipInfo 中的信息* @param ip       ip* @param function Function* @return 地址*/@Nullabledefault String getInfo(long ip, Function<IpInfo, String> function) {return IpInfoUtil.readInfo(memorySearch(ip), function);}/*** 读取 ipInfo 中的信息** @param ip       ip* @param function Function* @return 地址*/@Nullabledefault String getInfo(String ip, Function<IpInfo, String> function) {return IpInfoUtil.readInfo(memorySearch(ip), function);}/*** 获取地址信息** @param ip ip* @return 地址*/@Nullabledefault String getAddress(long ip) {return getInfo(ip, IpInfo::getAddress);}/*** 获取地址信息** @param ip ip* @return 地址*/@Nullabledefault String getAddress(String ip) {return getInfo(ip, IpInfo::getAddress);}/*** 获取地址信息包含 isp** @param ip ip* @return 地址*/@Nullabledefault String getAddressAndIsp(long ip) {return getInfo(ip, IpInfo::getAddressAndIsp);}/*** 获取地址信息包含 isp** @param ip ip* @return 地址*/@Nullabledefault String getAddressAndIsp(String ip) {return getInfo(ip, IpInfo::getAddressAndIsp);}
}

说明:

  • 分别提供了三大搜索算法的两种重载方法,区别就在于ip地址是自己转换为长整型的还是它来转长整型,返回为 IpInfo
  • 提供一个 自己来处理 IpInfo 的方法,getInfo() 根据自己传入的 Function 来处理得到的 IpInfo
  • 根据ip地址直接获取到地址信息 getAddress() / getAddressAndIsp()

原理介绍

1、为什么可以 @Autowired?

这里看到其实它写了一个配置类 Ip2regionConfiguration ,利用 @Configuration 注解,这样的话,SpringBoot 在启动的时候就会扫描注入,而Ip2regionConfiguration中其注入了一个Bean,默认Bean名称为 ip2regionSearcher ,即我们就可以可以根据 @Autowired 来注入Bean

/*** ip2region 自动化配置** @author L.cm*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(Ip2regionProperties.class)
@NativeHint(resources = @ResourceHint(patterns = "^ip2region/ip2region.db"))
public class Ip2regionConfiguration {@Beanpublic Ip2regionSearcher ip2regionSearcher(ResourceLoader resourceLoader,Ip2regionProperties properties) {return new Ip2regionSearcherImpl(resourceLoader, properties);}}
2、算法实现源码

基础篇介绍了其内置了三种查询算法,基于二分查找的binary算法、基于btree算法的b-tree算法、与binary算法类似的memory算法(整个数据库全部载入内存)

三种方法的本质 都是通过不同的方法先找到 index,然后根据二分查找找到 ip地址 处于的 index bolck(12个字节),根据后四个字节存储的数据信息找到数据,然后根据数据信息中的前三个字节找到对应的数据地址

binary搜索

基于二分查询方法,步骤如下:

  • 把 ip值 通过 ip2long 方法转为长整型
  • 通过 SUPER BLOCK 拿到 INDEX 的起始位置和结束位置
  • 两个位置相减,然后 +1 得出index block 总数
  • 采用二分法直接求解,比较 index block 和当前 ip 的大小,即可找到该ip属于的 index block(经典二分)
  • 拿到该 index block 的后面四个字节(数据信息), 分别得到数据长度和数据地址
  • 从数据地址读取拿到的所得长度的字节,即是搜索结果

源码:(基本上源码就是根据上面的步骤编写的,很容易看懂)

  • net.dreamlu.mica.ip2region.core.DbSearcher#binarySearch
public DataBlock binarySearch(long ip) throws IOException {int blen = IndexBlock.getIndexBlockLength();if (totalIndexBlocks == 0) {byte[] superBytes = new byte[8]; // super (memeory 这里是根据整个dbStr获取的,即这点区别)reader.readFully(0L, superBytes, 0, superBytes.length);firstIndexPtr = Ip2regionUtil.getIntLong(superBytes, 0);lastIndexPtr = Ip2regionUtil.getIntLong(superBytes, 4);// 1、根据index 获取到 index block 个数totalIndexBlocks = (int) ((lastIndexPtr - firstIndexPtr) / blen) + 1;}//2、搜索ip地址在的index block(典型二分)int l = 0, h = totalIndexBlocks;byte[] buffer = new byte[blen];long sip, eip, dataptr = 0;while (l <= h) {int m = (l + h) >> 1;reader.readFully(firstIndexPtr + m * blen, buffer, 0, buffer.length);sip = Ip2regionUtil.getIntLong(buffer, 0);if (ip < sip) {h = m - 1;} else {eip = Ip2regionUtil.getIntLong(buffer, 4);if (ip > eip) {l = m + 1;} else {dataptr = Ip2regionUtil.getIntLong(buffer, 8);break;}}}if (dataptr == 0) {return null;}//4. 根据index block 获取数据int dataLen = (int) ((dataptr >> 24) & 0xFF);int dataPtr = (int) ((dataptr & 0x00FFFFFF));byte[] data = new byte[dataLen];reader.readFully(dataPtr, data, 0, data.length);int cityId = (int) Ip2regionUtil.getIntLong(data, 0);String region = new String(data, 4, data.length - 4, StandardCharsets.UTF_8);return new DataBlock(cityId, region, dataPtr);
}

b-tree 搜索

b-tree 搜索用到了 HEADER INDEX,第一步先在 HEADER INDEX 中搜索,再定位到 INDEX 中的某个 4k index分区搜索。

步骤:

  • 把 ip值 通过 ip2long 转为长整型
  • 使用二分法在 HEADER INDEX 中搜索,比较得到对应的 header index block
  • header index block 指向 INDEX 中的一个 4K 分区,所以直接把搜索范围降低到 4K
  • 采用二分法在获取到的 4K 分区搜索,得到对应的 index block
  • 拿到该 index block 的后面四个字节(数据信息), 分别得到数据长度和数据地址
  • 从数据地址读取拿到的所得长度的字节,即是搜索结果

源码:(基本上源码就是根据上面的步骤编写的,很容易看懂)

  • net.dreamlu.mica.ip2region.core.DbSearcher#btreeSearch
// btreeSearch 算法
public DataBlock btreeSearch(long ip) throws IOException {//1、找到 HEADER(index的二级索引)if (HeaderSip == null) {byte[] b = new byte[8 * 1024];reader.readFully(8L, b, 0, b.length);int len = b.length >> 3, idx = 0;  //b.lenght / 8long[] headerSip = new long[len];int[] headerPtr = new int[len];long startIp, dataPtr;for (int i = 0; i < b.length; i += 8) {startIp = Ip2regionUtil.getIntLong(b, i);dataPtr = Ip2regionUtil.getIntLong(b, i + 4);if (dataPtr == 0) {break;}headerSip[idx] = startIp;headerPtr[idx] = (int) dataPtr;idx++;}headerLength = idx;this.HeaderPtr = headerPtr;this.HeaderSip = headerSip;}if (ip == HeaderSip[0]) {return getByIndexPtr(HeaderPtr[0]);} else if (ip == HeaderSip[headerLength - 1]) {return getByIndexPtr(HeaderPtr[headerLength - 1]);}//2、根据 Header 找到 indexint l = 0, h = headerLength, sptr = 0, eptr = 0;while (l <= h) {int m = (l + h) >> 1;//perfetc matched, just return itif (ip == HeaderSip[m]) {if (m > 0) {sptr = HeaderPtr[m - 1];eptr = HeaderPtr[m];} else {sptr = HeaderPtr[m];eptr = HeaderPtr[m + 1];}break;}//less then the middle valueif (ip < HeaderSip[m]) {if (m == 0) {sptr = HeaderPtr[m];eptr = HeaderPtr[m + 1];break;} else if (ip > HeaderSip[m - 1]) {sptr = HeaderPtr[m - 1];eptr = HeaderPtr[m];break;}h = m - 1;} else {if (m == headerLength - 1) {sptr = HeaderPtr[m - 1];eptr = HeaderPtr[m];break;} else if (ip <= HeaderSip[m + 1]) {sptr = HeaderPtr[m];eptr = HeaderPtr[m + 1];break;}l = m + 1;}}if (sptr == 0) {return null;}//3、典型的二分查找,根据index 找到 index blockint blockLen = eptr - sptr, blen = IndexBlock.getIndexBlockLength();byte[] iBuffer = new byte[blockLen + blen];reader.readFully(sptr, iBuffer, 0, iBuffer.length);l = 0;h = blockLen / blen;long sip, eip, dataptr = 0;while (l <= h) {int m = (l + h) >> 1;int p = m * blen;sip = Ip2regionUtil.getIntLong(iBuffer, p);if (ip < sip) {h = m - 1;} else {eip = Ip2regionUtil.getIntLong(iBuffer, p + 4);if (ip > eip) {l = m + 1;} else {dataptr = Ip2regionUtil.getIntLong(iBuffer, p + 8);break;}}}if (dataptr == 0) {return null;}//4. 根据index block 获取数据int dataLen = (int) ((dataptr >> 24) & 0xFF);int dataPtr = (int) ((dataptr & 0x00FFFFFF));byte[] data = new byte[dataLen];reader.readFully(dataPtr, data, 0, data.length);int cityId = (int) Ip2regionUtil.getIntLong(data, 0);String region = new String(data, 4, data.length - 4, StandardCharsets.UTF_8);return new DataBlock(cityId, region, dataPtr);
}

memory算法

该方法和 binary搜索 方法类似,区别就是它将 ip2region.db 全部读进内存中再进行查找

相关链接

GitHub ip2region

Java - ip2region - 使用篇相关推荐

  1. Java - ip2region - 基础篇(你知道ip2region吗?)

    Java - ip2region - 基础篇(你知道ip2region吗?) 本篇主要介绍 ip2region, ip2region 支持很多客户端,本次主要以Java来介绍 在进行系统开发时,我们一 ...

  2. idea配置jfinal_intellij idea安装与配置(Java开发配置篇)

    一.maven配置 在configure->settings的搜索框中输入maven,然后入下图所示,修改maven主目录.maven配置文件.maven本地仓库地址 二.SVN设置 在搜索框中 ...

  3. Java异常处理终结篇——如何进行Java异常处理设计

    [本文转自于Java异常处理终结篇--如何进行Java异常处理设计] 有一句这样话:一个衡量Java设计师水平和开发团队纪律性的好方法就是读读他们应用程序里的异常处理代码. 本文主要讨论开发Java程 ...

  4. java实现linkstring,【JAVA SE基础篇】32.String类入门

    [JAVA SE基础篇]32.String类入门 1.字符串 1.String类又称作不可变字符序列 2.String位于java.lang包中,java程序默认导入java.lang包下所有的类 3 ...

  5. Java微服务篇5——Docker

    Java微服务篇5--Docker 1.虚拟化技术 虚拟化技术是一种计算机资源管理技术,是将计算机的各种实体资源,如服务器.网络.内存及存储 等,予以抽象.转换后呈现出来.虚拟化技术打破了计算机实体结 ...

  6. Java微服务篇4——Elastic search

    Java微服务篇4--Elastic search 1.Elastic search安装配置 Elastic search官方:https://www.elastic.co/cn/products/e ...

  7. Java微服务篇3——Lucene

    Java微服务篇3--Lucene 1.数据分类 1.1.结构化数据 具有固定格式或有限长度的数据,如数据库,元数据等 常见的结构化数据也就是数据库中的数据,在数据库中搜索很容易实现,通常都是使用 s ...

  8. Java微服务篇2——SpringCloud

    Java微服务篇2--SpringCloud 1.微服务架构 1.1.单体应用架构 的⽤户量.数据量规模都⽐较⼩,项目所有的功能模块都放在一个工程中编码. 编译.打包并且部署在一个Tomcat容器中的 ...

  9. Java微服务篇1——SpringBoot

    Java微服务篇1--SpringBoot 1.什么是springboot 1.1.Spring出现的问题 Spring是Java企业版(Java Enterprise Edition,JEE,也称J ...

  10. Java番外篇4——BigInteger与BigDecimal

    Java番外篇4--BigInteger与BigDecimal 为了解决大数运算的问题 操作整型:BigInteger 操作小数:BigDecimal 1.BigInteger 方法声明 功能介绍 p ...

最新文章

  1. 中科大“九章”历史性突破,但实现真正的量子霸权还有多远?
  2. 衡阳之后,重估自动驾驶落地
  3. list循环添加数据_List 去除重复数据的 5 种正确姿势!你知道几种?
  4. python装饰器原理-深刻理解python装饰器
  5. 大数据为智慧城市建设添砖加瓦
  6. linux下set和eval的使用小案例精彩解答
  7. C++ 大话数据结构 09: 中缀表达式 转后缀表达式 计算器
  8. SAP Data Intelligence Graph使用浏览器访问的url规范
  9. java 异常面试问题_Java异常面试问答
  10. windows.location属性(常用知识点)
  11. React:Hook
  12. 自定义快捷键整理 - Windows
  13. 隐藏input的三种方法和区别
  14. Java的字面量和符号引用
  15. 计算机在医疗设备中的应用,计算机在医疗设备管理中的应用
  16. Android开发之ViewFlipper
  17. 【Java】Java中文分词器Ansj的使用
  18. java 羽化_JAVA10来了 - 羽化布凉的个人空间 - OSCHINA - 中文开源技术交流社区
  19. 计算机毕设——中文分词方法研究与实现
  20. matlab光顺拐点,Matlab的smooth方法(降噪\光顺\移动平均)

热门文章

  1. 物联网解决方案:智慧物流方案
  2. 员工管理系统-SpringBoot+Vue入门小项目实战
  3. HTML5如何把圆分成六等分,Photoshop怎么把一个圆64等分?
  4. AH8063,输入6.6V-30V,4A大电流,三节锂电池充电管理芯片iC
  5. HRD 必须了解的持续绩效的优势
  6. html鼠标悬停边框颜色,CSS实现鼠标悬停图片时的边框变色效果
  7. 如何在Ubuntu18.4中设置ERPNEXT开源ERP生产环境开机运行
  8. 灰度化处理——灰度化
  9. 数学建模matlab画图操作大全
  10. 数学建模作图准备(Python-Matplotlib)Matplotlib