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 == )
return;
BinaryWriter bw = new BinaryWriter(stream, encoding);
//设置偏移量在开头预留写入NumberInfoCompress的总数
this.WriteCount(bw, , _phoneInfoCompressCount);
//设置偏移量在开头预留号码类型的总数
this.WriteCount(bw, , );
//先读取第一条号码数据
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, , 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()) == && cityIdx == pre.GetCityIndex()) {
//设置号码段连续位置
pre.SetSkip((ushort)(phoneInfo.Code - pre.GetBegin()));
} else {
//递增一个
++_phoneInfoCompressCount;
//写入13708号码段的数据
this.Write(bw, pre);
//继续构造一个8字节存储的结构体等待下次循环比较
pre = new NumberInfoCompress(phoneInfo.Code, , cityIdx, cardIdx);
}
}
//写入最后的号码数据
this.Write(bw, pre);
++_phoneInfoCompressCount;//记录总数
//写入NumberInfoCompress的总数
this.WriteCount(bw, , _phoneInfoCompressCount);
//写入号码类型的总数
this.WriteCount(bw, , (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 = , right = phoneInfoCompressCount - ;
var per = new NumberInfoCompress();
var perSize = Marshal.SizeOf(per);
//使用折半查询(二分法)
while (left <= right) {
//折半
int middle = (left + right) / ;
//索引总数8字节 + middle * NumberInfoCompress字节数
stream.Position = sizeof(int) * + 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 - ;//在左半区间找
} else if (number > (per.GetBegin() + per.GetSkip())) {
left = middle + ;//在右半区间找
} 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) * + phoneInfoCompressCount * Marshal.SizeOf(infoMiddle)
+ infoMiddle.GetCityIndex() * this._maxCityLength;
br.BaseStream.Position = totalOffset;//设置偏移量
char[] charCity = br.ReadChars(this._maxCityLength);
return new string(charCity, , 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, , Array.IndexOf(charCard, '\0'));
}
AreaCode
封装了手机归属地查询函数。
FrmAreaCode
用来演示如何查询电话号码归属地以及把文本文件生成为压缩过的二进制文件(areacode.dat)。


结语
原作者的压缩算法我们也可以稍作改变,但是用这种算法的前提条件是必须有序且有规律,最后用二分法才会提高查询速度。
项目资源里面的文本文件是每行一个号码段,如:号码,区域,类型;读者可以自行存储到任何数据库等地方,方便日后管理。
C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改)的更多相关文章
- C# 号码归属地查询算法
C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改) 前言 近期有个项目需要用到号码归属查询,归属地数据库可能比不上ip138,淘宝上也有卖的-,-! 文本提供一个279188条 ...
- 淘宝SKU组合查询算法实现
淘宝SKU组合查询算法实现 2015-11-14 16:18 1140人阅读 评论(0) 收藏 举报 分类: JavaScript(14) 目录(?)[+] 前端有多少事情可以做,能做到多 ...
- AES加解密算法在Android中的应用及Android4.2以上版本调用问题
from://http://blog.csdn.net/xinzheng_wang/article/details/9159969 AES加解密算法在Android中的应用及Android4.2以上 ...
- Android Tasker应用之自动查询并显示话费流量套餐信息
Android Tasker应用之自动查询并显示话费流量套餐信息 虽然Android平台有非常多的流量监控软件,但最准确的流量数据还是掌握在运营商手里.有些朋友可能像我一样时不时地发短信查询流量信息, ...
- 常见排序&查询算法Java代码实现
1. 排序算法代码实现 /** * ascending sort * 外层循环边界条件:总共需要冒泡的轮数--每一轮都将最大或最小的数冒泡到最后 * 内层循环边界条件:冒泡数字移动的边界--最终数字需 ...
- 如何编写程序设置Android来电铃声
我们在拿到新手机后通常会为其设置来年铃声,那么怎样通过代码来设置Android来电铃声,本文就为大家实例讲解下. 1.如果读到的是音频文件路径,需要先将音乐文件插入到多媒体库. Java代码 //设置 ...
- Android来电监听和去电监听
我觉得写文章就得写得有用一些的,必须要有自己的思想,关于来电去电监听将按照下面三个问题展开 1.监听来电去电有什么用? 2.怎么监听,来电去电监听方式一样吗? 3.实战,有什么需要特别注意地方? 监听 ...
- myBatis的一对多查询,主要利用resultMap实现一次查询多个结果集
日常开发中有这中场景,一个用户有多个角色,一个角色又有多个菜单,想查出一个用户的所有菜单.除了常见的关联查询之外,更使用的应该是利用myBatis的resultMap来实现一次查询出多个结果集,缺点: ...
- sql条件为空查询全部,不为空按条件查询以及多条件筛选查询。
procedure queryLackLonOrLatTdCell(i_region_name varchar2, i_state varchar2) is begin select region_n ...
随机推荐
- MvcOptions配置
MvcOptions配置 程序模型处理 IApplicationModelConvention 在MvcOptions的实例对象上,有一个ApplicationModelConventions属性(类 ...
- React.js再探(二)
上文中说到了组件了. 我们使用组件的目的最大莫过于复用,提供生产效率. 那么,这时候组件就应该能够提供一些”api”出来,让开发者去定义在不同场景下的不同表现,比如,行为或外观等. 而这些“api”就 ...
- 【LeetCode】Set Matrix Zeroes 解题报告
今天看到CSDN博客的勋章换了图表,同一时候也添加显示了博客等级,看起来都听清新的,感觉不错! [题目] Given a m x n matrix, if an element is 0, set i ...
- Codeforces 442C Artem and Array(stack+贪婪)
题目连接:Codeforces 442C Artem and Array 题目大意:给出一个数组,每次删除一个数.删除一个数的得分为两边数的最小值,假设左右有一边不存在则算作0分. 问最大得分是多少. ...
- Duanxx的STM32学习:NVIC操作
版权声明:本文博客原创文章,博客,未经同意,不得转载.
- Github Pages 静态网页建站
创建仓库 略.详见GitHub使用教程. 仓库属性设置 找Github Pages项.点击自己主动生成,依照提示操作,就会得到该项目的gh-pages 分支. 公布站点成功,地址为 http://ch ...
- Redis MSET的极限在哪里
·背景 Redis以"快.准.狠"而著称,除了其主-从模式略失光彩(主从模式更多是被以讹传讹,3.0依旧在测试中),大部分的应用可谓尖兵利器.在一些常规写的时候,MSET和HMSE ...
- MonkeyRunner于Windows在下面Eclipse开发环境的搭建步骤(并解决在线Jython配置错误的问题)
网上有一shangdong_chu网友写了一篇文章介绍了如何Eclipse构造MonkeyRunner,他做了叙述一个很好的说明.但我对我的环境试验后会见Jython解析错误的问题,和制品缺少Pyde ...
- Java之String类的使用细节
String类的特点: 字符串对象一旦被初始化就不会被改变,字符串存储在字符串常量池中(字符串缓冲区).如果池中没有就创建,如果有就直接拿过来用. 代码验证如下: String s ...
- Jmeter性能测试
Jmeter性能测试 入门 Jmeter是一款优秀的开源测试工具, 是每个资深测试工程师,必须掌握的测试工具,熟练使用Jmeter能大大提高工作效率. 熟练使用Jmeter后, 能用Jmeter搞定的 ...