C# 号码归属地查询算法
C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改)
前言
近期有个项目需要用到号码归属查询,归属地数据库可能比不上ip138,淘宝上也有卖的-,-! 文本提供一个279188条记录并压缩成562KB的归属地数据。
我在互联网上搜索了相关文章,要不是数据库查询或者是访问网上的api,到底有没有更好的方式,我想各大手机软件的归属地都是属于本地查询的。
当我发现了Android Jni 使用C++对二进制文件查询 这篇文章,发现效率真是高,作者的算法也相当出色。
于是直接把它用C#来实现了一个版本,并且加上号码的类型,效率上没相差太多,起码我们的项目已经够用了。

这是原文的一段话:
随便去网络上搜索一个号码归属地数据库下载,你可能会找到各种格式,access,txt,db等。除了用insert sql语句外,你还可以用CSV文件格式来互相转换。因为SQLite Expert 支持CSV文件导入,导出。
数据最佳存放方式如上图中的表1CallerLoc和表2LocationInfo。这样用一条连表sql语句查询即可。类似这样的sql语句:select number, area from CallerLoc join LocationInfo on CallerLoc.location = LocationInfo.location。
假设你有了这样的xx.db文件,可以把该文件放在Android项目的assets文件下,然后在自定义的ContentProvider中的query方法中,尝试把xx.db 复制到手机的/data/data/你的项目包名/databases中,查询用上面提到的sql语句就行了。
这是一个解决方案,但是db文件太大了,280,000条记录差不多有8MB大小。 别人解压你的apk,dat文件一下子就被别人窃取走了。
有什么方式可以解决这个问题?分析表1,感觉数据还可以压缩(用自定义的格式),把数据写入到一个文件中,通过打开文件来搜索,写入方式用二进制的话,别人就窃取不了了。Java处理速度慢的话,还可以改用C++,通过JNI桥梁来处理。
相关技术和理论请参考原作者地址:
Android 号码,来电归属地 Jni 使用C++对二进制文件查询(一) 理论篇
Android 号码,来电归属地 Jni 使用C++对二进制文件查询(二) C++实现篇
Android 号码,来电归属地 Jni 使用C++对二进制文件查询(三) APK 实现篇
提供本文所修改过的源代码下载。
简单说下修改过的类库

areacode.dat(562KB)
内嵌的资源文件,此文件是根据areacode.txt(9,522KB)生成而来。(279188条数据)
NumberInfoCompress
号码压缩的结构体,和原文C++版本的基本一致,只是增加了号码类型的储存;(占用8个字节)

PhoneInfo
号码的结构信息,分别有号码段、地区、类型。

PhoneWriter
压缩号码归属地并生成二进制文件。
public void DoWriter(Stream stream, Encoding encoding) {
if (_data == null || _data.Count == 0)
return;
BinaryWriter bw = new BinaryWriter(stream, encoding);
//设置偏移量在开头预留写入NumberInfoCompress的总数
this.WriteCount(bw, 0, _phoneInfoCompressCount);
//设置偏移量在开头预留号码类型的总数
this.WriteCount(bw, 4, 0);
//先读取第一条号码数据
var enumerator = this._data.GetEnumerator();
if (!enumerator.MoveNext())
return;
//为什么要预先读取一条数据呢?获取第一条数据是为了和下一条进行对比
var phoneInfo = enumerator.Current;
//增加城市信息,并且返回集合所在索引位置
var cityIdx = this.AddCity(phoneInfo.City);
//增加号码类型信息,并且返回集合所在索引位置
var cardIdx = this.AddCard(phoneInfo.CardType);
//构造一个8字节存储的结构体
var pre = new NumberInfoCompress(phoneInfo.Code, 0, cityIdx, cardIdx);
while (enumerator.MoveNext()) {
//读取下一条数据,准备和上一条比较
phoneInfo = enumerator.Current;
cityIdx = this.AddCity(phoneInfo.City);
cardIdx = this.AddCard(phoneInfo.CardType);
//和上个号码对比是否连续的,比如 1370875 1370876 1370877。
//1370875开头有3个,表示13708 375:从75开始有3个连续的号码
if (phoneInfo.Code - (pre.GetBegin() + pre.GetSkip()) == 1 && cityIdx == pre.GetCityIndex()) {
//设置号码段连续位置
pre.SetSkip((ushort)(phoneInfo.Code - pre.GetBegin()));
} else {
//递增一个
++_phoneInfoCompressCount;
//写入13708号码段的数据
this.Write(bw, pre);
//继续构造一个8字节存储的结构体等待下次循环比较
pre = new NumberInfoCompress(phoneInfo.Code, 0, cityIdx, cardIdx);
}
}
//写入最后的号码数据
this.Write(bw, pre);
++_phoneInfoCompressCount;//记录总数
//写入NumberInfoCompress的总数
this.WriteCount(bw, 0, _phoneInfoCompressCount);
//写入号码类型的总数
this.WriteCount(bw, 4, (uint)(_listCard.Count));
//结尾写入城市地区数据
this.WriteCity(bw, encoding);
//结尾写入号码类型数据
this.WriteCard(bw, encoding);
bw.Close();
bw.Dispose();
}
PhoneReader
用来读取areacode.dat,比如查询号码归属地。
public PhoneInfo GetPhoneInfo(Stream stream, Encoding encoding, int number) {
PhoneInfo result = new PhoneInfo();
result.Code = number;
BinaryReader br = new BinaryReader(stream, encoding);
//获取索引总数
int phoneInfoCompressCount = br.ReadInt32();
//号码类型总数
int cardCount = br.ReadInt32();
int left = 0, right = phoneInfoCompressCount - 1;
var per = new NumberInfoCompress();
var perSize = Marshal.SizeOf(per);
//使用折半查询(二分法)
while (left <= right) {
//折半
int middle = (left + right) / 2;
//索引总数8字节 + middle * NumberInfoCompress字节数
stream.Position = sizeof(int) * 2 + middle * perSize;
//读取NumberInfoCompress数据
per.Before = br.ReadUInt16();
per.After = br.ReadUInt16();
per.CityIndex = br.ReadUInt16();
per.CardIndex = br.ReadUInt16();
//判断号码是否匹配
if (number < per.GetBegin()) {
right = middle - 1;//在左半区间找
} else if (number > (per.GetBegin() + per.GetSkip())) {
left = middle + 1;//在右半区间找
} else {
//已找到,直接查询城市和号码类型
result.City = DoFindCityThing(br, phoneInfoCompressCount, per);
result.CardType = DoFindCardThing(br, cardCount, per);
return result;
}
}
br.Close();
br.Dispose();
return result;
}
private string DoFindCityThing(BinaryReader br, int phoneInfoCompressCount, NumberInfoCompress infoMiddle) {
//计算城市区域信息位置
//sizeof(int) * 2 开头位置储存了一个4字节的NumberInfoCompress总数和类型总数
//phoneInfoCompressCount NumberInfoCompress总数
//Marshal.SizeOf(infoMiddle) NumberInfoCompress占用空间
//infoMiddle.GetCityIndex() 城市的所在位置
//_maxCityLength 城市总数
//偏移量 = 索引总数8字节 + 索引总数 * NumberInfoCompress字节数 + 城市的所在位置 * 城市大小
long totalOffset = sizeof(int) * 2 + phoneInfoCompressCount * Marshal.SizeOf(infoMiddle)
+ infoMiddle.GetCityIndex() * this._maxCityLength;
br.BaseStream.Position = totalOffset;//设置偏移量
char[] charCity = br.ReadChars(this._maxCityLength);
return new string(charCity, 0, Array.IndexOf(charCity, '\0'));
}
private string DoFindCardThing(BinaryReader br, int cardCount, NumberInfoCompress infoMiddle) {
//号码类型存储在尾端
//所以偏移量 = (流的总长度 - 类型总数 * 类型大小) + 所在位置 * 类型大小
long totalOffset = (br.BaseStream.Length - cardCount * this._maxCardLength) + infoMiddle.GetCardIndex() * this._maxCardLength;
br.BaseStream.Position = totalOffset;//设置偏移量
char[] charCard = br.ReadChars(this._maxCardLength);
return new string(charCard, 0, Array.IndexOf(charCard, '\0'));
}
AreaCode
封装了手机归属地查询函数。
FrmAreaCode
用来演示如何查询电话号码归属地以及把文本文件生成为压缩过的二进制文件(areacode.dat)。


结语
原作者的压缩算法我们也可以稍作改变,但是用这种算法的前提条件是必须有序且有规律,最后用二分法才会提高查询速度。
项目资源里面的文本文件是每行一个号码段,如:号码,区域,类型;读者可以自行存储到任何数据库等地方,方便日后管理。
作者:JohnWu
出处:http://www.cnblogs.com/johnwu/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接
如有问题,可以通过v.la@Live.cn 联系我,非常感谢。
C# 号码归属地查询算法的更多相关文章
- C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改)
前言 近期有个项目需要用到号码归属查询,归属地数据库可能比不上ip138,淘宝上也有卖的-,-! 文本提供一个279188条记录并压缩成562KB的归属地数据.我在互联网上搜索了相关文章,要不是数据库 ...
- [android] 手机卫士号码归属地查询
使用小米号码归属地数据库,有两张表data1和data2 先查询data1表,把手机号码截取前7位 select outkey from data1 where id=”前七位手机号” 再查询data ...
- WebService学习------小实例开发(号码归属地查询)
1.WebService简介: WebService是一种跨平台,跨语言的,可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独立的通讯技术.通过SOAP在Web上提 ...
- [android] 手机卫士号码归属地查询完成
正则表达式完成号码验证, ^以某开头,[] 字符集(匹配中括号里面的任意字符),\d是任意一个数字,{n}表示出现了多少次,$结尾 手机号的正则 ^1[34568]\d{9}$,以1开头,第二个数字是 ...
- 本地的手机号码归属地查询-oracle数据
最近做的项目中,有个功能是手机归属地查询,因为项目要在内网下运行,所以不能用提供的webservice,只好在网上找手机归属地的数据,很多都是access的,我们的项目是用oracle,只好自己转吧, ...
- 百度手机号码归属地查询api与返回json处理
前天无意间在网上看到百度ApiStore,然后好奇就进去看了看.正好最近在某博培训Android,刚学到java基础.抱着锻炼的心态选择手机号码归属地查询api进行练手.api地址 (http://a ...
- android134 360 07 归属地查询
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...
- 手机归属地查询-IP地址查询-身份证查询-域名备案查询--Api接口
使用这些接口是需要密钥的 公共密钥 appkey: 10003 secret: d1149a30182aa2088ef645309ea193bf 生成后sign: b59bc3ef6191eb9f ...
- C# Winform实现手机号码归属地查询工具
摘要:本文介绍使用C#开发基于Winform的手机号码归属地查询工具,并提供详细的示例代码供参考. 一.需求描述 输入正确的手机号码,查询该号码的归属地和其他相关信息. 二.需求分析 1.实现手机号码 ...
随机推荐
- crawler_网络爬虫之数据分析_httpwatcher
所谓爬虫,首先要通过各种手段爬取到想要站点的数据. web2.0之后,各种网络站点类型越来越多,早期的站点多为静态页面[html .htm],后来逐步加入 jsp.asp,等交互性强的页面.再后来随着 ...
- [LeetCode] 033. Search in Rotated Sorted Array (Hard) (C++)
指数:[LeetCode] Leetcode 解决问题的指数 (C++/Java/Python/Sql) Github: https://github.com/illuz/leetcode 033. ...
- java_tomcat_the_APR based Apache Tomcat 小喵咪死活启动报错_临时方案
报错信息如下: 信息: The APR based Apache Tomcat Native library which allows optimal performance in productio ...
- 私人定制javascript事件处理机制(浅谈)
看到园子里关于事件监听发表的文章,我都有点不好意思写了.不过想想我的题目以私人定制作开头也就妥妥地写吧. 事件相关概念 1.事件类型 发生事件的字符串 有传统事件类型 比如表单.window事件等 D ...
- C# - object有哪些基本方法类有
Name Description Equals(Object) Determines whether the specified object is equal to the current obje ...
- 记录近期小改K-Means至MapReduce上的心得
背景: 在所有聚类算法中KMeans算是表面上最简单的一种,没有过多恼人的古希腊符号公式,没有过分繁杂的公式嵌套.对于一个初学矩阵或者仅有向量概念的非专业人士的来说,不可不畏是一把踹门利器.这个世界上 ...
- C#中如何获取系统环境变量
原文:C#中如何获取系统环境变量 C#中获取系统环境变量需要用到Environment Class.其中提供了有关当前环境和平台的信息以及操作它们的方法.该类不能被继承. 以下代码得到%systemd ...
- jmeter之自定义java请求性能测试
一.环境准备 1.新建一个java工程 2.导入jar包:ApacheJMeter_core.jar ApacheJMeter_java.jar ...
- Oracle / PLSQL写语句的时候常使用的函数
最近在学习数据库方面的知识,做个标记. 这里有英文解释,建议多看看英文文档: https://www.techonthenet.com/oracle/functions/ 下面开始记录一下,自己在Or ...
- 初识Python-web框架的这两天
前段时间打算学习python,其实时间蛮紧张的,看看文字教程,累了就看视频教程.算是把基本的语法过了一遍,但是OOP就费了好大的气力 ,C里有的对象,继承,等等等,还算能理解.不过高级点的就理解起来吃 ...