安卓实战开发之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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来