剛才在寫一個將IP轉換為國家名稱的程式,使用了市面上提供的IP文字資料庫。資料庫官網建議使用mysql資料庫來記載資料,然而,如果mysql資料庫和PHP伺服器的主機不同,要使用網絡來拿取資料的話,相信會比本機查找慢。於是,最後我決定封裝一個本機查詢IP資料的程式。

初版是這樣的︰

//...
public function getCountry($ip){
	$fp = fopen($this->csvPath, "r");
	while (list($ipF, $ipT, $temp, $temp, $countryName) =fgetcsv($fp)){
		if ($ipF <= $this->ipToNum($ip) && $ipT >=$this->ipToNum($ip)){
			fclose($fp);
			return trim($countryName);
		}
	}
	fclose($fp);
	return FALSE;
}
//...

測試程式是查找一個IP的國家名稱,第一次運行結果使用了1.3s,當然不能接受,如果有一百個人同時觀看已經慢了不少,優化是必須的。IP資料庫有近八萬九千行,即while-loop要被執行八萬九千次,要做優化就當然要從while-loop著手。優化第一步,將重複使用的變數抽出來,即$this->ipToNum($ip)一句。

第一次改良後︰

//...
public function getCountry($ip){
	$fp = fopen($this->csvPath, "r");
	$ipNum = $this->ipToNum($ip);
	while (list($ipF, $ipT, $temp, $temp, $countryName) =fgetcsv($fp)){
		if ($ipF <= $ipNum && $ipT >= $ipNum){
			fclose($fp);
			return trim($countryName);
		}
	}
	fclose($fp);
	return FALSE;
}
//...

有著明顯的改善,足足快了0.4s,現在使用0.9s了,然而,還不是很令人滿意,再改良。奇怪的事情發生了,我本來以為用PHP內置的函數來解析CSV檔一定比自己寫的快,事實證明我錯了,原來自己寫explode比fgetcsv還要快,最後版是這樣的︰

//...
public function getCountry($ip){
	$fp = fopen($this->csvPath, "r");
	$ipNum = $this->ipToNum($ip);
	while (list($ipF, $ipT, $temp, $temp, $countryName) = explode(",", fgets($fp))){
		if ($ipF <= $ipNum && $ipT >= $ipNum){
			fclose($fp);
			return trim($countryName);
		}
	}
	fclose($fp);
	return FALSE;
}
//...

現在程式只需使用0.3s就可以從八萬九千多筆的資料找出答案,結果算是可以接受了。然而,一直都在語法上的優化,可不可以在邏輯上進行優化呢?那IP資料庫可是排序好的,那當然就不要用線性搜尋,來一個邏輯上的變革吧,實作二元搜尋。

//...
public function getCountry($ip){
	$strContent = file_get_contents($this->csvPath);
	$ipNum = $this->ipToNum($ip);
	$_DATA = explode("\n", $strContent);
 
	$min = 0;
	$max = count($_DATA);
	while ($min <= $max){
		$mid = floor(($min + $max) / 2);
		$d = explode(",", $_DATA[$mid]);
		if ($d[0] <= $ipNum && $d[1] >= $ipNum){
			return trim($d[4]);
		}else if ($d[0] > $ipNum){
			$max = $mid;
		}else if ($d[1] < $ipNum){
			$min = $mid;
		}
	}
	return FALSE;
}
//...

好了,現在只要0.07s就可以找出資料了,要更快的話,相信要使用hash table才做到,但要implement出來實在太麻煩。另一個方法是重新排序,把熱門的資料排上頭,那麼要找到合適的資料就更快了。

附上ip2country程式碼︰下載IP2Country

Tags : , , , , ,
Categories : PHP