02-设计概述

news2024/9/24 17:55:53

上一篇:01-导言


        本章重点讨论 JNI 中的主要设计问题。本节中的大多数设计问题都与本地方法有关。调用 API 的设计将在第 5 章:调用 API 中介绍。

2.1 JNI 接口函数和指针

        本地代码通过调用 JNI 函数来访问 Java 虚拟机功能。JNI 函数可通过接口指针使用。接口指针是指向指针的指针该指针指向一个指针数组,数组中每个指针指向一个接口函数每个接口函数都位于数组内预定义的偏移量处。下图 "接口指针 "说明了接口指针的组织结构。

        JNI 接口的组织方式类似于 C++ 虚拟函数表或 COM 接口。使用接口表而不是硬连线函数项的好处是,JNI 名称空间与本地代码分离。虚拟机可以轻松提供多个版本的 JNI 函数表。例如,虚拟机可以支持两个 JNI 函数表:

        ①. 一种会对非法参数进行彻底检查,适合调试;

        ②. 而另一种方法只执行 JNI 规范要求的最少检查量,因此效率更高。

        JNI 接口指针只在当前线程中有效。因此,本地方法不得将接口指针从一个线程传递到另一个线程。实现 JNI 的虚拟机可以在 JNI 接口指针指向的区域中分配和存储线程本地数据。

        本地方法接收 JNI 接口指针作为参数。当虚拟机从同一个 Java 线程多次调用本地方法时,会保证将同一个接口指针传递给本地方法。不过,本地方法可以从不同的 Java 线程调用,因此可能会收到不同的 JNI 接口指针

2.2 编译、加载和链接本地方法

        由于 Java 虚拟机是多线程的,因此本地库也应使用多线程感知本地编译器进行编译和链接。例如,使用 Sun Studio 编译器编译的 C++ 代码应使用 -mt 标志。对于使用 GNU gcc 编译器编译的代码,应使用标志 -D_REENTRANT 或 -D_POSIX_C_SOURCE 。

        本地方法用 System.loadLibrary 方法加载。在下面的示例中,类初始化方法加载了一个特定平台的本地库,其中定义了本地方法 f :

package p.q.r;

class A {
    native double f(int i, String s);
    static {
        // 
        // 
        /**
         * defined in "libcore/ojluni/src/main/java/java/lang/System.java"
         * public static void loadLibrary(String libname);
         * 加载由libname参数指定的本机库。
         * libname参数不能包含任何特定于平台的前缀、文件扩展名或路径。
         * 如果一个名为libname的本地库与虚拟机静态链接,则调用该库导出的JNI_OnLoad()函数。
         * 否则,libname参数将从系统库位置加载,并以依赖于实现的方式映射到本机库映像。
         */
        System.loadLibrary("p_q_r_A");
    }
}

        System.loadLibrary 的参数是程序员任意选择的库名。系统会采用一种标准的、但与特定平台有关的方法,将库名称转换为本地库名称。例如,Linux 系统会将库名 p_q_r_A 转换为 libp_q_r_A.so ,而 Windows 系统会将库名 p_q_r_A 转换为 p_q_r_A.dll

        程序员可以使用一个库来存储任意数量的类所需的所有本地方法,只要这些类是用同一个类加载器加载的虚拟机内部会为每个类加载器维护一个已加载的本地库列表。供应商在选择本地库名称时,应尽量减少名称冲突的机会。

        对动态链接和静态链接库的支持,以及它们各自的生命周期管理 "加载 "和 "卸载 "功能钩子,在库和版本管理的调用 API 部分有详细介绍。

2.2.1 解析本地方法名称

        JNI 定义了从 Java 中声明的 native 方法名称到本地库中本地方法名称的 1:1 映射。虚拟机使用该映射将 native 方法的 Java 调用动态链接到本地库中的相应实现。

        该映射通过连接从 native 方法声明中派生出来的以下组件来生成本地方法名称:

                ①. Java_前缀;

                ②. 给出了声明 native 方法的类的二进制内部名称转义该名称的结果

                ③.  ("_") 下划线

                ④. 转义方法名

                ⑤. 如果 native 方法声明是重载的:两个下划线("__"),后跟方法声明的转义参数描述符(JVMS 4.3.3)。

        转义时,每个字母数字 ASCII 字符 ( A-Za-z0-9 ) 都保持不变,并用相应的转义序列替换下表中的每个 UTF-16 代码单元。如果要转义的名称包含一对代用字符,则高代用代码单元和低代用代码单元将分别转义。转义的结果是一个仅由 ASCII 字符 A-Za-z0-9 和下划线组成的字符串。

        出于两个原因,转义是必要的。首先,为了确保 Java 源代码中可能包含 Unicode 字符的类名和方法名能转换成 C 源代码中有效的函数名。其次,确保 native 方法的参数描述符(使用"; "和"["字符对参数类型进行编码)可以在 C 函数名中编码

        当 Java 程序调用一个 native 方法时,虚拟机会搜索本地库,首先查找本地方法名称的简短版本,即不包含转义参数签名的名称如果找不到短名称的本地方法,虚拟机就会查找长版本的本地方法名称,即包含转义参数签名的名称

        先查找简短的名称,可以更方便地在本地库中声明实现。例如,给出 Java 中的 native 方法:

package p.q.r;
class A {
    native double f(int i, String s);
}

        相应的 C 函数可以命名为 Java_p_q_r_A_f ,而不是 Java_p_q_r_A_f__ILjava_lang_String_2 。

        只有当一个类中的两个或多个 native 方法具有相同的名称时,才有必要在本地库中声明具有长名称的实现。例如,Java 中有以下1 个方法:

package p.q.r;
class A {
    native double f(int i, String s);
    native double f(int i, Object s);
}

        相应的 C 语言函数必须命名为 Java_p_q_r_A_f__ILjava_lang_String_2 和 Java_p_q_r_A_f__ILjava_lang_Object_2 ,因为这两个方法是重载的。

        如果 Java 中的 native 方法只被非 native 方法重载,则无需在本地库中使用长名称。在下面的示例中, native 方法 g 不必使用长名称链接,因为另一个方法 g 不是 native 方法,因此不存在于本地库中。

package p.q.r;
class B {
    int g(int i);
    native int g(double d);
}

        请注意,转义序列可以安全地以 _0 、 _1 等开头,因为 Java 源代码中的类名和方法名从不以数字开头。但是,在非 Java 源代码生成的类文件中,情况并非如此。为了保持与本地方法名称的 1:1 映射,虚拟机会对生成的名称进行如下检查。如果从方法声明(类或方法名称或参数类型)中转义任何前导字符串的过程导致前导字符串中的 " 0 "、" 1 "、" 2 "或 " 3 "字符在结果中紧跟下划线后或在转义字符串的开头(在完全组装后的名称中,这些字符将紧跟下划线)出现,且未发生变化,则称转义过程 "失败"。在这种情况下,将不执行本地库搜索,并且在尝试链接 native 方法调用时将抛出 UnsatisfiedLinkError 。可以扩展目前的简单映射方案以涵盖这种情况,但复杂性成本将超过任何好处。

        本地方法和接口 API 都遵循特定平台上的标准库调用约定。例如,UNIX 系统使用 C 调用约定,而 Win32 系统使用 __stdcall。

        本机方法也可以使用 RegisterNatives 功能进行显式链接需要注意的是, RegisterNatives 函数可以通过更改给定本地 Java 方法要执行的本地代码来改变 JVM 的记录行为(包括加密算法、正确性、安全性、类型安全性)。因此,请谨慎使用使用 RegisterNatives 函数的本地库应用程序。

2.2.2 本地方法参数

        JNI 接口指针是本地方法的第一个参数。JNI 接口指针的类型是 JNIEnv。第二个参数根据本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对象的引用静态本地方法的第二个参数是对其 Java 类的引用

        其余参数与常规 Java 方法参数相对应。本地方法调用通过返回值将结果传回调用例程。第 3 章:JNI 类型和数据结构,介绍了 Java 和 C 语言类型之间的映射。

        下面的代码示例说明了如何使用 C 语言函数来实现本地方法 f 。本地方法 f 的声明如下:

package p.q.r;

class A {
    native double f(int i, String s);
    // ...
}

        长名称为 Java_p_q_r_A_f_ILjava_lang_String_2 的 C 语言函数实现了本地方法 f :

jdouble Java_p_q_r_A_f__ILjava_lang_String_2 (
     JNIEnv *env,        /* interface pointer */
     jobject obj,        /* "this" pointer */
     jint i,             /* argument #1 */
     jstring s)          /* argument #2 */
{
     /* Obtain a C-copy of the Java string */
     const char *str = (*env)->GetStringUTFChars(env, s, 0);

     /* process the string */
     ...

     /* Now we are done with str */
     (*env)->ReleaseStringUTFChars(env, s, str);

     return ...
}

        请注意,我们总是使用接口指针 env 来操作 Java 对象。如下面的代码示例所示,使用 C++ 可以编写稍微简洁的代码:

extern "C" /* specify the C calling convention */

jdouble Java_p_q_r_A_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */
     jobject obj,        /* "this" pointer */
     jint i,             /* argument #1 */
     jstring s)          /* argument #2 */

{
     const char *str = env->GetStringUTFChars(s, 0);

     // ...

     env->ReleaseStringUTFChars(s, str);

     // return ...
}

        在 C++ 中,额外的间接层次和接口指针参数从源代码中消失了。在 C++ 中,JNI 函数被定义为内联成员函数,可扩展为 C 语言的对应函数

2.3 引用 Java 对象

        原始类型(如整数、字符等)可在 Java 和本地代码之间复制而任意 Java 对象则通过引用传递虚拟机必须跟踪所有已传递给本地代码的对象,以免垃圾回收器释放这些对象。反过来,本地代码也必须有办法通知虚拟机它不再需要这些对象。此外,垃圾回收器必须能够移动本地代码引用的对象。

2.3.1 全局和本地引用

        JNI 将本地代码使用的对象引用分为两类:本地引用和全局引用。本地引用在本地方法调用期间有效,并在本地方法返回后自动释放。全局引用在显式释放之前一直有效

        对象以本地引用的形式传递给本地方法。JNI 函数返回的所有 Java 对象都是局部引用。JNI 允许程序员从局部引用创建全局引用。期望返回 Java 对象的 JNI 函数既接受全局引用,也接受局部引用本地方法可将本地引用或全局引用作为结果返回给 VM

        在大多数情况下,程序员应依靠虚拟机在本地方法返回后释放所有本地引用。不过,有时程序员应该显式释放本地引用。例如,请考虑以下情况:

                ①. 本地方法访问大型 Java 对象,从而创建 Java 对象的本地引用。然后,本地方法在返回给调用者之前会执行额外的计算。即使在剩余的计算中不再使用该对象,该大型 Java 对象的本地引用也会阻止该对象被垃圾回收

                ②. 本地方法会创建大量的本地引用,但并非所有引用都会同时使用。由于虚拟机需要一定的空间来跟踪本地引用,创建过多的本地引用可能会导致系统内存不足。例如,一个本地方法在一个大型对象数组中循环,以本地引用的形式检索元素,每次迭代对一个元素进行操作。每次迭代后,程序员就不再需要数组元素的本地引用了。

        JNI 允许程序员在本地方法的任意位置手动删除本地引用。为确保程序员能手动释放本地引用,JNI 函数不允许创建额外的本地引用,但作为结果返回的引用除外。

        本地引用只在创建引用的线程中有效。本地代码不得将本地引用从一个线程传递到另一个线程

2.3.2 本地引用的实现

        为了实现本地引用,Java 虚拟机会为每次从 Java 到本地方法的控制转换创建一个注册表。注册表将不可移动的本地引用映射到 Java 对象,并防止对象被垃圾回收。传递给本地方法的所有 Java 对象(包括作为 JNI 函数调用结果返回的对象)都会自动添加到注册表中。本地方法返回后,注册表将被删除,从而允许对其所有条目进行垃圾回收。

        实现注册表的方法有很多种,例如:使用表、链表或哈希表。虽然可以使用引用计数来避免注册表中的重复条目,但 JNI 实现没有义务检测和删除重复条目

        请注意,本地引用不能通过保守地扫描本地堆栈来忠实地实现。本地代码可能会将本地引用存储到全局或堆数据结构中。

2.4 访问 Java 对象

        JNI 为全局和局部引用提供了丰富的访问函数集。这意味着,无论虚拟机如何在内部表示 Java 对象,都能使用相同的本地方法实现。这也是 JNI 可以被各种虚拟机实现所支持的重要原因。

        通过不透明引用使用访问函数的开销要高于直接访问 C 数据结构的开销。我们相信,在大多数情况下,Java 程序员会使用本地方法来执行一些非繁琐的任务,而这些任务会掩盖该接口的开销。

2.4.1 访问原始数组

        对于包含许多原始数据类型(如整数数组和字符串)的大型 Java 对象来说,这种开销是不可接受的。(考虑一下用于执行向量和矩阵计算的本地方法)。通过函数调用遍历 Java 数组并检索每个元素的效率非常低。

        一种解决方案引入了 "锁定 "的概念,这样本地方法就可以要求虚拟机锁定数组的内容。然后,本地方法会收到指向元素的直接指针。不过,这种方法有两个影响:

                ①. 垃圾回收器必须支持“锁住”功能。

                ②. 虚拟机必须在内存中连续布局基元数组。虽然这对大多数基元数组来说是最自然的实现方式,但布尔数组可以以打包或未打包的方式实现。因此,依赖于布尔数组精确布局的本地代码将无法移植。

        我们采取了一种折中的方法来克服上述两个问题。

        首先,我们提供了一组函数,用于在 Java 数组段和本地内存缓冲区之间复制原始数组元素。如果本地方法只需访问大型数组中的少量元素,则使用这些函数。

        其次,程序员可以使用另一组函数来检索数组元素的固定版本。请记住,这些函数可能需要 Java 虚拟机执行存储分配和复制。这些函数实际上是否复制数组取决于 VM 的实现,具体如下:

                ①. 如果垃圾回收器支持"锁定",且数组布局与本地方法预期的相同,则无需复制。

                ②. 否则,数组将被复制到一个不可移动的内存块(例如,在 C 堆中),并执行必要的格式转换。系统将返回指向拷贝的指针

        最后,接口提供了一些函数,用于通知虚拟机本地代码不再需要访问数组元素。调用这些函数时,系统要么取消数组的锁定,要么将原始数组与其不可移动的副本进行核对,并释放副本

        我们的方法具有灵活性。垃圾回收器算法可以针对每个给定的数组分别做出复制或“锁定”的决定。例如,垃圾回收器可以复制小对象,但“锁定”较大的对象。

        JNI 实现必须确保在多个线程中运行的本地方法可以同时访问同一个数组。例如,JNI 可以为每个被钉住的数组保留一个内部计数器,这样一个线程就不会解除被另一个线程钉住的数组。请注意,JNI 不需要为本地方法的独占访问锁定原始数组不同线程同时更新 Java 数组会导致非确定性结果

2.4.2 访问字段和方法

        JNI 允许本地代码访问 Java 对象的字段和调用 Java 对象的方法。JNI 通过符号名称和类型签名来识别方法和字段。根据字段或方法的名称和签名,分两步计算出查找字段或方法的成本。例如,要调用类 cls 中的方法 f ,本地代码首先要获取一个方法 ID,如下所示:

jmethodID mid = env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");

        这样,本地代码就可以重复使用该方法 ID,而无需花费方法查找的成本,如下所示:

jdouble result = env->CallDoubleMethod(obj, mid, 10, str);

        字段或方法 ID 不会阻止虚拟机卸销毁 ID 所派生的类。类销毁后,方法或字段 ID 将失效,并且不能传递给任何使用该 ID 的函数。因此,本地代码如果打算长期使用某个方法或字段 ID,必须确保:

                ①. 保持对底层类的实时引用,或

                ②. 重新计算方法或字段 ID;

        JNI 对内部如何实现字段和方法 ID 没有施加任何限制。

2.4.2.1 调用对调用者敏感的方法

        少数 Java 方法具有一种特殊属性,称为调用者敏感性。对调用者敏感的方法可以根据直接调用者的身份做出不同的行为。例如,AccessibleObject::canAccess 需要知道调用者才能确定是否可访问。

        当本地代码调用此类方法时,调用栈上可能没有任何 Java 调用者。程序员有责任了解从本地代码调用的 Java 方法是否对调用者敏感,以及如果没有 Java 调用者,这些方法将如何响应。如果有必要,程序员可以提供 Java 代码供本地代码调用,然后本地代码再调用原始 Java 方法

2.5 报告编程错误

        JNI 不会检查编程错误,如:传递 NULL 指针或非法参数类型。非法参数类型包括:使用普通 Java 对象而非 Java 类对象等。JNI 不检查这些编程错误的原因如下:

                ①. 强制 JNI 函数检查所有可能的错误条件会降低正常(正确)本地方法的性能。

                ②. 在许多情况下,没有足够的运行时类型信息来执行这种检查。

        多数 C 库函数都不会防范编程错误。例如, printf() 函数在接收到无效地址时通常会导致运行时错误,而不是返回错误代码。强制 C 库函数检查所有可能的错误条件很可能会导致重复检查:在用户代码中检查一次,然后在库中再检查一次

        程序员不得向 JNI 函数传递非法指针或错误类型的参数。否则可能导致任意后果,包括系统状态损坏或虚拟机崩溃。

也就是说:传递这种空指针异常(nullptr)的检查,由用户负责;

2.6 Java异常

        JNI 允许本地方法引发任意 Java 异常。本地代码也可以处理未处理的 Java 异常。未处理的 Java 异常会传播回虚拟机

2.6.1 异常和错误代码

        某些 JNI 函数使用 Java 异常机制来报告错误条件。在大多数情况下,JNI 函数通过返回错误代码和抛出 Java 异常来报告错误条件。错误代码通常是超出正常返回值范围的特殊返回值(如 NULL)。因此,程序员可以:

                ①. 快速检查最后一次 JNI 调用的返回值,以确定是否发生错误,以及

                ②. 调用 ExceptionOccurred() 函数来获取异常对象,该对象包含对错误条件的更详细描述。

        在两种情况下,程序员需要检查异常,而无法首先检查错误代码:

                ①. 调用 Java 方法的 JNI 函数会返回 Java 方法的结果。程序员必须调用 ExceptionOccurred() 来检查 Java 方法执行过程中可能出现的异常。

                ②. 某些 JNI 数组访问函数不会返回错误代码,但可能会抛出 ArrayIndexOutOfBoundsException ArrayStoreException

        在所有其他情况下,非错误返回值保证没有抛出异常。

2.6.2 异步异常

        一个线程可以通过调用 Thread.stop() 方法在另一个线程中引发异步异常,该方法自 Java 2 SDK 1.2 版起已被弃用。强烈建议程序员不要使用 Thread.stop() ,因为它通常会导致应用程序状态不确定

        此外,JVM 可能会在当前线程中产生异常,但这些异常并不是 JNI API 调用的直接结果,而是因为 JVM 内部的各种错误,例如: 像 StackOverflowError OutOfMemoryError VirtualMachineError 。这些异常也被称为异步异常。

        异步异常不会立即影响当前线程中本地代码的执行,直到出现以下情况:

                ①. 本地代码调用了一个可能引发同步异常的 JNI 函数,或

                ②. 本地代码使用 ExceptionOccurred() 来明确检查同步和异步异常。

        请注意,只有那些可能引发同步异常的 JNI 函数才会检查异步异常。

        本地方法应在必要的地方插入 ExceptionOccurred() 检查,例如:在没有其他异常检查的任何长时间运行代码中(可能包括紧密循环)。这样可以确保当前线程在合理的时间内响应异步异常。不过,由于异步异常的特性,在调用前进行异常检查并不能保证在检查和调用之间不会引发异步异常。

2.6.3 异常处理

        在本地代码中有两种处理异常的方法:

                ①. 本机方法可以选择立即返回,从而在启动本机方法调用的 Java 代码中抛出异常。

                ②. 本地代码可以通过调用 ExceptionClear() 清除异常,然后执行自己的异常处理代码。

        出现异常后,本地代码必须先清除异常,然后才能调用其他 JNI 函数。当出现待处理异常时,可以安全调用的 JNI 函数有:

ExceptionOccurred()
ExceptionDescribe()
ExceptionClear()
ExceptionCheck()
ReleaseStringChars()
ReleaseStringUTFChars()
ReleaseStringCritical()
Release<Type>ArrayElements()
ReleasePrimitiveArrayCritical()
DeleteLocalRef()
DeleteGlobalRef()
DeleteWeakGlobalRef()
MonitorExit()
PushLocalFrame()
PopLocalFrame()
DetachCurrentThread()

下一篇: 03-JNI 类型和数据结构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1483344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot的企业头条管理系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

springBoot整合Redis(二、RedisTemplate操作Redis)

Spring-data-redis是spring大家族的一部分&#xff0c;提供了在srping应用中通过简单的配置访问redis服务&#xff0c;对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装&#xff0c;RedisTemplate提供了redis各种操作、异常处理及序列化&#xff0c;支持发布订阅&…

4.6.CVAT——带点的注释详细操作

文章目录 1.形状模式下的点2.单点线性插值 使用单个点或包含多个点的形状对任务进行标注的指南。 1.形状模式下的点 它用于面部、地标注释等。 在开始之前&#xff0c;您需要选择 Points .如有必要&#xff0c;您可以在 Number of points 字段中设置固定数量的点&#xff0c;…

并查集(Disjoint Set)

目录 1.定义 2.初始化 3.查找 4.合并 4.1.按秩合并&#xff08;启发式合并&#xff09; 5.例题 题目描述 输入格式 输出格式 输入输出样例 说明/提示 1.定义 并查集&#xff0c;也称为不相交集合数据结构&#xff0c;是一种用于管理元素分组以及查找元素所属组的数…

Julia语言中的元编程

在 Julia 语言中&#xff0c;元编程&#xff08;Metaprogramming&#xff09;可以生成或操作其他代码。这种技术允许程序员在编译时或运行时动态地创建、修改或分析代码&#xff0c;从而增强语言的功能和灵活性&#xff0c;以宏&#xff08;Macros&#xff09;、表达式和符号&a…

【python报错】Intel MKL FATAL ERROR: Cannot load mkl/../../../libmkl_rt.so.2.

python报错&#xff1a; Intel MKL FATAL ERROR: Cannot load mkl/../../../libmkl_rt.so.2.在切换旧版numpy版本的时候&#xff0c;出现了这个报错&#xff0c;表现就是将numpy切换到<1.24的版本的时候&#xff0c;只要import numpy就弹出以上报错。 尝试了网上的各种方法…

【Go语言】Go语言中的流程控制

Go语言中的流程控制 流程控制主要用于设定计算执行的顺序&#xff0c;简历程序的逻辑结果&#xff0c;Go语言的流程控制语句与其他语言类似&#xff0c;支持如下几种流程控制语句&#xff1a; 条件语句&#xff1a;用于条件判断&#xff0c;对应的关键字有if、else和else if&a…

STM32-ADC一步到位学习手册

1.按部就班陈述概念 ADC 是 Analog-to-Digital Converter 的缩写&#xff0c;指的是模拟/数字转换器。它将连续变量的模拟信号转换为离散的数字信号。在 STM32 中&#xff0c;ADC 具有高达 12 位的转换精度&#xff0c;有多达 18 个测量通道&#xff0c;其中 16 个为外部通道&…

搜索算法(算法竞赛、蓝桥杯)--DFS单词接龙

1、B站视频链接&#xff1a;B20 DFS 单词接龙_哔哩哔哩_bilibili 题目链接&#xff1a;[NOIP2000 提高组] 单词接龙 - 洛谷 #include <bits/stdc.h> using namespace std; const int N25; int n,ans; int used[N];//每个单词的使用次数 string word[N];void dfs(string…

姓名若有孤寡数,极不利婚姻!

易经云“同声相应&#xff0c;同气相求”&#xff0c;一个人的名字会形成一个磁场&#xff0c;在个人的生活当中会影响到自己的方方面面&#xff0c;如运势、事业工作、桃花运等&#xff0c;同时也会影响自己的感情婚姻哦。 峰民曾经在办公室接待了一位女士&#xff0c;看了她…

备战蓝桥杯---状态压缩DP进阶题1

我们来看一看一道比较难的问题&#xff08;十分十分的巧妙&#xff09;&#xff1a; 显然我们应该一行一行放&#xff0c;又竖的会对下一行产生影响&#xff0c;我们令横着放为0&#xff0c;竖着放的上方为1. 对于下一行&#xff0c;前一行放1的下面为0&#xff0c;但是会出现…

C++:内存管理|new和delete

创作不易&#xff0c;感谢三连支持&#xff01; 一、内存分布 为什么需要内存管理呢&#xff1f;&#xff1f;因为我们在程序的运行过程中会需要各种各样的数据&#xff0c;而我们根据数据的不同存储在不同的区域里面&#xff0c;是为了更高效地处理数据。而C语言相比Java来说…

知识图谱1——neo4j

2024年要搞知识图谱&#xff0c;因此没有办法&#xff0c;只能将我之前固守的JDK1.8&#xff0c;升级到JDK21&#xff0c;因为JDK21也是LTS版本&#xff0c;neo4j高版本就不支持JDK8&#xff0c;因此没有办法&#xff0c;只有升级了。写这篇只是一个搭建笔记&#xff0c;我的初…

【Qt学习】QSpinBox 与 QDateTimeEdit 控件 的介绍与实例()

文章目录 QSpinBox1.1 介绍1.2 实例使用 - &#xff08;模拟点餐-功能扩充&#xff09;1.3 资源文件 2. QDateTimeEdit2.1 介绍2.2 使用&#xff08;计算时间差值 / 间隔&#xff09;daysTo() 与 secsTo() 2.3 资源文件 QSpinBox 1.1 介绍 对于QSpinBox&#xff0c;我们可以查…

OSI七层模型/TCP四层模型

协议&#xff1a; 协议是双方共同指定的一组规则&#xff0c;在网络通信中表示通信双方传递数据和解释数据的一组规则。 从A上传文件到服务器B,需要在A和B之间制定一个双方都认可的规则&#xff0c;这个规则就叫文件传输协议&#xff0c;该协议是ftp协议的一个初级版本&#…

JavaScript练手小技巧:一文看懂<script>标签的 ansyc 和 defer

<script>标签的 ansyc 和 defer 属性。只对外部加载 JS 文件有效。 <script src"js/app.js" async></script> <script src"js/app.js" defer></script> 普通加载 js&#xff08;同步加载&#xff09;&#xff1a;会打断 …

uview2中上传文件和若依前后端分离配合的代码

uview2中的上传文件需要配合着自己后端的上传的代码 uview2 代码&#xff1a; uploadFilePromise(url) {return new Promise((resolve, reject) > {// 后续自己封装let a uni.uploadFile({//自己的请求路径url: that.$baseURL /uploadAvater, //上传的文件filePath: tha…

leetcode hot100 每日温度

在本题中&#xff0c;我们是通过单调栈来解决的&#xff0c;因为我们采用了栈的数据结构&#xff0c;并且&#xff0c;栈内存储的元素是单调的。 本题我们考虑&#xff0c;将气温数组元素的下标存入栈中&#xff0c;首先初始化要把0放入&#xff0c;0是下标的意思。然后我们拿…

诊所门诊电子处方软件操作教程及试用版下载,医务室处方笺管理系统模板教程

诊所门诊电子处方软件操作教程及试用版下载&#xff0c;医务室处方笺管理系统模板教程 一、前言 以下软件程序教程以 佳易王诊所电子处方软件V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 如上图&#xff0c;点击基本信息设置——处方配…

【JavaEE进阶】 代理模式

文章目录 &#x1f343;前言&#x1f38b;什么叫代理模式&#x1f334;静态代理&#x1f38d;动态代理&#x1f6a9;JDK动态代理&#x1f6a9;CGLIB动态代理 ⭕总结 &#x1f343;前言 前面对Spring AOP的详细使用进行了介绍&#xff0c;这篇博客博主将详细讲解一下Spring AOP…