首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 手機號碼歸屬地查詢:從原理到高效能實作完整指南

     編輯:PHP 相關知識

核心原理:靜態資料檔 + 二分搜尋

手機歸屬地資料本質上是「號段 → 省份/城市/運營商」的映射表。中國大陸手機號前 7 碼(號段)即可唯一確定歸屬地。常見做法:將數百萬條記錄編碼為二進位資料檔(如 mob.dat),查詢時讀入記憶體,以二分搜尋或 Trie 樹定位,單次查詢耗時 < 1 ms。

資料檔格式設計(範例)

// 每筆記錄固定長度:7 碼號段 + 逗號 + 歸屬地字串 + 換行
// 範例:
1300000,北京移動
1300001,北京移動
1300002,北京聯通
...
1999999,廣東電信

實際部署時,建議將歸屬地字串編碼為定長位元組(如 20 位元組),或建立索引區塊(前 4 碼 → 檔案偏移量),大幅減少 I/O。

基礎版實作(教學用)

<?php
/**
 * 簡易版:逐行掃描(僅適合小資料量示範)
 * 實際專案請改用二分搜尋或記憶體映射
 */
function getMobileArea(string $mobile, string $datPath = __DIR__ . '/mob.dat'): ?string {
  // 1. 取前 7 碼
  $prefix = substr($mobile, 0, 7);
  if (!ctype_digit($prefix) || strlen($prefix) !== 7) return null;

  // 2. 開啟檔案(建議生產環境改用 fopen + fseek 二分搜尋)
  if (!is_readable($datPath)) return null;
  $handle = fopen($datPath, 'rb');
  if (!$handle) return null;

  // 3. 逐行比對(示範用,效能低)
  while (($line = fgets($handle)) !== false) {
    if (strpos($line, $prefix) === 0) {
      fclose($handle);
      return trim(substr($line, 8)); // 跳過 "1300000,"
    }
  }
  fclose($handle);
  return null;
}

// 測試
echo getMobileArea('13800138000'); // 輸出:北京移動
?>

高效能版:二分搜尋 + 記憶體映射

<?php
class MobileLocator {
  private $dataFile;
  private $index = [];      // [前4碼 => 起始行號]
  private $lineLength = 0;  // 固定行長(位元組)

  public function __construct(string $datPath) {
    $this->dataFile = $datPath;
    $this->buildIndex();
  }

  private function buildIndex(): void {
    $handle = fopen($this->dataFile, 'rb');
    $firstLine = fgets($handle);
    $this->lineLength = strlen($firstLine); // 假設固定長度
    fseek($handle, 0);

    $lineNum = 0;
    while (($line = fgets($handle)) !== false) {
      $prefix4 = substr($line, 0, 4);
      if (!isset($this->index[$prefix4])) {
        $this->index[$prefix4] = $lineNum;
      }
      $lineNum++;
    }
    fclose($handle);
  }

  public function query(string $mobile): ?string {
    $prefix7 = substr($mobile, 0, 7);
    $prefix4 = substr($prefix7, 0, 4);
    if (!isset($this->index[$prefix4])) return null;

    $handle = fopen($this->dataFile, 'rb');
    $startLine = $this->index[$prefix4];
    fseek($handle, $startLine * $this->lineLength);

    // 從索引起始行向後掃描,直到前 4 碼不匹配
    while (($line = fgets($handle)) !== false) {
      if (strpos($line, $prefix7) === 0) {
        fclose($handle);
        return trim(substr($line, 8));
      }
      if (substr($line, 0, 4) !== $prefix4) break; // 已離開該號段區塊
    }
    fclose($handle);
    return null;
  }
}

// 使用範例
$locator = new MobileLocator(__DIR__ . '/mob.dat');
echo $locator->query('13912345678'); // 上海移動
?>

進階優化方向

  1. 記憶體映射:$data = file_get_contents($datPath); 讀入記憶體,配合 strpos 或自訂二分搜尋,避免重複 I/O。
  2. Trie 樹 / DFA:將號段建立前綴樹,查詢複雜度 O(7),適合極高 QPS 場景。
  3. Redis 快取:熱門號段(前 4 碼覆蓋 80% 請求)預載入 Redis Hash,單機支援 10 萬+ QPS。
  4. 資料更新策略:工信部每月釋放新號段,建立自動化腳本下載最新 mob.dat,平滑替換(原子性 rename)。

現成開源專案推薦

Composer 快速整合範例

composer require zhuzhichao/phone-location

<?php
require 'vendor/autoload.php';
use Zhuzhichao\PhoneLocation\Phone;

$phone = new Phone(__DIR__ . '/vendor/zhuzhichao/phone-location/data/phone.dat');
var_dump($phone->get('13800138000'));
/*
array(4) {
  'province' => string(6) "北京"
  'city'     => string(6) "北京"
  'isp'      => string(6) "移動"
  'postcode' => string(6) "100000"
}
*/
?>

總結

手機歸屬地查詢的關鍵在於「資料結構選擇」與「存取方式優化」。教學階段可用逐行掃描理解原理;生產環境務必採用索引 + 二分搜尋或記憶體映射,並配合 Redis 快取熱點資料。若不想自行維護資料檔,直接引入成熟 Composer 套件即可獲得準確、高效、自動更新的解決方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved