安卓实战开发之JNI再深入了解

2024-09-03 02:18

本文主要是介绍安卓实战开发之JNI再深入了解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JNI重新认识

头文件:

1.头文件中存放的是对某个库中所定义的函数、宏(define)、类型、全局变量等进行声明,它类似于一份仓库清单。若用户程序中需要使用某个库中的函数,则只需要将该库所对应的头文件include到程序中即可。

2.头文件中定义的是库中所有函数的函数原型。而函数的具体实现则是在库文件中。

3.在连接器连接程序时,会依据用户程序中导入的头文件,将对应的库函数导入到程序中。头文件以.h为后缀名。

头文件是给编译器用的,库文件是给连接器用的

函数库:

1.动态库:在编译用户程序时不会将用户程序内使用的库函数连接到用户程序的目标代码中,只有在运行时,且用户程序执行到相关函数时才会调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。

2.静态库:在编译用户程序时会将其内使用的库函数连接到目标代码中,程序运行时不再需要静态库。使用静态库生成可执行文件比较大。

为什么要进行交互?

首先,java语言提供的类库无法满足要求,且在数学运算,实时渲染的游戏上,音视频处理等方面上与c/c++相比效率稍低。然后,java语言无法直接操作硬件,c/c++代码不仅能操作硬件而且还能发挥硬件最佳性能。接着,使用java调用本地的c/c++代码所写的库,省去了重复开发的麻烦,并且可以利用很多开源的库提高程序效率。

java call c

Java调用C/C++大概有这样几个步骤

  1. 编写带有native方法的Java类, 使用javac工具编译Java类
  2. 使用javah来生成与native方法对应的头文件
  3. 实现相应的头文件, 并编译为动态链接库

我们对这个还是很清楚的,看代码:

c代码:

  //// Created by Administrator on 2016/8/1.//#include "JNIUtils.h"#include <string.h>#include<stdio.h>#include<stdlib.h>#include <android/log.h>/*** 把一个jstring转换成一个c语言的char* 类型.*/char* _JString2CStr(JNIEnv* env, jstring jstr) {char* rtn = NULL;jclass clsstring = (*env)->FindClass(env, "java/lang/String");jstring strencode = (*env)->NewStringUTF(env,"GB2312");jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");jsize alen = (*env)->GetArrayLength(env, barr);jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);if(alen > 0) {rtn = (char*)malloc(alen+1); //"\0"memcpy(rtn, ba, alen);rtn[alen]=0;}(*env)->ReleaseByteArrayElements(env, barr, ba,0);return rtn;}JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intMethod(JNIEnv *env, jclass jobj,jint num){return num*num;}JNIEXPORT jboolean JNICALL Java_com_losileeya_jnimaster_JNIUtils_booleanMethod(JNIEnv * env, jclass jobj,jboolean boolean){return !boolean;}JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_stringMethod(JNIEnv * env, jclass jobj,jstring jstr){//jstring jstr-->char*char* fromJava = _JString2CStr(env,jstr);char* fromC = "add I am from C!! ";//字符串的拼接函数,会把拼接后的结果放在第一个参数里面strcat(fromJava,fromC);return (*env)->NewStringUTF(env,fromJava);}JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intArrayMethod(JNIEnv * env, jclass jobj,jintArray array){int i, sum = 0;jsize len = (*env)->GetArrayLength(env, array);jint *body = (*env)->GetIntArrayElements(env, array, 0);for (i = 0; i < len; ++i){sum += body[i];}(*env)->ReleaseIntArrayElements(env, array, body, 0);return sum;}

c++代码:

 /// Created by Administrator on 2016/8/1.//#include "JNIUtils.h"#include <string.h>#include<stdlib.h>#include<stdio.h>#include <android/log.h>/*** 把一个jstring转换成一个c++语言的char* 类型.*/char* _JString2CStr(JNIEnv* env, jstring jstr) {char* rtn = NULL;jclass clsstring = env->FindClass( "java/lang/String");jstring strencode = env->NewStringUTF("GB2312");jmethodID mid = env->GetMethodID( clsstring, "getBytes", "(Ljava/lang/String;)[B");jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); // String .getByte("GB2312");jsize alen = env->GetArrayLength( barr);jbyte* ba = env->GetByteArrayElements( barr, JNI_FALSE);if(alen > 0) {rtn = (char*)malloc(alen+1); //"\0"memcpy(rtn, ba, alen);rtn[alen]=0;}env->ReleaseByteArrayElements(barr, ba,0);return rtn;}JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intMethod(JNIEnv * env, jclass jobj,jint num){return num *num;}JNIEXPORT jboolean JNICALL Java_com_losileeya_jnimaster_JNIUtils_booleanMethod(JNIEnv * env, jclass jobj,jboolean boolean){return !boolean;}JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_stringMethod(JNIEnv *env , jclass jobj, jstring jstr){//jstring jstr-->char*char* fromJava = _JString2CStr(env,jstr);char* fromC = "add I am from C!! ";//字符串的拼接函数,会把拼接后的结果放在第一个参数里面strcat(fromJava,fromC);return env->NewStringUTF(fromJava);}JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intArrayMethod(JNIEnv * env, jclass jobj,jintArray array){int sum = 0;jsize len = env->GetArrayLength(array);jint *arr = env->GetIntArrayElements(array, 0);for(int i = 0;i<len; i++){sum+=arr[i];}env->ReleaseIntArrayElements(array, arr,0);return sum;}

从上面Native函数的命名上我们可以了解到JNI函数的命名规则: Java代码中的函数声明需要添加native 关键 字;Native的对应函数名要以“Java”开头,后面依次跟上Java的“package名”、“class名”、“函数名”,中间以下划线“” 分割,在package名中的“.”也要改为“_”。此外,关于函数的参数和返回值也有相应的规则。对于Java中的基本类型如int 、double 、char 等,在Native端都有相对应的类型来表示,如jint 、jdouble 、jchar 等;其他的对象类型则统统由jobject 来表示(String 是个例外,由于其使用广泛,故在Native代码中有jstring 这个类型来表示,正如在上例中返回值String 对应到Native代码中的返回值jstring )。而对于Java中的数组,在Native中由jarray 对应,具体到基本类型和一般对象类型的数组则有jintArray 等和jobjectArray 分别对应(String 数组在这里没有例外,同样用jobjectArray 表示)。还有一点需要注意的是,在JNI的Native函数中,其前两个参数JNIEnv 和jobject* 是必需的——前者是一个JNIEnv 结构体的指针,这个结构体中定义了很多JNI的接口函数指针,使开发者可以使用JNI所定义的接口功能;后者指代的是调用这个JNI函数的Java对象,有点类似于C++中的this 指针。在上述两个参数之后,还需要根据Java端的函数声明依次对应添加参数。在上例中,Java中声明的JNI函数没有参数,则Native的对应函数只有类型为JNIEnv 和jobject* 的两个参数。
效果图:
这里写图片描述

c call java

一般来说,要在Native代码中访问Java对象,有如下几个步骤:

  1. 得到该Java对象的类定义。JNI定义了jclass 这个类型来表示Java的类的定义,并提供了FindClass接口,根据类的完整的包路径即可得到其jclass 。
  2. 根据jclass 创建相应的对象实体,即jobject 。在Java中,创建一个新对象只需要使用new 关键字即可,但在Native代码中创建一个对象则需要两步:首先通过JNI接口GetMethodID得到该类的构造函数,然后利用NewObject接口构造出该类的一个实例对象。
  3. 访问jobject 中的成员变量或方法。访问对象的方法是先得到方法的Method ID,然后使用CallMethod 接口调用,这里Type对应相应方法的返回值——返回值为基本类型的都有相对应的接口,如CallIntMethod;其他的返回值(包括String) 则为CallObjectMethod。可以看出,创建对象实质上是调用对象的一个特殊方法,即构造函数。访问成员变量的步骤一样:首先 GetFieldID得到成员变量的ID,然后Get/SetField读/写变量值。

寻找class对象, 并实例化

JVM在Java中都是自己启动的, 在C/C++中只能自己来启动了, 启动完之后的事情就和在Java中一样了, 不过要使用C/C++的语法.

获取class对象比较简单, FindClass(env, className).

cls = (*env)->FindClass(env, "xxxx");  

在Java中的类名格式是java.lang.String, 但是className的格式有点不同, 不是使用’.’作为分割, 而是’/’, 即java/lang/String.

我们知道Java中构造函数有两种, 一种是默认的没有参数的, 一种是自定义的带有参数的. 对应的在C/C++中, 有两种调用构造函数的方法.

调用默认构造函数

// 调用默认构造函数  obj = (*env)->AllocObjdect(env, cls);   

构造函数也是方法, 类似调用方法的方式.

// 调用指定的构造函数, 构造函数的名字叫做<init>  mid = (*env)->GetMethodID(env, cls, "<init>", "()V");  obj = (*env)->NewObject(env, cls, mid);  

调用方法和修改属性

关于方法和属性是有两个ID与之对应, 这两个ID用来标识方法和属性.

jmethodID mid;  jfieldID fid; 

方法分为静态和非静态的, 所以对应的有

mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");     mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");   

上面两个方法是同名的, 都叫sayHello, 但是签名不同, 所以可以区分两个方法.

JNI的函数都是有一定规律的, Static就表示是静态, 没有表示非静态.

方法的调用如下

jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);     jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);   

我们可以看到静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象.

属性也有静态和非静态, 示例中只有非静态的.

获取属性ID

fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");   

改属性的值

(*env)->SetObjectField(env, obj, fid, arg); // 修改属性  

关于jstring的说明

java的String都是使用了unicode, 是双字节的字符, 而C/C++中使用的单字节的字符。

从C转换为java的字符, 使用NewStringUTF方法

jstring arg = (*env)->NewStringUTF(env, name);  

从java转换为C的字符, 使用GetStringUTFChars

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

下面我们来看代码:

c代码:

  /** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_helloFromJava* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1helloFromJava(JNIEnv *env, jobject jobj){jclass jclazz=(*env)->FindClass(env,"com/losileeya/jnimaster/JNIUtils");jmethodID jmethodid=(*env)->GetMethodID(env,jclazz,"helloFromJava","()V");jobject jobjs=(*env)->AllocObject(env,jclazz);(*env)->CallVoidMethod(env,jobjs,jmethodid);}/** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_add* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1add(JNIEnv *env, jobject jobj){//1.得到类对应的字节码//全类名,把.改成///jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = (*env)->FindClass(env, "com/losileeya/jnimaster/JNIUtils");//2.得到要调用的方法名//第三个参数:方法名//第四个但是:方法签名//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "add","(II)I");//3.得到要调用的方法对应的类的实例// jobject     (*AllocObject)(JNIEnv*, jclass);jobject jobjs = (*env)->AllocObject(env, jclazz);//4.调用方法// jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);int reuslt =  (*env)->CallIntMethod(env,jobjs,jmethodid,99,1);}/** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_printString* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1printString(JNIEnv *env, jobject jobj){//1.得到类对应的字节码//全类名,把.改成///jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = (*env)->FindClass(env, "com/losileeya/jnimaster/JNIUtils");//2.得到要调用的方法名//第三个参数:方法名//第四个但是:方法签名//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "printString","(Ljava/lang/String;)V");//3.得到要调用的方法对应的类的实例// jobject     (*AllocObject)(JNIEnv*, jclass);jobject jobjs = (*env)->AllocObject(env, jclazz);//4.调用方法// void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);jstring text = (*env)->NewStringUTF(env,"I am from C!!");(*env)->CallVoidMethod(env, jobjs, jmethodid,text); //成功调用了Java中JNI里面的printString(String s);}/** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_sayHello* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1sayHello(JNIEnv * env, jobject jobj){//1.得到字节码jclass jclazz = (*env)->FindClass(env,"com/losileeya/jnimaster/JNIUtils");//2.得到方法jmethodID  jmethodid = (*env)->GetStaticMethodID(env,jclazz,"sayHello","(Ljava/lang/String;)V");//3.调用//void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);jstring text = (*env)->NewStringUTF(env,"I am from C!! I am static method !!!");(*env)->CallStaticVoidMethod(env,jclazz,jmethodid,text);//成功调用了Java中JNI类的静态方法sayHello(String text)}

c++代码:

 /** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_helloFromJava* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1helloFromJava(JNIEnv*env,jobject jobj){jclass jclazz = env->FindClass("com/losileeya/jnimaster/JNIUtils");jmethodID jmethodid = env->GetMethodID(jclazz, "helloFromJava", "()V");jobject jobjs = env->AllocObject(jclazz);env->CallVoidMethod(jobjs, jmethodid);}/** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_add* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1add(JNIEnv*env,jobject jobj){//1.得到类对应的字节码//全类名,把.改成///jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = env->FindClass( "com/losileeya/jnimaster/JNIUtils");//2.得到要调用的方法名//第三个参数:方法名//第四个但是:方法签名//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID jmethodid = env->GetMethodID(jclazz, "add","(II)I");//3.得到要调用的方法对应的类的实例// jobject     (*AllocObject)(JNIEnv*, jclass);jobject jobjs = env->AllocObject(jclazz);//4.调用方法// jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);int reusle =  env->CallIntMethod(jobjs,jmethodid,99,1);}/** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_printString* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1printString(JNIEnv*env,jobject jobj){//1.得到类对应的字节码//全类名,把.改成///jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = env->FindClass( "com/losileeya/jnimaster/JNIUtils");//2.得到要调用的方法名//第三个参数:方法名//第四个但是:方法签名//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID jmethodid = env->GetMethodID( jclazz, "printString","(Ljava/lang/String;)V");//3.得到要调用的方法对应的类的实例// jobject     (*AllocObject)(JNIEnv*, jclass);jobject jobjs = env->AllocObject(jclazz);//4.调用方法// void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);jstring text = env->NewStringUTF("I am from C!!");env->CallVoidMethod( jobjs, jmethodid,text); //成功调用了Java中JNI里面的printString(String s);}/** Class:     com_losileeya_jnimaster_JNIUtils* Method:    ccallJava_sayHello* Signature: ()V*/JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1sayHello(JNIEnv*env,jobject jobj){//1.得到字节码jclass jclazz = env->FindClass("com/losileeya/jnimaster/JNIUtils");//2.得到方法jmethodID  jmethodid = env->GetStaticMethodID(jclazz,"sayHello","(Ljava/lang/String;)V");//3.调用//void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);jstring text = env->NewStringUTF("I am from C!! I am static method !!!");env->CallStaticVoidMethod(jclazz,jmethodid,text);//成功调用了Java中JNI类的静态方法sayHello(String text)}

可以看到,上述代码和前面讲到的步骤完全相符。这里提一下编程时要注意的要点:1、FindClass要写明Java类的完整包路径,并将 “.”以“/”替换;2、GetMethodID的第三个参数是方法名(对于构造函数一律用“”表示),第四个参数是方法的“签 名”,需要用一个字符串序列表示方法的参数(依声明顺序)和返回值信息。由于篇幅所限,这里不再具体说明如何根据方法的声明构造相应的“签名”,请参考 JNI的相关文档。

关于上面谈到的步骤再补充说明一下:在JNI规范中,如上这种使用NewObject创建的对象实例被称为“Local Reference”,它仅在创建它的Native代码作用域内有效,因此应避免在作用域外使用该实例及任何指向它的指针。如果希望创建的对象实例在作用 域外也能使用,则需要使用NewGlobalRef接口将其提升为“Global Reference”——需要注意的是,当Global Reference不再使用后,需要显式的释放,以便通知JVM进行垃圾收集。

顺便看下截图:

JNI 更新UI

在Android使用Jni时,为了能够使UI线程即主线程与工作线程分开,经常要创建工作线程,然后在工作线程中调用C/C++函数.为了在C/C++ 函数中更新Android的UI,又时常使用回调。jni更新ui的话,我们就要注重jobject的使用了。

看代码:(使用)

 static {System.loadLibrary("CCallJavaForUI");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void CCallJavaForUI(View view){this.callShowToast();}public void showToast(){//this - Activity的实例//startActitity();-->//new MainActivity();System.out.println("showToast()----------");Toast.makeText(this, "showToast()---------", Toast.LENGTH_LONG).show();}/*** 调用MainActivity中的showToast()方法*/public native void callShowToast();

c代码 :

    //// Created by Administrator on 2016/8/6.//#include "JNIUtils.h"#include<stdio.h>#include<stdlib.h>/*** 调用java 中MainActivity中的showToast()方法* jobject jobj:谁调用就是谁的实例,当前是JNI.this--->MainActivity.this*/JNIEXPORT void JNICALL Java_com_losileeya_jniupdateui_MainActivity_callShowToast(JNIEnv * env, jobject jobj){//1.得到字节码jclass   jclazz = (*env)->FindClass(env,"com/losileeya/jniupdateui/MainActivity");//2.得到方法//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID jmethodid = (*env)->GetMethodID(env,jclazz,"showToast","()V");//3.得到对象//      jobject jobjs = (*env)->AllocObject(env,jclazz);//4.调用方法//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);(*env)->CallVoidMethod(env,jobj,jmethodid);//成功调用了中MainActivity中的showToast()方法};

c++代码:

  //// Created by Administrator on 2016/8/6.//#include "JNIUtils.h"#include<stdio.h>#include<stdlib.h>/*** 调用java 中MainActivity中的showToast()方法* jobject jobj:谁调用就是谁的实例,当前是JNI.this--->MainActivity.this*/JNIEXPORT void JNICALL Java_com_losileeya_jniupdateui_MainActivity_callShowToast(JNIEnv * env, jobject jobj){//1.得到字节码jclass   jclazz = (*env)->FindClass(env,"com/losileeya/jniupdateui/MainActivity");//2.得到方法//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID jmethodid = (*env)->GetMethodID(env,jclazz,"showToast","()V");//3.得到对象//      jobject jobjs = (*env)->AllocObject(env,jclazz);//4.调用方法//void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);(*env)->CallVoidMethod(env,jobj,jmethodid);//成功调用了中MainActivity中的showToast()方法};

效果图:
这里写图片描述

C和C++函数时的JNI使用区别

Java调用C和C++函数时的JNI使用区别:

注意:jni.h头文件中对于.c & .cpp采用不同的定义

在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针

C形式需要对env指针进行双重deferencing,而且须将env作为第一个参数传给jni函数

jclass (JNICALL *GetObjectClass) (JNIEnv *env, jobject obj);

jclass GetObjectClass(jobject obj)

{

return functions->GetObjectClass(this,obj);

}

对于*.c

1.jclass test_class = (*env)->GetObjectClass(env, obj);

2.jfieldID id_num = (*env)->GetFieldID(env, test_class, “num”, “I”);

对于 *.cpp

1.jclass test_class = env->GetObjectClass(obj);

2.jfieldID id_num = env->GetFieldID(test_class, “num”, “I”);

在 C 中,

JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。

在 C++ 中,

JNIEnv 类拥有处理函数指针查找的内联成员函数。

下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。

C 语法:jsize len = (*env)->GetArrayLength(env,array);

C++ 语法:jsize len =env->GetArrayLength(array);

1、jni 可以调用本地C函数。
2、jni 调用C++库时,首先要将C++库提供的功能封装成纯C格式的函数接口,然后jni里面调用这些C接口。总结,没什么区别。一个是 jni调用c。另一个是jni调用c,c调用c++。

传送门:jnimaster

总结

JNI使用c和cpp的基本使用和了解就讲的差不多了,更多的学习可以去看jni的使用安全手册。

这篇关于安卓实战开发之JNI再深入了解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1131742

相关文章

MySQL数据库约束深入详解

《MySQL数据库约束深入详解》:本文主要介绍MySQL数据库约束,在MySQL数据库中,约束是用来限制进入表中的数据类型的一种技术,通过使用约束,可以确保数据的准确性、完整性和可靠性,需要的朋友... 目录一、数据库约束的概念二、约束类型三、NOT NULL 非空约束四、DEFAULT 默认值约束五、UN

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.