前言

近期有个项目需要用到号码归属查询,归属地数据库可能比不上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来电归属地二进制文件查询修改)的更多相关文章

  1. C# 号码归属地查询算法

    C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改) 前言 近期有个项目需要用到号码归属查询,归属地数据库可能比不上ip138,淘宝上也有卖的-,-! 文本提供一个279188条 ...

  2. 淘宝SKU组合查询算法实现

    淘宝SKU组合查询算法实现 2015-11-14 16:18 1140人阅读 评论(0) 收藏 举报  分类: JavaScript(14)    目录(?)[+]   前端有多少事情可以做,能做到多 ...

  3. AES加解密算法在Android中的应用及Android4.2以上版本调用问题

     from://http://blog.csdn.net/xinzheng_wang/article/details/9159969 AES加解密算法在Android中的应用及Android4.2以上 ...

  4. Android Tasker应用之自动查询并显示话费流量套餐信息

    Android Tasker应用之自动查询并显示话费流量套餐信息 虽然Android平台有非常多的流量监控软件,但最准确的流量数据还是掌握在运营商手里.有些朋友可能像我一样时不时地发短信查询流量信息, ...

  5. 常见排序&查询算法Java代码实现

    1. 排序算法代码实现 /** * ascending sort * 外层循环边界条件:总共需要冒泡的轮数--每一轮都将最大或最小的数冒泡到最后 * 内层循环边界条件:冒泡数字移动的边界--最终数字需 ...

  6. 如何编写程序设置Android来电铃声

    我们在拿到新手机后通常会为其设置来年铃声,那么怎样通过代码来设置Android来电铃声,本文就为大家实例讲解下. 1.如果读到的是音频文件路径,需要先将音乐文件插入到多媒体库. Java代码 //设置 ...

  7. Android来电监听和去电监听

    我觉得写文章就得写得有用一些的,必须要有自己的思想,关于来电去电监听将按照下面三个问题展开 1.监听来电去电有什么用? 2.怎么监听,来电去电监听方式一样吗? 3.实战,有什么需要特别注意地方? 监听 ...

  8. myBatis的一对多查询,主要利用resultMap实现一次查询多个结果集

    日常开发中有这中场景,一个用户有多个角色,一个角色又有多个菜单,想查出一个用户的所有菜单.除了常见的关联查询之外,更使用的应该是利用myBatis的resultMap来实现一次查询出多个结果集,缺点: ...

  9. sql条件为空查询全部,不为空按条件查询以及多条件筛选查询。

    procedure queryLackLonOrLatTdCell(i_region_name varchar2, i_state varchar2) is begin select region_n ...

随机推荐

  1. javascript 中的location.pathname

    1 location.pathname; 这在之前我没怎么注意过,所以研究研究.location.pathname:返回URL的域名(域名IP)后的部分.例如 http://www.joymood.c ...

  2. C# 一个WCF简单实例

    以订票为例简单应用wcf 新建一个wcf服务应用程序 在IService1.cs定义服务契约 复制代码 代码如下: namespace WcfDemo { // 注意: 如果更改此处的接口名称 &qu ...

  3. 项目开发经常使用PHP功能

    日期操作 为了便于存储.比较和交付.我们通常使用strtotime()功能转换的日期UNIX时间戳.有仅用于在显示给用户时date()成经常使用的时间格式. strtotime()  函数将不论什么英 ...

  4. vs2012连接sql2008(错误类型:Could not load file or assembly)

    发生错误: Sql Server2008数据库中有一个数据库. 我想ORM-Entity FrameWork技术,在自己主动了一个项目,建立一个实体类! 解决的方法: 出现上面的情况.是由于缺少了这两 ...

  5. Java业务原子性的一种实现(key 独占访问)

    开发过程中,有时候为了解决多线程竞争问题需要加锁,通常锁定的对象是class,object,method,但在特定时候我们需要更细粒度的加锁,也就是根据不同输入参数来锁定不同的资源,这样只有调用此方法 ...

  6. POJ3342——Party at Hali-Bula

    Party at Hali-Bula Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 5418   Accepted: 192 ...

  7. Unofficial Microsoft SQL Server Driver for PHP (sqlsrv)非官方的PHP SQL Server 驱动

    原文 Unofficial Microsoft SQL Server Driver for PHP (sqlsrv) Here are unofficial modified builds of Mi ...

  8. 出现Deprecated: Function ereg_replace() is deprecated in 的原因及解决方法

    在 php5.3环境下运行oscommerce,常常会出现Deprecated: Function ereg() is deprecated in...和Deprecated: Function er ...

  9. Java 实现迭代器(Iterator)模式

    类图 /** * 自己定义集合接口, 相似java.util.Collection * 用于数据存储 * @author stone * */ public interface ICollection ...

  10. bzoj 1799: [Ahoi2009]self 类似的分布 解读

    [原标题] 1799: [Ahoi2009]self 同类分布 Time Limit: 50 Sec  Memory Limit: 64 MB Submit: 554  Solved: 194 [id ...