Dart Mixin介绍

关于 Dart mixin 的一些理解。理解 mixin 概念的关键在于理解中间类。

Mixins are a way of reusing code in multiple class hierarchies

先来看一个简单例子:

PilotedCraft 拥有 astronauts 字段和 describeCrew() 方法。

mixin 是什么?

维基百科中这样定义 mixin:

In object-oriented programming languages, a Mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.

即,mixin 是另外一个普通类,我们可以在不继承这个类的情况下从这个类”借用”方法和变量。

Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead.

从这个角度来讲,mixin 不过是 abstract class

Java tries to make up for this by using Interfaces, but that is not as useful or flexible as mixins.

从这个角度来讲,可以认为 mixin 是带实现的接口。

小节

  • mixin 有点类似 abstract class
  • mixin 有点类似 interface
  • 不能继承 mixin
  • 可以使用 mixinabstract classclass 来作为 mixin

如何使用 mixin?

使用 mixin 的方法很简单:with 关键字后面跟上 mixin 的名字即可。

实现 mixin 的方法同样也很简单:创建一个继承自 Object 的类并且不要声明构造方法。如果想让 mixin 作为普通类使用,使用 class 关键字;如果不想让 mixin 作为普通类使用,使用 mixin 关键字代替 class

on 的用法

The keyword on is used to restrict our mixin’s use to only classes which either extends or implements the class it is declared on. In order to use the on keyword, you must declare your mixin using the mixin keyword

则有如下错误提示:

Error: 'Object' doesn't implement 'B' so it can't be used with 'Y'.

on 关键字限制了 Y 的使用范围:Y 只能用于继承或实现了 B 的类。修复方式是让 Q 继承自 B

mixin 解决了什么问题?

mixin 解决了多重继承中的 Deadly Diamond of Death(DDD) 问题。

多重继承问题简单描述。各个类的继承关系如下:

问题来了,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是模糊的

来看 mixin 如何解决这个问题。见 Dart for Flutter : Mixins in Dart - Flutter Community - Medium

现在,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是确定的。在这里是调用 Singer.perform()

mixin 有一套明确的机制来选择调用哪个方法。

假设 Musician 类使用多个 mixin (DancerSinger)。该类有个方法名为 perform()Musician 类继承自 Performer 类。

  • 首先,将 Performer 类置于栈顶
  • 其次,后声明的 mixin 优先于先声明的 mixin。按顺序将 mixin 置于栈中,在这里分别是 DancerSinger
  • 最后,将 Musician 类自己置于栈中。Musician 类中的 perform() 被优先调用

Dart 使用的是单重继承 (Java 也是单重继承,C++ 是多重继承)。多重继承更为强大,但会引起 Deadly Diamond of Death(DDD) 问题。

Java 使用接口(interface)来部分实现多重继承。多重继承的问题是需要在每个类中实现接口(interface),所以并不是一个好的方案。(实际上 Java 已经通过默认方法修复了这个问题)

所以 Dart 中就有了 mixin。

理解 mixin

Mixins in Dart work by creating a new class that layers the implementation of the mixin on top of a superclass to create a new class — it is not “on the side” but “on top” of the superclass, so there is no ambiguity in how to resolve lookups.
Mixins is not a way to get multiple inheritance in the classical sense. Mixins is a way to abstract and reuse a family of operations and state. It is similar to the reuse you get from extending a class, but it is compatible with single-inheritance because it is linear.
StackOverflow

以上这段代码输出 BA

从语义上讲以上这段代码等同于:

继承结构图是这样的:

Since each mixin application creates a new class, it also creates a new interface (because all Dart classes also define interfaces). As described, the new class extends the superclass and includes copies of the mixin class members, but it also implements the mixin class interface.
In most cases, there is no way to refer to that mixin-application class or its interface; the class for Super with Mixin is just an anonymous superclass of the class declared like class C extends Super with Mixin {}. If you name a mixin application like class CSuper = Super with Mixin {}, then you can refer to the mixin application class and its interface, and it will be a sub-type of both Super and Mixin.

理解 mixin 的关键在于它是线性的。

使用场景

  • 在没有共同父类的各个类中共享代码时
  • 在父类中实现某种方法无意义时

在 Dart 语言中,我们经常可以看到对 mixin 关键字的使用,根据字面理解,就是混合的意思。那么,mixin 如何使用,它的使用场景是什么呢。

继续阅读Dart Mixin介绍

布隆过滤器

什么是 BloomFilter

布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。

通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为$O(n)$,$O(logn)$,$O(1)$。

这个时候,布隆过滤器(Bloom Filter)就应运而生。

布隆过滤器原理

了解布隆过滤器原理之前,先回顾下 Hash 函数原理。

哈希函数

哈希函数的概念是:将任意大小的输入数据转换成特定大小的输出数据的函数,转换后的数据称为哈希值或哈希编码,也叫散列值。下面是一幅示意图:

所有散列函数都有如下基本特性:

  • 如果两个散列值是不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。这个特性是散列函数具有确定性的结果,具有这种性质的散列函数称为单向散列函数
  • 散列函数的输入和输出不是唯一对应关系的,如果两个散列值相同,两个输入值很可能是相同的,但也可能不同,这种情况称为“散列碰撞(collision)”。

但是用 hash表存储大数据量时,空间效率还是很低,当只有一个 hash 函数时,还很容易发生哈希碰撞。

布隆过滤器数据结构

BloomFilter 是由一个固定大小的二进制向量或者位图(bitmap)和一系列映射函数组成的。

在初始状态时,对于长度为 m 的位数组,它的所有位都被置为0,如下图所示:

当有变量被加入集合时,通过 K 个映射函数将这个变量映射成位图中的 K 个点,把它们置为 1(假定有两个变量都通过 3 个映射函数)。

查询某个变量的时候我们只要看看这些点是不是都是 1 就可以大概率知道集合中有没有它了

  • 如果这些点有任何一个 0,则被查询变量一定不在;
  • 如果都是 1,则被查询变量很可能存在

为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的。

误判率

布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,因此误判的根源在于相同的 bit 位被多次映射且置 1。

这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。如果我们直接删除这一位的话,会影响其他的元素。(比如上图中的第 3 位)

特性
  • 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在
  • 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。
添加与查询元素步骤
添加元素
  1. 将要添加的元素给 k 个哈希函数
  2. 得到对应于位数组上的 k 个位置
  3. 将这k个位置设为 1
查询元素
  1. 将要查询的元素给k个哈希函数
  2. 得到对应于位数组上的k个位置
  3. 如果k个位置有一个为 0,则肯定不在集合中
  4. 如果k个位置全部为 1,则可能在集合中
优点

相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数 $O(K)$,另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

布隆过滤器可以表示全集,其它任何数据结构都不能;

缺点

但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面。这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。

布隆过滤器使用场景和实例

在程序的世界中,布隆过滤器是程序员的一把利器,利用它可以快速地解决项目中一些比较棘手的问题。

如网页 URL 去重、垃圾邮件识别、大集合中重复元素的判断和缓存穿透等问题。

布隆过滤器的典型应用有:

  • 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。
  • 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。
  • 缓存宕机、缓存击穿场景,一般判断用户是否在缓存中,如果在则直接返回结果,不在则查询db,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到,则穿透到db。如果不在布隆器中,则直接返回。
  • WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。Google Chrome浏览器使用了布隆过滤器加速安全浏览服务
  • Venti 文档存储系统也采用布隆过滤器来检测先前存储的数据。
  • SPIN 模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间。

Coding~

知道了布隆过滤去的原理和使用场景,我们可以自己实现一个简单的布隆过滤器

自定义的 BloomFilter

What?我们写的这些早有大牛帮我们实现,还造轮子,真是浪费时间,No,No,No,我们学习过程中是可以造轮子的,造轮子本身就是我们自己对设计和实现的具体落地过程,不仅能提高我们的编程能力,在造轮子的过程中肯定会遇到很多我们没有思考过的问题,成长看的见~~

实际项目使用的时候,领导和我说项目一定要稳定运行,没自信的我放弃了自己的轮子。

Guava 中的 BloomFilter

分布式环境中,布隆过滤器肯定还需要考虑是可以共享的资源,这时候我们会想到 Redis,是的,Redis 也实现了布隆过滤器。

当然我们也可以把布隆过滤器通过 bloomFilter.writeTo() 写入一个文件,放入OSS、S3这类对象存储中。

Redis 中的 BloomFilter

Redis 提供的 bitMap 可以实现布隆过滤器,但是需要自己设计映射函数和一些细节,这和我们自定义没啥区别。

Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布隆过滤器作为一个插件加载到 Redis Server 中,给 Redis 提供了强大的布隆去重功能。

在已安装 Redis 的前提下,安装 RedisBloom,有两种方式

直接编译进行安装

使用Docker进行安装

使用

布隆过滤器基本指令:

  • bf.add 添加元素到布隆过滤器
  • bf.exists 判断元素是否在布隆过滤器
  • bf.madd 添加多个元素到布隆过滤器,bf.add 只能添加一个
  • bf.mexists 判断多个元素是否在布隆过滤器

我们只有这几个参数,肯定不会有误判,当元素逐渐增多时,就会有一定的误判了,这里就不做这个实验了。

上面使用的布隆过滤器只是默认参数的布隆过滤器,它在我们第一次 add 的时候自动创建。

Redis 还提供了自定义参数的布隆过滤器,bf.reserve 过滤器名 error_rate initial_size

  • error_rate:允许布隆过滤器的错误率,这个值越低过滤器的位数组的大小越大,占用空间也就越大
  • initial_size:布隆过滤器可以储存的元素个数,当实际存储的元素个数超过这个值之后,过滤器的准确率会下降

但是这个操作需要在 add 之前显式创建。如果对应的 key 已经存在,bf.reserve 会报错

我是一名 Javaer,肯定还要用 Java 来实现的,Java 的 Redis 客户端比较多,有些还没有提供指令扩展机制,笔者已知的 Redisson 和 lettuce 是可以使用布隆过滤器的,我们这里用 Redisson

扩展

为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。论文《Cuckoo Filter:Better Than Bloom》作者将布谷鸟过滤器和布隆过滤器进行了深入的对比。相比布谷鸟过滤器而言布隆过滤器有以下不足:查询性能弱、空间利用效率低、不支持反向操作(删除)以及不支持计数。

由于使用较少,暂不深入。

参考链接


布隆过滤器,这一篇给你讲的明明白白

RocksDB使用入门macOS Big Sur (11.6.6)

这篇文章就简单介绍怎么在macOS下使用RocksDB。

1. RocksDB简要介绍

RocksDB是一个可嵌入的、持久型的Key-Value存储。

不像MySQL,PostgreSQL这样数据库分客户端与服务器端。实际上,可以把它当作一个第三方库,在自己的代码中进行引用,调用相应的接口就可以使用,比如使用C/C++语言,直接在源代码中include相应的头文件就可以。

与嵌入式数据库SQLite的使用比较类似。

目前: RocksDB使用LSM存储引擎,纯C++编写。Java版本RocksJava也已经完美支持。

更多有关RocksDB的介绍参考:

关于RocksDB的详细使用介绍:

2. RocksDB 安装

macOS上安装RocksDB非常简单,不用单独安装其他任何依赖。直接使用brew工具安装即可。

可以运行一下命令来查看安装信息。

安装结果如下。

其他操作系统上的安装可以参考:

3. 使用示例

代码目录:

  • main.cpp
  • Makefile

main.cpp文件内容:

Makefile文件内容:

运行结果:

编译时需要注意两点:
  • 支持C11, 添加选项-std=c++11
  • 添加链接库: -lrocksdb

参考链接


RocksDB使用入门 Mac

丰田RAV4 2016款空调滤芯更换以及安装方向

丰田RAV4 2016款空调滤芯更换教程:

1. 不带智能进入和起动系统的车辆:将发动机开关切换至“LOCK”位置。带智能进入和起动系统的车辆:关闭发动机开关。

2. 提起并取出隔板。

3. 拆下手套箱盖。

4. 拆下滤清器盖。

5. 拆下空调滤清器并更换为新滤清器。滤清器上的“↑UP”标记应朝上。

■更换间隔

根据保养计划,检查并更换空调滤清器。在多尘或交通繁忙的地区,更换间隔

可能需要缩短。

■如果通风口的气流明显减弱

滤清器可能堵塞。检查滤清器,如有必要进行更换。

■使用空调系统时

确保滤清器已安装。

使用不带滤清器的空调系统可能会损坏系统。

原厂滤芯的实际安装朝向如下:

博世等标注气流方向的滤芯的实际安装朝向如下:

也就是与原厂的滤芯的箭头方向相反,保证文字都是正的即可。

注意滤芯是长方形的,原厂的文字是印刻在短边的一侧。买的滤芯文字可能印在了长边一侧,这个时候我们需要转一下方向,不要强行把有文字的一侧露在外面。滤芯是很容易插入的。

参考链接


树莓派3B+/4B报告“low voltage warning”

需求场景

用的好好的树莓派,有一天连接VNC,发现桌面任务栏一直提示

如下图:

看样子是供电不足,可是树莓派的负载并不高啊。

解决方案

搜了一大圈基本都是说电源的问题,现在使用的电源是以前小米2/小米Note手机自带的电源。经过反复测试,发现跟温度有关,冬天的时候问题不大,到了夏天,由于老式电源转换效率低,大量电能被转换成了热量,导致长时间充电后,温度上升很快,引起降压,断电后过一段时间就可以了,但是一旦温度上升到一定程度,问题依旧。

后来买了个努比亚大白20W氮化镓充电器之后,就不会报告这个错误了。氮化镓的充电器转化效率更高,升温更少,并且更耐高温。

由于这个提示只有在连接显示器或VNC连接时才能在GUI桌面任务栏看到,如果是SSH登录就看不到了,非常的不方便。最好是能够通过命令判断是否电压不足。

具体的脚本详见 https://gist.github.com/maxme/d5f000c84a4313aa531288c35c3a8887

通过此脚本可以查看CPU和电压不足的问题。不过每次登录之后需要手动运行,实在不方面。而且脚本中采用了while循环需要手动中断脚本。所以可以把代码中的while循环去掉,并且在SSH登录的时候执行命令病输出到登录信息上。

注意电压充足的情况下,输出电压为1.2V,而不是 5V,主板的5V是没办法通过脚本直接测量的。我们只能通过脚本测量CPU、内存控制器、内存输入输出、内存本身的电压,主板电压只能用万用表来测量。

效果如下,具体方式参考 ubuntu修改ssh登录提示信息

修改后的raspberry-power-supply-check.sh代码如下:

参考链接


Flutter构建Linux应用

从3.0之后的版本,flutter已经正式支持构建Linux应用

条件

  • ubuntu 22.04
  • Android Studio Chipmunk |  2021.2.1
  • flutter sdk 3.x

方法

1. 打开Linux的平台支持

输入如下指令(如下命令可以使用flutter config查看,2.10以上的版本默认开启

可以使用flutter doctor查看情况。

安装编译依赖

2. 在旧项目中添加Linux平台支持

在项目地址输入如下指令

注意: 项目的名称必须是全小写,如果出现大小写则会报错

3. 构建Linux项目

4. 报错

出现这个问题

参考链接


Flutter构建Windows应用

从2.10之后的版本,flutter已经正式支持构建Windows应用。不过距离实现全平台构建,还需要考虑很多问题,flutter构建的应用更加适合移动端的使用习惯,如果需要构建Windows应用,我的建议是使用一些适合桌面端的widget。

具体方法如下:

条件

  • 需要安装Visual Studio 2019或者Visual Studio 2022,简言之,需要安装Windows 10 SDK。
  • 2.10之前的版本是默认关闭的。Linux和Mac是默认关闭的,可以手动进行打开。

方法

1. 打开Windows的平台支持

PowerShell输入如下指令(如下命令可以使用flutter config查看,2.10以上的版本默认开启

可以使用flutter doctor查看情况

如果报错:

则需要更新Flutter SDK到3.0.2以上的版本即可,如下:

具体原因,参考:[windows] Exception: Bad UTF-8 encoding when running flutter doctor or building/running for Windows #102451

2. 在旧项目中添加Windows平台支持

在项目地址输入如下指令

注意: 项目的名称必须是全小写,如果出现大小写则会报错

3. 构建Windows项目exe

参考链接


Android系统中是否开启定位及定位模式的判断

Android系统中包括3中定位模式:

  使用GPS、WLAN和移动网络 使用WLAN和移动网络 仅使用GPS
特点

同时使用GPS、WIFI及基站定位,速度快、精度高,室内定位效果好。

缺点:耗流量、耗电量

只使用WIFI和基站定位,需要WIFI或者基站才行,室内效果好。

缺点:依赖WIFI或基站,精度一般

不依赖WIFI和基站,室内效果差,户外可靠性好。

缺点:室内效果差

代码 LocationManager.GPS_PROVIDER = true;
LocationManager.NETWORK_PROVIDER = true;
LocationManager.GPS_PROVIDER = false;
LocationManager.NETWORK_PROVIDER = true;
LocationManager.GPS_PROVIDER = true;
LocationManager.NETWORK_PROVIDER = false;
是否打开定位服务 Settings.Secure.LOCATION_MODE = 3 Settings.Secure.LOCATION_MODE = 2 Settings.Secure.LOCATION_MODE = 1

Android系统中检查是否开启了定位服务的代码,参考如下:

参考链接


进程资源图理解与化简

一 了解进程资源图

二 化简资源分配图


方法步骤
  • 第一步:先看系统还剩下多少资源没分配,再看有哪些进程是不阻塞(“不阻塞”即:系统有足够的空闲资源分配给它)的
  • 第二步:把不阻塞的进程的所有边都去掉,形成一个孤立的点,再把系统分配给这个进程的资源回收回来
  • 第三步:看剩下的进程有哪些是不阻塞的,然后又把它们逐个变成孤立的点。
  • 第四步:最后,所有的资源和进程都变成孤立的点。这样的图就叫做“可完全简化”。

如果一个图可完全简化,则不会产生死锁;如果一个图不可完全简化(即:图中还有“边”存在),则会产生死锁。这就是“死锁定理”。 

实例

解析:

R1有两个资源,一个分配给了P1,一个分配给了P3,此时P2申请R1的资源,因为R1此时没有可用资源,P2堵塞。

R2有三个资源,已经给P1,P2,P3,各自分配了一个资源,而P1此时又再次申请资源R2,P1堵塞

R3有两个资源,已经分配给P2一个,P2申请一个资源,分配给它,所以P3是非阻塞结点

化简的话,看从没有阻塞的结点开始,删去P3周围所有的边,使其成为一个孤立的点,然后看剩下的资源按上述步骤再次进行分配,若到最后只剩下一群孤立的点,则说明该资源图是可以化简的。

参考链接


进程资源图理解与化简