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

MMKV(1)

  • 内存准备

通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

  • 数据组织

数据序列化方面选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。考虑到要提供的是通用 kv 组件,key 可以限定是 string 字符串类型,value 则多种多样(int/bool/double 等)。要做到通用的话,考虑将 value 通过 protobuf 协议序列化成统一的内存块(buffer),然后就可以将这些 KV 对象序列化到内存中。

message KV {string key = 1;buffer value = 2;
}-(BOOL)setInt32:(int32_t)value forKey:(NSString*)key {auto data = PBEncode(value);return [self setData:data forKey:key];
}-(BOOL)setData:(NSData*)data forKey:(NSString*)key {auto kv = KV { key, data };auto buf = PBEncode(kv);return [self write:buf];
}
  • 写入优化

考虑到主要使用场景是频繁地进行写入更新,需要有增量更新的能力。考虑将增量 kv 对象序列化后,append 到内存末尾。标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新,需要有增量更新的能力:将增量 kv 对象序列化后,直接 append 到内存末尾;这样同一个 key 会有新旧若干份数据,最新的数据在最后;那么只需在程序启动第一次打开 mmkv 时,不断用后读入的 value 替换之前的值,就可以保证数据是最新有效的。

  • 空间增长

使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。需要在性能和空间上做个折中。

以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;

排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。

-(BOOL)append:(NSData*)data {if (space >= data.length) {append(fd, data);} else {newData = unique(m_allKV);if (total_space >= newData.length) {write(fd, newData);} else {while (total_space < newData.length) {total_space *= 2;}ftruncate(fd, total_space);write(fd, newData);}}
}
  • 数据有效性

考虑到文件系统、操作系统都有一定的不稳定性, crc 校验,对无效数据进行甄别。在 iOS 微信现网环境上,有平均约 70万日次的数据校验不通过。

  • Android多进程访问

将 MMKV 迁移到 Android 平台之后,要支持多进程访问, iOS 不支持多进程。

  1. 性能:MMKV在性能方面表现更好。由于采用了内存映射技术,它可以直接在内存中读取和写入数据,减少了磁盘IO操作,因此读写速度更快。相比之下,SharedPreferences是基于XML文件存储的,读取和写入需要进行磁盘IO操作,速度较慢。

  2. 跨进程和跨线程支持:MMKV天然支持跨进程和跨线程的数据共享。多个进程或线程可以同时访问和修改MMKV中的数据,而无需额外的同步操作。而SharedPreferences的跨进程支持较差,需要进行额外的同步机制或使用ContentProvider等方式才能实现跨进程共享。

  3. 存储容量:MMKV支持更大的存储容量。SharedPreferences将所有数据都存储在一个XML文件中,如果数据较多,读取和解析整个文件可能会影响性能。而MMKV将数据划分为多个固定大小的内存页,可以高效地读取和写入大量数据。

  4. 序列化和加密:MMKV提供了数据的序列化和加密功能。它可以将复杂的数据结构序列化为字节数组进行存储,并支持对数据进行加密保护。而SharedPreferences只能存储基本数据类型,对于复杂的数据结构需要进行手动的序列化和反序列化操作。灵活性和易用性:MMKV提供了更灵活和易用的API。它的API设计更加简洁,使用起来更方便,支持链式调用和类型安全。同时,MMKV还提供了一些额外的功能,如数据的版本控制、数据迁移和备份等。

  5. import com.tencent.mmkv.MMKV;public class MMKVExample {public static void main(String[] args) {// 初始化MMKVString rootDir = MMKV.initialize("path_to_directory");// 获取MMKV实例MMKV mmkv = MMKV.defaultMMKV();// 存储数据mmkv.putString("key1", "value1");mmkv.putInt("key2", 123);mmkv.putBoolean("key3", true);// 读取数据String value1 = mmkv.getString("key1", "");int value2 = mmkv.getInt("key2", 0);boolean value3 = mmkv.getBoolean("key3", false);System.out.println("Value 1: " + value1);System.out.println("Value 2: " + value2);System.out.println("Value 3: " + value3);}
    }
    dependencies {implementation 'com.tencent:mmkv:1.0.23'// replace "1.0.23" with any available version
    }

    支持从SP迁移数据importFromSharedPreferences

    MMKV 还额外实现了一遍 SharedPreferences、SharedPreferences.Editor 这两个 interface。

    // 可以跟SP用法一样
    SharedPreferences.Editor editor = mmkv.edit();
    // 无需调用 commit()
    //editor.commit();

    MMKV 的使用非常简单,所有变更立马生效,无需调用 sync、apply。 在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在 MainActivity 里:

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);String rootDir = MMKV.initialize(this);System.out.println("mmkv root: " + rootDir);//……
    }

    MMKV 提供一个全局的实例,可以直接使用:

    import com.tencent.mmkv.MMKV;
    //……MMKV kv = MMKV.defaultMMKV();kv.encode("bool", true);
    boolean bValue = kv.decodeBool("bool");kv.encode("int", Integer.MIN_VALUE);
    int iValue = kv.decodeInt("int");kv.encode("string", "Hello from mmkv");
    String str = kv.decodeString("string");

    使用后:

public native void clearAll();// MMKV's size won't reduce after deleting key-values// call this method after lots of deleting f you care about disk usage// note that `clearAll` has the similar effect of `trim`public native void trim();// call this method if the instance is no longer needed in the near future// any subsequent call to the instance is undefined behaviorpublic native void close();// call on memory warning// any subsequent call to the instance will load all key-values from file againpublic native void clearMemoryCache();// you don't need to call this, really, I mean it// unless you care about out of batterypublic void sync() {sync(true);}
  • 限制

一个键会存入多分实例,最后存入的就是最新的。

MMKV 在大部分情况下都性能强劲,key/value 的数量和长度都没有限制。

然而 MMKV 在内存里缓存了所有的 key-value,在总大小比较大的情况下(例如 100M+),App 可能会爆内存,触发重整回写时,写入速度也会变慢。

锁 lock unlock tryLock

注意如果一个进程lock住,另一个进程mmkvWithID获取MMKV时就阻塞住,直到持有进程释放。

// get the lock immediatelyMMKV mmkv2 = MMKV.mmkvWithID(LOCK_PHASE_2, MMKV.MULTI_PROCESS_MODE);mmkv2.lock();Log.d("locked in child", LOCK_PHASE_2);Runnable waiter = new Runnable() {@Overridepublic void run() {//阻塞住 直到其他进程释放MMKV mmkv1 = MMKV.mmkvWithID(LOCK_PHASE_1, MMKV.MULTI_PROCESS_MODE);mmkv1.lock();Log.d("locked in child", LOCK_PHASE_1);}};

如果其他进程有进行修改,不会立即触发onContentChangedByOuterProcess,

checkLoadData如果变化,会clearMemoryState,重新loadFromFile。//数据量大时不要太频繁

读取decodeXXX会阻塞住,先回调onContentChangedByOuterProcess,再返回值,保证值是最新的。

  • Binder MMAP

Binder MMAP是Android系统中的一种机制,用于在跨进程通信(IPC)中传输大型数据或共享内存区域。

在Android中,进程间通信主要通过Binder框架实现。Binder框架使用Binder驱动程序在不同的进程之间建立通信通道。通常情况下,进程间通信是通过传输小型的数据结构,如整数、字符串等。然而,当需要传输大量数据或者共享内存时,效率会受到限制。

为了解决这个问题,Android引入了Binder MMAP机制。Binder MMAP允许进程通过内存映射(MMAP)的方式共享内存区域,从而实现高效的数据传输。它通过以下步骤实现:

  1. 发送端将数据写入内存区域:发送端将要传输的数据写入一个内存区域,该内存区域通过MMAP映射到物理内存中。

  2. 发送端将内存区域的描述符发送给接收端:发送端将内存区域的描述符(文件描述符)通过Binder传递给接收端。

  3. 接收端获取内存区域描述符并映射到自己的地址空间:接收端通过Binder接收内存区域的描述符,并将其映射到自己的地址空间。

  4. 接收端从内存区域读取数据:接收端可以直接从内存区域中读取发送端写入的数据,而无需进行数据拷贝。

Binder MMAP的优点:

  1. 高效的数据传输:通过内存映射的方式,避免了数据拷贝和序列化/反序列化操作,提高了数据传输的效率。

  2. 支持大型数据和共享内存:适用于传输或共享大量数据或大型内存区域的场景,可以减少内存占用和提高性能。

  3. 跨进程通信:作为Android的进程间通信机制,支持不同进程之间的数据传输,方便实现跨进程功能。

Binder MMAP的缺点:

  1. 复杂性:相比较其他传输方式,使用Binder MMAP需要更多的代码和配置,对开发者来说可能需要更多的学习和理解。

  2. 依赖Binder框架:作为Android系统的一部分,使用Binder MMAP需要依赖于Binder框架,需要遵循Binder框架的规范和约束。

MMKV的优点:

  1. 高性能:相对于SharedPreferences等传统存储方式,MMKV具有更好的读写性能,特别是在高并发操作下表现更出色。

  2. 跨进程支持:MMKV支持跨进程访问,多个进程可以同时读写同一个MMKV实例,方便实现进程间数据共享。

  3. 功能丰富:MMKV提供了丰富的功能,如加密、压缩等,可以满足不同的数据存储需求。

MMKV的缺点:

  1. 存储大小限制:MMKV存储的总大小受限于设备的存储空间,如果存储的数据量较大,可能会占用较多的存储空间。

  2. 适用性受限:由于MMKV是针对键值存储而设计的,适用于存储简单的键值对数据,不适合存储复杂的数据结构。

  • 比sp快的原因

  1. 内存映射(Memory Mapping):MMKV使用了内存映射的技术,将数据直接映射到内存中,而不是像SharedPreferences一样将数据写入磁盘文件。这种内存映射的方式避免了频繁的磁盘读写操作,减少了IO开销,从而提高了读写性能。

  2. 零拷贝(Zero-copy):MMKV利用了内存映射的特性,实现了零拷贝的读写操作。当读取或写入数据时,MMKV直接在内存中进行操作,避免了数据的拷贝和序列化/反序列化操作,进一步提高了读写性能。

  3. 文件锁(File Locking):MMKV使用文件锁机制来保证多个进程对同一个MMKV实例的安全访问。这种文件锁机制可以有效地控制并发访问,避免了数据冲突和竞争条件,提高了并发操作的性能。

  4. 自定义序列化(Custom Serialization):MMKV使用自定义的序列化方式,将数据以二进制的形式存储,而不是SharedPreferences中的XML格式。这种自定义的序列化方式更加高效,减少了存储和解析数据的开销,提高了读写性能。

  • MMKV在android中的使用(替换sp)

在项目根目录下的 build.gradle 文件中加入

dependencies {implementation 'com.tencent:mmkv-static:1.2.10'
}

在项目 app 模块下的 build.gradle 文件中加入 

buildscript {repositories {mavenCentral()//这行依赖}
}
allprojects {repositories {mavenCentral()//这行依赖}
}

在Application中初始化

MMKV.initialize(this)

MMKV 默认把文件存放在$(FilesDir)/mmkv/目录。你可以在 MMKV初始化时自定义根目录:

String dir = getFilesDir().getAbsolutePath() + "/mmkv";
String rootDir = MMKV.initialize(dir);

Kotlin中使用

import com.tencent.mmkv.MMKV;
//……//1. 获取默认全局实例 (与下面的几选一,一般就使用这个就行)
var mmkv: MMKV = MMKV.defaultMMKV()//2. 也可以自定义MMKV对象,设置自定ID  (根据业务区分的存取实例)
var mmkv: MMKV = MMKV.mmkvWithID("ID")//3. MMKV默认是支持单进程的,如果业务需要多进程访问,需要在初始化的时候添加多进程模式参数
var mmkv = MMKV.mmkvWithID("ID", MMKV.MULTI_PROCESS_MODE)  //多进程同步支持

存取方法

// 添加/更新数据
mmkv?.encode(key, value);// 获取数据
int value = mmkv.decodeInt(key);
String value = mmkv.decodeString(key);
//...获取等类型// 删除数据
mmkv.removeValueForKey(key);

如果需要存取对象,可以用存取对象json字符串的方法,将对象转成json存,取出json转回对象。

SP迁移

MMKV可以调用importFromSharedPreferences方法进行SP的数据迁移,示例代码如下: MMKV实现了SharedPreferences,Editor两个接口,所以在迁移之后SP的操作代码可以不用更改。

val mmkv = MMKV.mmkvWithID("myData")
val olderData = DemoApplication.mContext?.getSharedPreferences("myData", MODE_PRIVATE)
mmkv?.importFromSharedPreferences(olderData)
olderData?.edit()?.clear()?.apply()
  • Java中的使用

MMKV 提供一个全局的实例,可以直接使用

import com.tencent.mmkv.MMKV;
//……//1. 获取默认全局实例 (一般就使用这个就行)
MMKV kv = MMKV.defaultMMKV();//2. 也可以自定义MMKV对象,设置自定ID  (根据业务区分的存取实例)
MMKV kv = MMKV.mmkvWithID("ID");//3. MMKV默认是支持单进程的,如果业务需要多进程访问,需要在初始化的时候添加多进程模式参数
MMKV kv = MMKV.mmkvWithID("ID", MMKV.MULTI_PROCESS_MODE); //多进程同步支持

存取方法

/** 添加/更新数据 **/
//存boolean类型
kv.encode("bool", true);
//存int类型
kv.encode("int", Integer.MIN_VALUE);
//存string类型
kv.encode("string", "MyiSMMKV");/** 获取数据 **/
//获取boolean类型数据
boolean bValue = kv.decodeBool("bool");
//获取int类型数据
int iValue = kv.decodeInt("int");
//获取string类型数据
String str = kv.decodeString("string");
//...等类型的获取// 删除数据
mmkv.removeValueForKey(key);

如果需要存取对象,可以用存取对象json字符串的方法,将对象转成json存,取出json转回对象。

SP迁移

MMKV kv = MMKV.mmkvWithID("myData");
SharedPreferences olderData = App.getInstance().getSharedPreferences("myData", MODE_PRIVATE);
kv.importFromSharedPreferences(olderData);
olderData.edit().clear().apply();

相关文章:

MMKV(1)

内存准备 通过 mmap 内存映射文件&#xff0c;提供一段可供随时写入的内存块&#xff0c;App 只管往里面写数据&#xff0c;由操作系统负责将内存回写到文件&#xff0c;不必担心 crash 导致数据丢失。 数据组织 数据序列化方面选用 protobuf 协议&#xff0c;pb 在性能和空…...

centos 7.9 源码安装htop

1.下载源码 wget http://sourceforge.net/projects/htop/files/latest/download 2.上传到tmp目录&#xff0c;并解压 tar xvzf htop-1.0.2.tar.gz mv htop-1.0.2 /opt/ 进入到 cd /opt/htop-1.0.2/ 3.编译并安装 ./configure && make && make install 4.…...

Element UI之Button 按钮

Button 按钮 常用的操作按钮。 按需引入方式 如果是完整引入可跳过此步骤 import Vue from vue import { Button } from element-ui import element-ui/lib/theme-chalk/base.css import element-ui/lib/theme-chalk/button.css import element-ui/lib/theme-chalk/icon.cs…...

dig 简明教程

哈喽大家好&#xff0c;我是咸鱼 不知道大家在日常学习或者工作当中用 dig 命令多不多 dig 是 Domain Information Groper 的缩写&#xff0c;对于网络管理员和在域名系统(DNS)领域工作的小伙伴来说&#xff0c;它是一个非常常见且有用的工具。 无论是简单的 DNS 解析查找还…...

深度分析AMQP以及在rabbitMQ中的应用

文章目录 AMQP是什么AMQP在rabbitMQ中的应用AMQP协议的三层AMQP的三大组件AMQP的连接信道RabbitMQ 如何实现信道&#xff1a; AMQP是什么 AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;是一种开放标准的消息队列协议。它提供了一个统一的、可靠的、异步的消…...

GB/T 28627-2023 抹灰石膏检测

抹灰石膏是指以半水石膏、Ⅱ型无水石膏单独或两者混合后作为主要胶凝材料&#xff0c;掺入集料和外加剂制成的用于建筑物室内墙面和顶棚基底抹灰找平用的石膏砂浆。 GB/T 28627-2023抹灰石膏检测项目&#xff1a; 测试项目 测试方法 凝结时间 GB/T 28627 保水率 GB/T 286…...

JDK版本和Gradle版本配套关系

Java versionSupport for compiling/testing/…​Support for running Gradle 8 N/A 2.0 9 N/A 4.3 10 N/A 4.7 11 N/A 5.0 12 N/A 5.4 13 N/A 6.0 14 N/A 6.3 15 6.7 6.7 16 7.0 7.0 17 7.3 7.3 18 7.5 7.5 19 7.6 7.6 20 8.1 8.3 21 …...

在Linux中,怎么查看自己电脑的系统架构是什么?

2023年10月18日&#xff0c;周三晚上 这些命令会返回一个字符串&#xff0c;表示系统的架构。 常见的架构包括 x86&#xff08;32位&#xff09;、x86_64&#xff08;64位&#xff09;、ARM 等。 方法1&#xff1a;使用uname命令 uname -m方法2&#xff1a;使用arch命令 ar…...

自5月以来,俄罗斯Sandworm黑客侵入了11家乌克兰电信公司

导语&#xff1a;据乌克兰计算机应急响应团队&#xff08;CERT-UA&#xff09;的最新报告称&#xff0c;自2023年5月至9月&#xff0c;俄罗斯政府支持的黑客组织Sandworm成功侵入了乌克兰的11家电信服务提供商。这一组织被认为与俄罗斯武装部队的GRU有关。 简介 根据乌克兰计算…...

怎样做好接口自动化测试?

今天介绍一下在接口自动化测试相关实践中总结到的一些经验。 接口自动化测试的目的 自动化测试的主要目的是用来回归测试的&#xff0c;当代码有变化时&#xff0c;有可能影响不应该变化的逻辑&#xff0c;这个时候为了确认这种情况&#xff0c;就需要进行回归测试。有时候回…...

Leetcode刷题详解——找到字符串中所有字母异位词

1. 题目链接&#xff1a;438. 找到字符串中所有字母异位词 2. 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括…...

Android 自定义view 圆形进度条

Android 自定义view 圆形进度条 前言一、码前分析二、开码1.画笔2.弧度3.圆弧的位置4.暴露给外部设置进度条的方法三、使用四、完整代码 总结 前言 先来看看效果&#xff0c;大概要实现这么一个圆形的进度条 一、码前分析 要实现这么一个进度条的效果&#xff0c;实际上是要画…...

混凝土基础的智能设计:VisualFoundation 12.0 Crack

实现混凝土基础的智能设计:工程师依靠 VisualFoundation:使用这个专注的工具可以更轻松、更强大地对基础进行建模。通用 FEA 工具&#xff08;如VisualAnalysis&#xff09;可以做很多事情&#xff0c;但对于特定于基础的工程来说&#xff0c;这更快、更智能。 草图边界 快速绘…...

C++中成员函数的重载覆盖与隐藏

1.重载与覆盖 重载&#xff1a;成员函数被重载的特征&#xff1a;在同一个类中&#xff0c;函数名相同&#xff0c;参数不同&#xff0c;vritual关键字可有可无。 覆盖&#xff1a;覆盖是指派生类函数覆盖基类函数&#xff0c;特征是&#xff1a;在有继承关系的类中&#xff0…...

电子器件系列49:CD4050B缓冲器

同相和反向缓冲器 还搞不懂缓冲电路&#xff1f;看这一文&#xff0c;工作原理作用电路设计使用方法 - 知乎 (zhihu.com) 缓冲器_百度百科 (baidu.com) 1、缓冲器的定义 缓冲器是数字元件的其中一种&#xff0c;它对输入值不执行任何运算&#xff0c;其输出值和输入值一样&…...

Leetcode 349 两个数组的交集 (哈希表)

Leetcode 349 两个数组的交集 &#xff08;哈希表&#xff09; 解法1 &#x1f60b;解法2 解法1 &#x1f60b; 自己的笨比方法:【哇这居然是标准解法之一&#xff0c;我不是笨比&#x1f60b;&#x1f60b;&#x1f60b;】 创建了两个hash数组&#xff0c;nums1出现一个就对应…...

基于YOLOv8模型的水下目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型的水下目标检测系统可用于日常生活中检测与定位鱼、水母、企鹅、海鹦、鲨鱼、海星、黄貂鱼&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统…...

vue-cli脚手架创建项目时报错Error: command failed: npm install --loglevel error

项目背景 环境&#xff1a;vue-cli 5.x 在工程文件中&#xff0c;后端模块wms已经创建完成&#xff0c;现在想新建一个名为vue-web的前端模块 执行命令vue create vue-web时&#xff0c; 报错Error: command failed: npm install --loglevel error 问题分析及解决 排查过程…...

c语言练习92:链表的中间结点

链表的中间结点 链表的结点为空时无法访问其next成员否则会报错 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; struct ListNode* middleNode(struct ListNode* head){if(h…...

CentOS(4)——关于Linux软件下载时:amd64、x86、x86_64、arm64 的说明

目录 一、简介 二、常见的CPU架构 三、Linux查看CPU架构命令 ①arch命令 ②uname -a 命令 ③lscpu 一、简介 在安装GitLab Runner的时候&#xff0c;去清华源下载RPM包时发现同一个软件有许多不同架构的安装包&#xff0c;常见的有amd64、x86、x86_64、arm64这些架构&am…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...

LOOI机器人的技术实现解析:从手势识别到边缘检测

LOOI机器人作为一款创新的AI硬件产品&#xff0c;通过将智能手机转变为具有情感交互能力的桌面机器人&#xff0c;展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家&#xff0c;我将全面解析LOOI的技术实现架构&#xff0c;特别是其手势识别、物体识别和环境…...