【Rust笔记】Rust与Java交互-JNI模块编写-实践总结
近期工作中有Rust和Java互相调用需求,这篇文章主要介绍如何用Rust通过JNI和Java进行交互,还有记录一下开发过程中遇到的一些坑。
JNI简单来说是一套Java与其他语言互相调用的标准,主要是C语言,官方也提供了基于C的C++接口。 既然是C语言接口,那么理论上支持C ABI的语言都可以和Java语言互相调用,Rust就是其中之一。
关于JNI的历史背景以及更详细的介绍可以参考官方文档
在Rust中和Java互相调用,可以使用原始的JNI接口,也就是自己声明JNI的C函数原型,在Rust里按照C的方式去调用,但这样写起来会很繁琐,而且都是unsafe的操作; 不过Rust社区里已经有人基于原始的JNI接口,封装好了一套safe的接口,crate的名字就叫jni,用这个库来开发就方便多了
文中涉及的代码放在了这个github仓库 https://github.com/metaworm/rust-java-demo
Rust JNI 工程配置
如果你熟悉Cargo和Maven,可以跳过这一节,直接看我提供的github源码即可
Rust工程配置
首先,通过cargo new java-rust-demo创建一个rust工程
然后切换到工程目录cd java-rust-demo,并编辑Cargo.toml:修改类型为动态库、加上对 jni crate 的依赖
[package]
name = "rust-java-demo"
version = "0.1.0"
edition = "2021"[lib]
crate-type = ['cdylib'][dependencies]
jni = {version = '0.19'} 重命名src目录下的main.rs为lib.rs,Rust库类型的工程编译入口为 lib.rs,然后添加以下代码
use jni::objects::*;
use jni::JNIEnv;#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_init(env: JNIEnv, _class: JClass) {println!("rust-java-demo inited");
} 然后执行cargo build构建,生成的动态库默认会位于target/debug目录下,我这里用的linux系统,动态库文件名为librust_java_demo.so,如果是Windows系统,文件名为rust_java_demo.dll
这样,我们第一个JNI函数就创建成功了! 通过Java_pers_metaworm_RustJNI_init这个导出函数,给了Java的pers.metaworm.RustJNI这个类提供了一个native的静态方法init; 这里只是简单地打印了一句话,后面会通过这个初始化函数添加更多的功能
Java工程配置
还是在这个工程目录里,把Java部分的代码放在java这个目录下,在其中创建pers/metaworm/RustJNI.java文件
package pers.metaworm;public class RustJNI {static {System.loadLibrary("rust_java_demo");}public static void main(String[] args) {init();}static native void init();
} 我们使用流行的 maven 工具来构建Java工程,在项目根目录下创建 maven 的工程文件 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>pers.metaworm</groupId><artifactId>RustJNI</artifactId><version>1.0-SNAPSHOT</version><properties><exec.mainClass>pers.metaworm.RustJNI</exec.mainClass><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.encoding>UTF-8</maven.compiler.encoding></properties><dependencies></dependencies><build><sourceDirectory>java</sourceDirectory><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.4</version><configuration><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
</project> 运行 DMEO 工程
上面的工程配置弄好之后,就可以使用cargo build命令构建Rust提供的JNI动态库,mvn compile命令来编译Java代码
Rust和Java代码都编译好之后,执行java -Djava.library.path=target/debug -classpath target/classes pers.metaworm.RustJNI来运行
其中-Djava.library.path=target/debug指定了我们JNI动态库所在的路径,-classpath target/classes指定了Java代码的编译输出的类路径,pers.metaworm.RustJNI是Java main方法所在的类
不出意外的话,运行之后会在控制台输出init函数里打印的"rust-java-demo inited"
Java调用Rust
接口声明
前面的Java_pers_metaworm_RustJNI_init函数已经展示了如何给Java暴露一个native方法,即导出名称为Java_<类完整路径>_<方法名>的函数,然后在Java对应的类里声明对应的native方法
拓展:除了通过导出函数给Java提供native方法,还可以通过 RegisterNatives 函数动态注册native方法,对应的jni封装的函数为JNIEnv::register_native_methods,一般动态注册会在JNI_Onload这个导出函数里执行,jvm加载jni动态库时会执行这个函数(如果有的话)
当在Java里首次调用native方法时,JVM就会寻找对应名称的导出的或者动态注册的native函数,并将Java的native方法和Rust的函数关联起来;如果JVM没找到对应的native函数,则会报java.lang.UnsatisfiedLinkError异常
为了演示,我们再添加一些代码来覆盖更多的交互场景
lib.rs
use jni::objects::*;
use jni::sys::{jint, jobject, jstring};
use jni::JNIEnv;#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_addInt(env: JNIEnv,_class: JClass,a: jint,b: jint,
) -> jint {a + b
}#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_getThisField(env: JNIEnv,this: JObject,name: JString,sig: JString,
) -> jobject {let result = env.get_field(this,&env.get_string(name).unwrap().to_string_lossy(),&env.get_string(sig).unwrap().to_string_lossy(),).unwrap();result.l().unwrap().into_inner()
} RustJNI.java
package pers.metaworm;public class RustJNI {static {System.loadLibrary("rust_java_demo");}public static void main(String[] args) {init();System.out.println("test addInt: " + (addInt(1, 2) == 3));RustJNI jni = new RustJNI();System.out.println("test getThisField: " + (jni.getThisField("stringField", "Ljava/lang/String;") == jni.stringField));System.out.println("test success");}String stringField = "abc";static native void init();static native int addInt(int a, int b);native Object getThisField(String name, String sig);
} 其中,addInt方法接收两个int参数,并返回相加的结果;getThisField是一个实例native方法,它获取this对象指定的字段并返回
参数传递
从上一节的例子里可以看到,jni函数的第一个参数总是JNIEnv,很多交互操作都需要通过这个对象来进行; 第二个参数是类对象(静态native方法)或this对象(实例native方法); 从第三个参数开始,每一个参数对应Java的native方法所声明的参数
对于基础的参数类型,可以直接用use jni::sys::*提供的j开头的系列类型来声明,类型对照表:
| Java 类型 | Native 类型 | 类型描述 |
|---|---|---|
| boolean | jboolean | unsigned 8 bits |
| byte | jbyte | signed 8 bits |
| char | jchar | unsigned 16 bits |
| short | jshort | signed 16 bits |
| int | jint | signed 32 bits |
| long | jlong | signed 64 bits |
| float | jfloat | 32 bits |
| double | jdouble | 64 bits |
| void | void | not applicable |
对于引用类型(复合类型/对象类型),可以统一用jni::objects::JObject声明;JObject是对jobject的rust封装,带有生命周期参数;对于String类型,也可以用 JString 来声明,JString是对JObject的一层简单封装
抛异常
前面的Java_pers_metaworm_RustJNI_getThisField函数里,用了很多unwrap,这在生产环境中是非常危险的,万一传了一个不存在的字段名,就直接crash了;所以我们改进一下这个函数,让他支持抛异常,出错的时候能让Java捕获到
#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_getThisFieldSafely(env: JNIEnv,this: JObject,name: JString,sig: JString,
) -> jobject {let result = (|| {env.get_field(this,&env.get_string(name)?.to_string_lossy(),&env.get_string(sig)?.to_string_lossy(),)?.l()})();match result {Ok(res) => res.into_inner(),Err(err) => {env.exception_clear().expect("clear");env.throw_new("Ljava/lang/Exception;", format!("{err:?}")).expect("throw");std::ptr::null_mut()}}
} Java层的测试代码为
try {System.out.println("test getThisFieldSafely: " + (jni.getThisFieldSafely("stringField", "Ljava/lang/String;") == jni.stringField));jni.getThisFieldSafely("fieldNotExists", "Ljava/lang/String;");} catch (Exception e) {System.out.println("test getThisFieldSafely: catched exception: " + e.toString());} 通过env.throw_new("Ljava/lang/Exception;", format!("{err:?}"))抛出了一个异常,从JNI函数返回后,Java就会捕获到这个异常; 代码里可以看到在抛异常之前,调用了env.exception_clear()来清除异常,这是因为前面的get_field已经抛出一个异常了,当env里已经有一个异常的时候,后续再调用env的函数都会失败,这个异常也会继续传递到上层的Java调用者,所以其实这里没有这两句,直接返回null的话,Java也可以捕获到异常;但我们通过throw_new可以自定义异常类型及异常消息
这其实不是一个典型的场景,典型的场景应该是Rust里的某个调用返回了Error,然后通过抛异常的形式传递到Java层,比如除0错误
#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_divInt(env: JNIEnv,_class: JClass,a: jint,b: jint,
) -> jint {if b == 0 {env.throw_new("Ljava/lang/Exception;", "divide zero").expect("throw");0} else {a / b}
} Rust调用Java
创建对象、调用方法、访问字段...
下面用一段代码展示如何在Rust中创建Java对象、调用方法、获取字段、处理异常等常见用法
#[allow(non_snake_case)]
fn call_java(env: &JNIEnv) {match (|| {let File = env.find_class("java/io/File")?;// 获取静态字段let separator = env.get_static_field(File, "separator", "Ljava/lang/String;")?;let separator = env.get_string(separator.l()?.into())?.to_string_lossy().to_string();println!("File.separator: {}", separator);assert_eq!(separator, format!("{}", std::path::MAIN_SEPARATOR));// env.get_static_field_unchecked(class, field, ty)// 创建实例对象let file = env.new_object("java/io/File","(Ljava/lang/String;)V",&[JValue::Object(env.new_string("")?.into())],)?;// 调用实例方法let abs = env.call_method(file, "getAbsolutePath", "()Ljava/lang/String;", &[])?;let abs_path = env.get_string(abs.l()?.into())?.to_string_lossy().to_string();println!("abs_path: {}", abs_path);jni::errors::Result::Ok(())})() {Ok(_) => {}// 捕获异常Err(jni::errors::Error::JavaException) => {let except = env.exception_occurred().expect("exception_occurred");let err = env.call_method(except, "toString", "()Ljava/lang/String;", &[]).and_then(|e| Ok(env.get_string(e.l()?.into())?.to_string_lossy().to_string())).unwrap_or_default();env.exception_clear().expect("clear exception");println!("call java exception occurred: {err}");}Err(err) => {println!("call java error: {err:?}");}}
}#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_callJava(env: JNIEnv) {println!("call java");call_java(&env)
} 总结一下常用的函数,具体用法可以参考JNIEnv的文档
创建对象
new_object创建字符串对象
new_string调用方法
call_methodcall_static_method获取字段
get_fieldget_static_field修改字段
set_fieldset_static_field
要注意的是调用方法、创建对象等需要传一个方法类型签名,这是因为Java支持方法重载,同一个类里一个名称的函数可能有多个,所以需要通过类型签名来区分,类型签名的规则可以参考官方文档
异常处理
call_java函数展示了如何在Rust中处理Java的异常: 通过JNIEnv对象动态获取字段或者调用方法,都会返回一个jni::errors::Result类型,对应的Error类型为jni::errors::Error;如果Error是jni::errors::Error::JavaException则表明在JVM执行过程中,某个地方抛出了异常,这种情况下就可以用exception_occurred函数来获取异常对象进行处理,然后调用exception_clear来清除异常,如果再返回到Java便可以继续执行
在非Java线程中调用Java
从Java中调用的Rust代码,本身就处于一个Java线程中,第一个参数为JNIEnv对象,Rust代码用这个对象和Java进行交互; 实际应用场景中,可能需要从一个非Java线程或者说我们自己的线程中去调用Java的方法,但我们的线程没有JNIEnv对象,这时就需要调用JavaVM::attach_current_thread函数将当前线程附加到JVM上,来获得一个JNIEnv
#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_callJavaThread(env: JNIEnv) {let vm = env.get_java_vm().expect("get jvm");std::thread::spawn(move || {println!("call java in another thread");let env = vm.attach_current_thread().expect("attach");call_java(&env);});
} attach_current_thread函数返回一个AttachGuard对象,可以解引用为JNIEnv,并且在作用域结束drop的时候自动调用detach_current_thread函数;原始的AttachCurrentThreadJNI函数,如果当前线程已经attach了,则会抛异常,jni crate里的JavaVM::attach_current_thread做了一层封装,如果当前已经attach了,则会返回之前attach的对象,保证不会重复attach
JavaVM对象通过JNIEnv::get_java_vm函数获取,可以在初始化的时候将这个变量存起来,给后续的其他线程使用
局部引用、全局引用与对象缓存
关于局部引用与全局引用的官方文档
Rust提供的native函数,传过来的对象引用都是局部引用,局部引用只在本次调用JNI调用范围内有效,而且不能跨线程使用;如果跨线程,必须使用全局引用
可以通过JNIEnv::new_global_ref来获取JClass、JObject的全局引用,这个函数返回一个GlobalRef对象,可以通过GlobalRef::as_object转成JObject或者JClass等对象;GlobalRef对象drop的时候,会调用DeleteGlobalRef将JVM内部的引用删除
前面的代码,从Rust调用Java方法都是通过名称加方法签名调用的,这种方式,写起来很舒服,但运行效率肯定是非常低的,因为每次都要通过名称去查找对应的方法
其实JNI原始的C接口,是通过jobjectID、jclassID、jmethodID、jfieldID来和Java交互的,只不过是jni crate给封装了一层比较友好的接口
如果我们对性能要求比较高,则可以在初始化的时候获取一些JClass、JObject的全局引用,缓存起来,后面再转成JClass、JObject来使用,千万不要对jmethodID、jfieldID获取全局引用,因为这俩都是通过jclassID生成的,其声明周期和jclassID对应的对象相同,不是需要GC的对象,如果对jmethodID获取全局引用然后调用,会导致某些JVM Crash;对于jmethodID、jfieldID,则可以基于JClass、JObject的全局引用获取,后面直接使用即可
获取到这些全局的ID之后,就可以通过JNIEnv::call_method_unchecked系列函数,来更高效地调用Java
我用Rust强大的宏,实现了这个过程,可以让我们直接在Rust中以声明的方式缓存的所需类及其方法ID
#[allow(non_snake_case)]
pub mod cache {use anyhow::Context;use jni::errors::Result as JniResult;use jni::objects::*;use jni::JNIEnv;pub fn method_global_ref<'a>(env: JNIEnv<'a>,class: JClass,name: &str,sig: &str,) -> JniResult<JMethodID<'a>> {let method = env.get_method_id(class, name, sig)?.into_inner();Ok(JMethodID::from(method.cast()))}pub fn static_method_global_ref<'a>(env: JNIEnv<'a>,class: JClass,name: &str,sig: &str,) -> ::jni::errors::Result<JStaticMethodID<'a>> {let method = env.get_static_method_id(class, name, sig)?.into_inner();Ok(JStaticMethodID::from(method.cast()))}macro_rules! gen_global_ref {(@method_type) => { JMethodID<'static> };(@method_type static) => { JStaticMethodID<'static> };(@method_ref) => { method_global_ref };(@method_ref static) => { static_method_global_ref };($(#[name = $classname:literal]class $name:ident {$($method:ident : $($modify:ident)* $sig:literal,)*})*) => {$(#[allow(non_snake_case)]pub struct $name {pub class: JClass<'static>,$(pub $method: gen_global_ref!(@method_type $($modify)*),)*}impl $name {pub fn from_env(env: JNIEnv<'static>) -> anyhow::Result<Self> {Self::from_class(env, env.find_class($classname)?)}pub fn from_class(env: JNIEnv<'static>, class: JClass) -> anyhow::Result<Self> {let cls = env.new_global_ref(class)?;let class = JClass::from(*cls.as_obj());core::mem::forget(cls);Ok(Self {class,$($method: gen_global_ref!(@method_ref $($modify)*)(env, class, stringify!($method), $sig).context(stringify!($method))?,)*})}}// TODO: impl Drop)*pub struct CachedClasses {$(pub $name: $name,)*}impl CachedClasses {pub fn from_env(env: JNIEnv<'static>) -> anyhow::Result<Self> {Ok(Self {$($name: $name::from_env(env).context(stringify!($name))?,)*})}}unsafe impl Sync for CachedClasses {}unsafe impl Send for CachedClasses {}}}gen_global_ref! {#[name = "java/lang/Thread"]class Thread {currentThread: static "()Ljava/lang/Thread;",getStackTrace: "()[Ljava/lang/StackTraceElement;",}#[name = "java/lang/StackTraceElement"]class StackTraceElement {getLineNumber: "()I",toString: "()Ljava/lang/String;",}#[name = "java/io/File"]class File {getAbsolutePath: "()Ljava/lang/String;",}}static mut CLASSES: Option<Box<CachedClasses>> = None;pub unsafe fn init(env: JNIEnv<'static>) -> anyhow::Result<Option<Box<CachedClasses>>> {Ok(CLASSES.replace(CachedClasses::from_env(env)?.into()))}pub fn get() -> &'static CachedClasses {unsafe { CLASSES.as_ref().expect("Cached Java Classed not inited") }}
}
相关文章:
【Rust笔记】Rust与Java交互-JNI模块编写-实践总结
近期工作中有Rust和Java互相调用需求,这篇文章主要介绍如何用Rust通过JNI和Java进行交互,还有记录一下开发过程中遇到的一些坑。 JNI简单来说是一套Java与其他语言互相调用的标准,主要是C语言,官方也提供了基于C的C接口。 既然是C…...
uniapp:幸运大转盘demo
<template><view class"index"><image src"../../static/img/158.png" mode"" class"banner"></image><view class"title">绿色积分加倍卡拿到手软</view><almost-lottery :lottery…...
android 13.0 通过系统自定义服务控制屏幕亮屏和灭屏操作
1.前言 在13.0的产品开发中, 需要提供亮屏和灭屏的接口在8.0以后系统对于屏幕亮灭屏做了限制,直接调用亮屏和灭屏的方法就调不到了,所有就需要通过增加自定义服务的功能,来实现 通过系统服务的方法来调用系统关于控制屏幕亮屏灭屏的相关操作 2.通过系统自定义服务控制屏幕…...
【SQL】新建库表时,报错attempt to write a readonly database
目录 1.问题背景 2.问题原因 3.解决方式 4.结果 windows64位 Navicat sql vscode c 1.问题背景 需求是这样: 希望在调用初始化数据库方法时,查看是否有名为【POCT_PROCESS_CONFIG】的数据库表,如果没有就新建 我的数据库格式是这样 …...
C++ --STL
STL STL(Standard Template Library,标准模板库)STL从广义上分为: 容器(container)算法 (algorithm)迭代器 (iterator) 容器 和 算法之间通过迭代器进行无缝连接。STL几乎所有的代码都采用模板类或者模板函数 1、ST…...
一卷到底,大明哥带你横扫 Netty
上一个死磕 Java 专栏【死磕 NIO】(当然写的不是很好,争取今年将它重写一遍)是**【死磕 Netty】**的铺垫,对于我们 Java 程序员而言,我们在实际开发过程一般都不会直接使用 Java NIO 作为我们的网络编程框架,因为写出一套高质量的…...
Python Opencv实践 - 车辆统计(1)读取视频,移除背景,做预处理
示例中的图像的腐蚀、膨胀和闭运算等需要根据具体视频进行实验得到最佳效果。代码仅供参考。 import cv2 as cv import numpy as np#读取视频文件 video cv.VideoCapture("../../SampleVideos/Traffic.mp4") FPS 10 DELAY int(1000 / FPS) kernel cv.getStructu…...
ROS-6.参数的使用
参数的使用 参数服务结构命令行的使用方式运行小海龟命令介绍查看参数获取参数值设置参数保存参数到文件从文件导入参数 通过程序操作创建节点修改cmake编译运行 参数服务结构 ros中存在参数服务管理服务,管理这所有参数,所有节点剋订阅和发布这些节点 …...
机器视觉在自动驾驶汽车中的应用与挑战
机器视觉在自动驾驶汽车中扮演着至关重要的角色,它使车辆能够感知和理解周围环境,以便自主驾驶。以下是机器视觉在自动驾驶汽车中的应用以及相关挑战: 应用: 障碍物检测与避让: 机器视觉系统可以检测和识别路上的障碍…...
欠拟合、过拟合及优化:岭回归
问题:训练数据训练的很好啊,误差也不大,为什么在测试集上面有问题呢? 当算法在某个数据集当中出现这种情况,可能就出现了过拟合现象。 1、 什么是过拟合与欠拟合 欠拟合 过拟合 分析 第一种情况:因为机器学习到的天鹅特征太少了,导致区分标准太粗糙,不能准确识别出天鹅…...
Mybatis学习笔记注解/xml映射/动态SQL%%%Mybatis教程
介绍 Mybatis 是一款优秀的持久层框架,用于简化 JDBC 的开发 MyBatis中文网 Mybatis 入门 快速入门 步骤 创建 SpringBoot 工程、数据库表 user、实体类 User引入 Mybatis 相关依赖,配置 Mybatis(数据库连接信息)编写 SQL 语…...
Git纯操作版 项目添加和提交、SSH keys添加、远程仓库控制、冲突解决、IDEA连接使用
Git 文章目录 Git项目简单克隆通用操作添加和提交回滚分支变基分支优选 远程项目推送认证抓取、拉取和冲突解决 IEDA类软件连接 最近学原理学的快头秃了,特此想出点不讲原理的纯操作版,不过还是放个图吧 项目简单克隆 git在本人日常中最重要的功能还是…...
使用OpenSSL生成自签证书
什么是OpenSSL OpenSSL是一个开源的软件库和工具套件,用于安全地处理网络数据传输中的加密、解密、安全套接层(SSL)以及传输层安全(TLS)协议等功能。它广泛应用于网站和互联网服务中,以确保数据传输的安全…...
Spring源码解析——Spring事务是怎么通过AOP实现的?
正文 此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理。最全面的Java面试网站 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置:<tx:annot…...
机器人革命:脑洞大开的前沿机器人技术!
原创 | 文 BFT机器人 01 由生物启发的多模式移动形态机器人 在一个不断运动的世界中,一种新开发的名为M4(多模式移动形态机器人)的机器人展示了在包括滚动、飞行和行走在内的八种不同运动模式之间切换的能力。这款机器人由加州理工学院自主…...
微信小程序动态海报
参考文献: 微信小程序生成分享海报(附带二维码生成) - 简书 需求背景: 微信小程序固定图片,无法自动链接,分享页面内容 解决方案: 拆分海报内容,由以下几个组成 1、用户图像 …...
手写单例模式
一、单例模式的定义 定义: 确保一个类只有一个实例,并提供该实例的全局访问点。 这样做的好处是:有些实例,全局只需要一个就够了,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗…...
介绍6种解决电脑找不到vcomp140.dll,无法继续执行代码的方法。
在编程和软件开发领域,我们经常会遇到各种错误和问题。其中,找不到vcomp140.dll文件导致无法继续执行代码是一个非常常见的问题。这个问题可能会影响到软件的正常运行,甚至导致整个项目延期。因此,我们需要找到解决方案来解决这个…...
mysql数据物理迁移
文章目录 一、mysql数据物理迁移1.1 物理迁移 一、mysql数据物理迁移 1.1 物理迁移 速度快,需要停机 进入数据库,查看数据存放位置: select datadir; 一般默认存放在/var/lib/mysql 停机数据库,防止有写入数据 systemctl stop …...
构建图像金字塔:探索 OpenCV 的尺度变换技术
构建图像金字塔:探索 OpenCV 的尺度变换技术 引言什么是图像金字塔?为什么需要图像金字塔?构建高斯金字塔构建拉普拉斯金字塔图像金字塔的应用示例:在不同尺度下检测图像中的边缘 结论 引言 在计算机视觉领域,图像金字…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...
理想汽车5月交付40856辆,同比增长16.7%
6月1日,理想汽车官方宣布,5月交付新车40856辆,同比增长16.7%。截至2025年5月31日,理想汽车历史累计交付量为1301531辆。 官方表示,理想L系列智能焕新版在5月正式发布,全系产品力有显著的提升,每…...
EC2安装WebRTC sdk-c环境、构建、编译
1、登录新的ec2实例,证书可以跟之前的实例用一个: ssh -v -i ~/Documents/cert/qa.pem ec2-user70.xxx.165.xxx 2、按照sdk-c demo中readme的描述开始安装环境: https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c 2…...
