Dart 弱引用进阶

news2024/10/5 17:26:19

前言

村里的老人说:“真正的强者,都是扮猪吃老虎。

日常开发中经常需要用到弱引用,Dart 语言里也有提供弱引用的接口 ```WeakReference```,我们可以基于它开发更强大的复杂结构。

在前面的文章中,我们用到了一个以弱引用对象为元素的集合 ```WeakSet```,今天我们就来讲一下它是如何实现的。

Collections

常用的 collections 有 Set、List 和 Map,下面我将为大家介绍其对应的弱引用版本该如何快速实现。

WeakSet

弱集合也是集合,所以它应该实现一个集合接口 ```Set``` 的所有功能。

直接上代码:

class WeakSet<E extends Object> implements Set<E> {
  WeakSet() : _inner = {};

  final Set<WeakReference<E>> _inner;

  @override
  void clear() => _inner.clear();

  @override
  Set<E> toSet() {
    Set<E> entries = {};
    E? item;
    for (WeakReference<E> wr in _inner) {
      item = wr.target;
      if (item != null) {
        entries.add(item);
      }
    }
    return entries;
  }

  @override
  List<E> toList({bool growable = true}) => toSet().toList(growable: growable);

  @override
  String toString() => toSet().toString();

  @override
  Iterator<E> get iterator => toSet().iterator;

  @override
  int get length => toSet().length;

  @override
  bool get isEmpty => toSet().isEmpty;

  @override
  bool get isNotEmpty => toSet().isNotEmpty;

  @override
  bool contains(Object? value) => toSet().contains(value);

  @override
  bool containsAll(Iterable<Object?> other) => toSet().containsAll(other);

  @override
  E elementAt(int index) => toSet().elementAt(index);

  @override
  bool add(E value) => !contains(value) && _inner.add(WeakReference(value));

  @override
  bool remove(Object? value) {
    for (var wr in _inner) {
      if (wr.target == value) {
        return _inner.remove(wr);
      }
    }
    return false;
  }

  @override
  void forEach(void Function(E element) action) => toSet().forEach(action);

  // 从略...

}

首先,我们创建一个内置的集合 _inner,其元素是指向元素类型 E 的弱引用;
接下来实现 toSet() 函数,将 _inner 中所有实际指向的对象提取出来,构建一个新的集合返回;
然后其余的 Set 函数接口都可以基于这个 toSet() 来实现。

同样的思路,我们还可以继续实现列表与映射的弱引用版本。

WeakList

先实现最简单的那些接口函数:

class WeakList<E extends Object> implements List<E> {
  WeakList() : _inner = [];

  final List<WeakReference<E>> _inner;

  @override
  void clear() => _inner.clear();

  @override
  List<E> toList({bool growable = true}) {
    List<E> entries = [];
    E? item;
    for (WeakReference<E> wr in _inner) {
      item = wr.target;
      if (item != null) {
        entries.add(item);
      }
    }
    return entries;
  }

  @override
  Set<E> toSet() => toList().toSet();

  @override
  String toString() => toList().toString();

  @override
  Iterator<E> get iterator => toList().iterator;

  @override
  int get length => toList().length;

  @override
  bool get isEmpty => toList().isEmpty;

  @override
  bool get isNotEmpty => toList().isNotEmpty;

  @override
  E get first => toList().first;

  @override
  set first(E item) => _inner.first = WeakReference(item);

  @override
  E get last => toList().last;

  @override
  set last(E item) => _inner.last = WeakReference(item);

  @override
  E get single => toList().single;

  @override
  List<E> operator +(List<E> other) => toList() + other;

  @override
  E operator [](int index) => toList()[index];

  @override
  void add(E value) => _inner.add(WeakReference(value));

  @override
  void addAll(Iterable<E> iterable) {
    for (var item in iterable) {
      add(item);
    }
  }

  @override
  bool contains(Object? element) => toList().contains(element);

  @override
  E elementAt(int index) => toList().elementAt(index);

  @override
  void forEach(void Function(E element) action) => toList().forEach(action);

  @override
  int indexOf(E element, [int start = 0]) => toList().indexOf(element, start);

  @override
  int lastIndexOf(E element, [int? start]) => toList().lastIndexOf(element, start);

  @override
  String join([String separator = ""]) => toList().join(separator);

  @override
  bool remove(Object? value) {
    /// Removes the first occurrence of [value] from this list.
    for (var wr in _inner) {
      if (wr.target == value) {
        return _inner.remove(wr);
      }
    }
    return false;
  }

  // 从略...

}

跟上面的 WeakSet 类似,这里也是先创建一个内部的列表 _inner,其元素是指向实际对象的弱引用;接下来实现 toList() 函数,将内部列表中的实际指向对象提取出来构建新的列表;
然后其余关于 List 的接口函数基本都可以基于这个 toList() 函数来实现。

但是也有不同之处。由于 Set 是无序的,所以即使其中的弱引用所指向的对象已销毁,也不会影响集合运算。
而 List 是有序的,所以一些跟序号相关的操作则需要忽略那些对象已销毁的弱引用元素。
这里我采用的犯法是增加一个 purge() 函数,用于清除那些“无用”的元素,然后再基于它去实现那些和序号相关的接口函数。

class WeakList<E extends Object> implements List<E> {
  WeakList() : _inner = [];

  final List<WeakReference<E>> _inner;

  void purge() => _inner.removeWhere((wr) => wr.target == null);

  @override
  set length(int size) {
    purge();
    _inner.length = size;
  }

  @override
  void operator []=(int index, E value) {
    purge();
    _inner[index] = WeakReference(value);
  }

  @override
  void insert(int index, E element) {
    purge();
    _inner.insert(index, WeakReference(element));
  }

  @override
  void insertAll(int index, Iterable<E> iterable) {
    purge();
    for (var item in iterable) {
      _inner.insert(index, WeakReference(item));
      index += 1;
    }
  }

  @override
  E removeAt(int index) {
    purge();
    var wr = _inner.removeAt(index);
    return wr.target;
  }

  @override
  E removeLast() {
    purge();
    var wr = _inner.removeLast();
    return wr.target;
  }

  // 从略...

}

WeakValueMap

Talk is cheap, show you the codes!

class WeakValueMap<K, V> implements Map<K, V> {
  WeakValueMap() : _inner = {};

  final Map<K, WeakReference<dynamic>?> _inner;

  void purge() => _inner.removeWhere((key, wr) => wr?.target == null);

  @override
  void clear() => _inner.clear();

  Map<K, V> toMap() {
    // remove empty entries
    purge();
    // convert entries
    return _inner.map((key, wr) => MapEntry(key, wr?.target));
  }

  @override
  String toString() => toMap().toString();

  @override
  bool containsValue(Object? value) => toMap().containsValue(value);

  @override
  bool containsKey(Object? key) => _inner[key]?.target != null;

  @override
  V? operator [](Object? key) => _inner[key]?.target;

  @override
  void operator []=(K key, V value) =>
      _inner[key] = value == null ? null : WeakReference(value);

  @override
  Iterable<MapEntry<K, V>> get entries => toMap().entries;

  @override
  void addAll(Map<K, V> other) => other.forEach((key, value) {
    this[key] = value;
  });

  @override
  V? remove(Object? key) => _inner.remove(key)?.target;

  @override
  void forEach(void Function(K key, V value) action) => _inner.forEach((key, wr) {
    V val = wr?.target;
    if (val != null) {
      action(key, val);
    }
  });

  @override
  Iterable<K> get keys => toMap().keys;

  @override
  Iterable<V> get values => toMap().values;

  @override
  int get length => toMap().length;

  @override
  bool get isEmpty => toMap().isEmpty;

  @override
  bool get isNotEmpty => toMap().isNotEmpty;

  // 从略...

}

课余练习

  • WeakKeyMap 如何实现?

提示:可以先实现一个用于包裹 key 的类,以 key 对应的对象为参数初始化(内部用一个弱引用指向它),同时将 key 对象的 hash 值等信息保存起来,以便后面 key 对象消失时仍然可以进行相应的运算。

应用示例

以上类的使用很简单,根普通的 Set、List、Map 几乎没有差别:

// WeakSet
Set<Observer> listenerSet = WeakSet();
listenerSet.add(obs1);
listenerSet.add(obs2);


// WeakList
List<Observer> listenerList = WeakList();
listenerList.add(obs1);
listenerList.add(obs2);


// WeakMap
Map<String, Observer> listenerMap = WeakValueMap();
listenerMap['Albert'] = obs1;
listenerMap['Ben'] = obs2;

代码引用

由于我已将这部分代码提交到了 pub.dev,所以在实际应用中,你只需要在项目工程文件 ```pubspec.yaml``` 中添加:

dependencies:
    object_key: ^0.1.1

然后在需要使用的 dart 文件头引入即可:

import 'package:object_key/object_key.dart';

全部源码

GitHub 地址:

https://github.com/moky/ObjectKey/tree/main/object_key/lib/src

结语

弱引用可以有效避免对象循环引用等原因导致的内存泄漏问题。
在某些实践中,还可以实现类似“自动退出”的效果,比如前面介绍的观察者模式的实战中,合理使用基于弱引用的集合,可以达到当观察者销毁时即自动注销的效果,十分方便!

如有其他问题,可以下载登录 Tarsier 与我交流(默认通讯录里找 Albert Moky / 章北海)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1831805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

现代易货:创新交易模式引领物品交换新潮流

在繁华的现代经济浪潮中&#xff0c;物品交换的文化逐渐崭露头角&#xff0c;引领了一种新颖的交易潮流——现代易货交易模式。这种模式不仅是对古老“以物易物”交易的现代化诠释&#xff0c;更是对物品价值多元化和交换方式创新的深入探索。那么&#xff0c;现代易货交易究竟…

螺丝工厂vtk ThreadFactory(1)

螺丝工厂vtkThreadFactory (1) 缘起 几年前的探索在Python里应用Openscad实现3D建模之3D螺纹建模初探3 新的参考: generating nice threads in openscadvtkRotationalExtrusionFilter 辅助AI: coze 笔记&#x1f4d2;: openscad 代码分析 // 半径缩放函数&#xff0c;用…

国货骄傲精亿内存条颠覆游戏战场,推出超强DDR5 7200玄武系列电竞内存

随着科技的迅猛发展,对高性能电脑的需求不断增长,特别是在电竞领域。认识到这一点,国货知名品牌精亿(JINGYI)推出了其全新一代DDR5 7200 RGB电竞内存条,并命名系列为象征中国上古四大神兽的玄武-系列。这款产品凭借其卓越性能和令人印象深刻的海力士A-DIE颗粒配置,正在迅速成为…

环信beta版鸿蒙IM SDK发布!深度适配HarmonyOS NEXT系统

环信beta版鸿蒙IM SDK已正式发布&#xff01;欢迎有需求开发者体验集成&#xff01; 版本亮点 提供原生鸿蒙 SDK&#xff0c;支持原生 ArkTS 语言&#xff0c;全面拥抱鸿蒙生态提供鸿蒙系统上单聊、群聊、会话等能力和服务覆盖消息管理、用户属性、群租管理、离线推送.多设备…

【C++】认识STL

【C】认识STL STL的概念STL的版本STL的六大组件STL的三个境界STL的缺陷 STL的概念 SLT(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个保罗数据结构与算法的软件框架。 STL的版本 原…

深入学习html的步骤

推荐的学习步骤&#xff1a; 1. 深入了解HTML基础标签 列表 HTML提供有序列表(<ol>)和无序列表(<ul>)。 <h2>无序列表</h2> <ul><li>项目一</li><li>项目二</li><li>项目三</li> </ul><h2>…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 火星字符串(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

函数的一点点习题

1、利用递归计算0-n的和 #include <stdio.h> #include <string.h> #include <stdlib.h> int rec(int n) {if(n0)return 0;elsereturn nrec(n-1); } int main(int argc, const char *argv[]) {int n0;printf("please enter n:");scanf("%d&quo…

Ollama:本地部署大模型 + LobeChat:聊天界面 = 自己的ChatGPT

本地部署大模型 在本地部署大模型有多种方式&#xff0c;其中Ollama方式是最简单的&#xff0c;但是其也有一定的局限性&#xff0c;比如大模型没有其支持的GGUF二进制格式&#xff0c;就无法使用Ollama方式部署。 GGUF旨在实现快速加载和保存大语言模型&#xff0c;并易于阅读…

服务器新硬盘分区、格式化和挂载

文章目录 参考文献查看了一下起点现状分区(base) ~ sudo parted /dev/sdcmklabel gpt&#xff08;设置分区类型&#xff09;增加分区 格式化需要先退出quit&#xff08;可以&#xff09;(base) / sudo mkfs.xfs /dev/sdc/sdc1&#xff08;失败&#xff09;sudo mkfs.xfs /dev/s…

走近科学之《netty 的秘密》

Approaching science《the secret of netty》 IO 相关概念、五种 IO 模型、BIO NIO AIO 特点及区别、NIO 设计原理及核心组件、netty 简介及应用场景、netty 线程模型&#xff08;Reactor 线程模型&#xff09;、netty 设计原理及核心组件、netty 常用技巧实现&#xff08;心跳…

数据结构进阶——AVL树

数据结构进阶——AVL树 0. 前言1. AVL树的概念2. AVL树节点&#xff0c;和树的定义3. AVL树的插入4. AVL树的旋转5. AVL树的验证6. AVL树的删除&#xff08;了解&#xff09;7. AVL树实现完整代码8. AVL树的性能 0. 前言 学习本章&#xff0c;需要大家先掌握搜索二叉树&#xf…

04 远程访问及控制

目录 4.1 SSH远程管理 4.1.1 配置OpenSSH服务器 1. 服务监听选项 2. 用户登录控制 3. 登录验证方式 4.1.2 使用SSH客户端程序 1. 命令程序ssh、scp、sftp 1. ssh远程登录 2. scp远程复制 3. sftp安全FTP 2. 图形工具Xshell 4.1.3 构建密钥对验证的SSH体系 1. 在客户端创建密钥…

Hive笔记-3

3.2.2 查看表 1) 展示所有表 (1) 语法: 语法: SHOW TABLES [IN database_name] LIKE [identifier_with_wildcards]; In database_name 写的是查哪个数据库,一般不写默认是当前数据库 Like 后面跟通配符表达式 (2) 案例: 查看在 db_hive1 数据库里有没有以 stu 开头的表 …

实用软件下载:UltraEditUEStudio最新安装包及详细安装教程

​UEStudio简介&#xff1a;UEStudio建立在上文本编辑器UltraEdit的功能基础上&#xff0c;并为团队和开发人员提供了其他功能&#xff0c;例如深度Git集成&#xff0c;您可以直接在UEStudio中克隆&#xff0c;签出&#xff0c;更新&#xff0c;提交&#xff0c;推入/拉入等操作…

FPGA - 滤波器 - IIR滤波器设计

一&#xff0c;IIR滤波器 在FPGA - 滤波器 - FIR滤波器设计中可知&#xff0c;数字滤波器是一个时域离散系统。任何一个时域离散系统都可以用一个N阶差分方程来表示&#xff0c;即&#xff1a; 式中&#xff0c;x(n)和y(n)分别是系统的输入序列和输出序列&#xff1b;aj和bi均为…

Sermant标签路由能力在同城双活场景的应用

作者&#xff1a;聂子雄 华为云高级软件工程师 摘要&#xff1a;目前应用上云已成为趋势&#xff0c;用户也对应用在云上的高可靠方案有更高追求&#xff0c;目前同城双活场景作为应用高可靠方案中的一种常见实践方案&#xff0c;对微服务流量提出了数据中心亲和性的要求&…

Java_JDK下载与环境变量配置

目录 一、JDK下载安装 二、安装后配置环境变量 三、在编辑器里使用JDK 一、JDK下载安装 JDK 是Java开发工具包&#xff0c;它提供了用于开发和运行Java程序所需的工具和库。JDK包括Java编译器、Java虚拟机、Java标准库等。在IDEA中使用Java语言编写代码时&#xff0c;需要安…

海康视觉算法平台VisionMaster 4.3.0 C# 二次开发01 加载方案并获取结果

前言 第一次使用海康视觉算法平台VisionMaster 4.3.0&#xff0c;项目中要使用这个平台进行视觉处理并获取结果。 运行效果 开发环境 C#&#xff0c; WPF&#xff0c; vs2022, 海康视觉算法平台VisionMaster 4.3.0 基本概念 上图这些.sol为后缀的是vm的方案文件。 打开方案文…

[element-ui]el-select多选选择器选中其中一个选项,不可删除

背景&#xff1a; 产品真的很多奇奇怪怪的需求&#xff0c;一边吐槽一边实现。 前提&#xff1a;选择器作为表格的筛选项&#xff0c;提供三个选项值。 要求&#xff1a;默认选中其中一个值&#xff0c;这个值不可删除。 如图&#xff1a; 小声吐槽&#xff1a;搞这些有什么…