Flutter高效数据比较插件fast_immutable_equatable的使用

Flutter高效数据比较插件fast_immutable_equatable的使用

本Dart包由fast_equatable(添加了缓存值)和equatable(添加了不可变性)衍生而来。

迁移

如何从equatable迁移

  • class MyClass with EquatableMixin重命名为class MyClass with IFastEquatable
  • class MyClass extends Equatable重命名为class MyClass extends IFastEquatable
  • List<Object> get props重命名为List<Object?> get hashParameters
  • 添加:
    [@override](/user/override)
    bool get cacheHash => true;
    

如何从fast_equatable迁移

  • class MyClass with FastEquatable重命名为class MyClass with IFastEquatable
  • 通过添加final使字段变为不可变(由于类自动标记为不可变,dart linter会提示你)。

概览

哈希值通过双查找技巧在静态映射中进行缓存,以保持不变性。

需要注意的是,在速度方面,此库由于双查找而略慢于fast_equatable包,但仍然比equatable包快,特别是在进行大量比较时。

你可以像使用混入一样使用with IFastEquatable(这是首选选项,因为它更容易集成到现有代码中),或者作为类继承extends IFastEquatable

该库提供了哈希缓存功能,以提高ListMapSet和一般Iterable集合的速度。

默认情况下,使用广泛传播的詹金斯哈希函数。但你可以自由地实现并提供自己的哈希引擎。如果想创建一个包含新哈希引擎实现的PR。

hashParameters中的任何类型对象都允许(这与equatable包中的props工作方式相同)。默认支持简单类型和标准集合如ListIterableMapSet

为了使用来自fast_immutable_collections的不可变集合如IListISetIMap,只需启用深度相等和哈希:

defaultConfig = ConfigList(isDeepEquals: true, cacheHashCode: true);

然而,由于IFastEquatable被标记为不可变,你可以使用常规的ListMapSet而不必担心它们会被修改。

如何在新项目中使用IFastEquatable

推荐使用接口方法:

class A with IFastEquatable {
  final String arg1;
  final Map<String> arg2;
  const A(this.arg1, this.arg2);

  [@override](/user/override)
  bool get cacheHash => true;

  [@override](/user/override)
  List<Object?> get hashParameters => [arg1, arg2];
}

如何子类化A

库在处理子类和hashParameters时强制你遵循一定的风格。重要的是hashParameters必须用展开操作符调用,否则编译器会发出警告。

class AB extends A {
  final IList<int> arg3;
  AB(super.arg1, super.arg2, this.arg3);

  [@override](/user/override)
  bool get cacheHash => true;

  [@override](/user/override)
  List<Object?> get hashParameters => [...super.hashParameters, arg3];
}

性能测试

example目录下,你会发现性能测试代码,展示了由此带来的速度提升。然而,这并不完全可比,因为测试并没有尝试多次使用同一对象进行比较。如果是这种情况,fast_immutable_equatable会更快。

equatable for 1000000 elements(RunTime): 4789464.0 us.
fast_immutable_equatable (uncached) for 1000000 elements(RunTime): 4467237.0 us.
fast_immutable_equatable (cached) for 1000000 elements(RunTime): 3723849.5 us.

完整示例

以下是完整的示例代码:

import 'dart:math';

import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:equatable/equatable.dart';
import 'package:fast_immutable_equatable/fast_immutable_equatable.dart';

class FastEquatableCached with IFastEquatable {
  final String value1;
  final List<String>? value2;

  const FastEquatableCached(this.value1, this.value2);

  [@override](/user/override)
  bool get cacheHash => true;

  [@override](/user/override)
  List<Object?> get hashParameters => [value1, value2];
}

class FastEquatableUncached with IFastEquatable {
  final String value1;
  final List<String>? value2;

  const FastEquatableUncached(this.value1, this.value2);

  [@override](/user/override)
  bool get cacheHash => false;

  [@override](/user/override)
  List<Object?> get hashParameters => [value1, value2];
}

// Quick example of how to subclass A.
class A with IFastEquatable {
  final String arg1;
  final int arg2;
  const A(this.arg1, this.arg2);

  [@override](/user/override)
  bool get cacheHash => true;

  [@override](/user/override)
  List<Object?> get hashParameters => [arg1, arg2];
}

class AB extends A {
  final int arg3;
  AB(super.arg1, super.arg2, this.arg3);

  [@override](/user/override)
  bool get cacheHash => true;

  [@override](/user/override)
  List<Object?> get hashParameters => [...super.hashParameters, arg2];
}

class TestClassEquatable extends Equatable {
  final String value1;
  final List<String>? value2;

  const TestClassEquatable(this.value1, this.value2);

  [@override](/user/override)
  List<Object?> get props => [value1, value2];
}

class EquatableBenchmark extends BenchmarkBase {
  final List<String> _randsValA;
  final List<String> _randsValB;

  late final List<TestClassEquatable> objects;

  EquatableBenchmark(this._randsValA, this._randsValB)
      : assert(_randsValA.length == _randsValB.length),
        super('equatable for ${_randsValA.length} elements');

  [@override](/user/override)
  void setup() {
    objects = List.generate(_randsValA.length,
        (i) => TestClassEquatable(_randsValA[i], [_randsValB[i]]));
  }

  [@override](/user/override)
  void run() {
    final set = <TestClassEquatable>{};

    for (final obj in objects) {
      set.add(obj);
    }
  }
}

class FastEquatableUncachedBenchmark extends BenchmarkBase {
  final List<String> _randsValA;
  final List<String> _randsValB;

  late final List<FastEquatableUncached> objects;

  FastEquatableUncachedBenchmark(this._randsValA, this._randsValB)
      : assert(_randsValA.length == _randsValB.length),
        super(
            'fast_immutable_equatable (uncached) for ${_randsValA.length} elements');

  [@override](/user/override)
  void setup() {
    objects = List.generate(_randsValA.length,
        (i) => FastEquatableUncached(_randsValA[i], [_randsValB[i]]));
  }

  [@override](/user/override)
  void run() {
    final set = <FastEquatableUncached>{};

    for (final obj in objects) {
      set.add(obj);
    }
  }
}

class FastEquatableCachedBenchmark extends BenchmarkBase {
  final List<String> _randsValA;
  final List<String> _randsValB;

  late final List<FastEquatableCached> objects;

  FastEquatableCachedBenchmark(this._randsValA, this._randsValB)
      : assert(_randsValA.length == _randsValB.length),
        super(
            'fast_immutable_equatable (cached) for ${_randsValA.length} elements');

  [@override](/user/override)
  void setup() {
    objects = List.generate(_randsValA.length,
        (i) => FastEquatableCached(_randsValA[i], [_randsValB[i]]));
  }

  [@override](/user/override)
  void run() {
    final set = <FastEquatableCached>{};

    for (final obj in objects) {
      set.add(obj);
    }
  }
}

void main(List<String> args) {
  const nAcc = 1000000;

  final rand = Random();
  final randsVal1 = List.generate(nAcc, (_) => rand.nextInt(nAcc).toString());
  final randsVal2 = List.generate(nAcc, (_) => rand.nextInt(nAcc).toString());

  EquatableBenchmark(randsVal1, randsVal2).report();
  FastEquatableUncachedBenchmark(randsVal1, randsVal2).report();
  FastEquatableCachedBenchmark(randsVal1, randsVal2).report();
}

更多关于Flutter高效数据比较插件fast_immutable_equatable的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter高效数据比较插件fast_immutable_equatable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter开发中,高效地进行数据比较是优化性能和减少不必要渲染的关键步骤之一。fast_immutable_equatable 插件就是为了解决这一问题而设计的,它提供了一个快速且简便的方法来比较不可变对象的相等性。下面是如何在Flutter项目中使用 fast_immutable_equatable 的代码示例。

首先,确保你已经在 pubspec.yaml 文件中添加了 fast_immutable_equatable 依赖:

dependencies:
  flutter:
    sdk: flutter
  fast_immutable_equatable: ^x.y.z  # 请替换为最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,让我们创建一个简单的示例,展示如何使用 fast_immutable_equatable

1. 定义一个数据模型

假设我们有一个表示用户信息的模型 User

import 'package:fast_immutable_equatable/fast_immutable_equatable.dart';

class User extends EquatableMixin {
  final String id;
  final String name;
  final int age;

  User({required this.id, required this.name, required this.age});

  @override
  List<Object?> get props => [id, name, age];
}

这里,我们使用了 EquatableMixin,并实现了 props getter,它返回一个包含所有用于比较相等性的属性的列表。

2. 使用数据模型进行比较

现在,我们可以创建 User 对象并使用 == 运算符或 === 运算符(对于值类型比较)来检查它们的相等性:

void main() {
  User user1 = User(id: '1', name: 'Alice', age: 30);
  User user2 = User(id: '1', name: 'Alice', age: 30);
  User user3 = User(id: '2', name: 'Bob', age: 25);

  print(user1 == user2);  // 输出: true
  print(user1 == user3);  // 输出: false
}

在这个例子中,user1user2 被认为是相等的,因为它们的所有属性都相同。而 user3 的属性与 user1 不同,所以它们不相等。

3. 在Flutter应用中使用

在Flutter应用中,你可能会在状态管理中使用这些数据模型。例如,在使用 Provider 状态管理库时,可以这样使用:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'user_model.dart';  // 假设User类定义在这个文件中

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (_) => UserProvider(),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class UserProvider with ChangeNotifier {
  User? _user;

  User? get user => _user;

  void setUser(User user) {
    if (_user != user) {
      _user = user;
      notifyListeners();
    }
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('User Info')),
        body: Center(
          child: Consumer<UserProvider>(
            builder: (_, userProvider, __) {
              final User? user = userProvider.user;
              return user != null
                  ? Text('Name: ${user.name}, Age: ${user.age}')
                  : Text('No user data');
            },
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            final UserProvider userProvider =
                Provider.of<UserProvider>(context, listen: false);
            userProvider.setUser(User(id: '1', name: 'Alice', age: 30));
          },
          tooltip: 'Set User',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

在这个例子中,UserProvider 是一个 ChangeNotifier,它持有一个 User 对象。当 setUser 方法被调用时,如果新的 User 对象与当前对象不相等(根据 EquatableMixin 的逻辑),它会通知监听器更新UI。

通过这种方式,fast_immutable_equatable 插件帮助你在Flutter应用中高效地管理不可变对象的状态比较,从而优化性能和用户体验。

回到顶部