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

JNI原理及常用方法概述

1.1 JNI(Java Native Interface)

提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术。

img

1.2 NDK(Native Development Kit)

Android NDK 是一组允许您将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集。 能够在 Android 应用中使用原生代码对于想执行以下一项或多项操作的开发者特别有用:

  • 在平台之间移植其应用。
  • 重复使用现有库,或者提供其自己的库供重复使用。
  • 在某些情况下提高性能,特别是像游戏这种计算密集型应用。

1.3 JNI方法注册

1.3.1静态注册

当Java层调用navtie函数时,会在JNI库中根据函数名查找对应的JNI函数。如果没找到,会报错。如果找到了,则会在native函数与JNI函数之间建立关联关系,其实就是保存JNI函数的函数指针。下次再调用native函数,就可以直接使用这个函数指针。

  1. JNI函数名格式(需将”.”改为”_”):

Java_ + 包名(com.example.auto.jnitest)+ 类名(MainActivity) + 函数名(stringFromJNI)

  1. 静态方法的缺点:
  • 要求JNI函数的名字必须遵循JNI规范的命名格式;
  • 名字冗长,容易出错;
  • 初次调用会根据函数名去搜索JNI中对应的函数,会影响执行效率;
  • 需要编译所有声明了native函数的Java类,每个所生成的class文件都要用javah工具生成一个头文件;

1.3.2动态注册

通过提供一个函数映射表,注册给JVM虚拟机,这样JVM就可以用函数映射表来调用相应的函数,就不必通过函数名来查找需要调用的函数。

  1. Java与JNI通过JNINativeMethod的结构来建立函数映射表,它在jni.h头文件中定义,其结构内容如下:
typedef struct {const char* name;const char* signature;void*       fnPtr;
} JNINativeMethod;
  1. 创建映射表后,调用RegisterNatives函数将映射表注册给JVM;
  2. 当Java层通过System.loadLibrary加载JNI库时,会在库中查JNI_OnLoad函数。可将JNI_OnLoad视为JNI库的入口函数,需要在这里完成所有函数映射和动态注册工作,及其他一些初始化工作。

1.4 数据类型转换

1.4.1 基础数据类型转换

img

1.4.2 引用数据类型转换

除了Class、String、Throwable和基本数据类型的数组外,其余所有Java对象的数据类型在JNI中都用jobject表示。Java中的String也是引用类型,但是由于使用频率较高,所以在JNI中单独创建了一个jstring类型。

img

  • 引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用;
  • 多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;
    例如,二维整型数组就是指向一位数组的数组,其声明使用方式如下:
    //获得一维数组的类引用,即jintArray类型  jclass intArrayClass = env->FindClass("[I");   //构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsizejobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL);

1.4.3 JNI函数签名信息

由于Java支持函数重载,因此仅仅根据函数名是没法找到对应的JNI函数。为了解决这个问题,JNI将参数类型和返回值类型作为函数的签名信息。

  1. JNI规范定义的函数签名信息格式:
    (参数1类型字符…)返回值类型字符

  2. 函数签名例子:

    img

  3. JNI常用的数据类型及对应字符:

    img

1.4.4 JNIEnv介绍

  1. JNIEnv概念 :
    JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境。通过JNIEnv可以调用到一系列JNI系统函数。
  2. JNIEnv线程相关性:
    每个线程中都有一个 JNIEnv 指针。JNIEnv只在其所在线程有效, 它不能在线程之间进行传递。

注意:在C++创建的子线程中获取JNIEnv,要通过调用JavaVM的AttachCurrentThread函数获得。在子线程退出时,要调用JavaVM的DetachCurrentThread函数来释放对应的资源,否则会出错。

  1. JNIEnv 作用:
    • 访问Java成员变量和成员方法;
    • 调用Java构造方法创建Java对象等。

1.5 JNI编译

1.5.1 ndkBuild

使用ndk-build编译生成so文件

1.5.2 Cmake编译

CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译, 在Android Studio 2.2 之后支持Cmake编译。

  • add_library 指令

    语法:add_library(libname [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] [source])

    将一组源文件 source 编译出一个库文件,并保存为 libname.so (lib 前缀是生成文件时 CMake自动添加上去的)。其中有三种库文件类型,不写的话,默认为 STATIC;

    • SHARED: 表示动态库,可以在(Java)代码中使用 System.loadLibrary(name) 动态调用;
    • STATIC: 表示静态库,集成到代码中会在编译时调用;
    • MODULE: 只有在使用 dyId 的系统有效,如果不支持 dyId,则被当作 SHARED 对待;
    • EXCLUDE_FROM_ALL: 表示这个库不被默认构建,除非其他组件依赖或手工构建;
#将compress.c 编译成 libcompress.so 的共享库
add_library(compress SHARED compress.c)
  • target_link_libraries 指令
    语法:target_link_libraries(target library <debug | optimized> library2…)
    这个指令可以用来为 target 添加需要的链接的共享库,同样也可以用于为自己编写的共享库添加共享库链接。如:
#指定 compress 工程需要用到 libjpeg 库和 log 库
target_link_libraries(compress libjpeg ${log-lib})
  • find_library 指令
    语法:find_library( name1 path1 path2 …)
    VAR 变量表示找到的库全路径,包含库文件名 。例如:
find_library(libX  X11 /usr/lib)
find_library(log-lib log)  #路径为空,应该是查找系统环境变量路径

Android NDK 开发:CMake 使用

1.5.3 Abi架构

ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。

  • armeabi设备只兼容armeabi;
  • armeabi-v7a设备兼容armeabi-v7a、armeabi;
  • arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
  • X86设备兼容X86、armeabi;
  • X86_64设备兼容X86_64、X86、armeabi;
  • mips64设备兼容mips64、mips;
  • mips只兼容mips;

根据以上的兼容总结,我们还可以得到一些规律:

  • armeabi的SO文件基本上可以说是万金油,它能运行在除了mips和mips64的设备上,但在非armeabi设备上运行性能还是有所损耗;
  • 64位的CPU架构总能向下兼容其对应的32位指令集,如:x86_64兼容X86,arm64-v8a兼容armeabi-v7a,mips64兼容mips;

1.5.4 Jni技术实现原理

我们知道cpu只认得 “0101101” 类似这种符号, C、C++ 这些代码最终都得通过编译、汇编成二进制代码,cpu才能识别。而Java比C、C++又多了一层虚拟机,过程也复杂许多。Java代码经过编译成class文件、虚拟机装载等步骤最终在虚拟机中执行。class文件里面就是一个结构复杂的表,而最终告诉虚拟机怎么执行的就靠里面的字节码说明。

Java虚拟机在执行的时候,可以采用解释执行和编译执行的方式执行,但最终都是转化为机器码执行。

Java虚拟机运行时的数据区,包括方法区、虚拟机栈、堆、程序计数器、本地方法栈。

问题来了,按我目前的理解,如果是解释执行,那么方法区中应该存的是字节码,那执行的时候,通过JNI 动态装载的c、c++库,放哪去?怎么执行?这个问题,搜索了许多标题写着”JNI实现原理”的文章,都是抄来抄去,并没去探究如何实现的,只是讲了java如何使用JNI。好吧,就从如何使用JNI开始。

1.6 JNI的简单实现

参考文章:《Java JNI简单实现》、《JAVA基础之理解JNI原理》

假设当前的目录结构如下:

-
| - maniu| Test.java

1.首先编写java文件

Test.java

package maniu;
public class Test{static{System.loadLibrary("bridge");}public native int nativeAdd(int x,int y);public static void main(String[] args){Test obj = new Test();System.out.printf("%d\n",obj.nativeAdd(2012,3));}
} 

代码很简单,这里声明了nativeAdd(int x,inty)的方法,执行的时候简单的打出执行的结果。另外这里调用API加载名称叫bridge的库,接下来就来实现这个库。

2.生成JNI调用需要的头文件

javac maniu/Test.java
javah -jni maniu.Test

现在目录结构是这样的:

-
| - maniu| Test.java| Test.class
| - maniu_Test.h

maniu_Test.h头文件内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class maniu_Test */#ifndef _Included_maniu_Test
#define _Included_maniu_Test
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     maniu_Test* Method:    nativeAdd* Signature: (II)I*/
JNIEXPORT jint JNICALL Java_maniu_Test_nativeAdd(JNIEnv *, jobject, jint, jint);#ifdef __cplusplus
}
#endif

1.6.1 生成的代码阅读

经常会见到__cplusplus关键字,比如下面的代码:

#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_maniu_Test_nativeAdd(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif

这里面,两种关键字,都是为了实现C++与C兼容的,extern “C”是用来在C++程序中声明或定义一个C的符号,比如:

extern “C” {int func(int);int var;}

上面的代码,C++编译器会将在extern “C”的大括号内部的代码当做C语言来处理。

由于C和C++毕竟是不同的,为了实现某个程序在C和C++中都是兼容的,如果定义两套头文件,未免太过麻烦,所以就有了__cplusplus的出现,这个是在C++中特有的,__cplusplus其实就是C++,也就有了上面第一段代码的使用,如果这段代码是在C++文件中出现,那么经过编译后,该段代码就变成了:

/**********C++文件中条件编译后结果***************/
extern "C" {
JNIEXPORT jint JNICALL Java_maniu_Test_nativeAdd(JNIEnv *, jobject, jint, jint);
}

而在C文件中,经过条件编译,该段代码变成了:

/**********C文件中条件编译后结果*************/
JNIEXPORT jint JNICALL Java_maniu_Test_nativeAdd(JNIEnv *, jobject, jint, jint);

3.native方法的实现
这里新增bridge.c文件来实现之前声明的native方法,目录结构如下:

-
| - maniu| Test.java| Test.class
| - maniu_Test.h
| - bridge.c

bridge.c的内容如下:

#include "maniu_Test.h"JNIEXPORT jint JNICALL Java_maniu_Test_nativeAdd 
(JNIEnv * env, jobject obj, jint x, jint y){return x+y;
} 

这里的实现只是简单的把两个参数相加,然后返回。

4.生成动态链接库

gcc -shared -o dll_demo.dll bridge.c

最后需要注意一点的是 -o 选项,我们在java代码中调用的是System.loadLibrary("xxx"),那么生成的动态链接库的名称就必须是libxxx.so的形式(这里指Linux环境),否则在执行java代码的时候,就会报 java.lang.UnsatisfiedLinkError: no XXX in java.library.path 的错误!也就是说找不到这个库,我在这里被坑了一小段时间。
好了,现在的目录结构如下:

-
| - maniu| Test.java| Test.class
| - maniu_Test.h
| - bridge.c
| - libbridge.so

5.执行代码验证结果

java -Djava.library.path=. maniu.Test
2015

ok,Java 使用JNI的最简单的例子就完成了。


1.6.2 JNI实现原理

那么,我们的问题还没解决,刚刚生成的动态链接库”libbridge.so”是怎么装进内存的?native方法怎么调用?跟普通的方法调用有什么区别吗?

我们把Test.java改改,增加普通的方法”int add(int x,int y)”

Test.java

package maniu;
public class Test{static{System.loadLibrary("bridge");}public native int nativeAdd(int x,int y);public int add(int x,int y){return x+y;}public static void main(String[] args){Test obj = new Test();System.out.printf("%d\n",obj.nativeAdd(2012,3));System.out.printf("%d\n",obj.add(2012,3));}
} 

我们把它编译成class文件,再看看class文件中,native方法和普通方法有何区别:

javac maniu/Test.java
javap -verbose maniu.Test

解析后,”nativeAdd”和”add”两个方法的结果如下:

  public native int nativeAdd(int, int);flags: ACC_PUBLIC, ACC_NATIVEpublic int add(int, int);flags: ACC_PUBLICCode:stack=2, locals=3, args_size=30: iload_1       1: iload_2       2: iadd          3: ireturn       LineNumberTable:line 8: 0

可见,普通的“add”方法是直接把字节码放到code属性表中,而native方法,与普通的方法通过一个标志“ACC_NATIVE”区分开来。java在执行普通的方法调用的时候,可以通过找方法表,再找到相应的code属性表,最终解释执行代码,那么,对于native方法,在class文件中,并没有体现native代码在哪里,只有一个“ACC_NATIVE”的标识,那么在执行的时候改怎么找到动态链接库的代码呢?

只能从System.loadLibrary()入手了!

相关文章:

JNI原理及常用方法概述

1.1 JNI(Java Native Interface) 提供一种Java字节码调用C/C的解决方案&#xff0c;JNI描述的是一种技术。 1.2 NDK(Native Development Kit) Android NDK 是一组允许您将 C 或 C&#xff08;“原生代码”&#xff09;嵌入到 Android 应用中的工具&#xff0c;NDK描述的是工具集…...

【Docker】之docker-compose的介绍与命令的使用

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录docker-compose简介docker-compose基础…...

水果新鲜程度检测系统(UI界面+YOLOv5+训练数据集)

摘要&#xff1a;水果新鲜程度检测软件用于检测水果新鲜程度&#xff0c;利用深度学习技术识别腐败或损坏的水果&#xff0c;以辅助挑拣出新鲜水果&#xff0c;支持实时在线检测。本文详细介绍水果新鲜程度检测系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实…...

flask多并发

多线程 flask默认使用多进程处理请求&#xff0c;因此&#xff0c;是支持并发的。比如两个调用a.html和b.html&#xff0c; 请求a.html未运行完成&#xff0c;在浏览访问b.html不会阻塞。开两个不同浏览器&#xff0c;分别请求请求运行时间较长的a.html也不阻塞。只要不用一个…...

我用Python django开发了一个商城系统,已开源,求关注!

起始 2022年我用django开发了一个商城的第三方包&#xff0c;起名为&#xff1a;django-happy-shop。当时纯粹是利用业余时间来开发和维护这个包&#xff0c;想法也比较简单&#xff0c;Python语言做web可能用的人比较少&#xff0c;不一定有多少人去关注&#xff0c;就当是一个…...

大数据项目之数仓相关知识

第1章 数据仓库概念 数据仓库&#xff08;DW&#xff09;: 为企业指定决策&#xff0c;提供数据支持的&#xff0c;帮助企业&#xff0c;改进业务流程&#xff0c;提高产品质量等。 DW的输入数据通常包括&#xff1a;业务数据&#xff0c;用户行为数据和爬虫数据等 ODS: 数据…...

RK3588平台开发系列讲解(视频篇)RTP H264 码流打包详解

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、单 NALU 封包方式二、组合封包方式三、分片封包方式沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 H264 码流是放在 RTP 的有效载荷部分的。因此有效载荷前面的 RTP 头部跟码流本身是没有关系的,所以我…...

realloc的补充 柔性数组

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…...

【C语言】柔性数组

柔性数组1. 柔性数组介绍2. 柔性数组特点3. 用例3.1 代码一&#xff1a;3.2 代码二&#xff1a;4. 柔性数组优势&#xff1a;1. 柔性数组介绍 也许你从来没有听说过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但是它确实是存在的。 C99 中&#xff0c…...

【Linux】权限详解

前言首先我们先来看一下权限的概念&#xff1a;在多用户计算机系统的管理中&#xff0c;权限&#xff08;privilege&#xff09;是指某个特定的用户具有特定的系统资源使用权力&#xff0c;像是文件夹&#xff0c;特定系统指令的使用或存储量的限制。通常&#xff0c;系统管理员…...

Android 之 打开相机 打开相册

Android 之 打开系统摄像头拍照 打开系统相册&#xff0c;并展示1&#xff0c;清单文件 AndroidManifest.xml<uses-permission android:name"android.permission.INTERNET" /><!--文件读取权限--><uses-permission android:name"android.permiss…...

C语言数据结构初阶(8)----栈与队列OJ题

CSDN的uu们&#xff0c;大家好。这里是C语言数据结构的第八讲。 目标&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;数据结构与算法栈与队列的知识点我➡➡队列相关点我➡➡栈相关2. 用栈实现队列原题链接…...

JavaScript——原型对象

JavaScript——原型对象专题 文章目录JavaScript——原型对象专题1. 原型对象2. 原型对象的this指向3. 案例4. constructor属性5. 对象原型6. 总结7. 原型继承8. 原型链由先前的学习可知&#xff0c;构造函数实例创建的对象彼此独立、互不影响&#xff0c;很好的体现了面向对象…...

网络安全 2023 年为什么如此吃香?事实原来是这样....

前言由于我国网络安全起步晚&#xff0c;所以现在网络安全工程师十分紧缺。俗话说:没有网络安全就没有国家安全为什么选择网络安全&#xff1f;十四五发展规划建议明确提出建设网络强国&#xff0c;全面加强网络安全保障体系和能力建设&#xff0c;加强网络文明建设&#xff0c…...

(源码篇02)webpack5中的事件调度系统和NormalModuleFactary核心逻辑

1. 书接上回&#xff0c;从 this.factorizeQueue.add(options, callback); 开始 不是很清楚上下文的兄弟&#xff0c;可以去看下我之前写的 &#xff08;源码篇01&#xff09;浅析webpack5中Compiler中重要的hook调用过程。 此文比较干&#xff0c;各位读者开始阅读前&#xf…...

Vue2.x源码:new Vue()做了啥?

vue源码版本vue2.5.2 new Vue()做了啥? new Vue()会执行_init方法&#xff0c;而_init方法在initMixin函数中定义。 src/core/instance/index.js文件中定义了Vue function Vue (options) {this._init(options) }initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycl…...

WinForm | C# 弹出简易的消息提示框 (仿Android Toast消息提示)

ApeForms Toast消息提示 文章目录ApeForms Toast消息提示前言方法原型及参数释义消息驻留延时消息弹出模式队列模式抢占模式复用模式UI库安装与使用获取示例源码前言 在使用手机的时候经常会见到屏幕的中下方会弹出消息提示框&#xff0c;它就是Toast&#xff0c;以下是百度百…...

1、DRF实战总结:DRF特点、序列化与RESTful API规范

Django这种基于MVC开发模式的传统框架&#xff0c;非常适合开发基于PC的传统网站&#xff0c;因为它同时包括了后端的开发(逻辑层、数据库层) 和前端的开发(如模板语言、样式)。现代网络应用Web APP或大型网站一般是一个后台&#xff0c;然后对应各种客户端(iOS, android, 浏览…...

SIP协议及其简单介绍

SIP协议及其简单介绍概述流程SIP流程两台设备建立会话原理使用场景概述 SIP&#xff08;Session Initiation Protocol&#xff0c;会话初始化协议&#xff09;是一个应用层协议&#xff0c;用于在互联网上创建、修改和终止多媒体会话。SIP是一个客户端/服务器协议&#xff0c;…...

安全防御第四天:防病毒网关

一、恶意软件1.按照传播方式分类&#xff08;1&#xff09;病毒病毒是一种基于硬件和操作系统的程序&#xff0c;具有感染和破坏能力&#xff0c;这与病毒程序的结构有关。病毒攻击的宿主程序是病毒的栖身地&#xff0c;它是病毒传播的目的地&#xff0c;又是下一次感染的出发点…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...