PHP之位運算符


 

 

使用場景:

  1)斷奇數偶數

for ($i=0; $i < 10 ; $i++) { if($i & 1){ echo $i.PHP_EOL; } } //輸出所有奇數

 

  2)快速修改狀態

$status1 = 1; $status2 = 0; echo $status1 ^ 1;   // 0
echo $status2 ^ 1;  //1

 

  3)日常算法

<?php

//普通函數
function ip2int($ip)
{
    list($ip1,$ip2,$ip3,$ip4) = explode('.', $ip);
    return $ip1*pow(256, 3) + $ip2*pow(256, 2) + $ip3*256 + $ip4;
}

//位運算符
function ip2int2($ip)
{
    list($ip1,$ip2,$ip3,$ip4) = explode('.', $ip);
    return ( $ip1 * (1<<24) ) + ( $ip2 * (1<<16) ) + ( $ip3 * (1<<8) ) + ($ip4);
}

$ip = '192.168.0.192';

echo ip2int($ip).PHP_EOL;
echo ip2int2($ip).PHP_EOL;

注意點:有人問,為什么位運算不能直接這樣寫呢?

function ip2int2($ip)
{
    list($ip1,$ip2,$ip3,$ip4) = explode('.', $ip);
    return ($ip1<<24) + ($ip2<<16) + ($ip3<<8) + ($ip4);
}

  這樣不是更簡潔嗎?  但是你可以運行一下,會驚訝的發現並不一樣

為什么?  需要注意的數值型的上限  http://php.net/manual/zh/language.types.integer.php    可以通過常量 PHP_INT_MAX 來查看這個值的具體大小:   2147483647

 

應用
需求
這是一個設置消息提醒的功能。我們假設這是一個blog系統,用戶希望有人收藏自己的文章后,可以得到一個消息提醒。
這里有三種提醒方式:郵箱提醒、短信提醒、微信公眾號提醒,用戶可以通過開關按鈕控制某種提醒方式是開啟還是關閉。可以參考以下表格:

提醒操作	郵箱	短信	微信公眾號
文章被收藏	開關按鈕	開關按鈕	開關按鈕
被關注	開關按鈕	開關按鈕	開關按鈕
有人評論	開關按鈕	開關按鈕	開關按鈕
實現 1、數據庫設計,表名(remind): 字段 數據類型 默認值 collection(文章被收藏) int default(0) followed(被關注 ) int default(0) commented(有人評論) int default(0) 2、在Remind Model中設置執行操作的提醒方式類型: cosnt REMIND_NO = 0;//關閉所有的提醒 cosnt REMIND_EMAIL = 1;//郵箱提醒 2的0次方 轉化為二進制是:1 cosnt REMIND_SMS = 2;//短信提醒 2的1次方 轉化為二進制是:10 cosnt REMIND_WECHAT = 4;//微信公眾號提醒 2的2次方 轉化為二進制是:100 如果有新的提醒方式值應為4、8、16,依次類推,2的n次方。 仔細觀察二進制轉化后 email第一位是1,sms第二位是1,wechat第三位是1. 即email占領了第一位、sms占領了第二位、wechat占領了第三位 1,代表開啟提醒;0,代表關閉提醒 如果第一位是1,表示開啟郵箱提醒;如果第一位是0,表示關閉郵箱提醒 假設用戶希望有人評論他的文章時,收到來自系統的提醒: 只開啟郵箱提醒,那數據庫中commented字段對應的值應是:1;轉化為二進制:1 只開啟短信提醒,那數據庫中commented字段對應的值應是:2;轉化為二進制:10 只開啟微信公眾號提醒,那數據庫中commented字段對應的值應是:4;轉化為二進制:100 如果開啟郵箱提醒和短信提醒,那數據庫中commented字段對應的值應是:1+2=3;轉化為二進制:11 如果開啟郵箱提醒和微信公眾號提醒,那數據庫中commented字段對應的值應是:1+4=5;轉化為二進制:101 如果開啟短信提醒和微信公眾號提醒,那數據庫中commented字段對應的值應是:2+4=6;轉化為二進制:110 如果開啟所有提醒,那數據庫中commented字段對應的值應是:1+2+4=7;轉化為二進制:111 重點來了,前方高能,請注意啦~ 3、控制器中寫具體業務邏輯: 用戶希望有人關注他后,可以通過郵箱提醒他,所以用戶開啟了郵箱提醒: 前端傳值:['option'=>'followed','type'=>'email']; 后端接收到參數,從數據庫中找到當前用戶的這一條數據,得知followed字段值為0; email對應的值為1,進行&運算,判斷email提醒是否開啟 1&0=0,所以未開啟,那我們要開啟email提醒,此時followed=0+1=1; 后來用戶覺得短信提醒比較直接,所以又開啟了短信提醒: 前端傳值:['option'=>'followed','type'=>'sms']; 后端接收到參數,從數據庫中找到當前用戶的這一條數據,得知followed字段值為1; sms對應的值為2,進行&運算,判斷sms提醒是否開啟 2&1=0,所以未開啟,那我們要開啟sms提醒,此時followed=2+1=3; 后來該用戶越來越厲害,每天關注他的人特別多,每天都收到很多消息,所以他又想關閉短信提醒: 前端傳值:['option'=>'followed','type'=>'sms']; 后端接收到參數,從數據庫中找到當前用戶的這一條數據,得知followed字段值為3; sms對應的值為2,進行&運算,判斷sms提醒是否開啟 2&3=2,所以已開啟,那我們要關閉sms提醒,此時followed=3-2=1; 部分程序代碼: $option = $GET['option'];//提醒操作,由前端傳來的值 $type = $GET['type'];//提醒方式,由前端傳來的值 $remind = Remind::model()->find(1);//根據條件,在數據庫中找到的一條記錄// 如果是提醒方式一 if ($type == "sms") { if ($remind->$option & Remind::REMIND_SMS) { // true 代表已開啟sms提醒,此時應關閉sms提醒 $remind->$option -= Remind::REMIND_SMS; } else if (!($remind->$option & Remind::REMIND_SMS)) { // false 代表已關閉sms提醒,此時應開啟sms提醒 $remind->$option += Remind::REMIND_SMS; } }

  

有一個廣告表,我們要對廣告做顯示控制:

手動上下線。

只允許 VIP 查看。

可能的表結構如下:

CREATE TABLE `finger_ad` (
  `ad_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `ad_name` varchar(50) NOT NULL COMMENT '廣告名稱',
  `ad_image_url` varchar(255) NOT NULL COMMENT '廣告圖片',
  `ad_url` varchar(255) NOT NULL COMMENT '廣告圖片URL跳轉地址',
  `is_vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否僅限 VIP 顯示',
  `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT '顯示狀態:1顯示、0隱藏',
  PRIMARY KEY (`ad_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='廣告表';
假如后期,我們需求更改了。需要再增加幾種限制:

已登錄用戶

未登錄用戶

30 天內未登錄用戶

注冊 30 天的用戶

遇到這種限制條件的需求,開發同學是不是很傷腦筋?

可能很多開發第一反應就是在表結構增加這種新增的限制條件字段。一切看來似乎很美好。

的確,這樣添加字段是最快最容易的方式。也能完成我們的需求。

但是,這樣會引來如下毛病:

每次增加限制條件。我們都要增加字段。這種對數據庫的更動能少改就少改。畢竟,無限制的增加字段不可取。

假如廣告表數據量很大。大到增加一個字段需要幾分鍾的時候,這會給數據庫服務器造成讀寫壓力。

條件越多,SQL 條件語句就會越來越長。

那么,還有沒有更好的方式解決這些問題呢?

答案:有!

這就是我們今天要講的按位與運算符的高級技巧。

我們把上面的表結構改一下:

DROP TABLE IF EXISTS `finger_ad`;
CREATE TABLE `finger_ad` (
  `ad_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `ad_name` varchar(50) NOT NULL COMMENT '廣告名稱',
  `ad_image_url` varchar(255) NOT NULL COMMENT '廣告圖片',
  `ad_url` varchar(255) NOT NULL COMMENT '廣告圖片URL跳轉地址',
  `bit_condition` INT(11) UNSIGNED NOT NULL COMMENT '位運算條件:1-登錄可訪問、2-未登錄可訪問、4-30天注冊可訪問、8-30天未登錄可訪問、16-未消費可訪問、32-VIP可訪問',
  `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT '顯示狀態:1顯示、0隱藏',
  PRIMARY KEY (`ad_id`)
) ENGINE=InnoDB DEFAULT CHARSET UTF8 COMMENT='廣告表';
我們把所有的條件都去掉了。增加了一個字段:    bit_condition 。把所有的條件都組合到一個字段。

那我們此時該如何寫代碼呢?

比如,現在要添加如下限制條件的廣告:

只允許登錄用戶訪問或已注冊 30 天用戶或是 VIP 用戶才允許訪問該廣告。

那么,這個廣告的    bit_condition 該如何設置值呢?很簡單,把這幾個條件的位值直接相加。此時值為:37。

很多可能會很奇怪。設置為 37 ,我怎么知道是這幾個值的和呢?如果對 Linux 系統權限熟悉的同學就很容易理解這種做法。實際上,這里運用了按位與運算的特性:任意組合相加的值不會重復。

這個理解起來有一定難度。我三兩句也很難給你梳理明白。大家可以在網上深入挖掘一下這方面兒的知識。你只需要知道這一點特點即可。

那么,現在我們該如何寫 SQL 呢?

示例如下:

SELECT * FROM finger_ad WHERE display = 1 AND bit_condition & 3 = bit_condition
這條 SQL 語句當中的 3 對應的是當前用戶針對這么多條件得到的數值。如果    bit_condition位值是與 3 按位與與    bit_condition 結果相同,說明條件符合。

我們通過一個字段解決了所有條件的問題。着實得感謝按位與運算符的特性。同時也對    MySQL能支持位運算符感到開心。

那么,它有什么缺點呢?

想必有經驗的同學已經看出來了。這種寫法只能滿足包含關系。假如要實現同時滿足 3 個條件才能訪問就不行了。或者,一個滿足另外一個取反。

優點明顯,同樣缺點也很明顯。大家要根據實際情況來選用。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM