Android高级开发第三篇 - JNI异常处理与线程安全编程
Android高级开发第三篇 - JNI异常处理与线程安全编程
- Android高级开发第三篇 - JNI异常处理与线程安全编程
- 引言
- 为什么要关注异常处理和线程安全?
- 第一部分:JNI异常处理基础
- 什么是JNI异常?
- 检查和处理Java异常
- 从C代码抛出Java异常
- 异常处理的最佳实践
- 第二部分:线程安全基础
- 什么是线程安全问题?
- JNIEnv的线程安全性
- 使用互斥锁保护共享资源
- 线程安全的全局引用管理
- 第三部分:实际应用示例
- 调试技巧和常见错误
- 常见错误及解决方案
- 调试工具
- 总结
- 参考资源
Android高级开发第三篇 - JNI异常处理与线程安全编程
引言
在前两篇文章中,我们学习了JNI的基础知识和参数传递机制。然而,真正的生产环境中,我们必须面对两个关键挑战:异常处理和线程安全。这些看似复杂的概念其实是JNI开发中不可或缺的基础技能。本文将从新手的角度,逐步引导你理解和掌握这些重要概念。
为什么要关注异常处理和线程安全?
想象一下这样的场景:
- 你的C代码访问了一个null指针,导致应用崩溃
- 多个线程同时调用JNI方法,结果数据出现了不一致
- Java代码抛出异常,但C代码没有正确处理,导致内存泄漏
这些都是JNI开发中的常见问题。掌握异常处理和线程安全,就是为你的应用程序构建一道安全防线。
第一部分:JNI异常处理基础
什么是JNI异常?
JNI异常可以分为两类:
- Java异常传播到C代码:Java方法抛出异常,需要在C代码中检查和处理
- C代码中的异常传播到Java:C代码发现错误,需要抛出Java异常
检查和处理Java异常
当你在C代码中调用Java方法时,这些方法可能会抛出异常。让我们看一个简单的例子:
// Java代码
public class Calculator {public int divide(int a, int b) {if (b == 0) {throw new ArithmeticException("Division by zero");}return a / b;}public native void testDivision(int a, int b);
}
// C代码 - 错误的处理方式
JNIEXPORT void JNICALL
Java_com_example_Calculator_testDivision(JNIEnv *env, jobject thiz, jint a, jint b) {// 获取divide方法jclass cls = (*env)->GetObjectClass(env, thiz);jmethodID methodID = (*env)->GetMethodID(env, cls, "divide", "(II)I");// 调用divide方法 - 这里可能抛出异常!jint result = (*env)->CallIntMethod(env, thiz, methodID, a, b);// 如果上面抛出异常,这里的代码可能不会正确执行printf("Result: %d\n", result);
}
正确的处理方式:
// C代码 - 正确的异常处理
JNIEXPORT void JNICALL
Java_com_example_Calculator_testDivision(JNIEnv *env, jobject thiz, jint a, jint b) {jclass cls = (*env)->GetObjectClass(env, thiz);jmethodID methodID = (*env)->GetMethodID(env, cls, "divide", "(II)I");// 调用Java方法jint result = (*env)->CallIntMethod(env, thiz, methodID, a, b);// 检查是否有异常发生if ((*env)->ExceptionCheck(env)) {// 获取异常信息(可选)jthrowable exception = (*env)->ExceptionOccurred(env);// 打印异常堆栈(调试用)(*env)->ExceptionDescribe(env);// 清除异常(*env)->ExceptionClear(env);// 处理异常情况printf("An exception occurred in Java code\n");return;}// 只有在没有异常时才执行printf("Result: %d\n", result);
}
从C代码抛出Java异常
有时候,你需要在C代码中检测到错误并抛出Java异常:
// C代码 - 抛出Java异常
JNIEXPORT jstring JNICALL
Java_com_example_FileUtils_readFile(JNIEnv *env, jobject thiz, jstring filename) {// 获取文件名const char* file = (*env)->GetStringUTFChars(env, filename, NULL);// 尝试打开文件FILE* fp = fopen(file, "r");// 释放文件名字符串(*env)->ReleaseStringUTFChars(env, filename, file);if (fp == NULL) {// 文件打开失败,抛出Java异常jclass exceptionClass = (*env)->FindClass(env, "java/io/FileNotFoundException");(*env)->ThrowNew(env, exceptionClass, "Cannot open file");return NULL;}// 读取文件内容...char buffer[1024];fgets(buffer, sizeof(buffer), fp);fclose(fp);return (*env)->NewStringUTF(env, buffer);
}
异常处理的最佳实践
- 总是检查异常:调用Java方法后,使用
ExceptionCheck()
或ExceptionOccurred()
- 及时清除异常:使用
ExceptionClear()
清除异常状态 - 资源清理:即使发生异常,也要确保资源得到正确释放
- 异常信息:提供有意义的异常信息,帮助调试
// 完整的异常处理示例
JNIEXPORT jbyteArray JNICALL
Java_com_example_DataProcessor_processData(JNIEnv *env, jobject thiz, jbyteArray input) {jbyte* inputBytes = NULL;jbyteArray result = NULL;// 获取输入数据inputBytes = (*env)->GetByteArrayElements(env, input, NULL);if (inputBytes == NULL) {// 内存分配失败jclass exceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");(*env)->ThrowNew(env, exceptionClass, "Failed to get array elements");goto cleanup;}jsize length = (*env)->GetArrayLength(env, input);if (length <= 0) {jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException");(*env)->ThrowNew(env, exceptionClass, "Input array is empty");goto cleanup;}// 处理数据...// 假设我们简单地复制数据result = (*env)->NewByteArray(env, length);if (result == NULL) {jclass exceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");(*env)->ThrowNew(env, exceptionClass, "Failed to create result array");goto cleanup;}(*env)->SetByteArrayRegion(env, result, 0, length, inputBytes);cleanup:// 清理资源if (inputBytes != NULL) {(*env)->ReleaseByteArrayElements(env, input, inputBytes, JNI_ABORT);}return result;
}
第二部分:线程安全基础
什么是线程安全问题?
在多线程环境中,多个线程可能同时访问和修改相同的数据,导致数据不一致或程序崩溃。JNI中的线程安全问题主要包括:
- JNIEnv不是线程安全的:每个线程都有自己的JNIEnv指针
- 全局引用的并发访问:多个线程访问同一个全局引用
- 静态变量的并发修改:C代码中的静态变量被多个线程修改
JNIEnv的线程安全性
错误的做法:
// 全局变量 - 这是错误的!
JNIEnv* globalEnv = NULL;JNIEXPORT void JNICALL
Java_com_example_BadExample_initEnv(JNIEnv *env, jobject thiz) {// 错误:保存JNIEnv到全局变量globalEnv = env;
}void someFunction() {// 错误:在其他线程中使用全局的JNIEnvjclass cls = (*globalEnv)->FindClass(globalEnv, "java/lang/String");// 这可能导致崩溃!
}
正确的做法:
// 全局JavaVM指针是线程安全的
JavaVM* g_jvm = NULL;JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {g_jvm = vm;return JNI_VERSION_1_6;
}// 在其他线程中获取JNIEnv
void someFunction() {JNIEnv* env;int result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);if (result == JNI_EDETACHED) {// 当前线程没有附加到JVM,需要附加result = (*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL);if (result != JNI_OK) {// 处理错误return;}// 使用env...jclass cls = (*env)->FindClass(env, "java/lang/String");// 分离线程(*g_jvm)->DetachCurrentThread(g_jvm);} else if (result == JNI_OK) {// 线程已经附加,直接使用jclass cls = (*env)->FindClass(env, "java/lang/String");}
}
使用互斥锁保护共享资源
当多个线程需要访问共享数据时,我们需要使用同步机制:
#include <pthread.h>// 共享数据
static int sharedCounter = 0;
static pthread_mutex_t counterMutex = PTHREAD_MUTEX_INITIALIZER;JNIEXPORT jint JNICALL
Java_com_example_ThreadSafe_incrementCounter(JNIEnv *env, jobject thiz) {int result;// 获取锁pthread_mutex_lock(&counterMutex);// 修改共享数据sharedCounter++;result = sharedCounter;// 释放锁pthread_mutex_unlock(&counterMutex);return result;
}JNIEXPORT jint JNICALL
Java_com_example_ThreadSafe_getCounter(JNIEnv *env, jobject thiz) {int result;pthread_mutex_lock(&counterMutex);result = sharedCounter;pthread_mutex_unlock(&counterMutex);return result;
}
线程安全的全局引用管理
#include <pthread.h>// 线程安全的全局引用管理
static jobject g_callback = NULL;
static pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER;JNIEXPORT void JNICALL
Java_com_example_ThreadSafe_setCallback(JNIEnv *env, jobject thiz, jobject callback) {pthread_mutex_lock(&g_callback_mutex);// 删除旧的全局引用if (g_callback != NULL) {(*env)->DeleteGlobalRef(env, g_callback);}// 创建新的全局引用if (callback != NULL) {g_callback = (*env)->NewGlobalRef(env, callback);} else {g_callback = NULL;}pthread_mutex_unlock(&g_callback_mutex);
}void callbackFromNativeThread() {JNIEnv* env;jobject callback;// 获取当前线程的JNIEnvif ((*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {return;}// 安全地获取回调对象pthread_mutex_lock(&g_callback_mutex);callback = g_callback;if (callback != NULL) {// 创建局部引用以防止回调对象在使用过程中被删除callback = (*env)->NewLocalRef(env, callback);}pthread_mutex_unlock(&g_callback_mutex);if (callback != NULL) {// 调用回调方法jclass cls = (*env)->GetObjectClass(env, callback);jmethodID method = (*env)->GetMethodID(env, cls, "onCallback", "()V");(*env)->CallVoidMethod(env, callback, method);// 删除局部引用(*env)->DeleteLocalRef(env, callback);}
}
第三部分:实际应用示例
让我们创建一个完整的示例,展示如何在实际项目中应用异常处理和线程安全:
// Java代码
public class SecureFileProcessor {public interface ProgressCallback {void onProgress(int percentage);void onError(String error);void onComplete(String result);}static {System.loadLibrary("securefileprocessor");}public native void processFileAsync(String filename, ProgressCallback callback);public native void cancelProcessing();
}
// C代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 全局变量
static JavaVM* g_jvm = NULL;
static pthread_t g_processing_thread;
static volatile int g_should_cancel = 0;
static pthread_mutex_t g_cancel_mutex = PTHREAD_MUTEX_INITIALIZER;// 线程参数结构
typedef struct {char* filename;jobject callback;
} ProcessingParams;// 线程安全的取消检查
int shouldCancel() {int result;pthread_mutex_lock(&g_cancel_mutex);result = g_should_cancel;pthread_mutex_unlock(&g_cancel_mutex);return result;
}// 调用Java回调方法
void callJavaCallback(JNIEnv* env, jobject callback, const char* methodName, const char* signature, ...) {if (callback == NULL) return;jclass cls = (*env)->GetObjectClass(env, callback);jmethodID method = (*env)->GetMethodID(env, cls, methodName, signature);if (method == NULL) {// 方法不存在,抛出异常jclass exceptionClass = (*env)->FindClass(env, "java/lang/NoSuchMethodError");(*env)->ThrowNew(env, exceptionClass, "Callback method not found");return;}va_list args;va_start(args, signature);if (strcmp(signature, "(I)V") == 0) {int value = va_arg(args, int);(*env)->CallVoidMethod(env, callback, method, value);} else if (strcmp(signature, "(Ljava/lang/String;)V") == 0) {const char* str = va_arg(args, const char*);jstring jstr = (*env)->NewStringUTF(env, str);(*env)->CallVoidMethod(env, callback, method, jstr);(*env)->DeleteLocalRef(env, jstr);}va_end(args);// 检查回调是否抛出异常if ((*env)->ExceptionCheck(env)) {(*env)->ExceptionDescribe(env);(*env)->ExceptionClear(env);}
}// 处理线程函数
void* processingThread(void* params) {ProcessingParams* p = (ProcessingParams*)params;JNIEnv* env;// 附加到JVMint result = (*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL);if (result != JNI_OK) {free(p->filename);(*g_jvm)->DeleteGlobalRef(g_jvm, p->callback);free(p);return NULL;}// 检查文件是否存在FILE* file = fopen(p->filename, "r");if (file == NULL) {callJavaCallback(env, p->callback, "onError", "(Ljava/lang/String;)V", "File not found");goto cleanup;}// 模拟文件处理for (int i = 0; i <= 100; i += 10) {if (shouldCancel()) {callJavaCallback(env, p->callback, "onError", "(Ljava/lang/String;)V", "Processing cancelled");goto cleanup;}// 报告进度callJavaCallback(env, p->callback, "onProgress", "(I)V", i);// 模拟工作usleep(100000); // 100ms}// 处理完成callJavaCallback(env, p->callback, "onComplete", "(Ljava/lang/String;)V", "File processed successfully");cleanup:if (file) fclose(file);free(p->filename);(*env)->DeleteGlobalRef(env, p->callback);free(p);// 分离线程(*g_jvm)->DetachCurrentThread(g_jvm);return NULL;
}JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {g_jvm = vm;return JNI_VERSION_1_6;
}JNIEXPORT void JNICALL
Java_com_example_SecureFileProcessor_processFileAsync(JNIEnv *env, jobject thiz, jstring filename, jobject callback) {// 参数验证if (filename == NULL || callback == NULL) {jclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException");(*env)->ThrowNew(env, exceptionClass, "Filename and callback cannot be null");return;}// 准备线程参数ProcessingParams* params = malloc(sizeof(ProcessingParams));if (params == NULL) {jclass exceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");(*env)->ThrowNew(env, exceptionClass, "Failed to allocate memory");return;}// 复制文件名const char* file = (*env)->GetStringUTFChars(env, filename, NULL);params->filename = malloc(strlen(file) + 1);if (params->filename == NULL) {(*env)->ReleaseStringUTFChars(env, filename, file);free(params);jclass exceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");(*env)->ThrowNew(env, exceptionClass, "Failed to allocate memory for filename");return;}strcpy(params->filename, file);(*env)->ReleaseStringUTFChars(env, filename, file);// 创建回调的全局引用params->callback = (*env)->NewGlobalRef(env, callback);// 重置取消标志pthread_mutex_lock(&g_cancel_mutex);g_should_cancel = 0;pthread_mutex_unlock(&g_cancel_mutex);// 创建处理线程int result = pthread_create(&g_processing_thread, NULL, processingThread, params);if (result != 0) {free(params->filename);(*env)->DeleteGlobalRef(env, params->callback);free(params);jclass exceptionClass = (*env)->FindClass(env, "java/lang/RuntimeException");(*env)->ThrowNew(env, exceptionClass, "Failed to create processing thread");}
}JNIEXPORT void JNICALL
Java_com_example_SecureFileProcessor_cancelProcessing(JNIEnv *env, jobject thiz) {pthread_mutex_lock(&g_cancel_mutex);g_should_cancel = 1;pthread_mutex_unlock(&g_cancel_mutex);
}
调试技巧和常见错误
常见错误及解决方案
-
忘记检查异常
// 错误 (*env)->CallVoidMethod(env, obj, method); // 继续执行...// 正确 (*env)->CallVoidMethod(env, obj, method); if ((*env)->ExceptionCheck(env)) {(*env)->ExceptionClear(env);return; }
-
在错误的线程中使用JNIEnv
// 错误:直接使用其他线程的JNIEnv// 正确:获取当前线程的JNIEnv JNIEnv* env; (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
-
没有正确管理全局引用
// 错误:创建了全局引用但没有删除 jobject globalRef = (*env)->NewGlobalRef(env, obj);// 正确:记得删除全局引用 (*env)->DeleteGlobalRef(env, globalRef);
调试工具
- 使用CheckJNI:在开发阶段启用CheckJNI检查
- AddressSanitizer:检测内存错误
- 日志记录:在关键位置添加日志
- 异常堆栈:使用
ExceptionDescribe()
打印异常信息
总结
异常处理和线程安全是JNI开发中的核心技能。记住以下要点:
异常处理:
- 总是检查Java方法调用后的异常状态
- 在C代码中适当地抛出Java异常
- 确保异常情况下的资源清理
线程安全:
- JNIEnv不能跨线程使用
- 使用JavaVM获取当前线程的JNIEnv
- 保护共享资源访问
- 正确管理全局引用的生命周期
虽然这些概念初看起来可能复杂,但通过实践和遵循最佳实践,你会发现它们是构建稳定JNI应用的基石。在下一篇文章中,我们将探讨JNI性能优化技巧和高级调试方法。
参考资源
- JNI异常处理指南
- Android NDK线程安全
- pthread编程指南
相关文章:
Android高级开发第三篇 - JNI异常处理与线程安全编程
Android高级开发第三篇 - JNI异常处理与线程安全编程 Android高级开发第三篇 - JNI异常处理与线程安全编程引言为什么要关注异常处理和线程安全?第一部分:JNI异常处理基础什么是JNI异常?检查和处理Java异常从C代码抛出Java异常异常处理的最佳…...
企业级应用狂潮:从Spotify到LinkedIn的Llama实战手册
当Spotify用Llama生成的个性化推荐文案让用户播放时长激增30%, 当LinkedIn靠开源框架将社交推荐延迟降低40%—— 企业级AI战场正经历从“技术炫技”到“利润引擎”的残酷蜕变。 核心数据:企业采用率爆发式增长(2025 Gartner调研) 指标2023年2025年增幅开源模型采用率42%87%…...

高效管理 Python 项目的 UV 工具指南
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 持续学习,不断…...
QT中子线程触发主线程弹窗并阻塞等待用户响应
目录 QT中子线程触发主线程弹窗并阻塞等待用户响应一、使用QMetaObject::invokeMethod实现子线程安全触发主线程弹窗并阻塞等待:🔧 Qt多线程弹窗:安全阻塞等待方案(QMetaObject::invokeMethod详解)🧠 一、核…...

初识vue3(vue简介,环境配置,setup语法糖)
一,前言 今天学习vue3 二,vue简介及如何创建vue工程 Vue 3 简介 Vue.js(读音 /vjuː/,类似 “view”)是一款流行的渐进式 JavaScript 框架,用于构建用户界面。Vue 3 是其第三代主要版本,于 …...
HarmonyOS NEXT~鸿蒙开发工具CodeGenie:AI驱动的开发效率革命
HarmonyOS NEXT~鸿蒙开发工具CodeGenie:AI驱动的开发效率革命 一、CodeGenie概述 DevEco CodeGenie是华为鸿蒙开发生态中的一款AI辅助编程工具,集成于DevEco Studio IDE中,为开发者提供全方位的智能编程支持。这款工具通过AI技术…...

LeetCode-链表操作题目
虚拟头指针,在当前head的前面建立一个虚拟头指针,然后哪怕当前的head的val等于提供的val也能进行统一操作 203移除链表元素简单题 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(…...

【ARM】MDK浏览信息的生成对于构建时间的影响
1、 文档目标 用于了解MDK的代码浏览信息的生成对于工程的构建是否会产生影响。 2、 问题场景 客户在MDK中使用Compiler 5对于工程进行构建过程中发现,对于是否产生浏览信息会对于构建时间产生一定的影响。在Options中Output栏中勾选了Browse Information后&#…...
Python模块中__all__变量失效问题深度解析
文章目录 Python模块中__all__变量失效问题深度解析一、__all__ 的正确作用场景二、__all__ 不起作用的常见原因1. 未使用 from ... import \* 导入2. __all__ 定义不完整或错误3. 子模块未正确导出4. Python 解释器缓存问题5. 相对导入路径错误 三、解决方案1. 确保使用 from …...

py爬虫的话,selenium是不是能完全取代requests?
selenium适合动态网页抓取,因为它可以控制浏览器去点击、加载网页,requests则比较适合静态网页采集,它非常轻量化速度快,没有浏览器开销,占用资源少。当然如果不考虑资源占用和速度,selenium是可以替代requ…...

docker B站学习
镜像是一个只读的模板,用来创建容器 容器是docker的运行实例,提供了独立可移植的环境 https://www.bilibili.com/video/BV11L411g7U1?spm_id_from333.788.videopod.episodes&vd_sourcee60c804914459274157197c4388a4d2f&p3 目录挂载 尚硅谷doc…...

SpringBoot高校宿舍信息管理系统小程序
概述 基于SpringBoot的高校宿舍信息管理系统小程序项目,这是一款非常适合高校使用的信息化管理工具。该系统包含了完整的宿舍管理功能模块,采用主流技术栈开发,代码结构清晰,非常适合学习和二次开发。 主要内容 这个宿舍管理系…...
深度解析 Dockerfile 配置:构建高效轻量的FastAPI 应用镜像
目录 引言 Dockerfile构建FastAPI镜像的示例 一、基础镜像选择:轻量与安全优先 二、元数据声明:镜像维护者信息 三、依赖管理:分层构建与缓存优化 1. 复制依赖文件 2. 安装依赖 四、应用代码复制:最小化镜像内容 五、启动…...

ICASSP2025丨融合语音停顿信息与语言模型的阿尔兹海默病检测
阿尔兹海默病(Alzheimers Disease, AD)是一种以认知能力下降和记忆丧失为特征的渐进性神经退行性疾病,及早发现对于其干预和治疗至关重要。近期,清华大学语音与音频技术实验室(SATLab)提出了一种将停顿信息…...
[蓝桥杯]春晚魔术【算法赛】
目录 输入格式 输出格式 样例输入 样例输出 运行限制 解决思路 代码说明 复杂度分析 问题描述 在蓝桥卫视春晚的直播现场,魔术师小蓝表演了一个红包魔术。只见他拿出了三个红包,里边分别装有 A、B 和 C 个金币。而后,他挥动魔术棒&a…...
LeetCode - 965. 单值二叉树
目录 题目 深度优先搜索方法 正确的写法 题目 965. 单值二叉树 - 力扣(LeetCode) 深度优先搜索方法 什么是深度优先搜索:深度优先搜索(DFS)是一种图或树的遍历算法,它从起始节点开始,尽可能深地沿着一条路径探索&…...

LabVIEW杂草识别与精准喷洒
基于LabVIEW构建了一套集成机器视觉、智能决策与精准控制的农业杂草识别系统。通过高分辨率视觉传感器采集作物图像,利用 LabVIEW 的 NI Vision 模块实现图像颜色匹配与特征分析,结合 Arduino 兼容的工业级控制硬件,实现杂草定位与除草剂精准…...
分布式不同数据的一致性模型
1. 强一致性(Strong Consistency) 定义:所有节点在任何时间点看到的数据完全一致,读操作总是返回最近的写操作结果。特点: 写操作完成后,所有后续读操作都能立即看到更新。通常需要同步机制(如…...
“application/json“,“text/plain“ 分别表示什么
这两个字符串:“application/json” 和 “text/plain” 是 MIME 类型(媒体类型),用于告诉接收方消息内容的格式,它们出现在 ContentType 字段中。 它告诉系统或程序:“这段数据是什么格式?” 格…...
SQL: 窗口滑动(Sliding Window)
目录 什么是“窗口”? 什么是“滑动”? 🔍 滑动窗口的核心: 🕒 什么是时间窗口?(Time Window) 时间窗口的基本结构 时间窗口的三种常见形式 📊 什么是行窗口&…...

学习日记-day20-6.1
完成目标: 知识点: 1.集合_Collections集合工具类 方法:static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素 static void shuffle(List<?> list) ->将集合中的元素顺序打乱static <T>…...

【音视频】 FFmpeg 解码H265
一、概述 实现了使用FFmpeg读取对应H265文件,并且保存为对应的yuv文件 二、实现流程 读取文件 将H265/H264文件放在build路径下,然后指定输出为yuv格式 在main函数中读取外部参数 if (argc < 2){fprintf(stderr, "Usage: %s <input file&…...
Linux 系统 Docker Compose 安装
个人博客地址:Linux 系统 Docker Compose 安装 | 一张假钞的真实世界 本文方法是直接下载 GitHub 项目的 release 版本。项目地址:GitHub - docker/compose: Define and run multi-container applications with Docker。 执行以下命令将发布程序加载至…...

软件测试|FIT故障注入测试工具——ISO 26262合规下的智能汽车安全验证引擎
FIT(Fault Injection Tester)是SURESOFT专为汽车电子与工业控制设计的自动化故障注入测试工具,基于ISO 26262等国际安全标准开发,旨在解决传统测试中效率低、成本高、安全隐患难以复现的问题,其核心功能包括…...

3D拟合测量水杯半径
1,目的。 测量水杯的半径 如图所示: 2,原理。 对 3D 点云对象 进行圆柱体拟合,获取拟合后的半径。 3,注意事项。 在Halcon中使用fit_primitives_object_model_3d进行圆柱体拟合时,输出的primitive_para…...
(21)量子计算对密码学的影响
文章目录 2️⃣1️⃣ 量子计算对密码学的影响 🌌🔍 TL;DR🚀 量子计算:密码学的终结者?⚡ 量子计算的破坏力 🔐 Java密码学体系面临的量子威胁🔥 受影响最严重的Java安全组件 🛡️ 后…...

Python训练打卡Day38
Dataset和Dataloader类 知识点回顾: Dataset类的__getitem__和__len__方法(本质是python的特殊方法)Dataloader类minist手写数据集的了解 在遇到大规模数据集时,显存常常无法一次性存储所有数据,所以需要使用分批训练的…...

Selenium基础操作方法详解
Selenium基础操作方法详解:从零开始编写自动化脚本(附完整代码) 引言 Selenium是自动化测试和网页操作的利器,但对于新手来说,掌握基础操作是成功的第一步。本文将手把手教你使用Selenium完成浏览器初始化、元素定位、…...
Kali Linux从入门到实战:系统详解与工具指南
一、Kali Linux简介 Kali Linux是一款基于Debian的Linux发行版,专为渗透测试和网络安全审计设计,由Offensive Security团队维护。其前身是BackTrack,目前集成了超过600款安全工具,覆盖渗透测试全流程,是网络安全领域…...
【大模型】Bert变种
1. RoBERTa(Robustly optimized BERT approach) 核心改动 取消 NSP(Next Sentence Prediction)任务,研究发现 NSP 对多数下游任务贡献有限。动态遮蔽(dynamic masking):每个 epoch …...