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

【48】Android通过libjpeg-turbo库实现图片压缩

(1)公司为节约图片占用服务器存储资源成本,需要对Android手机客户端所传递到云存储服务器中的图片进行压缩,在不影响图片失真程度的情况下,最大限度的压缩图片以节省图片所占用的存储空间。
(2)本文即实战了通过jni调用libjpeg-gurbo库实现图片压缩的目的。
(3)实战结果:可以将一张4.9M的图片压缩至600kb大小,而且图片并不失真。
(4)本文细节尚存疏漏之处,请各位看观多多谅解,但能搞定这件事情已经实属不易,或许可以给你一些解决问题的思路供你参考。

Android通过libjpeg-turbo库实现图片压缩

文章目录

  • Android通过libjpeg-turbo库实现图片压缩
    • 1.Android CPU基础知识
      • 1.1安卓CPU类型的说明
      • 1.2安卓CPU类型的兼容性说明
      • 1.3其他说明
      • 1.4abiFilters 'armeabi-v7a'
    • 2.使用AndroidStudio编译生成libjpeg-turbo动态链接库so文件
      • 2.1libjpeg-turbo git clone地址
      • 2.2使用AndroidStudio编译生成so动态链接库文件
    • 3.将libjpeg_turbo的so库文件拷贝到其他项目使用,以zipphoto项目为例
      • 3.1将自己编写的cpp源文件生成so动态链接库
      • 3.2在Activity中调用图片压缩的本地方法
        • 3.2.1图片压缩工具源码
        • 3.2.2Activity调用源码
        • 3.2.3Activity布局文件
    • 4.总结
      • 4.1注意:
    • 5.参考文档

本尊

1.Android CPU基础知识

1.1安卓CPU类型的说明

(1)arm64-v8a: 第8代、64位ARM处理器,目前手机大多数是此架构.
(2)armeabiv-v7a: 第七代及以上的 ARM 处理器。2011年5月以后生产的大部分安卓设备都使用它
(3)armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多,缺乏对浮点数计算的硬件支持,在须要大量计算时有性能瓶颈。
(4)x86: 平板、模拟器用得比较多。x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现对arm .so 的兼容。考虑 x86不到1% 的市场占有率,x86 相关的两个 .so 也是可以忽略的。
(5)x86_64: 64位的平板
(6)mips/mips64 极少用于手机可忽略。

1.2安卓CPU类型的兼容性说明

(1)armeabi设备只兼容armeabi,不支持硬件辅助浮点运算,支持所有的 ARM* 设备;
(2)armeabi-v7a设备兼容armeabi-v7a、armeabi;
(3)arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
(4)x86设备兼容x86、armeabi;
(5)x86_64设备兼容x86_64、x86、armeabi;
(6)mips64设备兼容mips64、mips;
(7)mips只兼容mips;

1.3其他说明

(1)以arm64-v8a设备为例,该Android设备优先寻找libs目录下的arm64-v8a文件夹。如果有文件夹,但是没有so库,则会报错。如果没有arm64-v8a文件夹,则会去找armeabi-v7a文件夹。如果找不到armeabi-v7a文件夹,则寻找armeabi文件夹,兼容运行该文件夹下的so,
(2)从上面解释就可以大概知道下载哪种APK了。普通手机用户,建议下载arm64-v8a(第8代、64位ARM处理器)版本,能够发挥手机最佳性能(只要本型号手机支持8G运存或8G以上就是64位处理器)。如果是很老的手机,也有可能不是64位处理器,那么就选择armeabi-v7a,几乎通用所有手机,而且也兼容64位处理器。

1.4abiFilters ‘armeabi-v7a’

(1)指的是以armeabi-v7a指令环境运行
(2)默认会编译出4个平台,arm64-v8a、armeabi-v7a、x86、x86_64

2.使用AndroidStudio编译生成libjpeg-turbo动态链接库so文件

2.1libjpeg-turbo git clone地址

git clone https://github.com/libjpeg-turbo/libjpeg-turbo

2.2使用AndroidStudio编译生成so动态链接库文件

(1)新建Android Native C++项目
在这里插入图片描述

(2)将libjpeg-turbo的源代码(注意是所有文件)复制到 native c++项目的cpp目录中
在这里插入图片描述

(3)配置app目录下的build.gradle文件,主要检查两个配置项是否已经配置了

a.android的defaultConfig配置中是否存在ndk关于CPU类型的配置

ndk{abiFilters 'arm64-v8a'
}

b.android下是否存在externalNativeBuild的配置,即

externalNativeBuild {cmake {path file('src/main/cpp/CMakeLists.txt')version '3.22.1'}
}

(4)运行项目生成so库
(1)生成CPU类型为arm64-v8a与armeabiv-v7a的so库需要在Android真机上运行项目,项目运行完之后,到项目app目录下的.cxx目录里面去找生成的so库文件。
在这里插入图片描述

(2)生成CPU类型为x86与x86_64的so库需要在Android模拟器上运行项目如雷电或夜神或其他Android模拟器上运行,项目运行完之后,同样是在app目录下的.cxx目录里面去找生成的so库文件。

(3)以下是多次设置CPU类型运行项目后生成so库文件的列表

在这里插入图片描述

3.将libjpeg_turbo的so库文件拷贝到其他项目使用,以zipphoto项目为例

(1)新建Android项目,取名zipphoto。
(2)新建jni目录
(3)把libjpeg-turbo关于图片压缩相关的头文件拷贝到jni目录。
注意:到libjpeg-turbo的源代码中去搜索头文件所在的目录,然后拷贝过来,主要是以下几个头文件。
在这里插入图片描述

(4)编写图片压缩zipimg.cpp源文件

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
#include <malloc.h>
#define TAG "image "
#define LOGE(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
/*** 导入jpeg的头文件*/
extern "C"{#include "jpeglib.h"
}typedef uint8_t BYTE;/** (1)extern "C":声明下面的代码,采用C的编译方式。* (2)JNIEnv *env:是整个JNI的所有API的一个桥梁,只需要将这个里面的所有函数学完了,那么JNI就学完了。即所有的操作都需要通过它来。* (3)JNIEXPORT:JNI重要标记关键字,不能少(VS编译能通过,运行会报错)或(AS运行不会报错),规则(标记为该方法可以被外部调用),Windows内部规则。* (4)JNICALL:也是一个关键字,(可以少的) jni call(约束函数入栈顺序和堆栈内存清理的规则)。* (5)jclass clazz:如果java本地方法声明成static native,jclass就是指的这个类,如果java本地方法未声明成static,就会变成jobject thiz,即指的是这个类的实例* (6)如果当前是native-lib.c,(*env)->xxx函数,如果是C语言,JNIEnv *env是二级指针,C是没有对象的,想持有env环境,就必须传递进去,(*env).AllocObject(clazz);* (7)如果当前是native-lib.cpp,env->xxx函数,如果是C++语言,JNIEnv *env是一级指针,evn可以直接调用一级指针下的函数,C++是有对象的,本身就会持有this,所以不需要传,env->AllocObject(clazz);*/
/*
extern "C"
JNIEXPORT jstring JNICALL
Java_com_gdc_imagezip_ImageZipUtil_compressBitmap(JNIEnv *env, jclass clazz, jobject bitmap, jint width, jint height, jint quality, jbyteArray file_name, jboolean optimize) {}*//*** 1.写数据,将b、g、r通过jpeg的方式写入进去* 2.APP需要通过jpeg_compress_struct去调用* @param data* @param path* @param w* @param h*/void writeJpg(BYTE *data, const char *path, int w, int h) {struct jpeg_compress_struct jpeg_struct;//1.初始化//()设置错误信息struct jpeg_error_mgr jerr;//()错误信息初始化,设置错误的处理信息jpeg_struct.err = jpeg_std_error(&jerr);//()设置缓冲区,创建开始压缩任务jpeg_create_compress(&jpeg_struct);//()因为我们要输出,所以打开一个文件,即输出到文件中去,FILE *file = fopen(path,"wb");//()设置输出路径jpeg_stdio_dest(&jpeg_struct,file);//()设置图片宽高jpeg_struct.image_width = w;jpeg_struct.image_height = h;//()设置使用哈夫曼算法进行压缩,为false的时候采用哈夫曼算法进行压缩jpeg_struct.arith_code = FALSE;//()设置对结构进行优化jpeg_struct.optimize_coding = TRUE;//()初始化位深jpeg_struct.in_color_space = JCS_RGB;//()初始化组成,R、G、B三个为一组,所以组成为3.jpeg_struct.input_components = 3;//()设置其他的参数函数,设置成默认的jpeg_set_defaults(&jpeg_struct);//()设置压缩质量,范围是0~100,一般20优化比是最好的。jpeg_set_quality(&jpeg_struct,20,true);//2.开始压缩jpeg_start_compress(&jpeg_struct,TRUE);//()写入数据JSAMPROW row_pointer[1];//()行的rgbint row_stride = w*3;while(jpeg_struct.next_scanline < h){row_pointer[0] = reinterpret_cast<JSAMPROW>(&data[jpeg_struct.next_scanline * w * 3]);jpeg_write_scanlines(&jpeg_struct,row_pointer,1);//让next_scanline++自动加1}//()结束压缩//()释放结构体jpeg_finish_compress(&jpeg_struct);//()jpeg_destroy_compress(&jpeg_struct);//()关闭文件fclose(file);
}/**1.jpeg压缩* (1)条件:压缩程度比较低,采用的是哈夫曼的算法进行压缩,所有的压缩数据必须是元数据,即不能够被分割的数据。* (2)bitmap不是元数据,像素也不是元数据,因为像素还可以分为a r g b。* (3)一个像素由多少位去表示呢?由4个字节表示。* 一个像素是一个int类型,一个像素由高8位,第二个8位,第三个8位,第四个8位,每个8位分别表示A、R、G、B.* 4个字节就由4个8位组成。* (4)A、R、G、B整张力片取出来之后,放到一个数组里面去。* (5)将一张bitmap取出来之后,我肯定要取出它的像素数据,怎么取出像素数据呢?** 2.取出Bitmap的像素数据* int AndroidBitmap_lockPixels(_JNIEnv *env, jobject jbitmap, void **addrPtr)* (1)_JNIEnv *env:结构体* (2)jobject jbitmap:bitmap* (3)void **addrPtr:入参与出参对象,它是一个数组,将这个数组传进来之后,经过这个方法,它就将bitmap像素数据全部转化到数组里面去.** 3.在NDK里面去取出图片的宽和高* static int AndroidBitmap_getInfo(_JNIEnv *env, jobject jbitmap, struct__anonymous *info)* (1)_JNIEnv *env:结构体* (2)jobject jbitmap:bitmap* (3)struct__anonymous *info:入参与出参结构体** 4.整体需求就是* (1)将一张图片全部解压成元数据,并且将它放到一个datas数组里面去。*/
extern "C"
JNIEXPORT void JNICALL
Java_com_gdc_zipphoto_util_ImageZipUtil_compressBitmap(JNIEnv *env, jobject thiz, jobject bitmap, jstring path_) {//()获取图片存储路径const char *path = env->GetStringUTFChars(path_, 0);LOGE("==================1.进入方法==================");//()定义入参出参对象AndroidBitmapInfo bitmapInfo,在AndroidBitmapInfo结构体中就有了图片的宽、高属性。AndroidBitmapInfo bitmapInfo;//()在NDK里面去取出图片的宽和高,调用此函数之后,bitmapInfo中会有bitmap的具体宽高值。AndroidBitmap_getInfo(env,bitmap,&bitmapInfo);//()取出Bitmap的像素数据,放到pixels数组里面去BYTE *pixels;//()将bitmap中的数据转换到pixels里面AndroidBitmap_lockPixels(env,bitmap,reinterpret_cast<void **>(&pixels));//()遍历pixels里面的内容,它的内容是4个字节的int数据。遍历图片的宽、高。int h = bitmapInfo.height;int w = bitmapInfo.width;//()定义像素数据,用于存放取出的像素数据int color;//()定义R、G、B分别存放红、绿、蓝三种颜色BYTE r,g,b;//()定义新的数组用于存放取出来的元数据,tmpDatas是防止丢失的数组。BYTE *datas, *tmpDatas;//()这个数组有多大呢?它应该是w*h的3倍datas = static_cast<BYTE *>(malloc(w * h * 3));tmpDatas = datas;for(int i = 0; i < h ; ++i){for(int j = 0 ; j < w ; ++j){//()将数组元素像素数据取出来color = *((int*)pixels);//()取出A、R、G、B,但本例中只取R、G、B,要取R、G、B就需要用到左移与右移r = ((color & 0x00FF0000) >> 16);g = ((color & 0x0000FF00) >> 8);b = (color & 0x000000FF);//()将元数据放到一个新的数组里面去,如何放呢?是按b、g、r、a的方式放的,即是倒着放的,*datas = b;*(datas+1) = g;*(datas+2) = r;datas += 3;//()取下一个像素数据需要+4,因为每个像素是由4个字节的32位int数据表示的。pixels += 4;}}LOGE("==================2.压缩完毕==================");//()释放图片所占用的资源AndroidBitmap_unlockPixels(env,bitmap);LOGE("==================3.写入SD卡==================");//()写数据,将b、g、r通过jpeg的方式写入进去writeJpg(tmpDatas,path,w,h);env->ReleaseStringUTFChars(path_,path);
}

3.1将自己编写的cpp源文件生成so动态链接库

(1)在jni目录新建Android.mk文件与Application.mk文件
(2)Android.mk文件主要用于说明我自己的so库需要哪些so动态库的支持,有哪些cpp源文件,本例的内容如下:

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := libjpeg
LOCAL_SRC_FILES := libjpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)LOCAL_MODULE    := zipimg
LOCAL_SRC_FILES := zipimg.cpp
LOCAL_SHARED_LIBRARIES := libjpeg
LOCAL_LDLIBS := -ljnigraphics -llog
LOCAL_C_INCLUDES := $(LOCAL_PATH)include $(BUILD_SHARED_LIBRARY)

(3)Application.mk文件主要用于说明我自己的so库支持哪些CPU类型 ,以及最高支持到哪一个Android平台使用,内容如下:

APP_ABI :=arm64-v8a
APP_PLATFORM := android-33

(3)为zipphoto配置ndk编译环境
File----- > Project Structure----->设置自己下载的ndk路径
在这里插入图片描述

(4)配置项目app目录下的build.gradle文件
a.在android节点下,添加sourceSets节点配置,用于指定生成自己的so动态链接库成功后,存放输出so库到哪个目录,本例是放到libs目录下

    sourceSets{main{jni.srcDirs = []//设置禁止gradle生成Android.mkjniLibs.srcDirs = ['libs']}}

b.在android节点下,添加一个task构建任务,配置内容如下

task ndkBuild(type:Exec){commandLine "D:\\android-ndk-r27c\\ndk-build.cmd",'NDK_PROJECT_PATH=build/intermediates/ndk','NDK_LIBS_OUT=libs','APP_BUILD_SCRIPT=jni/Android.mk','NDK_APPLICATION_MK=jni/Application.mk'}

(5)通过ndk构建生成so动态链接库
我所采用的是通过命令行的方式生成动态链接库。
a.将自己下载的ndk设置到windows系统环境变量中。过程略(自己网上查)
b.打开命令提示符
win+R cmd 回车
c.进入到zipphoto的jni目录,即执行命令:
cd F:\cxzworkspace\zipphoto\app\jni
d.执行ndk-build命令
在这里插入图片描述

通过以上步骤就已经完成了自己编写的c++程序的so动态链接库的生成。

3.2在Activity中调用图片压缩的本地方法

3.2.1图片压缩工具源码
public class ImageZipUtil {//1.引入动态链接库static {//(1)zipimg动态链接库用于调用图片压缩jpeg.so动态链接库中所提供的api函数实现图片的压缩System.loadLibrary("zipimg");//(2)引入图片压缩jpeg.so动态链接库System.loadLibrary("jpeg");}//2.图片压缩本地方法,在zipphoto.cpp的c++源文件中具体实现public native void compressBitmap(Bitmap bitmap, String path);
}
3.2.2Activity调用源码
package com.gdc.zipphoto;import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;import com.gdc.zipphoto.permission.PermissionsUtil;
import com.gdc.zipphoto.util.FilePathManager;
import com.gdc.zipphoto.util.ImageZipUtil;import java.io.File;public class MainActivity extends Activity implements View.OnClickListener , PermissionsUtil.IPermissionsResult {private Button mCompressBtn;private ImageView mImage;/*** 图片存放根目录*/private final String mImageRootDir = FilePathManager.getFileDirectory().getAbsolutePath();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//(1)批量申请多个权限PermissionsUtil.getInstance().checkPermissions(MainActivity.this,PermissionsUtil.getInstance().getPermissionList(),this);//(2)压缩后保存临时文件目录File tempFile = new File(mImageRootDir);if (!tempFile.exists()) {tempFile.mkdirs();}//(3)mCompressBtn = (Button) findViewById(R.id.compress_btn);mImage = (ImageView) findViewById(R.id.image);//(4)mCompressBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {if(v == mCompressBtn){new Thread(new Runnable() {@Overridepublic void run() {//(1)压缩后的图片存储路径final File afterCompressImgFile = new File(mImageRootDir + "/temp.jpg");//(2)需要压缩的图片路径String tempCompressImgPath = mImageRootDir + File.separator + "temp.jpg";//(3)直接使用jni libjpeg压缩Bitmap bitmap = BitmapFactory.decodeFile(tempCompressImgPath);new ImageZipUtil().compressBitmap(bitmap,tempCompressImgPath);MainActivity.this.runOnUiThread(new Runnable() {@Overridepublic void run() {mImage.setImageBitmap(BitmapFactory.decodeFile(afterCompressImgFile.getPath()));}});}}).start();}}@Overridepublic void permitPermissions() {}@Overridepublic void refusePermissions() {}
}
3.2.3Activity布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><Buttonandroid:id="@+id/compress_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="15dp"android:layout_marginLeft="15dp"android:text="终极压缩" /><ImageViewandroid:id="@+id/image"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="15dp"android:layout_marginLeft="15dp"android:contentDescription="@null"android:scaleType="centerInside" />
</LinearLayout>

4.总结

(1)libjpeg-turbo库的so库的编译生成是通过AndroidStudio的native c++项目生成
(2)目前用的是cmake方式,即借助libjpeg-turbo本身存在的CMakeLists.txt文件去生成。
(3)只需要在app目录下的build.gradle文件中去指定CMakeLists.txt文件所在位置即可
(4)运行项目在生成so动态链接库的时候,会去读取CMakeLists.txt中的文件信息生成libjpeg-turbo的so库文件
(5)我们自己的项目需要用到libjpeg-turbo的so库时,只需要将其添加到jni目录,并且复制图片压缩相关的.h头文件,并在自己编写的扩展名为.cpp的文件中去引入这些头文件,以达到调用libjpeg-turbo函数库实现图片压缩的目的。
(6)自己写的.cpp文件(C或C++)函数希望能在Android Activity或其他java程序中调用,需要将其生成so动态链接库,然后通过JNI去调用。
(7)生成so动态链接库的过程,可以ndk来实现,主要是在JNI目录中添加Android.mk与Application.mk文件中去写清楚编译so库需要的依赖库有哪些,比如图片压缩so库,即前述生成的jpeg.so库,还要写清楚编译的c++源文件是什么,so库最高支持到Android哪个版本,等等,也就是要告诉ndk,编译的内容有哪些,编译成哪种CPU类型的指令集。
(8)要运行Android.mk和Application.mk文件需要用到ndk-build命令。
前提是ndk必须配置系统环境变量。
首先是要打开命令提示符,并进入Android.mk文件所处的目录,然后再执行ndk-build命令。

4.1注意:

(1)在执行ndk-build命令之前,需要将不同CPU型号指令库,即jpeg.so库复制到jni目录下,去生成对应的CPU型号的so库,不然可能在生成so库的过程中会出问题。
(2)这句话的意思就是
arm-v8a的jpeg.so库复制到jni目录生成arm-v8a的zipimg.so库。
armeabi-v7a的jpeg.so库复制到jni目录生成armeabi-v7a的zipimg.so库。
x86的jpeg.so库复制到jni目录生成x86的zipimg.so库。
x86_64的jpeg.so库复制到jni目录生成x86_64的zipimg.so库。
(3)主要目的就是为了避免不同CPU生成的指令不一样,导致程序无法运行的问题。因此编译之前一定要在Application.mk中设置编译的CPU类型名称,arm64-v8a、armeabi-v7a、x86、x86_64,可以一个一个的设置,然后编译。

5.参考文档

参考文档

相关文章:

【48】Android通过libjpeg-turbo库实现图片压缩

&#xff08;1&#xff09;公司为节约图片占用服务器存储资源成本&#xff0c;需要对Android手机客户端所传递到云存储服务器中的图片进行压缩&#xff0c;在不影响图片失真程度的情况下&#xff0c;最大限度的压缩图片以节省图片所占用的存储空间。 &#xff08;2&#xff09;…...

Linux输入设备应用编程

本章学习输入设备的应用编程&#xff0c;首先要知道什么是输入设备&#xff1f;输入设备其实就是能够产生输入事件的设备就称为输入设备&#xff0c;常见的输入设备包括鼠标、键盘、触摸屏、按钮等等&#xff0c;它们都能够产生输入事件&#xff0c;产生输入数据给计算机系统。…...

【Vulkan入门】03-创建Device

目录 先叨叨git信息关键代码VulkanEnv::CreateDevice() 编译并运行程序题外话 先叨叨 在上篇已经选择了一个合适的PhysicalDevice。 本篇要为这个PhysicalDevice创将一个Device。Device可以理解为APP与PhysicalDevice之间的代理。 所有APP与PhysicalDevice之间交互的资源都通过…...

【jvm】C2编译器

目录 1. 说明2. 编译流程3. 使用与配置4. 性能优化与监控5. 局限性 1. 说明 1.JVM&#xff08;Java虚拟机&#xff09;C2编译器是Java编译过程中的重要环节&#xff0c;专门用于将Java字节码编译成高效的本地机器代码&#xff0c;以提升Java程序的执行效率。2.特点&#xff1a…...

使用 Acme.sh 自动生成和续签免费 SSL 证书(含通配符支持)

Acme.sh 是一个开源的脚本&#xff0c;能够从 ZeroSSL、Let’s Encrypt 等证书颁发机构&#xff08;CA&#xff09;获取免费的 HTTPS 证书。该脚本特别简单易用&#xff0c;并且支持多种验证方式。下面将详细介绍使用 Acme.sh 生成、安装和更新证书的各个步骤。 Github地址 使用…...

Android 图形系统之四:Choreographer

Choreographer 是 Android 系统中负责帧同步的核心组件&#xff0c;它协调输入事件、动画和绘制任务&#xff0c;以确保界面以固定频率&#xff08;通常是每 16ms&#xff0c;一帧&#xff09;流畅渲染。通过管理 VSYNC 信号和调度任务&#xff0c;Choreographer 是实现流畅 UI…...

CAP定理和BASE理论

CAP定理 CAP定理&#xff0c;也称为布鲁尔定理&#xff08;Brewer’s Theorem&#xff09;&#xff0c;是分布式系统设计中的一个基本原理。它指出在分布式系统中&#xff0c;一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;和分区容…...

笔记软件:我来、思源笔记、Obsidian、OneNote

最近wolai的会员到期了&#xff0c;促使我更新了一下笔记软件。 首先&#xff0c;wolai作为一个笔记软件&#xff0c;我觉得有很多做得不错的方面&#xff08;否则我也不会为它付费2年了&#xff09;&#xff0c;各种功能集成得很全&#xff08;公式识别这个功能我写论文的时候…...

试探互联网如何工作?

Reading&#xff1a; How_does_the_Internet_workhow-does-internet-work Watching&#xff1a;How the Internet Works in 5 Minutes Outline: 互联网通过全球互联的计算机和服务器网络工作&#xff0c;通过标准化协议进行通信。数据被分解成数据包&#xff0c;并使用互联…...

【c++笔试强训】(第三十篇)

目录 爱丽丝的⼈偶&#xff08;贪⼼构造&#xff09; 题目解析 讲解算法原理 编写代码 集合&#xff08;排序&#xff09; 题目解析 讲解算法原理 编写代码 爱丽丝的⼈偶&#xff08;贪⼼构造&#xff09; 题目解析 1.题目链接&#xff1a;登录—专业IT笔试面试备考平…...

微信小程序购物车全选反选功能以及合计

微信小程序基于Vant Weapp的购物车功能实现 1、单选 使用微信小程序原生表单组件checkbox和checkbox-group 注意&#xff1a;checkbox原生不支持bind:change事件&#xff0c;checkbox-group支持 <checkbox-group bindchange"handleCheck"><checkbox val…...

vue-qr在线生成二维码组件(vue2版本)

在对于二维码生成中有许多组件&#xff0c;下面介绍关于自定义比较高的vue-qr组件&#xff0c;能自定义设置背景颜色、背景图片、背景Gif图、实点和空白区的颜色、中心Logo的图片和边距。 依赖下载 注意&#xff1a; 直接npm下载最新版 有些项目可能运行会抱错 这时候你可以降…...

大语言模型技术相关知识-笔记整理

系列文章目录 这个系列攒了很久。主要是前段之间面试大语言模型方面的实习&#xff08;被拷打太多次了&#xff09;&#xff0c;然后每天根据面试官的问题进行扩展和补充的这个笔记。内容来源主要来自视频、个人理解以及官方文档中的记录。方便后面的回顾。 文章目录 系列文章…...

SCP命令实现Linux中的文件传输

CP命令的主要作用是实现Linux与Linux系统之间的文件传输。 SCP命令时基于SSH协议,所以两台服务器的sshd服务必须处于开启状态,否则无法完成上传与下载操作。 #1.上传文件 scp linux本地文件路径 远程用户名@linux主机地址:远程路径 #2.下载文件 scp 远程用户名@linux主机地址…...

linux环境中后台运行java程序

在生产环境&#xff0c;我们通常需要让java进程后台运行&#xff0c;并且即使会话关闭&#xff0c;进程也依然存在。 使用的命令&#xff1a; nohup java -jar xxx.jar -> aaa.log 2>&1 & 详细介绍下上面这条命令 &#xff08;1&#xff09;nohup&#xff1a;…...

Go学习:变量

目录 1. 变量的命名 2. 变量的声明 3. 变量声明时注意事项 4. 变量的初始化 5. 简单例子 变量主要用来存储数据信息&#xff0c;变量的值可以通过变量名进行访问。 1. 变量的命名 在Go语言中&#xff0c;变量名的命名规则 与其他编程语言一样&#xff0c;都是由字母、数…...

在Unity编辑模式下运行Mono中的方法

[ExecuteAlways] 最简单的方法当然是直接给Mono加上[ExecuteAlways]修饰&#xff0c;这样Mono中的Awake&#xff0c;Update等等都可以在编辑模式下按照原本的时机运行。 [ExecuteAlways] public class TestScript : MonoBehaviour {void TestMethod(){Debug.Log("TestMe…...

Y20030028 JAVA+SSM+MYSQL+LW+基于JAVA的考研监督互助系统的设计与实现 源代码 配置 文档

基于JAVA的考研监督互助系统 1.项目描述2. 课题开发背景及意义3.项目功能4.界面展示5.源码获取 1.项目描述 随着高等教育的普及和就业竞争的加剧&#xff0c;越来越多的学生选择继续深造&#xff0c;参加研究生入学考试。考研人数的不断增加&#xff0c;使得考研过程中的学习监…...

MATLAB期末复习笔记(下)

目录 五、数据和函数的可视化 1.MATLAB的可视化对象 2.二维图形的绘制 3.图形标识 4.多子图绘图 5.直方图的绘制 &#xff08;1&#xff09;分类 &#xff08;2&#xff09;垂直累计式 &#xff08;3&#xff09;垂直分组式 &#xff08;4&#xff09;水平分组式 &…...

「Mac畅玩鸿蒙与硬件37」UI互动应用篇14 - 随机颜色变化器

本篇将带你实现一个随机颜色变化器应用。用户点击“随机颜色”按钮后&#xff0c;界面背景会随机变化为淡色系颜色&#xff0c;同时显示当前的颜色代码&#xff0c;页面还会展示一只猫咪图片作为装饰&#xff0c;提升趣味性。 关键词 UI互动应用随机颜色生成状态管理用户交互…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

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;点…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

CppCon 2015 学习:Time Programming Fundamentals

Civil Time 公历时间 特点&#xff1a; 共 6 个字段&#xff1a; Year&#xff08;年&#xff09;Month&#xff08;月&#xff09;Day&#xff08;日&#xff09;Hour&#xff08;小时&#xff09;Minute&#xff08;分钟&#xff09;Second&#xff08;秒&#xff09; 表示…...