日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

PHP的SPL擴展庫(一)數(shù)據(jù)結構

 硬核項目經(jīng)理 2021-09-16

PHP的SPL擴展庫(一)數(shù)據(jù)結構

SPL 庫也叫做 PHP 標準庫,主要就是用于解決典型問題的一組接口或類的集合。這些典型問題包括什么呢?比如我們今天要講的數(shù)據(jù)結構,還有一些設計模式的實現(xiàn),就像我們之前講過的觀察者模式相關的接口在 SPL 庫中都有提供。話說回來,在 PHP 中,由于語言的特點,其實很多數(shù)據(jù)結構都和我們用 C 語言實現(xiàn)的略有不同,比如說鏈表,由于沒有結構的概念,所以我們一般會使用類來代表鏈表的結點。除了這個之外,要手寫鏈表還需要鏈表的增、刪、改、查等操作,而 SPL 庫中其實已經(jīng)幫我們提供了一個雙向鏈表的實現(xiàn),并且還可以在這個鏈表的基礎上直接實現(xiàn)棧和隊列的操作。

雙向鏈表

在 SPL 庫中,雙向鏈表只需要實例化一個 SplDoublyLinkedList 類就可以了,然后我們就可以對這個實例化之后的雙向鏈表對象進行各種操作。

$dll = new SplDoublyLinkedList();

var_dump($dll->isEmpty()); // bool(true)

$dll->push(200);
$dll->push(300);
$dll->unshift("五號");
$dll->add(2"六號");

var_dump($dll->isEmpty()); // bool(false)

var_dump($dll);
// object(SplDoublyLinkedList)#1 (2) {
//     ["flags":"SplDoublyLinkedList":private]=>
//     int(0)
//     ["dllist":"SplDoublyLinkedList":private]=>
//     array(4) {
//       [0]=>
//       string(6) "五號"
//       [1]=>
//       int(200)
//       [2]=>
//       string(6) "六號"
//       [3]=>
//       int(300)
//     }
//   }

從代碼中可以看出,push() 、 unshift() 、add() 方法都是向鏈表中添加數(shù)據(jù),而 isEmpty() 則用于判斷鏈表是否為空。直接打印顯示鏈表的內(nèi)容,可以看到鏈表的內(nèi)部是一個數(shù)組數(shù)據(jù)。

var_dump($dll->top()); // int(300)
var_dump($dll->bottom()); // string(6) "五號"

var_dump($dll->pop()); // int(300)
var_dump($dll->shift()); // string(6) "五號"

var_dump($dll->serialize()); // string(25) "i:0;:i:200;:s:6:"六號";"
var_dump($dll->count()); // int(2)

top() 和 bottom() 分別獲取的是鏈表的頂部和底部的數(shù)據(jù)。而 pop() 和 shift() 則是分別從底部和頂部彈出數(shù)據(jù)。后面我們會看到,根據(jù)設置的不同,它他們也會遵循使用棧還是隊列的方式來彈出數(shù)據(jù)。

serialize() 方法可以直接獲得序列化后的鏈表內(nèi)容。count() 方法就是返回鏈表內(nèi)元素的數(shù)量了。

$dll->offsetSet(1'修改成新六號');
var_dump($dll->offsetGet(1)); // string(18) "修改成新六號"
var_dump($dll->offsetExists(1)); // bool(true)
$dll->offsetUnset(1);
var_dump($dll->offsetExists(1)); // bool(false)

offset 相關的方法函數(shù)是根據(jù)偏移值來操作鏈表內(nèi)的數(shù)據(jù),其實就可以理解成是根據(jù)位置下標來操作數(shù)據(jù)。

在默認情況下,我們遍歷鏈表的話,是類似于隊列的形式進行輸出的,也就是先進先出的狀態(tài)。

for($i=1;$i<5;$i++){
    $dll->push($i);
}

var_dump($dll->getIteratorMode()); // int(0)
$dll->rewind();
while($dll->valid()){
    echo '============', PHP_EOL;
    echo 'key:', $dll->key(), PHP_EOL;
    echo '    current:', $dll->current(), PHP_EOL;
    $dll->next();
}
// ============
// key:0
//     current:200
// ============
// key:1
//     current:1
// ============
// key:2
//     current:2
// ============
// key:3
//     current:3
// ============
// key:4
//     current:4

通過 rewind() 將鏈表指針恢復到開頭,然后通過 valid() 方法判斷當前數(shù)據(jù)是否有效,next() 用于將鏈表指針移動到下一個,就可以進行數(shù)據(jù)的遍歷。我們通過設置鏈表的迭代模式,就可以改變鏈表的迭代輸出規(guī)則,比如我們需要類似棧類型的后進先出。

$dll->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
$dll->rewind();
while($dll->valid()){
    echo 'IT_MODE_LIFO============', PHP_EOL;
    echo 'key:', $dll->key(), PHP_EOL;
    echo '    current:', $dll->current(), PHP_EOL;
    $dll->next();
}
// IT_MODE_LIFO============
// key:4
//     current:4
// IT_MODE_LIFO============
// key:3
//     current:3
// IT_MODE_LIFO============
// key:2
//     current:2
// IT_MODE_LIFO============
// key:1
//     current:1
// IT_MODE_LIFO============
// key:0
//     current:200

另外,它還有一個比較好玩的迭代模式,就是直接邊遍歷,邊刪除。

$dll->setIteratorMode(SplDoublyLinkedList::IT_MODE_DELETE);
$dll->rewind();
while($dll->valid()){
    echo 'IT_MODE_DELETE============', PHP_EOL;
    echo 'key:', $dll->key(), PHP_EOL;
    echo '    current:', $dll->current(), PHP_EOL;
    $dll->next();
}
var_dump($dll);
// object(SplDoublyLinkedList)#1 (2) {
//     ["flags":"SplDoublyLinkedList":private]=>
//     int(1)
//     ["dllist":"SplDoublyLinkedList":private]=>
//     array(0) {
//     }
//   }

在使用 IT_MODE_DELETE 進行遍歷之后,鏈表里面的數(shù)據(jù)內(nèi)容也就變成空的了。默認情況下,這個 IteraotrMode 的內(nèi)容是 SplDoublyLinkedList::IT_MODE_KEEP | SplDoublyLinkedList::IT_MODE_FIFO 這個值,表示的就是數(shù)據(jù)保持原來的狀態(tài)并且使用先進先出的規(guī)則。

棧類 SplStack 其實和后面的隊列類 SplQueue 一樣,都是繼承自鏈表類的,也就是說它們其實就是相當于設置好了 IteratorMode 的鏈表對象。所以它們的方法函數(shù)其實都沒有什么太大的區(qū)別。

// 棧
$stack = new SplStack();
for($i=1;$i<5;$i++){
    $stack->push($i);
}
var_dump($stack->getIteratorMode()); // int(6)
var_dump($stack);
// object(SplStack)#2 (2) {
//     ["flags":"SplDoublyLinkedList":private]=>
//     int(6)
//     ["dllist":"SplDoublyLinkedList":private]=>
//     array(4) {
//       [0]=>
//       int(1)
//       [1]=>
//       int(2)
//       [2]=>
//       int(3)
//       [3]=>
//       int(4)
//     }
//   }

$stack->rewind();
while($stack->valid()){
    echo '============', PHP_EOL;
    echo 'key:', $stack->key(), PHP_EOL;
    echo '    current:', $stack->current(), PHP_EOL;
    $stack->next();
}
// ============
// key:3
//     current:4
// ============
// key:2
//     current:3
// ============
// key:1
//     current:2
// ============
// key:0
//     current:1

隊列

SplQueue 隊列相對于鏈表類和棧類來說,多了兩個方法。

// 隊列
$queue = new SplQueue();
for($i=1;$i<5;$i++){
    $queue->enqueue($i);
}
var_dump($queue->getIteratorMode()); // int(4)
var_dump($queue);
// object(SplQueue)#3 (2) {
//     ["flags":"SplDoublyLinkedList":private]=>
//     int(4)
//     ["dllist":"SplDoublyLinkedList":private]=>
//     array(4) {
//       [0]=>
//       int(1)
//       [1]=>
//       int(2)
//       [2]=>
//       int(3)
//       [3]=>
//       int(4)
//     }
//   }

$queue->rewind();
while($queue->valid()){
    echo '============', PHP_EOL;
    echo 'key:', $queue->key(), PHP_EOL;
    echo '    current:', $queue->current(), PHP_EOL;
    echo '    info:', $queue->dequeue(), PHP_EOL;
    $queue->next();
}
// ============
// key:0
//     current:1
//     info:1
// ============
// key:1
//     current:2
//     info:2
// ============
// key:2
//     current:3
//     info:3
// ============
// key:3
//     current:4
//     info:4

enqueue() 和 dequeue() 方法分別就是入隊和出隊的意思,其實就可以看成是 push() 和 shift() 的操作,底部添加頂部彈出。

堆棧堆棧,總會有人把堆和棧說成是一個東西,其實它們可是完全不同的兩個數(shù)據(jù)結構哦。棧是線性的邏輯結構,而堆則一般是樹形的邏輯結構,當然,它們的存儲結構都是可以用相同的鏈表或順序表來表示的。在堆中,有大頂堆和小頂堆的概念,SPL 也為我們分別提供了這兩種實現(xiàn)。(不了解堆的同學可以自行查閱相關資料)

大頂堆

$maxHeap = new SplMaxHeap();
for($i=1;$i<5;$i++){
    $maxHeap->insert($i);
}

var_dump($maxHeap);
// object(SplMaxHeap)#4 (3) {
//     ["flags":"SplHeap":private]=>
//     int(0)
//     ["isCorrupted":"SplHeap":private]=>
//     bool(false)
//     ["heap":"SplHeap":private]=>
//     array(4) {
//       [0]=>
//       int(4)
//       [1]=>
//       int(3)
//       [2]=>
//       int(2)
//       [3]=>
//       int(1)
//     }
//   }

var_dump($maxHeap->count()); // int(4)
var_dump($maxHeap->top()); // int(4)

var_dump($maxHeap->extract()); // int(4)

var_dump($maxHeap->count()); // int(3)
var_dump($maxHeap->top()); // int(3)

var_dump($maxHeap);
// object(SplMaxHeap)#4 (3) {
//     ["flags":"SplHeap":private]=>
//     int(0)
//     ["isCorrupted":"SplHeap":private]=>
//     bool(false)
//     ["heap":"SplHeap":private]=>
//     array(3) {
//       [0]=>
//       int(3)
//       [1]=>
//       int(1)
//       [2]=>
//       int(2)
//     }
//   }

$maxHeap->rewind();
while($maxHeap->valid()){
    echo '============', PHP_EOL;
    echo 'key:', $maxHeap->key(), PHP_EOL;
    echo '    current:', $maxHeap->current(), PHP_EOL;
    $maxHeap->next();
}
// ============
// key:2
//     current:3
// ============
// key:1
//     current:2
// ============
// key:0
//     current:1


var_dump($maxHeap->isCorrupted()); // bool(false)
$maxHeap->recoverFromCorruption(); 

SplMaxHeap 類就是用于生成大頂堆實例的類模板。它通過 insert() 方法插入數(shù)據(jù),通過 extract() 方法抽取數(shù)據(jù),同樣也包括 count() 和 top() 這類的常用方法函數(shù),以及遍歷相關的那些方法函數(shù)。

另外,堆的操作中還包括兩個方法函數(shù),分別用于判斷堆是否處于損壞狀態(tài) isCorrupted() 以及從損壞狀態(tài)恢復 recoverFromCorruption() 相關的操作函數(shù)。

小頂堆

小頂堆的內(nèi)容和大頂堆就完全一樣了,只是它的內(nèi)部結構不同,大頂堆是父結點總是最大的,而小頂堆就是反過來父結點總是最小的數(shù)據(jù)。

$minHeap = new SplMinHeap();
for($i=1;$i<5;$i++){
    $minHeap->insert($i);
}
var_dump($minHeap);
// object(SplMinHeap)#5 (3) {
//     ["flags":"SplHeap":private]=>
//     int(0)
//     ["isCorrupted":"SplHeap":private]=>
//     bool(false)
//     ["heap":"SplHeap":private]=>
//     array(4) {
//       [0]=>
//       int(1)
//       [1]=>
//       int(2)
//       [2]=>
//       int(3)
//       [3]=>
//       int(4)
//     }
//   }

var_dump($minHeap->top()); // int(1)

大頂堆實現(xiàn)的優(yōu)先隊列

除了大頂堆和小頂堆的普通操作之外,SPL 庫中還有一個通過大頂堆來實現(xiàn)的優(yōu)先隊列的類模板。

$pQueue = new SplPriorityQueue();
for($i=1;$i<5;$i++){
    $pQueue->insert($i, random_int(1,10));
}
var_dump($pQueue);
// object(SplPriorityQueue)#6 (3) {
//     ["flags":"SplPriorityQueue":private]=>
//     int(1)
//     ["isCorrupted":"SplPriorityQueue":private]=>
//     bool(false)
//     ["heap":"SplPriorityQueue":private]=>
//     array(4) {
//       [0]=>
//       array(2) {
//         ["data"]=>
//         int(3)
//         ["priority"]=>
//         int(10)
//       }
//       [1]=>
//       array(2) {
//         ["data"]=>
//         int(4)
//         ["priority"]=>
//         int(7)
//       }
//       [2]=>
//       array(2) {
//         ["data"]=>
//         int(1)
//         ["priority"]=>
//         int(3)
//       }
//       [3]=>
//       array(2) {
//         ["data"]=>
//         int(2)
//         ["priority"]=>
//         int(2)
//       }
//     }
//   }

while($pQueue->valid()){
    var_dump($pQueue->extract());
}
// int(3)
// int(4)
// int(1)
// int(2)

它的操作方法函數(shù)和堆的操作方法函數(shù)都是一樣的,只是 insert() 方法中多了一個參數(shù)可以設置數(shù)據(jù)的優(yōu)先級。通過設置不同的優(yōu)先級我們可以看到數(shù)據(jù)以及遍歷輸出的結果都會發(fā)生變化,順序都是以優(yōu)先級來確定的。

固定數(shù)組

什么叫固定數(shù)組呢?在 PHP 中,數(shù)組這個結構非常強大,它即可以是普通下標類型的數(shù)組,也可以 HashMap鍵值對 形式的數(shù)組,它的長度也是不受限制的,只要內(nèi)存夠就可以靈活地處理數(shù)組的長度。不過在靜態(tài)語言中,特別是我們學習過的 C 語言中,數(shù)組都是固定長度的,也就是說,數(shù)組的內(nèi)存大小是在數(shù)組初始化的時候就確定好的,如果超出了數(shù)組長度的操作發(fā)生,就會產(chǎn)生越界問題。還是通過一個例子來看吧。

// 數(shù)組
$norArr = [];
$norArr[1] = 'b';
$norArr[4] = 'f';

var_dump($norArr);
// array(2) {
//     [1]=>
//     string(1) "b"
//     [4]=>
//     string(1) "f"
//   }

$fArr = new SplFixedArray(5);
$fArr[1] = 'b';
$fArr[4] = 'f';

var_dump($fArr);
// object(SplFixedArray)#7 (5) {
//     [0]=>
//     NULL
//     [1]=>
//     string(1) "b"
//     [2]=>
//     NULL
//     [3]=>
//     NULL
//     [4]=>
//     string(1) "f"
//   }

norArr 是普通的 PHP 數(shù)組,我們添加了兩個數(shù)據(jù)之后在這個數(shù)組中只有兩個元素。下面的 SplFixedArray 類實例化出來的 fArr 則是固定數(shù)組。它在實例化的時候必須傳遞一個構造參數(shù)來指定數(shù)組長度。可以看到,fArr 輸出的結果是固定有 5 個數(shù)據(jù)的,并且我們沒有賦值的數(shù)據(jù)都會給一個默認的 NULL 值。是不是和 C 的數(shù)組一樣一樣的。

當然,固定數(shù)組就會有數(shù)組下標越界的問題了。

$fArr[6] = 'h'// Fatal error: Uncaught RuntimeException: Index invalid or out of range 

不過我們可以手動地修改數(shù)組的大小來改變數(shù)據(jù)的長度。

$fArr->setSize(7);
$fArr[6] = 'h';
var_dump($fArr->getSize()); // int(7)

現(xiàn)在,數(shù)組的長度就是 7 了,可以存放 7 條數(shù)據(jù)。它也可以直接從一個普通數(shù)組轉換過來,不過需要注意的是,轉換數(shù)組必須是數(shù)字下標類型的數(shù)組,字符串鍵的 HashMap 數(shù)組是不可以的哦。

$fArr2 = SplFixedArray::fromArray(range(1,3));
var_dump($fArr2);
// object(SplFixedArray)#8 (3) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//   }

// $fArr3 = SplFixedArray::fromArray(['new'=>1, 'old'=>2]);
// var_dump($fArr3);
// PHP Fatal error:  Uncaught InvalidArgumentException: array must contain only positive integer keys

剩下的就是和其它數(shù)據(jù)結構一樣的一些操作方法函數(shù)了。

var_dump($fArr->count()); // int(7)

var_dump($fArr->offsetGet(2)); // NULL
$fArr->offsetSet(2'new'); 
var_dump($fArr->offsetGet(2)); // string(3) "new"
var_dump($fArr->offsetExists(2)); // bool(true)
$fArr->offsetUnset(2);
var_dump($fArr->offsetExists(2)); // bool(false)


$fArr->rewind();
while($fArr->valid()){
    echo '============', PHP_EOL;
    echo 'key:', $fArr->key(), PHP_EOL;
    echo '    current:', $fArr->current(), PHP_EOL;
    $fArr->next();
}
// ============
// key:0
//     current:
// ============
// key:1
//     current:b
// ============
// key:2
//     current:
// ============
// key:3
//     current:
// ============
// key:4
//     current:f
// ============
// key:5
//     current:
// ============
// key:6
//     current:h

既然它是一種數(shù)組對象,那么迭代其實不用這么麻煩的,我們直接通過 for 和 foreach 就可以了。它和其它的數(shù)組結構一樣,都實現(xiàn)了 Iterator 和 Countable 這兩個接口,都是可以通過 for 和 foreach 來進行遍歷的。

foreach($fArr as $f){
    var_dump($f);
}
for($i=0;$i<count($fArr);$i++){
    var_dump($fArr[$i]);
}

對象數(shù)據(jù)映射

最后一種數(shù)據(jù)結構,對象數(shù)據(jù)映射。這是個什么鬼?最簡單直接的理解其實就是把一個對象當成是 【鍵】,然后以這些鍵形成一個數(shù)組結構。

$os = new SplObjectStorage();

$o1 = new stdClass;
$o2 = new stdClass;
$o3 = new stdClass;
$o4 = new stdClass;
$o5 = new stdClass;

$os->attach($o1);
$os->attach($o2);
$os->attach($o3);
$os->attach($o4, 'd4');
$os->attach($o5, 'd5');

var_dump($os);
// object(SplObjectStorage)#9 (1) {
//     ["storage":"SplObjectStorage":private]=>
//     array(5) {
//       ["00000000736a0aba000000002f97228d"]=>
//       array(2) {
//         ["obj"]=>
//         object(stdClass)#10 (0) {
//         }
//         ["inf"]=>
//         NULL
//       }
//       ["00000000736a0abb000000002f97228d"]=>
//       array(2) {
//         ["obj"]=>
//         object(stdClass)#11 (0) {
//         }
//         ["inf"]=>
//         NULL
//       }
//       ["00000000736a0abc000000002f97228d"]=>
//       array(2) {
//         ["obj"]=>
//         object(stdClass)#12 (0) {
//         }
//         ["inf"]=>
//         NULL
//       }
//       ["00000000736a0abd000000002f97228d"]=>
//       array(2) {
//         ["obj"]=>
//         object(stdClass)#13 (0) {
//         }
//         ["inf"]=>
//         string(2) "d4"
//       }
//       ["00000000736a0abe000000002f97228d"]=>
//       array(2) {
//         ["obj"]=>
//         object(stdClass)#14 (0) {
//         }
//         ["inf"]=>
//         string(2) "d5"
//       }
//     }
//   }

是不是有點意思,attach() 就可以向這個 SplObjectStorage 對象存儲映射類中添加數(shù)據(jù)。它的第二個參數(shù)可以指定一個數(shù)據(jù)內(nèi)容,其實就可以看作是普通數(shù)組中的 值 。

var_dump($os->count()); // int(5)
$os->detach($o2);
var_dump($os->count()); // int(4)

var_dump($os->contains($o2)); // bool(false)
var_dump($os->contains($o3)); // bool(true)

var_dump($os->getHash($o4)); // string(32) "000000003e67a2330000000040e598c9"

var_dump($os->offsetGet($o4)); // string(2) "d4"
$os->offsetSet($o4, 'new d4'); 
var_dump($os->offsetGet($o4)); // string(6) "new d4"
var_dump($os->offsetExists($o4)); // bool(true)
$os->offsetUnset($o4);
var_dump($os->offsetExists($o4)); // bool(false)

$os->rewind();
$os[$o1] = 'new d1';
while($os->valid()){
    echo '============', PHP_EOL;
    echo 'key:', $os->key(), PHP_EOL;
    if($os->getInfo() === NULL){
        $os->setInfo('new iter info');
    }
    echo '    info:', $os->getInfo(), PHP_EOL;
    echo '    current:', PHP_EOL;
    var_dump($os->current());
    
    $os->next();
}
// ============
// key:0
//     info:new d1
//     current:
// object(stdClass)#10 (0) {
// }
// ============
// key:1
//     info:new iter info
//     current:
// object(stdClass)#12 (0) {
// }
// ============
// key:2
//     info:d5
//     current:
// object(stdClass)#14 (0) {
// }

其它的遍歷查詢操作都是和其它數(shù)據(jù)結構的操作類似的,這里就不多說了。其中比較特別的是 detach() 方法是刪除數(shù)據(jù)的,getHash() 則是獲取這個對象在存儲集合中的 Hash 值的,這個值也可以看做是這個對象在這個對象映射集合中的下標,我們其它的針對對象的操作判斷其實是都是在內(nèi)部轉換成這個數(shù)組下標來進行操作的。

總結

其實這一圈學習下來,突然發(fā)現(xiàn)有了 SPL 的這幾個數(shù)據(jù)結構之后,我們在 PHP 下面還真不太需要關心什么數(shù)據(jù)結構方面的實現(xiàn)了,直接通用點就上個雙向鏈表就完了,簡單的就只是寫算法了。好吧,學習還是要扎實點,數(shù)據(jù)結構和算法真正要學習的其實是它內(nèi)部的思想和邏輯。當然,既然已經(jīng)提供了,那么我們平常的業(yè)務開發(fā)中還是更建議直接使用 SPL 的這些數(shù)據(jù)結構來處理!

測試代碼:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/01/source/3.PHP的SPL擴展庫(一)數(shù)據(jù)結構.php

參考文檔:

https://www./manual/zh/spl.datastructures.php

    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多