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.实现手机号码 ...
随机推荐
- 准备战争“软测试”之DB基础知识
"数据库"东西这个陌生和数据,进入提高班,从第二年开始接触,的项目还是自考的学习加起来也有3遍了.这仅仅是一个開始,软考又要对数据库进行全面的分析,那么如今就让我们再一次剖析它吧! ...
- WIN2003+IIS6+FastCGI+PHP5.4.30的安装配置
原文:WIN2003+IIS6+FastCGI+PHP5.4.30的安装配置 说明:PHP5.5已不支持win2003了,Win2003最高能安装PHP5.4.30. 安装好系统:并且安装好IIS6. ...
- csu 1503: 点弧之间的距离-湖南省第十届大学生计算机程序设计大赛
这是--比量p并用交点连接中心不上弧.在于:它至p距离.是不是p与端点之间的最短距离 #include<iostream> #include<map> #include< ...
- Redhat Enterprise server 6.3 构造VPN
一.软体 dkms.kernel_ppp_mppe.pptpd 二.下载软件 wget http://sourceforge.net/projects/poptop/files/mppe%20modu ...
- c# md5
还可以加盐,更难以破解 public static string GetMD5(string sDataIn) { MD5Crypt ...
- JS代码的几个注意点规范
也谈谈规范JS代码的几个注意点 也谈谈规范JS代码的几个注意点 写JS代码差不多也有两年了吧,从刚开始的“初生牛犊不怕虎”乱写一通到后来也慢慢知道去规范一下自己写的代码.这种感觉就像是代码是你的作品, ...
- asp.net mvc3 数据验证(四)—Remote验证的一个注意事项
原文:asp.net mvc3 数据验证(四)-Remote验证的一个注意事项 前几篇把asp.net mvc3 中基于Model的主要数据验证的方法都已经讲完了,本节纯粹只是讲一个我 ...
- 写给初学前端工程师的一封信 (转于Kejun)
大家好: 应波波的邀请写一写我对这个话题的想法.从去年开始不少朋友让我帮忙介绍前端工程师,绝大部分忙都没帮上,原因是真找不到人.我当时是这么跟他们分析的:过去的客户端以browser为主,所以HTML ...
- asp.net web api2.0 ajax跨域解决方案
asp.net web api2.0 ajax跨域解决方案 Web Api的优缺点就不说了,直接说怎么跨域,我搜了一下,主要是有两种. 一,ASP.NET Web API支持JSONP,分两种 1, ...
- or1200于IMMU分析
以下摘录<步骤吓得核心--软-core处理器的室内设计与分析>一本书 1 IMMU结构 OR1200中实现IMMU的文件有or1200_immu_top.v.or1200_immu_tlb ...