手把手教你在java后端使用bsdiff实现增量更新(代码片段)

XeonYu XeonYu     2023-03-01     144

关键词:

之前写过一篇博客是:手把手教你在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实现增量更新(代码片段)

之前写过一篇博客是:手把手教你在Android中使用bsdiff实现文件增量更新。由于AndroidStudio自带NDK的环境,所以实现JNI是比较简单的。但是在博客中也说到了,文件差分的功能肯定是要在服务端进行的。而服务端运行的... 查看详情

手把手教你在java后端使用bsdiff实现增量更新(代码片段)

之前写过一篇博客是:手把手教你在Android中使用bsdiff实现文件增量更新。由于AndroidStudio自带NDK的环境,所以实现JNI是比较简单的。但是在博客中也说到了,文件差分的功能肯定是要在服务端进行的。而服务端运行的... 查看详情

手把手教你在android中使用bsdiff实现文件增量更新(超详细)(代码片段)

全量更新和增量更新在Android开发中,版本更新是一个非常常见的需求。目前更新主要分为两种方式,全量更新、增量更新如下图,分别是酷安和应用宝两个商店的更新页面:可以明显的看到酷安的更新方式是全量更新&... 查看详情

手把手教你在android中使用bsdiff实现文件增量更新(超详细)(代码片段)

全量更新和增量更新在Android开发中,版本更新是一个非常常见的需求。目前更新主要分为两种方式,全量更新、增量更新如下图,分别是酷安和应用宝两个商店的更新页面:可以明显的看到酷安的更新方式是全量更新&... 查看详情

手把手教你在android中使用bsdiff实现文件增量更新(超详细)(代码片段)

全量更新和增量更新在Android开发中,版本更新是一个非常常见的需求。目前更新主要分为两种方式,全量更新、增量更新如下图,分别是酷安和应用宝两个商店的更新页面:可以明显的看到酷安的更新方式是全量更新&... 查看详情

手把手教你实现java权限管理系统后端篇(十三):系统备份还原

系统备份还原在很多时候,我们需要系统数据进行备份还原。我们这里就使用MySql的备份还原命令实现系统备份还原的功能。新建工程新建一个maven项目,并添加相关依赖,可以用Springboot脚手架生成。新建kitty-bakcup工程,这是一... 查看详情

游戏开发实战手把手教你在unity中使用lua实现红点系统(前缀树|数据结构|设计模式|算法|含工程源码)

文章目录一、前言二、环境说明三、红点系统设计1、红点系统规则2、科普:前缀树3、用前缀树组织红点四、红点系统具体实现1、前缀树封装1.1、节点:RedpointNode.lua1.2、树:RedpointTree.lua1.2.1、创建根节点1.2.2、定义... 查看详情

手把手教你实现java权限管理系统后端篇(十六):容器部署项目

容器部署项目这一章我们引入docker,采用docker容器的方式部署我们的项目。首先需要有一个linux环境,并且安装java和maven以及docker环境,这个教程多如牛毛,不再赘述。这里以kitty-monitor为例。添加配置在 pom.xml的properties ... 查看详情

手把手教你实现java权限管理系统后端篇:解决跨域问题

什么是跨域?同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。同源策略是浏览器安全的基石。如果一个请求地址里面的协议、域名和端口号都相同,就属于同源。举个栗子... 查看详情

手把手教你在vue中使用jsx,不怕学不会!建议收藏

文末有配套demo代码:点我直达JSX是什么JSX是一种Javascript的语法扩展,JSX=Javascript+XML,即在Javascript里面写XML,因为JSX的这个特性,所以他即具备了Javascript的灵活性,同时又兼具html的语义化和直观性。(个人建议灵活度强的部分... 查看详情

手把手教你在linux(deepin)使用idea连接mysql(代码片段)

JDK可以直接用IDEA安装IDEA直接在Deepin软件商店安装即可IDEA官网链接:https://www.jetbrains.com/idea/download/#section=linuxMYSQL安装:https://blog.csdn.net/weixin_46285416/article/details/117925764IDEA安装后࿰ 查看详情

手把手教你使用java实现一个神经网络(代码片段)

首先看一下运行效果:下面是项目整体目录:0.实现神经网络总览神经网络由层、神经元、权重、激活函数和偏置组成。每层都有一个或者多个神经元,每一个神经元都和神经输入/输出连接,这些连接就是权重。需要重点强调一... 查看详情

手把手教你在vue中使用jsx,不怕学不会!建议收藏(代码片段)

学习这篇文章希望您已经具备以下知识:vue.js的基本使用对前端三件套(html、css、js)已经可以熟练使用了文末有配套demo代码:点我直达JSX是什么JSX是一种Javascript的语法扩展,JSX=Javascript+XML,即在Javascript里面... 查看详情

手把手教你在vue中使用jsx,不怕学不会!建议收藏(代码片段)

学习这篇文章希望您已经具备以下知识:vue.js的基本使用对前端三件套(html、css、js)已经可以熟练使用了文末有配套demo代码:点我直达JSX是什么JSX是一种Javascript的语法扩展,JSX=Javascript+XML,即在Javascript里面... 查看详情

手把手教你在linux(deepin)使用rider开发.net项目(代码片段)

Rider安装.net环境安装Rider官网如果觉得下载慢就用百度网盘链接链接:https://pan.baidu.com/s/1WoEh2l7zEGZV8JJoAFsGtQ提取码:6sp7找到下载好的文件右键Rider,解压到文件夹,(也可以根据个人解压到其他地方)找到当前文件夹... 查看详情

手把手教你用java实现syslog消息的收发,学不会你打我喽!

大家好,我是道哥,专注于后端java开发,喜欢写作和分享。如果觉得文章对你有用,那就点个赞呗!如果能转发那是对道哥最大的支持!syslog的定义见文知义,syslog,从英文名字上可以看出是指系统日志。以下内容摘自百度百... 查看详情

手把手教你在linux(deepin)安装mysql和mariadb(代码片段)

笔者以前使用的是win下的SQLserver对于MySQL稍许有点陌生,可能介绍的比较基础,如有描述不正确的地方还望大佬指点两者的关系安装MySQL安装MariaDB(一行代码安装)日常的简单使用两者的关系MariaDB和MySQL的大概关系MariaDB数据... 查看详情

手把手教你在fpga上搭建一个cortex-m3软核(代码片段)

...号,不错过精彩内容转自| 电子电路开发学习本文将手把手教你如何基于ARMDesignStart计划,在FPGA上搭建一个Cortex-M3软核处理器。以XilinxArtix-7™系列FPGA为例,介绍如何定制一颗ARMCortex-M3SoC软核,并添加GPIO和UART外设... 查看详情