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#手机归属地

C# 号码归属地查询算法的更多相关文章

  1. C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改)

    前言 近期有个项目需要用到号码归属查询,归属地数据库可能比不上ip138,淘宝上也有卖的-,-! 文本提供一个279188条记录并压缩成562KB的归属地数据.我在互联网上搜索了相关文章,要不是数据库 ...

  2. [android] 手机卫士号码归属地查询

    使用小米号码归属地数据库,有两张表data1和data2 先查询data1表,把手机号码截取前7位 select outkey from data1 where id=”前七位手机号” 再查询data ...

  3. WebService学习------小实例开发(号码归属地查询)

    1.WebService简介: WebService是一种跨平台,跨语言的,可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独立的通讯技术.通过SOAP在Web上提 ...

  4. [android] 手机卫士号码归属地查询完成

    正则表达式完成号码验证, ^以某开头,[] 字符集(匹配中括号里面的任意字符),\d是任意一个数字,{n}表示出现了多少次,$结尾 手机号的正则 ^1[34568]\d{9}$,以1开头,第二个数字是 ...

  5. 本地的手机号码归属地查询-oracle数据

    最近做的项目中,有个功能是手机归属地查询,因为项目要在内网下运行,所以不能用提供的webservice,只好在网上找手机归属地的数据,很多都是access的,我们的项目是用oracle,只好自己转吧, ...

  6. 百度手机号码归属地查询api与返回json处理

    前天无意间在网上看到百度ApiStore,然后好奇就进去看了看.正好最近在某博培训Android,刚学到java基础.抱着锻炼的心态选择手机号码归属地查询api进行练手.api地址 (http://a ...

  7. android134 360 07 归属地查询

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  8. 手机归属地查询-IP地址查询-身份证查询-域名备案查询--Api接口

    使用这些接口是需要密钥的 公共密钥 appkey: 10003  secret: d1149a30182aa2088ef645309ea193bf  生成后sign: b59bc3ef6191eb9f ...

  9. C# Winform实现手机号码归属地查询工具

    摘要:本文介绍使用C#开发基于Winform的手机号码归属地查询工具,并提供详细的示例代码供参考. 一.需求描述 输入正确的手机号码,查询该号码的归属地和其他相关信息. 二.需求分析 1.实现手机号码 ...

随机推荐

  1. Codeforces Round #248 (Div. 2) (ABCD解决问题的方法)

    比赛链接:http://codeforces.com/contest/433 A. Kitahara Haruki's Gift time limit per test:1 second memory ...

  2. Android - View Alpha值

    Android - View Alpha值 本文地址: http://blog.csdn.net/caroline_wendy Alpha值主要控制图像的透明度(0-1),0代表透明.1代表不透明. ...

  3. eclipse打不开data目录解决的方法

    1.首先手机必须详细root权限.没有的话,先去root. 2.root过后若还是不能打开,下载一个R.E管理器,然后将须要打开的目录设置为可读.可写.可运行. 附图:

  4. Swift # 异常处理

    面向轨道编程 - Swift 中的异常处理 问题 在开发过程中,异常处理算是比较常见的问题了. 举一个比较常见的例子:用户修改注册的邮箱,大概分为以下几个步骤: 接收到一个用户的请求:我要修改邮箱地址 ...

  5. Python学习笔记16:标准库多线程(threading包裹)

    Python主要是通过标准库threading包来实现多线程. 今天,互联网时代,所有的server您将收到大量请求. server要利用多线程的方式的优势来处理这些请求,为了改善网络port读写效率 ...

  6. Redis MSET的极限在哪里

    ·背景 Redis以"快.准.狠"而著称,除了其主-从模式略失光彩(主从模式更多是被以讹传讹,3.0依旧在测试中),大部分的应用可谓尖兵利器.在一些常规写的时候,MSET和HMSE ...

  7. Oracle在rownum使用结果集排序

    Oracle在rownum使用结果集排序    对于 Oracle 的 rownum 问题,非常多资料都说不支持>,>=,=,between...and,仅仅能用以上符号(<.< ...

  8. cocos2d-x-lua基础系列教程三(lua面向对象)

    lua 类 Lua 事实上不是面向对象语言 我们能够用table 模拟仿照面向对象编程 lua 中的this 类似的是self  table 也具有生命周期 2,使用table  创建类 projed ...

  9. oracle获取字符串长度函数length()和hengthb()

    原文:oracle获取字符串长度函数length()和hengthb() lengthb(string)计算string所占的字节长度:返回字符串的长度,单位是字节 length(string)计算s ...

  10. The Native POSIX Thread Library for Linux - 设计文档

    nptl-design