手把手教你在Java后端使用bsdiff实现增量更新
Posted XeonYu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你在Java后端使用bsdiff实现增量更新相关的知识,希望对你有一定的参考价值。
之前写过一篇博客是:手把手教你在Android中使用bsdiff实现文件增量更新 。
由于android Studio自带NDK的环境,所以实现JNI是比较简单的。
但是在博客中也说到了,文件差分的功能肯定是要在服务端进行的。而服务端运行的环境可能是在window,mac或者linux等。所以,我们需要把bsdiff的源码编译成对应环境需要的 native 库文件,以便于给Java调用。
由于大多数项目基本都是在linux上运行的,这里我就只演示下如何把bsdiff源码编译成so文件,然后在java代码中调用。
废话不多说,开始干!
Clion搭建编译环境
这里我使用的Jetbrains全家桶中的Clion. Jetbrains的IDE不用多说了,懂得都懂。
首先是需要准备一个Linux的系统,Windows的话本地调试推荐直接用WSL安装ubuntu。安装方式也比较简单,按照官方文档走即可。WSL安装文档:
如果是MAC的话,本地调试推荐使用docker去安装linux。在使用Docker作为Clion的编译环境时,一定要安装那种已经配置过cmake和gcc等编译环境的镜像,否则Clion识别不出来,我之前是先安装的ubuntu,然后再安装cmake,gdb等环境,结果Clion死活找不到,一直提示未找到安装包,在这个地方卡了快一天,给我整的都怀疑人生了。后来还是按照官方文档的方式才成功的,官方文档:Using Docker with CLion
jetbrains在github上也提供了几个写好的Dockerfile,自己根据需要选择即可。clion-remote
另外一种是比较推荐的方式,去阿里云购买一个云主机,直接远端编译,然后把写好的代码发布到远端进行测试,windows和mac都能用,比较方便。
安装完linux环境准备好之后需要安装cmake,gdb等,这里就不多BB了。安装后去Clion配置下工具链,根据自己需要配置,如下。
然后是配置cmake
至此环境就配置好了
在Linux上将C编译成so
在之前Android使用bsdiff的博客中,我们已经对bsdiff做了处理了,也能在Android上打出so。
那在java web项目中也是一样的步骤,准备java声明的native代码,更改cpp代码,配置cmake即可。
需要注意的是Android项目中的NDK可以直接引入jni.h,在Clion中我们要把需要手动把jdk中的jni.h和jni_md.h 拷贝到项目里,
jni.h在jdk中的includ目录里,jni_md.h在win32的目录里。
代码的结构如下
然后就可以去编译so文件了。点击build->Rebuild Project即可。
等待项目构建完成,如下
可以看到完成了,然后去上面红框给定的目录看一下文件。
可以看到,linux上是有源码的,相当于Clion把你本地的代码上传到linux中,然后通过linux的环境来进行编译了。
切到指定的最终目录看下
上面红框的so文件就是我们需要的了。
至此,linux上编译出so文件就搞定了。
Java Web Jni实现
然后就简单了,就是正常的JNi实现。先贴一下项目目录。
注意要加一下java代码访问resources目录中so文件的配置
下面是一些关键代码,这里只是demo代码,以实现功能为主,在生产环境下要注意下代码健壮性。
package com.yzq.bsdiffserver.utils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
/**
* @description: 用来load so文件,先将项目的so拷贝到linux中,然后加载
* @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
* @date : 2021/12/18
* @time : 14:33
*/
public class LibLoader
public static void loadLib(String libName, String resourcePath)
System.out.println("libName = " + libName);
System.out.println("resourcePath = " + resourcePath);
/*获取当前项目所在的linux路径 示例:/home/admin/webapp */
final String projectPath = System.getProperty("user.dir");
System.out.println("projectPath = " + projectPath);
/*创建一个目录 用来放so*/
String nativeLibPath = projectPath + File.separator + "tmp" + File.separator + "bsdiffdemo" + File.separator + "lib" + File.separator;
File nativeLibFolder = new File(nativeLibPath);
if (!nativeLibFolder.exists())
nativeLibFolder.mkdirs();
/*用来存放临时文件的目录*/
String filePath = projectPath + File.separator + "tmp" + File.separator + "bsdiffdemo" + File.separator + "file" + File.separator;
File fileFolder = new File(filePath);
if (!fileFolder.exists())
fileFolder.mkdirs();
File libFile = new File(nativeLibFolder, libName);
System.out.println("libFile.getAbsolutePath() = " + libFile.getAbsolutePath());
if (libFile.exists())
System.out.println("libFile 文件存在 libFile.getAbsolutePath() = " + libFile.getAbsolutePath());
System.load(libFile.getAbsolutePath());
else
try
final InputStream inputStream = new ClassPathResource(resourcePath).getInputStream();
// InputStream in = LibLoader.class.getResourceAsStream(resourcePath);
final FileOutputStream fileOutputStream = new FileOutputStream(libFile);
/*把文件存到临时目录里*/
final int copy = FileCopyUtils.copy(inputStream, fileOutputStream);
System.out.println("copy result= " + copy);
inputStream.close();
fileOutputStream.close();
System.out.println("libFile.getAbsolutePath() = " + libFile.getAbsolutePath());
System.load(libFile.getPath());
catch (Exception e)
e.printStackTrace();
throw new RuntimeException("Failed to load required lib", e);
工具类代码,跟Android上类似。
package com.yzq.bsdiffserver.utils;
import java.io.File;
/**
* @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
* @description: BsDiffUtil
* @date : 2021/12/18
* @time : 14:33
*/
public class BsDiffUtil
static
String systemType = System.getProperty("os.name");
System.out.println("systemType = " + systemType);
try
/*获取当前项目所在的linux路径 示例:/home/admin/webapp */
// final String projectPath = System.getProperty("user.dir");
String libPath = "lib" + File.separator + "libxeon_bsdiff.so";
String libName = "libxeon_bsdiff.so";
/*mac上使用dylib*/
// String libPath = "lib" + File.separator + "libxeon_bsdiff.dylib";
// String libName = "libxeon_bsdiff.dylib";
LibLoader.loadLib(libName, libPath);
System.out.println("load so success");
catch (Exception e)
System.out.println("e = " + e.getMessage());
e.printStackTrace();
/**
* 生成补丁文件
*
* @param newFilePath
* @param oldFilePath
* @param patchFilePath
* @return
*/
public static native int fileDiff(String newFilePath, String oldFilePath, String patchFilePath);
/**
* 合并文件
*
* @param oldFilePath
* @param patchFilePath
* @param combineFilePath
* @return
*/
public static native int filePatch(String oldFilePath, String patchFilePath, String combineFilePath);
测试方法
package com.yzq.bsdiffserver.service;
import com.yzq.bsdiffserver.utils.BsDiffUtil;
import java.io.File;
/**
* @description: 测试类
* @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
* @date : 2021/12/18
* @time : 14:55
*/
public class BsDiffService
public static void testBsDiff()
try
/*测试load so*/
// BsDiffUtil.test();
final String projectPath = System.getProperty("user.dir");
String filePath = projectPath + File.separator + "tmp" + File.separator + "bsdiffdemo" + File.separator + "file" + File.separator;
/*旧文件路径*/
String oldFilePath = filePath + "old.txt";
/*新文件*/
String newFilePath = filePath + "new.txt";
/*补丁文件*/
String patchFilePath = filePath + "patch.txt";
/*合并后的文件*/
String combineFilePath = filePath + "combine.txt";
System.out.println("oldFilePath = " + oldFilePath);
System.out.println("newFilePath = " + newFilePath);
System.out.println("patchFilePath = " + patchFilePath);
System.out.println("combineFilePath = " + combineFilePath);
/*生成补丁文件*/
final int diffResult = BsDiffUtil.fileDiff(newFilePath, oldFilePath, patchFilePath);
System.out.println("diffResult = " + diffResult);
/*合并补丁文件*/
final int patchResult = BsDiffUtil.filePatch(oldFilePath, patchFilePath, combineFilePath);
System.out.println("patchResult = " + patchResult);
catch (Exception e)
e.printStackTrace();
java代码准备好之后直接部署到远端服务器测试即可。
这里就直接放出测试的结果了
存放so文件的目录
测试合并差分文件的目录
可以看到,合并后的文件和新文件大小是一致的,内容我看了也是完全一致的。
到这里,从Android到后端的基于bsdiff增量更新功能技术方面就打通了,具体应用的话根据你们的需求来定。
大冬天的码字不易,手都冻僵了,觉得有帮助的点个赞支持一下…
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!
以上是关于手把手教你在Java后端使用bsdiff实现增量更新的主要内容,如果未能解决你的问题,请参考以下文章