本地下载 qqwry.dat
IPSeeker.java 用来读取QQwry.dat文件,以根据ip获位置信息;
注意:qqwry.dat在IPSeeker.java 文件中的路径;
1 package com.sxt.etl.util.ip; 2 3 import java.io.FileNotFoundException; 4 import java.io.IOException; 5 import java.io.RandomAccessFile; 6 import java.io.UnsupportedEncodingException; 7 import java.nio.ByteOrder; 8 import java.nio.MappedByteBuffer; 9 import java.nio.channels.FileChannel; 10 import java.util.ArrayList; 11 import java.util.Hashtable; 12 import java.util.List; 13 14 /** 15 * * 用来读取QQwry.dat文件,以根据ip获得好友位置,QQwry.dat的格式是 一. 文件头,共8字节 1. 第一个起始IP的绝对偏移, 4字节 16 * 2. 最后一个起始IP的绝对偏移, 4字节 二. "结束地址/国家/区域"记录区 四字节ip地址后跟的每一条记录分成两个部分 1. 国家记录 2. 17 * 地区记录 但是地区记录是不一定有的。而且国家记录和地区记录都有两种形式 1. 以0结束的字符串 2. 4个字节,一个字节可能为0x1或0x2 a. 18 * 为0x1时,表示在绝对偏移后还跟着一个区域的记录,注意是绝对偏移之后,而不是这四个字节之后 b. 为0x2时,表示在绝对偏移后没有区域记录 19 * 不管为0x1还是0x2,后三个字节都是实际国家名的文件内绝对偏移 20 * 如果是地区记录,0x1和0x2的含义不明,但是如果出现这两个字节,也肯定是跟着3个字节偏移,如果不是 则为0结尾字符串 三. 21 * "起始地址/结束地址偏移"记录区 1. 每条记录7字节,按照起始地址从小到大排列 a. 起始IP地址,4字节 b. 结束ip地址的绝对偏移,3字节 22 * 23 * 注意,这个文件里的ip地址和所有的偏移量均采用little-endian格式,而java是采用 big-endian格式的,要注意转换 24 * 25 * @author root 26 */ 27 public class IPSeeker { 28 29 // 一些固定常量,比如记录长度等等 30 private static final int IP_RECORD_LENGTH = 7; 31 private static final byte AREA_FOLLOWED = 0x01; 32 private static final byte NO_AREA = 0x2; 33 34 // 用来做为cache,查询一个ip时首先查看cache,以减少不必要的重复查找 35 private Hashtable ipCache; 36 // 随机文件访问类 37 private RandomAccessFile ipFile; 38 // 内存映射文件 39 private MappedByteBuffer mbb; 40 // 单一模式实例 41 private static IPSeeker instance = null; 42 // 起始地区的开始和结束的绝对偏移 43 private long ipBegin, ipEnd; 44 // 为提高效率而采用的临时变量 45 private IPLocation loc; 46 private byte[] buf; 47 private byte[] b4; 48 private byte[] b3; 49 50 /** */ 51 /** 52 * 私有构造函数 53 */ 54 protected IPSeeker() { 55 ipCache = new Hashtable(); 56 loc = new IPLocation(); 57 buf = new byte[100]; 58 b4 = new byte[4]; 59 b3 = new byte[3]; 60 try { 61 String ipFilePath = IPSeeker.class.getResource("/qqwry.dat") 62 .getFile(); 63 ipFile = new RandomAccessFile(ipFilePath, "r"); 64 } catch (FileNotFoundException e) { 65 System.out.println("IP地址信息文件没有找到,IP显示功能将无法使用"); 66 ipFile = null; 67 68 } 69 // 如果打开文件成功,读取文件头信息 70 if (ipFile != null) { 71 try { 72 ipBegin = readLong4(0); 73 ipEnd = readLong4(4); 74 if (ipBegin == -1 || ipEnd == -1) { 75 ipFile.close(); 76 ipFile = null; 77 } 78 } catch (IOException e) { 79 System.out.println("IP地址信息文件格式有错误,IP显示功能将无法使用"); 80 ipFile = null; 81 } 82 } 83 } 84 85 /** */ 86 /** 87 * @return 单一实例 88 */ 89 public static IPSeeker getInstance() { 90 if (instance == null) { 91 instance = new IPSeeker(); 92 } 93 return instance; 94 } 95 96 /** */ 97 /** 98 * 给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录 99 * 100 * @param s 101 * 地点子串 102 * @return 包含IPEntry类型的List 103 */ 104 public List getIPEntriesDebug(String s) { 105 List ret = new ArrayList(); 106 long endOffset = ipEnd + 4; 107 for (long offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { 108 // 读取结束IP偏移 109 long temp = readLong3(offset); 110 // 如果temp不等于-1,读取IP的地点信息 111 if (temp != -1) { 112 IPLocation loc = getIPLocation(temp); 113 // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续 114 if (loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { 115 IPEntry entry = new IPEntry(); 116 entry.country = loc.country; 117 entry.area = loc.area; 118 // 得到起始IP 119 readIP(offset - 4, b4); 120 entry.beginIp = IPSeekerUtils.getIpStringFromBytes(b4); 121 // 得到结束IP 122 readIP(temp, b4); 123 entry.endIp = IPSeekerUtils.getIpStringFromBytes(b4); 124 // 添加该记录 125 ret.add(entry); 126 } 127 } 128 } 129 return ret; 130 } 131 132 /** */ 133 /** 134 * 给定一个地点的不完全名字,得到一系列包含s子串的IP范围记录 135 * 136 * @param s 137 * 地点子串 138 * @return 包含IPEntry类型的List 139 */ 140 public List getIPEntries(String s) { 141 List ret = new ArrayList(); 142 try { 143 // 映射IP信息文件到内存中 144 if (mbb == null) { 145 FileChannel fc = ipFile.getChannel(); 146 mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFile.length()); 147 mbb.order(ByteOrder.LITTLE_ENDIAN); 148 } 149 150 int endOffset = (int) ipEnd; 151 for (int offset = (int) ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { 152 int temp = readInt3(offset); 153 if (temp != -1) { 154 IPLocation loc = getIPLocation(temp); 155 // 判断是否这个地点里面包含了s子串,如果包含了,添加这个记录到List中,如果没有,继续 156 if (loc.country.indexOf(s) != -1 157 || loc.area.indexOf(s) != -1) { 158 IPEntry entry = new IPEntry(); 159 entry.country = loc.country; 160 entry.area = loc.area; 161 // 得到起始IP 162 readIP(offset - 4, b4); 163 entry.beginIp = IPSeekerUtils.getIpStringFromBytes(b4); 164 // 得到结束IP 165 readIP(temp, b4); 166 entry.endIp = IPSeekerUtils.getIpStringFromBytes(b4); 167 // 添加该记录 168 ret.add(entry); 169 } 170 } 171 } 172 } catch (IOException e) { 173 System.out.println(e.getMessage()); 174 } 175 return ret; 176 } 177 178 /** */ 179 /** 180 * 从内存映射文件的offset位置开始的3个字节读取一个int 181 * 182 * @param offset 183 * @return 184 */ 185 private int readInt3(int offset) { 186 mbb.position(offset); 187 return mbb.getInt() & 0x00FFFFFF; 188 } 189 190 /** */ 191 /** 192 * 从内存映射文件的当前位置开始的3个字节读取一个int 193 * 194 * @return 195 */ 196 private int readInt3() { 197 return mbb.getInt() & 0x00FFFFFF; 198 } 199 200 /** */ 201 /** 202 * 根据IP得到国家名 203 * 204 * @param ip 205 * ip的字节数组形式 206 * @return 国家名字符串 207 */ 208 public String getCountry(byte[] ip) { 209 // 检查ip地址文件是否正常 210 if (ipFile == null) 211 return "错误的IP数据库文件"; 212 // 保存ip,转换ip字节数组为字符串形式 213 String ipStr = IPSeekerUtils.getIpStringFromBytes(ip); 214 // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件 215 if (ipCache.containsKey(ipStr)) { 216 IPLocation loc = (IPLocation) ipCache.get(ipStr); 217 return loc.country; 218 } else { 219 IPLocation loc = getIPLocation(ip); 220 ipCache.put(ipStr, loc.getCopy()); 221 return loc.country; 222 } 223 } 224 225 /** */ 226 /** 227 * 根据IP得到国家名 228 * 229 * @param ip 230 * IP的字符串形式 231 * @return 国家名字符串 232 */ 233 public String getCountry(String ip) { 234 return getCountry(IPSeekerUtils.getIpByteArrayFromString(ip)); 235 } 236 237 /** */ 238 /** 239 * 根据IP得到地区名 240 * 241 * @param ip 242 * ip的字节数组形式 243 * @return 地区名字符串 244 */ 245 public String getArea(byte[] ip) { 246 // 检查ip地址文件是否正常 247 if (ipFile == null) 248 return "错误的IP数据库文件"; 249 // 保存ip,转换ip字节数组为字符串形式 250 String ipStr = IPSeekerUtils.getIpStringFromBytes(ip); 251 // 先检查cache中是否已经包含有这个ip的结果,没有再搜索文件 252 if (ipCache.containsKey(ipStr)) { 253 IPLocation loc = (IPLocation) ipCache.get(ipStr); 254 return loc.area; 255 } else { 256 IPLocation loc = getIPLocation(ip); 257 ipCache.put(ipStr, loc.getCopy()); 258 return loc.area; 259 } 260 } 261 262 /** 263 * 根据IP得到地区名 264 * 265 * @param ip 266 * IP的字符串形式 267 * @return 地区名字符串 268 */ 269 public String getArea(String ip) { 270 return getArea(IPSeekerUtils.getIpByteArrayFromString(ip)); 271 } 272 273 /** */ 274 /** 275 * 根据ip搜索ip信息文件,得到IPLocation结构,所搜索的ip参数从类成员ip中得到 276 * 277 * @param ip 278 * 要查询的IP 279 * @return IPLocation结构 280 */ 281 public IPLocation getIPLocation(byte[] ip) { 282 IPLocation info = null; 283 long offset = locateIP(ip); 284 if (offset != -1) 285 info = getIPLocation(offset); 286 if (info == null) { 287 info = new IPLocation(); 288 info.country = "未知国家"; 289 info.area = "未知地区"; 290 } 291 return info; 292 } 293 294 /** 295 * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法 用了这么一个函数来做转换 296 * 297 * @param offset 298 * @return 读取的long值,返回-1表示读取文件失败 299 */ 300 private long readLong4(long offset) { 301 long ret = 0; 302 try { 303 ipFile.seek(offset); 304 ret |= (ipFile.readByte() & 0xFF); 305 ret |= ((ipFile.readByte() << 8) & 0xFF00); 306 ret |= ((ipFile.readByte() << 16) & 0xFF0000); 307 ret |= ((ipFile.readByte() << 24) & 0xFF000000); 308 return ret; 309 } catch (IOException e) { 310 return -1; 311 } 312 } 313 314 /** 315 * 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法 用了这么一个函数来做转换 316 * 317 * @param offset 318 * @return 读取的long值,返回-1表示读取文件失败 319 */ 320 private long readLong3(long offset) { 321 long ret = 0; 322 try { 323 ipFile.seek(offset); 324 ipFile.readFully(b3); 325 ret |= (b3[0] & 0xFF); 326 ret |= ((b3[1] << 8) & 0xFF00); 327 ret |= ((b3[2] << 16) & 0xFF0000); 328 return ret; 329 } catch (IOException e) { 330 return -1; 331 } 332 } 333 334 /** 335 * 从当前位置读取3个字节转换成long 336 * 337 * @return 338 */ 339 private long readLong3() { 340 long ret = 0; 341 try { 342 ipFile.readFully(b3); 343 ret |= (b3[0] & 0xFF); 344 ret |= ((b3[1] << 8) & 0xFF00); 345 ret |= ((b3[2] << 16) & 0xFF0000); 346 return ret; 347 } catch (IOException e) { 348 return -1; 349 } 350 } 351 352 /** 353 * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是 354 * 文件中是little-endian形式,将会进行转换 355 * 356 * @param offset 357 * @param ip 358 */ 359 private void readIP(long offset, byte[] ip) { 360 try { 361 ipFile.seek(offset); 362 ipFile.readFully(ip); 363 byte temp = ip[0]; 364 ip[0] = ip[3]; 365 ip[3] = temp; 366 temp = ip[1]; 367 ip[1] = ip[2]; 368 ip[2] = temp; 369 } catch (IOException e) { 370 System.out.println(e.getMessage()); 371 } 372 } 373 374 /** 375 * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是 376 * 文件中是little-endian形式,将会进行转换 377 * 378 * @param offset 379 * @param ip 380 */ 381 private void readIP(int offset, byte[] ip) { 382 mbb.position(offset); 383 mbb.get(ip); 384 byte temp = ip[0]; 385 ip[0] = ip[3]; 386 ip[3] = temp; 387 temp = ip[1]; 388 ip[1] = ip[2]; 389 ip[2] = temp; 390 } 391 392 /** 393 * 把类成员ip和beginIp比较,注意这个beginIp是big-endian的 394 * 395 * @param ip 396 * 要查询的IP 397 * @param beginIp 398 * 和被查询IP相比较的IP 399 * @return 相等返回0,ip大于beginIp则返回1,小于返回-1。 400 */ 401 private int compareIP(byte[] ip, byte[] beginIp) { 402 for (int i = 0; i < 4; i++) { 403 int r = compareByte(ip[i], beginIp[i]); 404 if (r != 0) 405 return r; 406 } 407 return 0; 408 } 409 410 /** 411 * 把两个byte当作无符号数进行比较 412 * 413 * @param b1 414 * @param b2 415 * @return 若b1大于b2则返回1,相等返回0,小于返回-1 416 */ 417 private int compareByte(byte b1, byte b2) { 418 if ((b1 & 0xFF) > (b2 & 0xFF)) // 比较是否大于 419 return 1; 420 else if ((b1 ^ b2) == 0)// 判断是否相等 421 return 0; 422 else 423 return -1; 424 } 425 426 /** 427 * 这个方法将根据ip的内容,定位到包含这个ip国家地区的记录处,返回一个绝对偏移 方法使用二分法查找。 428 * 429 * @param ip 430 * 要查询的IP 431 * @return 如果找到了,返回结束IP的偏移,如果没有找到,返回-1 432 */ 433 private long locateIP(byte[] ip) { 434 long m = 0; 435 int r; 436 // 比较第一个ip项 437 readIP(ipBegin, b4); 438 r = compareIP(ip, b4); 439 if (r == 0) 440 return ipBegin; 441 else if (r < 0) 442 return -1; 443 // 开始二分搜索 444 for (long i = ipBegin, j = ipEnd; i < j;) { 445 m = getMiddleOffset(i, j); 446 readIP(m, b4); 447 r = compareIP(ip, b4); 448 // log.debug(Utils.getIpStringFromBytes(b)); 449 if (r > 0) 450 i = m; 451 else if (r < 0) { 452 if (m == j) { 453 j -= IP_RECORD_LENGTH; 454 m = j; 455 } else 456 j = m; 457 } else 458 return readLong3(m + 4); 459 } 460 // 如果循环结束了,那么i和j必定是相等的,这个记录为最可能的记录,但是并非 461 // 肯定就是,还要检查一下,如果是,就返回结束地址区的绝对偏移 462 m = readLong3(m + 4); 463 readIP(m, b4); 464 r = compareIP(ip, b4); 465 if (r <= 0) 466 return m; 467 else 468 return -1; 469 } 470 471 /** 472 * 得到begin偏移和end偏移中间位置记录的偏移 473 * 474 * @param begin 475 * @param end 476 * @return 477 */ 478 private long getMiddleOffset(long begin, long end) { 479 long records = (end - begin) / IP_RECORD_LENGTH; 480 records >>= 1; 481 if (records == 0) 482 records = 1; 483 return begin + records * IP_RECORD_LENGTH; 484 } 485 486 /** 487 * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构 488 * 489 * @param offset 490 * @return 491 */ 492 private IPLocation getIPLocation(long offset) { 493 try { 494 // 跳过4字节ip 495 ipFile.seek(offset + 4); 496 // 读取第一个字节判断是否标志字节 497 byte b = ipFile.readByte(); 498 if (b == AREA_FOLLOWED) { 499 // 读取国家偏移 500 long countryOffset = readLong3(); 501 // 跳转至偏移处 502 ipFile.seek(countryOffset); 503 // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向 504 b = ipFile.readByte(); 505 if (b == NO_AREA) { 506 loc.country = readString(readLong3()); 507 ipFile.seek(countryOffset + 4); 508 } else 509 loc.country = readString(countryOffset); 510 // 读取地区标志 511 loc.area = readArea(ipFile.getFilePointer()); 512 } else if (b == NO_AREA) { 513 loc.country = readString(readLong3()); 514 loc.area = readArea(offset + 8); 515 } else { 516 loc.country = readString(ipFile.getFilePointer() - 1); 517 loc.area = readArea(ipFile.getFilePointer()); 518 } 519 return loc; 520 } catch (IOException e) { 521 return null; 522 } 523 } 524 525 /** 526 * @param offset 527 * @return 528 */ 529 private IPLocation getIPLocation(int offset) { 530 // 跳过4字节ip 531 mbb.position(offset + 4); 532 // 读取第一个字节判断是否标志字节 533 byte b = mbb.get(); 534 if (b == AREA_FOLLOWED) { 535 // 读取国家偏移 536 int countryOffset = readInt3(); 537 // 跳转至偏移处 538 mbb.position(countryOffset); 539 // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向 540 b = mbb.get(); 541 if (b == NO_AREA) { 542 loc.country = readString(readInt3()); 543 mbb.position(countryOffset + 4); 544 } else 545 loc.country = readString(countryOffset); 546 // 读取地区标志 547 loc.area = readArea(mbb.position()); 548 } else if (b == NO_AREA) { 549 loc.country = readString(readInt3()); 550 loc.area = readArea(offset + 8); 551 } else { 552 loc.country = readString(mbb.position() - 1); 553 loc.area = readArea(mbb.position()); 554 } 555 return loc; 556 } 557 558 /** 559 * 从offset偏移开始解析后面的字节,读出一个地区名 560 * 561 * @param offset 562 * @return 地区名字符串 563 * @throws IOException 564 */ 565 private String readArea(long offset) throws IOException { 566 ipFile.seek(offset); 567 byte b = ipFile.readByte(); 568 if (b == 0x01 || b == 0x02) { 569 long areaOffset = readLong3(offset + 1); 570 if (areaOffset == 0) 571 return "未知地区"; 572 else 573 return readString(areaOffset); 574 } else 575 return readString(offset); 576 } 577 578 /** 579 * @param offset 580 * @return 581 */ 582 private String readArea(int offset) { 583 mbb.position(offset); 584 byte b = mbb.get(); 585 if (b == 0x01 || b == 0x02) { 586 int areaOffset = readInt3(); 587 if (areaOffset == 0) 588 return "未知地区"; 589 else 590 return readString(areaOffset); 591 } else 592 return readString(offset); 593 } 594 595 /** 596 * 从offset偏移处读取一个以0结束的字符串 597 * 598 * @param offset 599 * @return 读取的字符串,出错返回空字符串 600 */ 601 private String readString(long offset) { 602 try { 603 ipFile.seek(offset); 604 int i; 605 for (i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile 606 .readByte()) 607 ; 608 if (i != 0) 609 return IPSeekerUtils.getString(buf, 0, i, "GBK"); 610 } catch (IOException e) { 611 System.out.println(e.getMessage()); 612 } 613 return ""; 614 } 615 616 /** 617 * 从内存映射文件的offset位置得到一个0结尾字符串 618 * 619 * @param offset 620 * @return 621 */ 622 private String readString(int offset) { 623 try { 624 mbb.position(offset); 625 int i; 626 for (i = 0, buf[i] = mbb.get(); buf[i] != 0; buf[++i] = mbb.get()) 627 ; 628 if (i != 0) 629 return IPSeekerUtils.getString(buf, 0, i, "GBK"); 630 } catch (IllegalArgumentException e) { 631 System.out.println(e.getMessage()); 632 } 633 return ""; 634 } 635 636 public String getAddress(String ip) { 637 String country = getCountry(ip).equals(" CZ88.NET") ? "" 638 : getCountry(ip); 639 String area = getArea(ip).equals(" CZ88.NET") ? "" : getArea(ip); 640 String address = country + " " + area; 641 return address.trim(); 642 } 643 644 /** 645 * * 用来封装ip相关信息,目前只有两个字段,ip所在的国家和地区 646 * 647 * 648 * @author swallow 649 */ 650 public class IPLocation { 651 public String country; 652 public String area; 653 654 public IPLocation() { 655 country = area = ""; 656 } 657 658 public IPLocation getCopy() { 659 IPLocation ret = new IPLocation(); 660 ret.country = country; 661 ret.area = area; 662 return ret; 663 } 664 } 665 666 /** 667 * 一条IP范围记录,不仅包括国家和区域,也包括起始IP和结束IP * 668 * 669 * 670 * @author root 671 */ 672 public class IPEntry { 673 public String beginIp; 674 public String endIp; 675 public String country; 676 public String area; 677 678 public IPEntry() { 679 beginIp = endIp = country = area = ""; 680 } 681 682 public String toString() { 683 return this.area + " " + this.country + "IP Χ:" + this.beginIp 684 + "-" + this.endIp; 685 } 686 } 687 688 /** 689 * 操作工具类 690 * 691 * @author root 692 * 693 */ 694 public static class IPSeekerUtils { 695 /** 696 * 从ip的字符串形式得到字节数组形式 697 * 698 * @param ip 699 * 字符串形式的ip 700 * @return 字节数组形式的ip 701 */ 702 public static byte[] getIpByteArrayFromString(String ip) { 703 byte[] ret = new byte[4]; 704 java.util.StringTokenizer st = new java.util.StringTokenizer(ip, 705 "."); 706 try { 707 ret[0] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 708 ret[1] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 709 ret[2] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 710 ret[3] = (byte) (Integer.parseInt(st.nextToken()) & 0xFF); 711 } catch (Exception e) { 712 System.out.println(e.getMessage()); 713 } 714 return ret; 715 } 716 717 /** 718 * 对原始字符串进行编码转换,如果失败,返回原始的字符串 719 * 720 * @param s 721 * 原始字符串 722 * @param srcEncoding 723 * 源编码方式 724 * @param destEncoding 725 * 目标编码方式 726 * @return 转换编码后的字符串,失败返回原始字符串 727 */ 728 public static String getString(String s, String srcEncoding, 729 String destEncoding) { 730 try { 731 return new String(s.getBytes(srcEncoding), destEncoding); 732 } catch (UnsupportedEncodingException e) { 733 return s; 734 } 735 } 736 737 /** 738 * 根据某种编码方式将字节数组转换成字符串 739 * 740 * @param b 741 * 字节数组 742 * @param encoding 743 * 编码方式 744 * @return 如果encoding不支持,返回一个缺省编码的字符串 745 */ 746 public static String getString(byte[] b, String encoding) { 747 try { 748 return new String(b, encoding); 749 } catch (UnsupportedEncodingException e) { 750 return new String(b); 751 } 752 } 753 754 /** 755 * 根据某种编码方式将字节数组转换成字符串 756 * 757 * @param b 758 * 字节数组 759 * @param offset 760 * 要转换的起始位置 761 * @param len 762 * 要转换的长度 763 * @param encoding 764 * 编码方式 765 * @return 如果encoding不支持,返回一个缺省编码的字符串 766 */ 767 public static String getString(byte[] b, int offset, int len, 768 String encoding) { 769 try { 770 return new String(b, offset, len, encoding); 771 } catch (UnsupportedEncodingException e) { 772 return new String(b, offset, len); 773 } 774 } 775 776 /** 777 * @param ip 778 * ip的字节数组形式 779 * @return 字符串形式的ip 780 */ 781 public static String getIpStringFromBytes(byte[] ip) { 782 StringBuffer sb = new StringBuffer(); 783 sb.append(ip[0] & 0xFF); 784 sb.append('.'); 785 sb.append(ip[1] & 0xFF); 786 sb.append('.'); 787 sb.append(ip[2] & 0xFF); 788 sb.append('.'); 789 sb.append(ip[3] & 0xFF); 790 return sb.toString(); 791 } 792 } 793 794 /** 795 * 获取全部ip地址集合列表 796 * 797 * @return 798 */ 799 public List<String> getAllIp() { 800 List<String> list = new ArrayList<String>(); 801 byte[] buf = new byte[4]; 802 for (long i = ipBegin; i < ipEnd; i += IP_RECORD_LENGTH) { 803 try { 804 this.readIP(this.readLong3(i + 4), buf); // 读取ip,最终ip放到buf中 805 String ip = IPSeekerUtils.getIpStringFromBytes(buf); 806 list.add(ip); 807 } catch (Exception e) { 808 // nothing 809 } 810 } 811 return list; 812 } 813 }
IPSeekerExt.java 继承IPSeeker.java ,将解析的结果做了进一步优化;
eg: 北京, 北京市
1 package com.sxt.etl.util; 2 3 import com.sxt.common.GlobalConstants; 4 import com.sxt.etl.util.ip.IPSeeker; 5 6 /** 7 * 定义具体的ip解析的类,最终调用IpSeeker类(父类)<br/> 8 * 解析ip最终的返回时:国家名称 省份名称 城市名称<br/> 9 * 如果是国外的ip,那么直接设置为unknown unknown unknown<br/> 10 * 如果是国内ip,如果没法进行解析,那么就设置为中国 unknown unknown<br/> 11 * 12 * @author root 13 * 14 */ 15 public class IPSeekerExt extends IPSeeker { 16 private RegionInfo DEFAULT_INFO = new RegionInfo(); 17 18 /** 19 * 解析ip地址,返回该ip地址对应的国家省份信息<br/> 20 * 如果该ip解析失败,那么直接返回默认值 21 * 22 * @param ip 23 * 要解析的ip地址,格式为: 120.197.87.216 24 * @return 25 */ 26 public RegionInfo analyticIp(String ip) { 27 if (ip == null || ip.trim().isEmpty()) { 28 return DEFAULT_INFO; 29 } 30 31 RegionInfo info = new RegionInfo(); 32 try { 33 String country = super.getCountry(ip); 34 if ("局域网".equals(country)) { 35 info.setCountry("中国"); 36 info.setProvince("上海市"); 37 } else if (country != null && !country.trim().isEmpty()) { 38 // 表示该ip还一个可以解析的ip 39 country = country.trim(); 40 int length = country.length(); 41 int index = country.indexOf('省'); 42 if (index > 0) { 43 // 当前ip属于23个省之间的一个,country的格式为:xxx省(xxx市)(xxx县/区) 44 info.setCountry("中国"); 45 if (index == length - 1) { 46 info.setProvince(country); // 设置省份,格式列入: 广东省 47 } else { 48 // 格式为:广东省广州市 49 info.setProvince(country.substring(0, index + 1)); // 设置省份 50 int index2 = country.indexOf('市', index); // 查看下一个出现市的位置 51 if (index2 > 0) { 52 country.substring(1, 1); 53 info.setCity(country.substring(index + 1, 54 Math.min(index2 + 1, length))); // 设置city 55 } 56 } 57 } else { 58 // 其他的五个自治区 四个直辖市 2个特别行政区 59 String flag = country.substring(0, 2); // 拿字符串前两位 60 switch (flag) { 61 case "内蒙": 62 info.setCountry("中国"); 63 info.setProvince("内蒙古自治区"); 64 country = country.substring(3); 65 if (country != null && !country.isEmpty()) { 66 index = country.indexOf('市'); 67 if (index > 0) { 68 info.setCity(country.substring(0, 69 Math.min(index + 1, country.length()))); // 设置市 70 } 71 } 72 break; 73 case "广西": 74 case "西藏": 75 case "宁夏": 76 case "新疆": 77 info.setCountry("中国"); 78 info.setProvince(flag); 79 country = country.substring(2); 80 if (country != null && !country.isEmpty()) { 81 index = country.indexOf('市'); 82 if (index > 0) { 83 info.setCity(country.substring(0, 84 Math.min(index + 1, country.length()))); // 设置市 85 } 86 } 87 break; 88 case "上海": 89 case "北京": 90 case "天津": 91 case "重庆": 92 info.setCountry("中国"); 93 info.setProvince(flag + "市"); 94 country = country.substring(3); // 去除这个省份/直辖市 95 if (country != null && !country.isEmpty()) { 96 index = country.indexOf('区'); 97 if (index > 0) { 98 char ch = country.charAt(index - 1); 99 if (ch != '校' || ch != '小') { 100 info.setCity(country.substring( 101 0, 102 Math.min(index + 1, 103 country.length()))); // 设置区 104 } 105 } 106 107 if (RegionInfo.DEFAULT_VALUE.equals(info.getCity())) { 108 // city还是默认值 109 index = country.indexOf('县'); 110 if (index > 0) { 111 info.setCity(country.substring( 112 0, 113 Math.min(index + 1, 114 country.length()))); // 设置区 115 } 116 } 117 } 118 break; 119 case "香港": 120 case "澳门": 121 info.setCountry("中国"); 122 info.setProvince(flag + "特别行政区"); 123 break; 124 default: 125 break; 126 } 127 } 128 } 129 } catch (Exception e) { 130 // 解析过程中出现异常 131 e.printStackTrace(); 132 } 133 return info; 134 } 135 136 /** 137 * ip地域相关的一个model 138 * 139 * @author root 140 * 141 */ 142 public static class RegionInfo { 143 public static final String DEFAULT_VALUE = GlobalConstants.DEFAULT_VALUE; // 默认值 144 private String country = DEFAULT_VALUE; // 国家 145 private String province = DEFAULT_VALUE; // 省份 146 private String city = DEFAULT_VALUE; // 城市 147 148 public String getCountry() { 149 return country; 150 } 151 152 public void setCountry(String country) { 153 this.country = country; 154 } 155 156 public String getProvince() { 157 return province; 158 } 159 160 public void setProvince(String province) { 161 this.province = province; 162 } 163 164 public String getCity() { 165 return city; 166 } 167 168 public void setCity(String city) { 169 this.city = city; 170 } 171 172 @Override 173 public String toString() { 174 return "RegionInfo [country=" + country + ", province=" + province 175 + ", city=" + city + "]"; 176 } 177 } 178 }
package com.sxt.test;
import java.util.List;
import com.sxt.etl.util.IPSeekerExt;
import com.sxt.etl.util.IPSeekerExt.RegionInfo;
public class TestIPSeekerExt {
public static void main(String[] args) {
IPSeekerExt ipSeekerExt = new IPSeekerExt();
RegionInfo info = ipSeekerExt.analyticIp("114.114.114.114");
System.out.println(info);
// List<String> ips = ipSeekerExt.getAllIp();
// for (String ip : ips) {
// System.out.println(ip + " --- " + ipSeekerExt.analyticIp(ip));
// }
}
}
大数据>0722>20170722>项目>BIG_DATA_SXT_1
浙公网安备 33010602011771号