近期有個項目須要用到號碼歸屬查詢,歸屬地數據庫可能比不上ip138,淘寶上也有賣的-,-! 文本提供一個279188條記錄並壓縮成562KB的歸屬地數據。
我在互聯網上搜索了相關文章,要不是數據庫查詢或者是訪問網上的api,到底有沒有更好的方式,我想各大手機軟件的歸屬地都是屬於本地查詢的。
當我發現了Android Jni 使用C++對二進制文件查詢 這篇文章,發現效率真是高,做者的算法也至關出色。
因而直接把它用C#來實現了一個版本,而且加上號碼的類型,效率上沒相差太多,起碼咱們的項目已經夠用了。算法
這是原文的一段話:
sql
隨便去網絡上搜索一個號碼歸屬地數據庫下載,你可能會找到各類格式,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橋樑來處理。數據庫
相關技術和理論請參考原做者地址:api
Android 號碼,來電歸屬地 Jni 使用C++對二進制文件查詢(一) 理論篇網絡
Android 號碼,來電歸屬地 Jni 使用C++對二進制文件查詢(二) C++實現篇ide
Android 號碼,來電歸屬地 Jni 使用C++對二進制文件查詢(三) APK 實現篇函數
提供本文所修改過的源代碼下載。ui
areacode.dat(562KB)
內嵌的資源文件,此文件是根據areacode.txt(9,522KB)生成而來。(279188條數據)this
號碼壓縮的結構體,和原文C++版本的基本一致,只是增長了號碼類型的儲存;(佔用8個字節)spa
號碼的結構信息,分別有號碼段、地區、類型。
壓縮號碼歸屬地並生成二進制文件。
1 public void DoWriter(Stream stream, Encoding encoding) { 2 if (_data == null || _data.Count == 0) 3 return; 4 5 BinaryWriter bw = new BinaryWriter(stream, encoding); 6 7 //設置偏移量在開頭預留寫入NumberInfoCompress的總數 8 this.WriteCount(bw, 0, _phoneInfoCompressCount); 9 10 //設置偏移量在開頭預留號碼類型的總數 11 this.WriteCount(bw, 4, 0); 12 13 //先讀取第一條號碼數據 14 var enumerator = this._data.GetEnumerator(); 15 16 if (!enumerator.MoveNext()) 17 return; 18 19 //爲何要預先讀取一條數據呢?獲取第一條數據是爲了和下一條進行對比 20 var phoneInfo = enumerator.Current; 21 22 //增長城市信息,而且返回集合所在索引位置 23 var cityIdx = this.AddCity(phoneInfo.City); 24 //增長號碼類型信息,而且返回集合所在索引位置 25 var cardIdx = this.AddCard(phoneInfo.CardType); 26 27 //構造一個8字節存儲的結構體 28 var pre = new NumberInfoCompress(phoneInfo.Code, 0, cityIdx, cardIdx); 29 30 while (enumerator.MoveNext()) { 31 //讀取下一條數據,準備和上一條比較 32 phoneInfo = enumerator.Current; 33 cityIdx = this.AddCity(phoneInfo.City); 34 cardIdx = this.AddCard(phoneInfo.CardType); 35 36 //和上個號碼對比是否連續的,好比 1370875 1370876 1370877。 37 //1370875開頭有3個,表示13708 375:從75開始有3個連續的號碼 38 if (phoneInfo.Code - (pre.GetBegin() + pre.GetSkip()) == 1 && cityIdx == pre.GetCityIndex()) { 39 //設置號碼段連續位置 40 pre.SetSkip((ushort)(phoneInfo.Code - pre.GetBegin())); 41 } else { 42 //遞增一個 43 ++_phoneInfoCompressCount; 44 45 //寫入13708號碼段的數據 46 this.Write(bw, pre); 47 48 //繼續構造一個8字節存儲的結構體等待下次循環比較 49 pre = new NumberInfoCompress(phoneInfo.Code, 0, cityIdx, cardIdx); 50 } 51 } 52 53 //寫入最後的號碼數據 54 this.Write(bw, pre); 55 ++_phoneInfoCompressCount;//記錄總數 56 57 //寫入NumberInfoCompress的總數 58 this.WriteCount(bw, 0, _phoneInfoCompressCount); 59 60 //寫入號碼類型的總數 61 this.WriteCount(bw, 4, (uint)(_listCard.Count)); 62 63 //結尾寫入城市地區數據 64 this.WriteCity(bw, encoding); 65 66 //結尾寫入號碼類型數據 67 this.WriteCard(bw, encoding); 68 69 bw.Close(); 70 bw.Dispose(); 71 }
用來讀取areacode.dat,好比查詢號碼歸屬地。
1 public PhoneInfo GetPhoneInfo(Stream stream, Encoding encoding, int number) { 2 PhoneInfo result = new PhoneInfo(); 3 4 result.Code = number; 5 6 BinaryReader br = new BinaryReader(stream, encoding); 7 8 //獲取索引總數 9 int phoneInfoCompressCount = br.ReadInt32(); 10 //號碼類型總數 11 int cardCount = br.ReadInt32(); 12 int left = 0, right = phoneInfoCompressCount - 1; 13 14 var per = new NumberInfoCompress(); 15 var perSize = Marshal.SizeOf(per); 16 17 //使用折半查詢(二分法) 18 while (left <= right) { 19 //折半 20 int middle = (left + right) / 2; 21 //索引總數8字節 + middle * NumberInfoCompress字節數 22 stream.Position = sizeof(int) * 2 + middle * perSize; 23 24 //讀取NumberInfoCompress數據 25 per.Before = br.ReadUInt16(); 26 per.After = br.ReadUInt16(); 27 per.CityIndex = br.ReadUInt16(); 28 per.CardIndex = br.ReadUInt16(); 29 30 //判斷號碼是否匹配 31 if (number < per.GetBegin()) { 32 right = middle - 1;//在左半區間找 33 } else if (number > (per.GetBegin() + per.GetSkip())) { 34 left = middle + 1;//在右半區間找 35 } else { 36 //已找到,直接查詢城市和號碼類型 37 result.City = DoFindCityThing(br, phoneInfoCompressCount, per); 38 result.CardType = DoFindCardThing(br, cardCount, per); 39 return result; 40 } 41 } 42 br.Close(); 43 br.Dispose(); 44 return result; 45 } 46 47 private string DoFindCityThing(BinaryReader br, int phoneInfoCompressCount, NumberInfoCompress infoMiddle) { 48 //計算城市區域信息位置 49 //sizeof(int) * 2 開頭位置儲存了一個4字節的NumberInfoCompress總數和類型總數 50 //phoneInfoCompressCount NumberInfoCompress總數 51 //Marshal.SizeOf(infoMiddle) NumberInfoCompress佔用空間 52 //infoMiddle.GetCityIndex() 城市的所在位置 53 //_maxCityLength 城市總數 54 //偏移量 = 索引總數8字節 + 索引總數 * NumberInfoCompress字節數 + 城市的所在位置 * 城市大小 55 long totalOffset = sizeof(int) * 2 + phoneInfoCompressCount * Marshal.SizeOf(infoMiddle) 56 + infoMiddle.GetCityIndex() * this._maxCityLength; 57 58 br.BaseStream.Position = totalOffset;//設置偏移量 59 char[] charCity = br.ReadChars(this._maxCityLength); 60 return new string(charCity, 0, Array.IndexOf(charCity, '\0')); 61 } 62 63 private string DoFindCardThing(BinaryReader br, int cardCount, NumberInfoCompress infoMiddle) { 64 //號碼類型存儲在尾端 65 //因此偏移量 = (流的總長度 - 類型總數 * 類型大小) + 所在位置 * 類型大小 66 long totalOffset = (br.BaseStream.Length - cardCount * this._maxCardLength) + infoMiddle.GetCardIndex() * this._maxCardLength; 67 68 br.BaseStream.Position = totalOffset;//設置偏移量 69 char[] charCard = br.ReadChars(this._maxCardLength); 70 return new string(charCard, 0, Array.IndexOf(charCard, '\0')); 71 }
封裝了手機歸屬地查詢函數。
用來演示如何查詢電話號碼歸屬地以及把文本文件生成爲壓縮過的二進制文件(areacode.dat)。
原做者的壓縮算法咱們也能夠稍做改變,可是用這種算法的前提條件是必須有序且有規律,最後用二分法纔會提升查詢速度。項目資源裏面的文本文件是每行一個號碼段,如:號碼,區域,類型;讀者能夠自行存儲到任何數據庫等地方,方便往後管理。