手機歸屬地資料本質上是「號段 → 省份/城市/運營商」的映射表。中國大陸手機號前 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'); // 上海移動
?>
$data = file_get_contents($datPath); 讀入記憶體,配合 strpos 或自訂二分搜尋,避免重複 I/O。mob.dat,平滑替換(原子性 rename)。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 套件即可獲得準確、高效、自動更新的解決方案。