1.  java读取纯真IP数据库QQwry.dat的源代码,要运行此程序必须到网上下载QQwry.da,由于太大,我这里就不提供了。    
2.  一、IPEntry.java   
3. /** *
4. * 一条IP范围记录,不仅包括国家和区域,也包括起始IP和结束IP *
5.    
6.  * 
7.  * @author 
8.  */
9. public   class
10. public
11. public
12. public
13. public
14.         
15. /**
16.      * 构造函数
17.      */
18.        
19.     
20. public
21. ""
22.      }   
23.         
24. public
25. this .area+ "  " + this .country+ "IP范围:" + this .beginIp+ "-" + this
26.      }   
27.     }    
28.     
29.  二、Utils.java   
30.     
31. /*
32.  * Created on 2004-8-4
33.  *
34.  */
35. import
36. /**
37.  * @author LJ-silver
38.  */
39. public   class
40. /**
41.      * 从ip的字符串形式得到字节数组形式
42.      * @param ip 字符串形式的ip
43.      * @return 字节数组形式的ip
44.      */
45. public   static   byte
46. byte [] ret =  new   byte [ 4
47. new  java.util.StringTokenizer(ip,  "."
48. try
49. 0 ] = ( byte )(Integer.parseInt(st.nextToken()) &  0xFF
50. 1 ] = ( byte )(Integer.parseInt(st.nextToken()) &  0xFF
51. 2 ] = ( byte )(Integer.parseInt(st.nextToken()) &  0xFF
52. 3 ] = ( byte )(Integer.parseInt(st.nextToken()) &  0xFF
53. catch
54.              System.out.println(e.getMessage());   
55.          }   
56. return
57.      }   
58.         
59. public   static   void
60. byte [] a=getIpByteArrayFromString(args[ 0
61. for ( int  i= 0
62.                  System.out.println(a[i]);   
63.            System.out.println(getIpStringFromBytes(a));    
64.      }   
65. /**
66.      * 对原始字符串进行编码转换,如果失败,返回原始的字符串
67.      * @param s 原始字符串
68.      * @param srcEncoding 源编码方式
69.      * @param destEncoding 目标编码方式
70.      * @return 转换编码后的字符串,失败返回原始字符串
71.      */
72. public   static
73. try
74. return   new
75. catch
76. return
77.          }   
78.      }   
79.         
80. /**
81.      * 根据某种编码方式将字节数组转换成字符串
82.      * @param b 字节数组
83.      * @param encoding 编码方式
84.      * @return 如果encoding不支持,返回一个缺省编码的字符串
85.      */
86. public   static  String getString( byte
87. try
88. return   new
89. catch
90. return   new
91.          }   
92.      }   
93.         
94. /**
95.      * 根据某种编码方式将字节数组转换成字符串
96.      * @param b 字节数组
97.      * @param offset 要转换的起始位置
98.      * @param len 要转换的长度
99.      * @param encoding 编码方式
100.      * @return 如果encoding不支持,返回一个缺省编码的字符串
101.      */
102. public   static  String getString( byte [] b,  int  offset,  int
103. try
104. return   new
105. catch
106. return   new
107.          }   
108.      }   
109.         
110. /**
111.      * @param ip ip的字节数组形式
112.      * @return 字符串形式的ip
113.      */
114. public   static  String getIpStringFromBytes( byte
115. new
116. 0 ] &  0xFF
117. '.'
118. 1 ] &  0xFF
119. '.'
120. 2 ] &  0xFF
121. '.'
122. 3 ] &  0xFF
123. return
124.      }   
125.  }    
126.     
127.   三、IPSeeker.java   
128.     
129. /*
130. * LumaQQ - Java QQ Client
131. *
132. * Copyright (C) 2004 luma < stubma@163.com>
133. *
134. * This program is free software; you can redistribute it and/or modify
135. * it under the terms of the GNU General Public License as published by
136. * the Free Software Foundation; either version 2 of the License, or
137. * (at your option) any later version.
138. *
139. * This program is distributed in the hope that it will be useful,
140. * but WITHOUT ANY WARRANTY; without even the implied warranty of
141. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
142. * GNU General Public License for more details.
143. *
144. * You should have received a copy of the GNU General Public License
145. * along with this program; if not, write to the Free Software
146. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
147. */
148. import
149. import
150. import
151. import
152. import
153. import
154. import
155. import
156. import
157. /**
158.  * 
159.    
160.  * 用来读取QQwry.dat文件,以根据ip获得好友位置,QQwry.dat的格式是
161.  * 一. 文件头,共8字节
162.  *     1. 第一个起始IP的绝对偏移, 4字节
163.  *     2. 最后一个起始IP的绝对偏移, 4字节
164.  * 二. "结束地址/国家/区域"记录区
165.  *     四字节ip地址后跟的每一条记录分成两个部分
166.  *     1. 国家记录
167.  *     2. 地区记录
168.  *     但是地区记录是不一定有的。而且国家记录和地区记录都有两种形式
169.  *     1. 以0结束的字符串
170.  *     2. 4个字节,一个字节可能为0x1或0x2
171.  *        a. 为0x1时,表示在绝对偏移后还跟着一个区域的记录,注意是绝对偏移之后,而不是这四个字节之后
172.  *        b. 为0x2时,表示在绝对偏移后没有区域记录
173.  *        不管为0x1还是0x2,后三个字节都是实际国家名的文件内绝对偏移
174.  *        如果是地区记录,0x1和0x2的含义不明,但是如果出现这两个字节,也肯定是跟着3个字节偏移,如果不是
175.  *        则为0结尾字符串
176.  * 三. "起始地址/结束地址偏移"记录区
177.  *     1. 每条记录7字节,按照起始地址从小到大排列
178.  *        a. 起始IP地址,4字节
179.  *        b. 结束ip地址的绝对偏移,3字节
180.  *
181.  * 注意,这个文件里的ip地址和所有的偏移量均采用little-endian格式,而java是采用
182.  * big-endian格式的,要注意转换
183.  * 
184.    
185.    
186.  *
187.  * @author 马若劼
188.  */
189. public   class
190. /**
191.      * 
192.    
193.      * 用来封装ip相关信息,目前只有两个字段,ip所在的国家和地区
194.      * 
195.    
196.    
197.      *
198.      * @author 马若劼
199.      */
200. private   class
201. public
202. public
203. public
204. ""
205.          }   
206. public
207. new
208.              ret.country = country;   
209.              ret.area = area;   
210. return
211.          }   
212.      }   
213. private   static   final  String IP_FILE = IPSeeker. class .getResource( "/QQWry.dat" ).toString().substring( 5
214. // 一些固定常量,比如记录长度等等
215. private   static   final   int  IP_RECORD_LENGTH =  7
216. private   static   final   byte  AREA_FOLLOWED =  0x01
217. private   static   final   byte  NO_AREA =  0x2
218. // 用来做为cache,查询一个ip时首先查看cache,以减少不必要的重复查找
219. private
220. // 随机文件访问类
221. private
222. // 内存映射文件
223. private
224. // 单一模式实例
225. private   static  IPSeeker instance =  new
226. // 起始地区的开始和结束的绝对偏移
227. private   long
228. // 为提高效率而采用的临时变量
229. private
230. private   byte
231. private   byte
232. private   byte
233. /**
234.      * 私有构造函数
235.      */
236. private
237. new
238. new
239. new   byte [ 100
240. new   byte [ 4
241. new   byte [ 3
242. try
243. new  RandomAccessFile(IP_FILE,  "r"
244. catch
245. class .getResource( "/QQWry.dat"
246.                          System.out.println(IP_FILE);   
247. "IP地址信息文件没有找到,IP显示功能将无法使用"
248. null
249.          }   
250. // 如果打开文件成功,读取文件头信息
251. if (ipFile !=  null
252. try
253. 0
254. 4
255. if (ipBegin == - 1  || ipEnd == - 1
256.                      ipFile.close();   
257. null
258.                  }   
259. catch
260. "IP地址信息文件格式有错误,IP显示功能将无法使用"
261. null
262.              }   
263.          }   
264.      }   
265. /**
266.      * @return 单一实例
267.      */
268. public   static
269. return
270.      }   
271. /**
272.      * 给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录
273.      * @param s 地点子串
274.      * @return 包含IPEntry类型的List
275.      */
276. public
277. new
278. long  endOffset = ipEnd +  4
279. for ( long  offset = ipBegin +  4
280. // 读取结束IP偏移
281. long
282. // 如果temp不等于-1,读取IP的地点信息
283. if (temp != - 1
284.                  IPLocation loc = getIPLocation(temp);   
285. // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续
286. if (loc.country.indexOf(s) != - 1  || loc.area.indexOf(s) != - 1
287. new
288.                      entry.country = loc.country;   
289.                      entry.area = loc.area;   
290. // 得到起始IP
291. 4
292.                      entry.beginIp = Utils.getIpStringFromBytes(b4);   
293. // 得到结束IP
294.                      readIP(temp, b4);   
295.                      entry.endIp = Utils.getIpStringFromBytes(b4);   
296. // 添加该记录
297.                      ret.add(entry);   
298.                  }   
299.              }   
300.          }   
301. return
302.      }   
303. /**
304.      * 给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录
305.      * @param s 地点子串
306.      * @return 包含IPEntry类型的List
307.      */
308. public
309. new
310. try
311. // 映射IP信息文件到内存中
312. if (mbb ==  null
313.                  FileChannel fc = ipFile.getChannel();   
314. 0
315.                  mbb.order(ByteOrder.LITTLE_ENDIAN);   
316.              }   
317. int  endOffset = ( int
318. for ( int  offset = ( int )ipBegin +  4
319. int
320. if (temp != - 1
321.                      IPLocation loc = getIPLocation(temp);   
322. // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续
323. if (loc.country.indexOf(s) != - 1  || loc.area.indexOf(s) != - 1
324. new
325.                          entry.country = loc.country;   
326.                          entry.area = loc.area;   
327. // 得到起始IP
328. 4
329.                          entry.beginIp = Utils.getIpStringFromBytes(b4);   
330. // 得到结束IP
331.                          readIP(temp, b4);   
332.                          entry.endIp = Utils.getIpStringFromBytes(b4);   
333. // 添加该记录
334.                          ret.add(entry);   
335.                      }   
336.                  }   
337.              }   
338. catch
339.              System.out.println(e.getMessage());   
340.          }   
341. return
342.      }   
343. /**
344.      * 从内存映射文件的offset位置开始的3个字节读取一个int
345.      * @param offset
346.      * @return
347.      */
348. private   int  readInt3( int
349.          mbb.position(offset);   
350. return  mbb.getInt() &  0x00FFFFFF
351.      }   
352. /**
353.      * 从内存映射文件的当前位置开始的3个字节读取一个int
354.      * @return
355.      */
356. private   int
357. return  mbb.getInt() &  0x00FFFFFF
358.      }   
359. /**
360.      * 根据IP得到国家名
361.      * @param ip ip的字节数组形式
362.      * @return 国家名字符串
363.      */
364. public  String getCountry( byte
365. // 检查ip地址文件是否正常
366. if (ipFile ==  null )  return   "错误的IP数据库文件"
367. // 保存ip,转换ip字节数组为字符串形式
368.          String ipStr = Utils.getIpStringFromBytes(ip);   
369. // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件
370. if
371.              IPLocation loc = (IPLocation)ipCache.get(ipStr);   
372. return
373. else
374.              IPLocation loc = getIPLocation(ip);   
375.              ipCache.put(ipStr, loc.getCopy());   
376. return
377.          }   
378.      }   
379. /**
380.      * 根据IP得到国家名
381.      * @param ip IP的字符串形式
382.      * @return 国家名字符串
383.      */
384. public
385. return
386.      }   
387. /**
388.      * 根据IP得到地区名
389.      * @param ip ip的字节数组形式
390.      * @return 地区名字符串
391.      */
392. public  String getArea( byte
393. // 检查ip地址文件是否正常
394. if (ipFile ==  null )  return   "错误的IP数据库文件"
395. // 保存ip,转换ip字节数组为字符串形式
396.          String ipStr = Utils.getIpStringFromBytes(ip);   
397. // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件
398. if
399.              IPLocation loc = (IPLocation)ipCache.get(ipStr);   
400. return
401. else
402.              IPLocation loc = getIPLocation(ip);   
403.              ipCache.put(ipStr, loc.getCopy());   
404. return
405.          }   
406.      }   
407. /**
408.      * 根据IP得到地区名
409.      * @param ip IP的字符串形式
410.      * @return 地区名字符串
411.      */
412. public
413. return
414.      }   
415. /**
416.      * 根据ip搜索ip信息文件,得到IPLocation结构,所搜索的ip参数从类成员ip中得到
417.      * @param ip 要查询的IP
418.      * @return IPLocation结构
419.      */
420. private  IPLocation getIPLocation( byte
421. null
422. long
423. if (offset != - 1
424.              info = getIPLocation(offset);   
425. if (info ==  null
426. new
427. "未知国家"
428. "未知地区"
429.          }   
430. return
431.      }   
432. /**
433.      * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法
434.      * 用了这么一个函数来做转换
435.      * @param offset
436.      * @return 读取的long值,返回-1表示读取文件失败
437.      */
438. private   long  readLong4( long
439. long  ret =  0
440. try
441.              ipFile.seek(offset);   
442. 0xFF
443. 8 ) &  0xFF00
444. 16 ) &  0xFF0000
445. 24 ) &  0xFF000000
446. return
447. catch
448. return  - 1
449.          }   
450.      }   
451. /**
452.      * 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法
453.      * 用了这么一个函数来做转换
454.      * @param offset
455.      * @return 读取的long值,返回-1表示读取文件失败
456.      */
457. private   long  readLong3( long
458. long  ret =  0
459. try
460.              ipFile.seek(offset);   
461.              ipFile.readFully(b3);   
462. 0 ] &  0xFF
463. 1 ] <<  8 ) &  0xFF00
464. 2 ] <<  16 ) &  0xFF0000
465. return
466. catch
467. return  - 1
468.          }   
469.      }   
470. /**
471.      * 从当前位置读取3个字节转换成long
472.      * @return
473.      */
474. private   long
475. long  ret =  0
476. try
477.              ipFile.readFully(b3);   
478. 0 ] &  0xFF
479. 1 ] <<  8 ) &  0xFF00
480. 2 ] <<  16 ) &  0xFF0000
481. return
482. catch
483. return  - 1
484.          }   
485.      }   
486. /**
487.      * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是
488.      * 文件中是little-endian形式,将会进行转换
489.      * @param offset
490.      * @param ip
491.      */
492. private   void  readIP( long  offset,  byte
493. try
494.              ipFile.seek(offset);   
495.              ipFile.readFully(ip);   
496. byte  temp = ip[ 0
497. 0 ] = ip[ 3
498. 3
499. 1
500. 1 ] = ip[ 2
501. 2
502. catch
503.              System.out.println(e.getMessage());   
504.          }   
505.      }   
506. /**
507.      * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是
508.      * 文件中是little-endian形式,将会进行转换
509.      * @param offset
510.      * @param ip
511.      */
512. private   void  readIP( int  offset,  byte
513.          mbb.position(offset);   
514.          mbb.get(ip);   
515. byte  temp = ip[ 0
516. 0 ] = ip[ 3
517. 3
518. 1
519. 1 ] = ip[ 2
520. 2
521.      }   
522. /**
523.      * 把类成员ip和beginIp比较,注意这个beginIp是big-endian的
524.      * @param ip 要查询的IP
525.      * @param beginIp 和被查询IP相比较的IP
526.      * @return 相等返回0,ip大于beginIp则返回1,小于返回-1。
527.      */
528. private   int  compareIP( byte [] ip,  byte
529. for ( int  i =  0 ; i <  4
530. int
531. if (r !=  0
532. return
533.          }   
534. return   0
535.      }   
536. /**
537.      * 把两个byte当作无符号数进行比较
538.      * @param b1
539.      * @param b2
540.      * @return 若b1大于b2则返回1,相等返回0,小于返回-1
541.      */
542. private   int  compareByte( byte  b1,  byte
543. if ((b1 &  0xFF ) > (b2 &  0xFF ))  // 比较是否大于
544. return   1
545. else   if ((b1 ^ b2) ==  0 ) // 判断是否相等
546. return   0
547. else
548. return