純真IP庫 數據多,更新及時,很多同學在用,網上關於其讀取的帖子也有不少(當然其中有一些是有BUG的),但卻很少有關於其寫入的帖子。OK,下面分享下寫QQWry.dat。
QQWry.dat 分三個部分 :文件頭,記錄區,索引區。
一:首先寫文件頭,文件頭的內容只有8個字節,首四個字節是第一條索引的絕對偏移,后四個字節是最后一條索引的絕對偏移。但是一開始我們還不知道這兩個偏移量,那么就先隨便寫點啥,占個位吧,等后面索引寫完了再回來修改。

string path = HttpContext.Current.Server.MapPath("~/App_Data/QQWry.dat"); FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite); long firstIPIndexOffset = 0; //第一條索引的絕對偏移量 long lastIPIndexOffset = 0; //最后一條索引的絕對偏移量 byte[] head = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; fs.Write(head, 0, head.Length);
二:寫記錄區
記錄區的寫入最復雜,分析QQwry.dat的數據我們可以很容易發現,一個“國家”下面有N多個“地址”,一個“地址”下面有N多個IP段。很明顯這個有三個對象,並且層級關系。

public class IPEntity { public string StartIP { get; set; } public string EndIP { get; set; } /// <summary> /// 該IP記錄在文件中的絕對偏移量 /// </summary> public long Offset { get; set; } } public class AdressEntity { public AdressEntity() { IPS = new List<IPEntity>(); } public string Address { get; set; } public List<IPEntity> IPS { get; set; } } public class CountryEntity { public CountryEntity() { Addrs = new List<AdressEntity>(); } public string Country { get; set; } public List<AdressEntity> Addrs { get; set; } }
記錄區的數據格式不定,數據主要有以下類型:
A:結束IP(4個字節)
B:國家記錄 (以0x 00結束,不定長 )
C: 地區記錄 (以0x 00結束 ,不定長)
D:重定向模式標記(1或者2,1個字節)
E:絕對偏移量(3個字節)
每條記錄的組成結構可能有三種情況:
第一種(最簡單): [結束IP][國家]0[地址]0

//寫國家 byte[] byCountry = System.Text.Encoding.GetEncoding("GB2312").GetBytes(country); fs.Write(byCountry, 0, byCountry.Length); fs.WriteByte(0); //寫地址 byte[] byAdress = System.Text.Encoding.GetEncoding("GB2312").GetBytes(addr); fs.Write(byAdress, 0, byAdress.Length); fs.WriteByte(0);
使用場景:在該記錄所對應的“國家”和“地址”之前都沒有寫入時。
讀取時,找到這條記錄的位置依次讀下去就OK了
第二種:[結束IP]1[絕對偏移量]

fs.WriteByte(1); byte[] byOffset = BitConverter.GetBytes((data[i].Addrs[j].IPS[0].Offset + 4)); fs.Write(byOffset, 0, 3);
使用場景:在該記錄所對應的“國家”和“地址”之前都已經寫入過。
讀取時根據絕對偏移量跳轉到指定位置,(可能會跳轉兩次,因為可能跳到的下一個位置是重定向模式2)然后就和第一種情況類似。
第三種:[結束IP]2[絕對偏移量][地址]0

fs.WriteByte(2); byte[] byOffset = BitConverter.GetBytes((data[i].Addrs[0].IPS[0].Offset + 4)); fs.Write(byOffset, 0, 3); //寫地址 byte[] byAdress = System.Text.Encoding.GetEncoding("GB2312").GetBytes(addr); fs.Write(byAdress, 0, byAdress.Length); fs.WriteByte(0);
使用場景:在該記錄之前“國家”已經出現,但“地址”沒有出現過。
讀取時先按指定偏移跳轉到指定位置讀取到“國家”,然后讀取該記錄本身后面的“地址”
其就是為了使字符串盡量重用,同一個國家名稱只寫入一次,同一個地址名稱也只寫入一次
三:寫索引區
索引區的結構:[起始IP][絕對偏移量]
其實在寫記錄區的時候需要把每條IP記錄的絕對偏移量記下來。
寫入索引時需要記錄第一條索引和最后一條索引的絕對偏移量,用來更新前面提到的文件頭。
寫索引時必須將IP從小到大順序寫入,或者說我們提供數據源是按照IP從小到大的順序排列的,這樣我們在查找時才能使用二分法查找。
下面看一個完整的代碼段:
public class IPEntity { public string StartIP { get; set; } public string EndIP { get; set; } public long Offset { get; set; } } public class AdressEntity { public AdressEntity() { IPS = new List<IPEntity>(); } public string Address { get; set; } public List<IPEntity> IPS { get; set; } } public class CountryEntity { public CountryEntity() { Addrs = new List<AdressEntity>(); } public string Country { get; set; } public List<AdressEntity> Addrs { get; set; } } public class QQWryWriter { /// <summary> /// 寫文件 /// </summary> /// <param name="data">數據源,這個要事先准備好</param> public void Write(List<CountryEntity> data) { string path = HttpContext.Current.Server.MapPath("~/App_Data/QQWry.dat"); using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite)) { #region 寫文件頭 //文件頭,8個字節。暫時都為0,先占個位,預留着,等索引全部寫好了,再回過頭來修改。 long firstIPIndexOffset = 0; //第一條索引的絕對偏移量 long lastIPIndexOffset = 0; //最后一條索引的絕對偏移量 byte[] head = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; fs.Write(head, 0, head.Length); #endregion #region 寫記錄區 //每條記錄的組成結構有三種:[結束IP][重定向模式標記][國家][地址] //第一種(最簡單): [結束IP][國家]0[地址]0 //第二種: [結束IP]1[絕對偏移量] //第三種: [結束IP]2[絕對偏移量][地址]0 //其就是為了使字符串盡量重用,同一個國家名稱只寫入一次,同一個地址名稱也只寫入一次 for (int i = 0; i < data.Count; i++) { string country = data[i].Country;//國家名 for (int j = 0; j < data[i].Addrs.Count; j++) { string addr = data[i].Addrs[j].Address;//地址名 for (int k = 0; k < data[i].Addrs[j].IPS.Count; k++) { var ipEntity = data[i].Addrs[j].IPS[k]; //記下IP記錄的絕對偏移,方便后面索引區的寫入 ipEntity.Offset = fs.Position; //寫結束IP long intEndIP = IpToInt(ipEntity.EndIP); byte[] byEndIP = BitConverter.GetBytes(intEndIP); fs.Write(byEndIP, 0, 4); //重定向模式1 if (k > 0) { fs.WriteByte(1); byte[] byOffset = BitConverter.GetBytes((data[i].Addrs[j].IPS[0].Offset + 4)); fs.Write(byOffset, 0, 3); } else { //第一條記錄肯定是最簡單的,直接以第一種結構寫入。 if (j == 0) { //寫國家 byte[] byCountry = System.Text.Encoding.GetEncoding("GB2312").GetBytes(country); fs.Write(byCountry, 0, byCountry.Length); fs.WriteByte(0); //寫地址 byte[] byAdress = System.Text.Encoding.GetEncoding("GB2312").GetBytes(addr); fs.Write(byAdress, 0, byAdress.Length); fs.WriteByte(0); } //重定向模式2 else { fs.WriteByte(2); byte[] byOffset = BitConverter.GetBytes((data[i].Addrs[0].IPS[0].Offset + 4)); fs.Write(byOffset, 0, 3); //寫地址 byte[] byAdress = System.Text.Encoding.GetEncoding("GB2312").GetBytes(addr); fs.Write(byAdress, 0, byAdress.Length); fs.WriteByte(0); } } } } } #endregion #region 寫索引區 for (int i = 0; i < data.Count; i++) { for (int j = 0; j < data[i].Addrs.Count; j++) { for (int k = 0; k < data[i].Addrs[j].IPS.Count; k++) { var ipEntity = data[i].Addrs[j].IPS[k]; long intStartIP = IpToInt(ipEntity.StartIP); if (i == 0 && j == 0 && k == 0) { firstIPIndexOffset = fs.Position; } if (i == data.Count - 1 && j == data[i].Addrs.Count - 1 && k == data[i].Addrs[j].IPS.Count - 1) { lastIPIndexOffset = fs.Position; } byte[] byStartIP = BitConverter.GetBytes(intStartIP); fs.Write(byStartIP, 0, 4); byte[] byOffset = BitConverter.GetBytes(ipEntity.Offset); fs.Write(byOffset, 0, 3); } } } #endregion #region 更新文件頭 fs.Position = 0; byte[] byFirstIPIndexOffset = BitConverter.GetBytes(firstIPIndexOffset); fs.Write(byFirstIPIndexOffset, 0, 4); fs.Position = 4; byte[] bylastIPIndexOffset = BitConverter.GetBytes(lastIPIndexOffset); fs.Write(bylastIPIndexOffset, 0, 4); #endregion fs.Flush(); fs.Close(); } } /// <summary> /// IP地址轉換成Int數據 /// </summary> /// <param name="ip"></param> /// <returns></returns> private long IpToInt(string ip) { char[] dot = new char[] { '.' }; string[] ipArr = ip.Split(dot); if (ipArr.Length == 3) ip = ip + ".0"; ipArr = ip.Split(dot); long ip_Int = 0; long p1 = long.Parse(ipArr[0]) * 256 * 256 * 256; long p2 = long.Parse(ipArr[1]) * 256 * 256; long p3 = long.Parse(ipArr[2]) * 256; long p4 = long.Parse(ipArr[3]); ip_Int = p1 + p2 + p3 + p4; return ip_Int; } }
科普一下:
.Net中的各種流Stream、FileStream、xxxStream 都是字節數組,每個字節中存儲的都是0~255 之間的無符號數(即非負整數) 。
1個字節(Byte)==8個二進制位(Bit),所以1個Byte能存儲的最大無符號數為2^8-1=255 。
現在你應該能徹底理解一個IP為什么需要4個字節來存儲了。
原文地址:http://www.cnblogs.com/xumingxiang/archive/2013/02/17/2914524.html
出處: http://www.cnblogs.com/xumingxiang
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,為了保存作者的創作熱情,請按要求【轉載】,謝謝
要求:未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任