QQWry.dat 數據寫入


純真IP庫 數據多,更新及時,很多同學在用,網上關於其讀取的帖子也有不少(當然其中有一些是有BUG的),但卻很少有關於其寫入的帖子。OK,下面分享下寫QQWry.dat。

QQWry.dat 分三個部分 :文件頭,記錄區,索引區。

 

一:首先寫文件頭,文件頭的內容只有8個字節,首四個字節是第一條索引的絕對偏移,后四個字節是最后一條索引的絕對偏移。但是一開始我們還不知道這兩個偏移量,那么就先隨便寫點啥,占個位吧,等后面索引寫完了再回來修改。

View Code
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段。很明顯這個有三個對象,並且層級關系。

View Code
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

View Code
//寫國家
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[絕對偏移量]

View Code
fs.WriteByte(1);
byte[] byOffset = BitConverter.GetBytes((data[i].Addrs[j].IPS[0].Offset + 4));
fs.Write(byOffset, 0, 3);

使用場景:在該記錄所對應的“國家”和“地址”之前都已經寫入過。

讀取時根據絕對偏移量跳轉到指定位置,(可能會跳轉兩次,因為可能跳到的下一個位置是重定向模式2)然后就和第一種情況類似。

第三種:[結束IP]2[絕對偏移量][地址]0 

View Code
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 
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,為了保存作者的創作熱情,請按要求【轉載】,謝謝
要求:未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM