Webアプリケーションでのビットフラグ
どうもお久しぶりです。
データ容量やメモリ容量を抑える為に、ビットフラグで情報を管理するのは、常套手段ですよね。
PHPではenumがないので(OrbitEnumは非推奨ですしね)、defineかハッシュを使います。
すなわち、
<?php define("STATUS_01", 0x00000001); ///< ステータス1フラグ define("STATUS_02", 0x00000002); ///< ステータス2フラグ define("STATUS_03", 0x00000004); ///< ステータス3フラグ define("STATUS_04", 0x00000008); ///< ステータス4フラグ $status = 0; // ステータス1フラグを設定 $status |= STATUS_01; // ステータス2フラグを解除 $status &= ~STATUS_02; // ステータス3フラグが立っていたら if (0 != (STATUS_03 & $status)) { echo("立った!(゚∀゚)\n"); }
か、
<?php /// ステータスビット列挙 $enSTATUS = array( "STATUS_01" => 0x00000001 ///< ステータス1フラグ ,"STATUS_02" => 0x00000002 ///< ステータス2フラグ ,"STATUS_03" => 0x00000004 ///< ステータス3フラグ ,"STATUS_04" => 0x00000008 ///< ステータス4フラグ ); $status = 0; // ステータス1フラグを設定 $status |= $enSTATUS['STATUS_01']; // ステータス2フラグを解除 $status &= ~$enSTATUS['STATUS_02']; // ステータス3フラグが立っていたら if (0 != ($enSTATUS['STATUS_03'] & $status)) { echo("立った!(゚∀゚)\n"); }
と言った具合に。
このビットフラグのステータスをDBに保存する場合、いちいちビットで分解して保存するのはコストがかかるので、そのまま保存します。
その後、ステータスの状態に関わらず、ビットフラグを設定したり解除したいケースは、ままあります。
削除フラグを立てる、だとか。
そうした場合、SELECTしてビットフラグを設定してUPDATEするのはコストがかかります。
そこで、SQLでビット演算をしてしまえば、コストは抑えられます。
具体的には、
mysql> -- フラグを設定 mysql> UPDATE user SET status = status | 0x00000001 WHERE id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> -- フラグを解除 mysql> UPDATE user SET status = status & ~0x00000002 WHERE id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> -- フラグを反転 mysql> UPDATE user SET status = status ^ 0x00000004 WHERE id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
このように行います。
うちのMySQL環境ではビット演算は64bitで計算されているようです。
mysql> SELECT ~0; +----------------------+ | ~0 | +----------------------+ | 18446744073709551615 | +----------------------+ 1 row in set (0.00 sec)
ちなみに、CONV関数やBIN関数、HEX関数を用いることで、基数を変更して出力することができてデバッグも便利です。
mysql> -- 8進数で出力 mysql> SELECT CONV(~0, 10, 8); +------------------------+ | CONV(~0, 10, 8) | +------------------------+ | 1777777777777777777777 | +------------------------+ 1 row in set (0.00 sec) mysql> -- 2進数で出力 mysql> SELECT BIN(~0); +------------------------------------------------------------------+ | BIN(~0) | +------------------------------------------------------------------+ | 1111111111111111111111111111111111111111111111111111111111111111 | +------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> -- 16進数で出力 mysql> SELECT HEX(~0); +------------------+ | HEX(~0) | +------------------+ | FFFFFFFFFFFFFFFF | +------------------+ 1 row in set (0.00 sec)
追記
抽出のことを忘れていました。
ビットフラグを用いて、抽出も行えます。
mysql> -- フラグが降りているユーザを抽出 mysql> SELECT * FROM user WHERE 0 = status & 0x00000004; +----+--------+--------+ | id | name | status | +----+--------+--------+ | 1 | test | 0 | | 2 | tanaka | 8 | +----+--------+--------+ 2 rows in set (0.01 sec) mysql> -- フラグが立っているユーザを抽出 mysql> SELECT * FROM user WHERE 0 <> status & 0x00000004; +----+---------+--------+ | id | name | status | +----+---------+--------+ | 3 | teraken | 14 | +----+---------+--------+ 1 row in set (0.00 sec)
当たり前ですが、複数のビットフラグを|結合しても、抽出できます。