Dart 弱引用进阶

前言

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

日常开发中经常需要用到弱引用,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 / 章北海)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无需配置MySQL,Navicat也有在线版了?

前言 随着互联网技术的飞速发展&#xff0c;远程办公和在线协作成为了新的趋势。为了满足这一需求&#xff0c;TitanIDE模板市场近日上线了Navicat模板&#xff0c;使得数据库管理变得更加便捷、高效。现在&#xff0c;用户只需在浏览器打开TitanIDE&#xff0c;即可轻松使用N…

2024年上网行为审计软件排名,推荐这五款上网行为管理软件

上网行为审计软件是企业IT管理中不可或缺的一部分&#xff0c;它们旨在帮助组织监控、管理、审计员工的互联网使用情况&#xff0c;确保网络资源的合理利用&#xff0c;提高工作效率&#xff0c;同时维护企业信息安全。下面将介绍几款市场上知名的上网行为审计软件&#xff0c;…

通用大模型VS垂直大模型 难兄难弟?

在互联网&#x1f30f;背景下的快速发展与人工智能AI的崛起是21世纪科技进步的重要标志&#x1f3c5;&#xff0c; 近年来&#xff0c;随着计算能力的显著提升&#x1f680;、海量数据的积累以及算法创新&#xff0c;尤其是深度学习技术的突破&#xff0c;人工智能领域迎来了…

Windows 与 Java 环境下的 Redis 利用分析

1 前言 在最近的一次攻防演练中&#xff0c;遇到了两个未授权访问的 Redis 实例。起初以为可以直接利用&#xff0c;但后来发现竟然是Windows Java (Tomcat)。因为网上没有看到相关的利用文章&#xff0c;所以在经过摸索&#xff0c;成功解决之后决定简单写一写。 本文介绍了…

树莓派pico入坑笔记,快捷键键盘制作

使用usb_hid功能制作快捷键小键盘&#xff0c;定义了6个键&#xff0c;分别是 ctrlz ctrlv ctrlc ctrla ctrlw ctrln 对应引脚 board.GP4, board.GP8, board.GP13 board.GP28, board.GP20, board.GP17 需要用到的库&#xff0c;记得复制进单片机存储里面 然后是main主程…

【leetcode刷题】面试经典150题 88.合并两个有序数组

leetcode刷题 面试经典150 88. 合并两个有序数组 难度&#xff1a;简单 文章目录 一、题目内容二、自己实现代码2.1 实现思路2.2 实现代码2.3 结果分析 三、 官方解法3.1 直接合并后排序3.1.1 算法实现3.1.2 代码实现3.1.3 代码分析 3.2 双指针3.2.1 算法实现3.2.2 代码实现3.2…

列表(list)(Python)

文章目录 一、定义二、列表常用操作 一、定义 list ["张三", "李四", "王五", "赵六"]二、列表常用操作 分类关键字/函数/方法说明增加列表.append(值)在列表末尾追加值列表.insert(索引&#xff0c; 值)在指定位置插入值&#xff…

从11个视角看全球Rust程序员1/4:深度解读JetBrains最新报告

讲动人的故事,写懂人的代码 五个月前,编程界的大佬JetBrains发布了他们的全球开发者年度报告。 小吾从这份报告中找出了下面11个关于全球程序员如何使用Rust的有趣的趋势,让你学习和使用Rust更轻松。 1 这两年有多少程序员在工作中使用了Rust? 2 全球程序员使用Rust有多…

2024年数字媒体、新闻与管理国际会议(DMJM 2024)

2024年数字媒体、新闻与管理国际会议&#xff08;DMJM 2024&#xff09; 2024 International Conference on Digital Media, Journalism, and Management 【重要信息】 大会地点&#xff1a;长沙 大会官网&#xff1a;http://www.cdmjm.com 投稿邮箱&#xff1a;cdmjmsub-conf…

colab挂载googledrive云盘

参考&#xff1a; Google Colab简易\入门\常规\常用操作和命令_colab快捷键-CSDN博客 首先新建一个或者打开一个笔记本。 等待连接成功。 点击这个图标&#xff0c;变为如下这样: 挂载成功。 这里我是用现有的ipynb文件挂载&#xff1a; 他让我运行代码: 他会提示这个运行这…

相约北京“信通院数据智能大会”

推动企业数智化转型发展&#xff0c;凝聚产业共识&#xff0c;引领行业发展方向&#xff0c;摩斯将参与信通院首届“数据智能大会”&#xff08;6月19-20日&#xff0c;北京&#xff09;。 本次大会设置多个主题论坛&#xff0c;将发布多项研究成果&#xff0c;分享产业最新实…

微信核销通知地址设置返回:请开通回调通知产品权限

1.背景 微信代金券设置核销通知地址时返回: {"code":"REQUEST_BLOCKED","message":"请开通回调通知产品权限\n"} 2.解决方法 登录对应的微信商户号,然后访问如下链接: 微信支付 - 中国领先的第三方支付平台 &#xff5c; 微信支付提…

从11个视角看全球Rust程序员2/4:深度解读JetBrains最新报告

讲动人的故事,写懂人的代码 5 Rust代码最常使用什么协议与其他代码交互? REST API: 2022年:51%2023年:51%看上去REST API的使用比例挺稳定的,没啥变化。语言互操作性(Language Interop): 2022年:53%2023年:43%语言互操作性的比例在2023年下来了一些,掉了10个百分点…

编译器优化入门(基于ESP32)

主要参考资料&#xff1a; kimi: https://kimi.moonshot.cn/ ESP-IDF 支持多种编译器&#xff0c;但默认情况下&#xff0c;它使用的是乐鑫官方提供的 Xtensa 编译器&#xff0c;这是一个针对 ESP32 芯片架构&#xff08;Tensilica Xtensa LX6 微处理器&#xff09;优化的交叉编…

springboot应用启动太慢排查 半天才打印日志

springboot应用启动太慢排查 半天才打印日志 解决办法 hostnamectl 命令查看主机名 vim /etc/hosts 加上主机名配置 127.0.0.1 hostname

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

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

Elixir学习笔记——Erlang 库

Elixir 提供了与 Erlang 库的出色互操作性。事实上&#xff0c;Elixir 不鼓励简单地包装 Erlang 库&#xff0c;而是直接与 Erlang 代码交互。在本节中&#xff0c;我们将介绍一些 Elixir 中没有的最常见和最有用的 Erlang 功能。 Erlang 模块的命名约定与 Elixir 不同&#x…

电商风控指南 | 直播间里的藏匿的“羊毛党”,普通消费者看不到

目录 直播间里的羊毛党 电商要针对性进行防范 随着618网购节的开启&#xff0c;各大电商平台的直播间再次成为消费者关注的焦点。在5月20日的一场酒水电商直播中&#xff0c;主播仅用43分钟便实现了成交额破亿&#xff0c;售出3万瓶白酒。然而&#xff0c;这些“秒杀”特价商品…

Excel加密怎么设置?这5个方法不容错过!(2024总结)

Excel加密怎么设置&#xff1f;如何不让别人未经允许查看我的excel文件&#xff1f;如果您也有这些疑问&#xff0c;那么千万不要错过本篇文章了。今天小编将向大家分享excel加密的5个简单方法&#xff0c;保证任何人都可以轻松掌握&#xff01;毫无疑问的是&#xff0c;为Exce…

SpringBoot配置第三方专业缓存技术jetcache远程缓存方案和本地缓存方案

JetCache 是一个基于 Java 的分布式缓存解决方案&#xff0c;旨在提供高性能和可扩展性。它支持多种后端存储&#xff0c;如 Redis、Hazelcast、Tair 等&#xff0c;可以作为应用程序的缓存层&#xff0c;有效地提升数据访问性能和响应速度。 JetCache 的主要特点包括&#x…