QQWry.dat 数据写入
纯真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
QQWry.dat 数据写入的更多相关文章
- 程序一 用记事本建立文件src.dat,其中存放若干字符。编写程序,从文件src.dat中读取数据,统计其中的大写字母、小写字母、数字、其它字符的个数,并将这些数据写入到文件test.dat中。
用记事本建立文件src.dat,其中存放若干字符.编写程序,从文件src.dat中读取数据,统计其中的大写字母.小写字母.数字.其它字符的个数,并将这些数据写入到文件test.dat中. #inclu ...
- Python使用纯真年代数据库qqwry.dat转换物理位置
PS:网上直接找的,贴出来,方便以后随时用,感谢分享的人. #!/usr/bin/python #encoding: utf-8 import socket import codecs import ...
- 使用C++将OpenCV中Mat的数据写入二进制文件,用Matlab读出
在使用OpenCV开发程序时,如果想查看矩阵数据,比较费劲,而matlab查看数据很方便,有一种方法,是matlab和c++混合编程,可以用matlab访问c++的内存,可惜我不会这种方式,所以我就把 ...
- 优化读取纯真IP数据库QQWry.dat获取地区信息
改自HeDaode 2007-12-28的代码 将之改为从硬盘读取后文件后,将MemoryStream放到内存中,提高后续查询速度 ///<summary> /// 提供从纯真IP数据库搜 ...
- php 根据ip获取城市以及网络运营商名称(利用qqwry.dat)
根据用户IP地址判定出所在城市以及网络运营商 qqwry.dat下载地址:http://files.cnblogs.com/guangxiaoluo/qqwry.rar 解压出来即可 //获取用户真 ...
- qqwry.dat输出乱码问题及maven打包后资源文件大小不一致的问题
使用qqwry.dat进行IP地理位置查询时,遇到一个问题即在本地测试时查询纯真库时正常,没有任何问题,但是打包传到服务器上便出现了乱码问题. 1.首先排除服务器的字符集编码的影响 使用如下命令验证了 ...
- 纯真IP数据库(qqwry.dat)转换成最新的IP数据库格式(ipwry.dat)
纯真IP数据库(qqwry.dat)转换成最新的IP数据库格式(ipwry.dat) 转载自:http://blog.cafeboy.org/2011/02/25/qqwry-to-ipwry/ ht ...
- 将Oracle数据库中的数据写入Excel
将Oracle数据库中的数据写入Excel 1.准备工作 Oracle数据库"TBYZB_FIELD_PRESSURE"表中数据如图: Excel模板(201512.xls): 2 ...
- JavaIO 将数据写入到文件中去
package com.Practice_FileWriter; import java.io.FileWriter; import java.io.IOException; public class ...
随机推荐
- 【windows核心编程】IO完成端口(IOCP)复制文件小例前简单说明
1.关于IOCP IOCP即IO完成端口,是一种高伸缩高效率的异步IO方式,一个设备或文件与一个IO完成端口相关联,当文件或设备的异步IO操作完成的时候,去IO完成端口的[完成队列]取一项,根据完成键 ...
- iOS数据存储之属性列表理解
iOS数据存储之属性列表理解 数据存储简介 数据存储,即数据持久化,是指以何种方式保存应用程序的数据. 我的理解是,开发了一款应用之后,应用在内存中运行时会产生很多数据,这些数据在程序运行时和程序一起 ...
- C++实现网格水印之调试笔记(六)——补充
调用matlab生成的网格水印特征向量矩阵 从文件中读取的原始网格的特征向量矩阵 好吧,之前得出的结果不正确是因为代码写错了.因为实现论文中的提取方案时代码写错了,自己想了另外一个方法,结果方向两者在 ...
- oc_转_NSInteger 和 NSNumber
Objective-C 支持的类型有两种:基本类型和类. 基本类型,如同 C 语言中的 int 类型一样,拿来就可以直接用.而类在使用时,必须先创建一个对象,再为对象分配空间,接着做初始化和赋值.类的 ...
- JSON 省市数据包括港澳
data: [{ name: "北京", cities: ["西城", "东城", "崇文", "宣武&quo ...
- 题目1434:今年暑假不AC (项目安排类:结束时间快排,判断开始时间)
题目描述: “今年暑假不AC?”“是的.”“那你干什么呢?”“看世界杯呀,笨蛋!”“@#$%^&*%...”确实如此,世界杯来了,球迷的节日也来了,估计很多ACMer也会抛开电脑,奔向电视作为 ...
- ACM2050
问题描述: 平面上有n条折线,问这些折线最多能将平面分割成多少块? 样例输入 1 2 样例输出 2 7 答案是: 2n ( 2n + 1 ) / 2 + 1 - 2n = 2 n^2 – n + ...
- redis的string类型
string : string类型是二进制安全的, 可以包含任何数据,比如jpg图片或者序列化的对象 . 方法 : set : 设置key对应的值为string类型的value set name ...
- GDB中应该知道的几个调试方法
七.八年前写过一篇<用GDB调试程序>,于是,从那以后,很多朋友在MSN上以及给我发邮件询问我关于GDB的问题,一直到今天,还有人在问GDB的相关问题.这么多年来,有一些问题是大家反复在问 ...
- cocos2d-x获得系统的语言
获得手机系统的语言 CCSize winSize = CCDirector::sharedDirector()->getWinSize(); CCLabelTTF *label = CCLabe ...