WeakMap:PHP 中隐藏的宝石
我们有一个项目,我们将其存储在一个数组中,其中包含类似的项目,如下所示:
/**
* @template T
*/
class Store
{
/**
* @param array<T> $items
*/
public function __construct(
protected array $items = []
) {
}
/**
* @param T $item
*/
public function addItem(mixed $item): void
{
$this->items[] = $item;
}
}
目前没有太花哨的东西。Store
应保留配置数量的项目,因此当我们添加新项目并且已达到限制时,我们会删除添加的第一个项目。我们总是对后添加的项目更感兴趣。
这样的事情可以很容易地实现:
/**
* @template T
*/
class Store
{
/**
* @param array<T> $items
*/
public function __construct(
protected array $items = [],
protected int $maxEntries = 200,
) {
}
/**
* @param T $item
*/
public function addItem(mixed $item): void
{
$this->items[] = $item;
if (count($this->items) > $this->maxEntries) {
array_shift($this->items);
}
}
}
现在开始变得复杂了。我们定义了一个名为 Node
的全新结构。Node
可以有子节点,其也就是 Node
。基本上我们是在创建一个树形结构。
事情是这样的:我们的 Node
也有一个项目数组,与我们之前在 Store
中使用的项目相同:
/**
* @template T
*/
class Node
{
/**
* @param array<Node> $children
* @param array<T> $items
*/
public function __construct(
public readonly string $id,
public array $children = [],
public array $items = [],
)
{
}
}
我们更新我们的需求,以便在向 store 中添加项目时,我们也会将该项目添加到节点。为了找到节点,我们使用 MagicNodeFinder
(我只是为了让这篇文章更清晰而制作的东西)。MagicNodeFinder
将只返回一个应该存储项目的节点,但它取决于它运行的时间,因此它将根据调用的时间返回不同的节点。
我们 Store
现在看起来像这样:
/**
* @template T
*/
class Store
{
/**
* @param array<T> $items
*/
public function __construct(
protected array $items = [],
protected int $maxEntries = 200,
) {
}
/**
* @param T $item
*/
public function addItem(mixed $item): void
{
$node = (new MagicNodeFinder())->execute();
$this->items[] = $item;
$node->items[] = $item;
if (count($this->items) > $this->maxEntries) {
array_shift($this->items);
}
}
}
我们需要更新项目删除代码,以便在达到项目限制时可以从节点中删除项目。
虽然这看起来很容易,但事实并非如此,因为不能信任 MagicNodeFinder
返回存储项目的正确节点。
我们无从知道项目存储在哪个节点中,即使我们知道节点,每次需要删除项目时,我们也必须遍历树,从而浪费计算时间
引入 WeakMap
在 PHP 8.0 中,WeakMap 被添入到该语言中。WeakMap 是将对象作为键保存,但不提高该对象的引用计数的映射。因此,每当存储在 WeakMap 中的对象被垃圾回收时(它不再存在,因为它超出了作用域或 unset),它也会立即从映射中删除。
这一切听起来都很困难;让我们来看一个例子。我们重构 Node
类如下:
/**
* @template T
*/
class Node
{
/**
* @param string $id
* @param array<Node> $children
* @param WeakMap<T, null> $items
*/
public function __construct(
public readonly string $id,
public array $children = [],
public WeakMap $items = new WeakMap(),
) {
}
}
让我们将一个项目添加到节点中:
$item = new Item('Some info here');
$node = new Node(42);
$node->items[$item] = null;
我们计算 WeapMap 中项目的数量:
$node->items->count(); // 1
如果我们通过 unset 垃圾回收项目:
unset($item);
WeakMap 的计数有什么变化?
$node->items->count(); // 0
太酷了!通过垃圾回收该项目,该项目已自动从该 map 中删除。
回到 Store
让我们看看如何在 Store
中使用这个 Weakmap
。在 Node
中使用 WeakMap
时,Store
的 addItem
方法如下:
/**
* @param T $item
*/
public function addItem(mixed $item): void
{
$node = (new MagicNodeFinder())->execute();
$this->items[] = $item;
$node->items[$item] = null;
if (count($this->items) > $this->maxEntries) {
array_shift($this->items);
}
}
通过移动 items 数组,由于作用域规则,当到达方法末尾时,对象将被 unset。因此,它也将从 WeakMap 中删除,这很好!完成了;这里不需要额外的更改!
结论
WeakMap 是我以为我永远不会使用 PHP功能,我错了!它使我们的代码性能更高,更易阅读。