当前位置: 首页 > news >正文

实体解析实施的复杂性

                                                实体的艺术表现斯特凡·伯克纳

一、说明

        实体解析是确定数据集中的两条或多条记录是否引用同一现实世界实体(通常是个人或公司)的过程。乍一看,实体分辨率可能看起来像一个相对简单的任务:例如,给定一张人物的两张照片,即使是一个小孩子也可以确定它是否以相当高的精度显示同一个人。计算机也是如此:比较包含姓名、地址、电子邮件等属性的两条记录可以很容易地完成。然而,深入探讨该主题,它就越具有挑战性:需要评估各种匹配算法,处理数百万或数十亿条记录意味着二次复杂性,更不用说实时和数据删除用例了。

二、模糊文本匹配

        让我们从比较著名艺术家文森特梵高的两张唱片开始——还是梵高?

        第二条记录中有一些错误(除了一个世纪后出生和电子邮件地址):姓名拼写错误,出生日期混淆,邮政编码丢失,电子邮件地址略有不同。

        那么我们如何比较这些值呢?如果,假设名称相等,那么对这些值进行简单的字符串比较就足够了。由于情况并非如此,我们需要一些更高级的模糊匹配。有许多不同的算法可用于基于文本的模糊匹配,它们可以大致分为三组。语音算法侧重于文本的发音相似程度。最著名的算法是Soundex和Metaphone,它们主要用于英语文本,但其他语言也存在这些算法的变体,例如德语的Kölner Phonetik(科隆语音)。文本距离算法通常定义文本需要更改多少个字符才能到达其他文本。Levenshtein和Hamming距离是该组中两个众所周知的算法。相似性算法,如余弦相似性或杰卡德指数,计算文本的结构相似性,通常以百分比表示相似性。

        出于本文的目的,我们将使用一种非常简单的方法,仅使用名称上的Levenshtein距离和城市的相等性。此示例和以下所有示例将使用 golang 作为编程语言,并尽可能使用现有库。将其转换为python,java或任何其他语言应该是微不足道的。此外,它只会对 name 属性执行匹配。添加更多属性甚至使其可配置不是本文的目的。

package mainimport ("fmt""github.com/hbollon/go-edlib"
)type Record struct {ID intName stringCity string
}func matches(a, b Record) bool {distance := edlib.LevenshteinDistance(a.Name, b.Name)return distance <= 3 && a.City == b.City
}func main() {a := Record{Name: "Vincent Van Gogh",City: "Paris",}b := Record{Name: "Vince Van Gough",City: "Paris",}if matches(a, b) {fmt.Printf("%s and %s are probably the same person\n", a.Name, b.Name)} else {fmt.Printf("%s and %s are probably not the same person\n", a.Name, b.Name)}
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        两个名字之间的列文森距离正好是3。这是因为,还有三个附加字符(名字中的“en”和姓氏中的“u”)。请注意,这适用于此特定输入。然而,它离完美还很远。例如,“Joe Smith”和“Amy Smith”这两个名字的Levenshtein距离也是三个,但显然不是同一个人。将距离算法与语音算法相结合可以解决这个问题,但这超出了本文的范围。

        使用基于规则的方法而不是基于 ML 的方法时,选择为您的使用案例产生最佳结果的正确算法是业务成功的最关键方面。这是您应该花费大部分时间的地方。不幸的是,正如我们现在将发现的那样,如果您决定自己开发实体解析引擎,还有很多其他事情会分散您优化这些规则的注意力。

三、朴素实体解析

        现在我们知道了如何比较两个记录,我们需要找到彼此匹配的所有记录。最简单的方法是简单地将每条记录与所有其他记录进行比较。出于此示例的目的,我们使用随机选择的名称和城市。对于名称,我们最多强制三个错误(用 x 替换任何字符)。

var firstNames = [...]string{"Wade", "Dave", "Seth", "Ivan", "Riley", "Gilbert", "Jorge", "Dan", "Brian", "Roberto", "Daisy", "Deborah", "Isabel", "Stella", "Debra", "Berverly", "Vera", "Angela", "Lucy", "Lauren"}
var lastNames = [...]string{"Smith", "Jones", "Williams", "Brown", "Taylor"}func randomName() string {fn := firstNames[rand.Intn(len(firstNames))]ln := lastNames[rand.Intn(len(lastNames))]name := []byte(fmt.Sprintf("%s %s", fn, ln))errors := rand.Intn(4)for i := 0; i < errors; i++ {name[rand.Intn(len(name))] = 'x'}return string(name)
}var cities = [...]string{"Paris", "Berlin", "New York", "Amsterdam", "Shanghai", "San Francisco", "Sydney", "Cape Town", "Brasilia", "Cairo"}func randomCity() string {return cities[rand.Intn(len(cities))]
}func loadRecords(n int) []Record {records := make([]Record, n)for i := 0; i < n; i++ {records[i] = Record{ID:   i,Name: randomName(),City: randomCity(),}}return records
}func compare(records []Record) (comparisons, matchCount int) {for _, a := range records {for _, b := range records {if a == b {continue // don't compare with itself}comparisons++if matches(a, b) {fmt.Printf("%s and %s are probably the same person\n", a.Name, b.Name)matchCount++}}}return comparisons, matchCount
}func main() {records := loadRecords(100)comparisons, matchCount := compare(records)fmt.Printf("made %d comparisons and found %d matches\n", comparisons, matchCount)
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        您应该看到一些类似的输出(如果您没有得到随机数据的任何匹配项,您可能需要多次运行它):

Daisy Williams and Dave Williams are probably the same person
Deborax Browx and Debra Brown are probably the same person
Riley Brown and RxxeyxBrown are probably the same person
Dan Willxams and Dave Williams are probably the same person
made 9900 comparisons and found 16 matches

        如果幸运的话,您还会得到像“黛西”和“戴夫”这样的不匹配。这是因为我们使用的 Levenshtein 距离为 3,作为短名称的唯一模糊算法,这是高的方式。请随时自行改进。

        性能方面,真正有问题的一点是获得结果所需的 9,900 次比较,因为输入量加倍将大约使所需比较量翻两番。39 条记录需要 800,200 次比较。对于只有 100,000 条记录的少量数据,这意味着需要近 10 亿次比较。无论您的系统有多大,随着数据量的增长,系统都将无法在可接受的时间内完成此操作。

        一个快速但几乎无用的优化是不对每个组合进行两次比较。我们将 A 与 B 进行比较或将 B 与 A 进行比较应该无关紧要。然而,这只会减少因子 2 所需的比较量,由于二次增长,这是可以忽略的。

四、通过阻塞降低复杂性

        如果我们查看我们创建的规则,我们很容易注意到,如果城市不同,我们将永远不会有匹配。所有这些比较都是完全浪费的,应该加以防止。将您怀疑相似的记录放入公共存储桶中,而将其他不相同的记录放入另一个存储桶中,在实体解析中称为阻塞。由于我们想使用城市作为我们的阻塞键,因此实现相当简单。

func block(records []Record) map[string][]Record {blocks := map[string][]Record{}for _, record := range records {blocks[record.City] = append(blocks[record.City], record)}return blocks
}func main() {records := loadRecords(100)blocks := block(records)comparisons := 0matchCount := 0for _, blockRecords := range blocks {c, m := compare(blockRecords)comparisons += cmatchCount += m}fmt.Printf("made %d comparisons and found %d matches\n", comparisons, matchCount)
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        现在的结果将是相同的,但我们只有大约十分之一的比较,因为我们有十个不同的城市。在实际应用中,由于城市的差异要大得多,这种影响会大得多。此外,每个块可以独立于其他块进行处理,例如在相同或不同的服务器上并行处理。

        找到正确的阻止密钥本身就是一个挑战。使用像城市这样的属性可能会导致分布不均匀,因此会导致一个巨大的区块(例如大城市)比所有其他区块花费更长的时间。或者城市包含微小的拼写错误,不再被视为有效匹配。使用多个属性和/或使用拼音键或 q-gram 作为阻塞键可以解决这些问题,但会增加软件的复杂性。

五、从匹配项到实体

        到目前为止,关于我们的记录,我们只能说,其中两个是否匹配。对于非常基本的用例,这可能已经足够了。但是,在大多数情况下,您想知道属于同一实体的所有匹配项。这可以从简单的星形模式,其中 A 与 B、C 和 D 匹配,到 A 匹配 B、B 匹配 C 和 C 匹配 D 的链状模式,到非常复杂的图形模式。这种所谓的传递记录链接可以使用连接的组件算法轻松实现,只要所有数据都适合单个服务器上的内存。同样,在实际应用中,这更具挑战性。

func compare(records []Record) (comparisons int, edges [][2]int) {for _, a := range records {for _, b := range records {if a == b {continue // don't compare with itself}comparisons++if matches(a, b) {edges = append(edges, [2]int{a.ID, b.ID})}}}return comparisons, edges
}func connectedComponents(edges [][2]int) [][]int {components := map[int][]int{}nextIdx := 0idx := map[int]int{}for _, edge := range edges {a := edge[0]b := edge[1]aIdx, aOk := idx[a]bIdx, bOk := idx[b]switch {case aOk && bOk && aIdx == bIdx: // in same componentcontinuecase aOk && bOk && aIdx != bIdx: // merge two componentscomponents[nextIdx] = append(components[aIdx], components[bIdx]...)delete(components, aIdx)delete(components, bIdx)for _, x := range components[nextIdx] {idx[x] = nextIdx}nextIdx++case aOk && !bOk: // add b to component of aidx[b] = aIdxcomponents[aIdx] = append(components[aIdx], b)case bOk && !aOk: // add a to component of bidx[a] = bIdxcomponents[bIdx] = append(components[bIdx], a)default: // create new component with a and bidx[a] = nextIdxidx[b] = nextIdxcomponents[nextIdx] = []int{a, b}nextIdx++}}cc := make([][]int, len(components))i := 0for k := range components {cc[i] = components[k]i++}return cc
}func main() {records := loadRecords(100)blocks := block(records)comparisons := 0edges := [][2]int{}for _, blockRecords := range blocks {c, e := compare(blockRecords)comparisons += cedges = append(edges, e...)}cc := connectedComponents(edges)fmt.Printf("made %d comparisons and found %d matches and %d entities\n", comparisons, len(edges), len(cc))for _, component := range cc {names := make([]string, len(component))for i, id := range component {names[i] = records[id].Name}fmt.Printf("found the following entity: %s from %s\n", strings.Join(names, ", "), records[component[0]].City)}
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        连接的组件功能遍历所有边,然后创建新组件、将新 id 添加到现有组件或将两个组件合并为一个组件。结果如下所示:

made 1052 comparisons and found 6 matches and 2 entities
found the following entity: Ivan Smxth, Ixan Smith, Ivax Smitx from Cairo
found the following entity: Brxan Williams, Brian Williams from Cape Town

        保持这些边缘给我们带来了一些优势。我们可以使用它们来使生成的实体易于理解和解释,理想情况下,即使有一个漂亮的 UI 来显示实体的记录是如何连接的。或者,在使用实时实体解析系统时,我们可以使用边缘在删除数据时拆分实体。或者,您在构建图神经网络 (GNN) 时使用它们,从而获得更好的 ML 结果,而不仅仅是记录。

实体的可视化表示(作者图片)

        当有很多非常相似的记录时,可能会出现一个来自边缘的问题。例如,如果 A 与 B 匹配,B 与 C 匹配,则 C 也可能与 A 匹配,具体取决于使用的规则。如果 D、E、F 等也与现有记录匹配,那么我们又回到了二次增长问题,很快导致如此多的边变得不再可处理。

        还记得我们是如何构建阻塞桶的吗?惊喜!对于非常相似的数据,这些数据最终都集中在几个巨大的桶中,计算性能再次急剧下降——即使您遵循了之前从多个属性创建桶的建议。

        这种不相同的重复项的典型示例是有人定期在同一家商店订购,但具有来宾访问权限(抱歉,没有很好的客户 ID)。该人可能几乎总是使用相同的送货地址,并且大多能够正确写下自己的名字。因此,应以特殊方式处理这些记录,以确保稳定的系统性能,但这本身就是一个主题。

        在你对所获得的知识感到太舒服并想开始实施自己的解决方案之前,让我快速粉碎你的梦想。我们还没有讨论实时执行任何操作的挑战。即使你认为你不需要一个总是最新的实体(显而易见的好处),实时方法也会产生进一步的价值:你不需要一遍又一遍地做同样的计算,而只需要对新数据。另一方面,实现起来要复杂得多。想要阻止?将新记录与其所属存储桶的所有记录进行比较,但这可能需要一段时间,并且可以被视为增量批处理。同样在最终完成之前,还有大量新记录等待处理。想要使用连接的组件计算实体?当然,将整个图形保留在内存中,只需添加新的边。但是不要忘记跟踪由于新记录而刚刚合并在一起的两个实体。

        因此,您仍然愿意自己实现这一点。你做出了(在这种情况下)明智的决定,不存储边缘,不支持实时。因此,您成功地运行了包含所有数据的第一个实体解析批处理作业。这花了一段时间,但你每个月只会这样做一次,所以这很好。当您看到您的数据保护官跑到拐角处并告诉您由于GDPR投诉而从数据集中删除该人时,可能就在那时。因此,您再次为单个已删除的实体运行整个批处理作业 — 耶。

六、结论

        进行实体解析乍一看可能相当简单,但它包含许多重大的技术挑战。其中一些可以简化和/或忽略,但其他问题需要解决以获得良好的性能。

相关文章:

实体解析实施的复杂性

实体的艺术表现斯特凡伯克纳 一、说明 实体解析是确定数据集中的两条或多条记录是否引用同一现实世界实体&#xff08;通常是个人或公司&#xff09;的过程。乍一看&#xff0c;实体分辨率可能看起来像一个相对简单的任务&#xff1a;例如&#xff0c;给定一张人物的两张照片&a…...

MAKEFLAGS += -rR --include-dir=$(CURDIR)的含义

一、目的 在看uboot顶层Makefile文件时遇到这个代码不甚明白&#xff0c;故查找了一下资料以供大家学习 二、介绍 MAKEFLAGS -rR 表示禁止使用内置的隐含规则和变量定义&#xff1b;这个选项用于启用recursive make&#xff0c;使得Makefile目标可以调用其他Makefile目标&…...

maven问题与解决方案、部署

问题一、was cached in the local repository, resolution will not be reattempted until the update interval of idea中 Maven中Lifecycle时&#xff0c;能正常clean 和 install&#xff0c;但在idea的Terminal中mvn install出现&#xff1a; was cached in the local repo…...

【大数据】Hadoop MapReduce与Hadoop YARN(学习笔记)

一、Hadoop MapReduce介绍 1、设计构思 1&#xff09;如何对付大数据处理场景 对相互间不具有计算依赖关系的大数据计算任务&#xff0c;实现并行最自然的办法就是采取MapReduce分而治之的策略。 不可拆分的计算任务或相互间有依赖关系的数据无法进行并行计算&#xff01; …...

接口测试文档

接口测试的总结文档 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1a;主要介绍为什…...

Ubuntu中不能使用ifconfig命令

​ 问题 打开终端使用如下命令不能运行&#xff1a; ifconfig显示如下错误: 解决方法 在VMware中的虚拟机下面打开“编辑虚拟机设置”&#xff0c;或者在已经打开的虚拟机面板上面打开“虚拟机—设置” 选择网络适配器&#xff0c;选择“NAT模式”&#xff0c;没开机的就…...

BAT020:将文本文档中多行文本拼接为;分隔的单行文本

引言&#xff1a;编写批处理程序&#xff0c;实现将文本文档中多行文本拼接为;分隔的单行文本。 一、新建Windows批处理文件 参考博客&#xff1a; CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件&#xff0c;点击【…...

安防初识命令【学习笔记】

1、美杜莎爆破 medusa -M ssh -h 192.168.42.135 -u guest -P top1000.txt -t 8 2、passwd文件与shadow文件 root:x:0:0:root:/root:/usr/bin/zsh 用户名&#xff1a;密码&#xff1a;uid&#xff1a;gid&#xff1a;描述性信息&#xff1a;主目录&#xff1a;默认…...

idea 启动出现 Failed to create JVM JVM Path

错误 idea 启动出现如下图情况 Error launching IDEA If you already a 64-bit JDK installed, define a JAVA_HOME variable in Computer > System Properties> System Settings > Environment Vanables. Failed to create JVM. JVM Path: D:\Program Files\JetB…...

凉鞋的 Unity 笔记 108. 第二个通识:增删改查

在这一篇&#xff0c;我们来学习此教程的第二个通识&#xff0c;即&#xff1a;增删改查。 增删改查我们不只是一次接触到了。 在最先接触的场景层次窗口中&#xff0c;我们是对 GameObject 进行增删改查。 在 Project 文件窗口中&#xff0c;我们是对文件&文件夹进行增删…...

angular项目指定端口,实现局域网内ip访问

直接修改package.json文件 "dev": "ng serve --host 0.0.0.0 --port 8080"终端运行npm run dev启动项目。 这里就指定了使用8080端口运行项目&#xff0c;同时局域网内的其他电脑可以通过访问运行项目主机的ip来访问项目 例如项目运行在ip地址为192.168.2…...

解决uniapp里scroll-view横向滚动的问题

一、前言 本以为是一件很简单的事&#xff0c;结果浪费了整整一个上午&#xff0c;并且问题并没有全部解决....后来没办法&#xff0c;用了touchmove模拟的滑动&#xff0c;如果有好的解决方法麻烦告诉我...非常感谢~ 一、问题 其实我想要实现的功能很简单&#xff0c;就是一…...

LeetCode——动态规划(五)

刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 目录 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 123. 买卖股票的最佳时机 III …...

与HTTP相关的各种概念

网络世界 网络世界中最重要的一个名词就是互联网&#xff08;Internet&#xff09;,它以TCP/IP协议族为基础&#xff0c;构建成了一望无际的信息传输网络。而我们通常所说的“上网”&#xff0c;主要就是访问互联网的一个子集——万维网&#xff08;World Wide Web&#xff09…...

CentOS 7 编译安装Boost

1、前提条件 linux平台/CentOS 7 下要编译安装Boost除gcc和gcc-c之外&#xff0c;还需要两个开发库&#xff1a;bzip2-devel 和python-devel &#xff0c;因此在安装前应该先保证这两个库已经安装。 安装指令: yum install bzip2 bzip2-devel bzip2-libs python-devel Cent…...

vue图表制作

Vue.js是一个非常流行的JavaScript框架&#xff0c;可以用于开发交互式Web应用程序。Vue.js的优点之一是它的灵活性和可扩展性。因此&#xff0c;可以使用Vue.js与许多其他库和框架集成&#xff0c;包括图表库。 下面是使用Vue.js制作图表的一些步骤&#xff1a; 1.选择一个适…...

使用 GitHub Action 自动更新 Sealos 集群的应用镜像

在 IT 领域&#xff0c;自动化无疑已成为提高工作效率和减少人为错误的关键。Sealos 作为一个强大的云操作系统&#xff0c;已经为许多企业和开发者提供了稳定可靠的服务。与此同时&#xff0c;随着技术不断发展&#xff0c;集成更多的功能和服务变得尤为重要。考虑到这一点&am…...

windows频繁更新问题解决方案

解决方案&#xff1a;将更新策略增加到无穷大 1.windowsr 输入regedit 2.找到&#xff1a;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 3.右键新建DWORD32 4.命名&#xff1a;FlightSettingsMaxPauseDays 5.双击&#xff1a;数值数据改为4321 基数&#…...

day05-前后端项目上传到gitee、后端多方式登录接口、发送短信功能、发送短信封装、短信验证码接口、短信登录接口

1 前后端项目上传到gitee 2 后端多方式登录接口 2.1 序列化类 2.2 视图类 2.3 路由 3 发送短信功能 4 发送短信封装 4.0 目录结构 4.1 settings.py 4.2 sms.py 5 短信验证码接口 6 短信登录接口 6.1 视图类 6.2 序列化类 1 前后端项目上传到gitee # 我们看到好多开源项目…...

046:mapboxGL加载天地图路网图+标记(wmts方式)

第046个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载天地图路网图+标记(wmts方式)。瓦片中的url地址引用的是天地图的wmts的形式。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共99行)相关AP…...

【ICer的脚本练习】tcl语法熟悉和工具tcl的实例

系列的目录说明请见:ICer的脚本练习专栏介绍与全流程目录_尼德兰的喵的博客-CSDN博客 前言 TCL(Tool Command Language)是一种简单但功能强大的脚本语言,它经常用于自动化任务、测试和快速原型开发。你看这个名字就能知道,这个语言最主要的作用就是用来操作工具,尤其我们…...

uniapp+vue3+ts+uview-plus搭建项目步骤

创建项目 使用Vue3/Vite版&#xff0c;创建以 typescript 开发的工程 下载仓库 DCloud/uni-preset-vue - Gitee.com node版本&#xff1a;v16.18.0 npm版本&#xff1a; v8.19.2 依赖下载 解压之后&#xff0c;在vscode打开 通过终端运行 npm 命令下载依赖&#xff1a;npm ins…...

在PHP中,可以使用不同的加密算法(如MD5、SHA1、SHA256)结合RSA算法进行公钥加密和私钥解密。

下面是使用这三种算法进行加密和解密的示例代码&#xff1a; // 生成RSA密钥对 $keyPair openssl_pkey_new(array(private_key_bits > 2048,private_key_type > OPENSSL_KEYTYPE_RSA, ));// 获取私钥和公钥 openssl_pkey_export($keyPair, $privateKey); $publicKey o…...

第六章:路由交换机及操作系统

路由交换机及操作系统 一、路由器与交换机的作用与特点1.路由器1.1 作用1.2 特点 2.交换机2.1 作用2.2 特点 二、H3C路由器与交换机介绍1. 路由器2. 交换机 三、 H3C网络设备操作系统Comware1. 介绍2. 特点![在这里插入图片描述](https://img-blog.csdnimg.cn/2b24103028654878…...

Kafka SASL认证授权(六)全方位性能测试

Kafka SASL认证授权(六)全方位性能测试。 官网地址:https://kafka.apache.org/ 一、场景 线上已经有kafka集群,服务运行稳定。但是因为产品升级,需要对kakfa做安全测试,也就是权限验证。 但是增加权限验证,会不会对性能有影响呢?影响大吗?不知道呀! 因此,本文就此…...

基于nodejs+vue校园失物招领平台设计与实现

科学技术日新月异的如今&#xff0c;计算机在生活各个领域都占有重要的作用&#xff0c;尤其在信息管理方面&#xff0c;在这样的大背景下&#xff0c;学习计算机知识不仅仅是为了掌握一种技能&#xff0c;更重要的是能够让它真正地使用到实目 录 摘 要 I ABSTRACT II 目 录 II…...

Open Winding-PMSM-开绕组永磁同步电机基本介绍

文章目录 前言简介Open Widing电机数学模型零序模型 双逆变器调制零序电流抑制基本思路 前言 最近看了些Open Winding永磁同步电机及其控制策略的文献资料&#xff0c;现做个总结。未来的研究方向也大概率围绕Open Winding开展&#xff0c;期待同行交流学习。 简介 开绕组(O…...

uniapp 一次性上传多条视频 u-upload accept=“video“ uni.chooseMedia uni.uploadFile

方式 一 部分安卓机 只能一条一条传视频 文档地址 uview 2.0 Upload 上传组件 html <view class"formupload"><u-upload accept"video":fileList"fileList3" afterRead"afterRead" delete"deletePic" name"…...

CentOS7卸载硬盘报错:umount: /data: target is busy.

问题描述 umount: /data: target is busy. 问题分析 硬盘正在被使用&#xff0c;不能被卸载。 解决方案 查看哪些程序在使用硬盘 [rootlocalhost ~]# lsof /data/ COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME mysqld 15655 polkitd cwd …...

Chrome插件精选 — 鼠标手势插件

Chrome实现同一功能的插件往往有多款产品&#xff0c;逐一去安装试用耗时又费力&#xff0c;在此为某一类型插件记录下比较好用的一款或几款&#xff0c;便于节省尝试的时间和精力。 下面是两款比较好用的鼠标手势插件&#xff0c;支持很多设置选项&#xff0c;可以自定义手势&…...