NDK--文件的拆分和合并

2020-04-17  本文已影响0人  aruba

断点续传中,我们需要将一个文件拆分多个文件,并通过多线程上传,今天利用JNI实现文件的拆分和合并,调用c/c++的方式,性能会有所提升。

1.创建文件工具类

这边将文件封装成c++类,减少了繁琐的操作

_FileClass.h

//
// Created by aruba on 2020/4/16.
//

#ifndef FILECLASS_H
#define FILECLASS_H

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>

class CFILE {

private:
    FILE *m_fp;
    char m_filename[301];
    bool m_buffenable;

public:
    //构造函数
    CFILE();

    //构造函数,带参数
    CFILE(bool buffenable);

    //打开文件
    bool Open(const char *filename, const char *mode);

    //fgets,读文件
    bool Fgets(char *buff, int readsize);

    //fgets,一个字节读文件
    int Fgetc();
    
    //写文件
    void Fprintf(const char *write);

    //写文件
    void Fprintf(const char *format, ...);

    //一个字节写文件
    void Fputc(const int c);
    
    //获取文件大小
    long GetFileSize();
    
    //析构函数
    ~CFILE();
};


#endif //FILECLASS_H

_FileClass.cpp

//
// Created by aruba on 2020/4/16.
//

#include "_FileClass.h"

//构造函数初始化
CFILE::CFILE() {
    m_fp = 0;
    memset(m_filename, 0, sizeof(m_filename));
    m_buffenable = true;
}

//带参数构造函数初始化
CFILE::CFILE(bool buffenable) {
    m_fp = 0;
    memset(m_filename, 0, sizeof(m_filename));
    m_buffenable = buffenable;
}

//打开文件
bool CFILE::Open(const char *filename, const char *mode) {
    if ((m_fp = fopen(filename, mode)) == 0) return false;

    strcpy(m_filename, filename);

    return true;
}

//fgets,读文件
bool CFILE::Fgets(char *buff, int readsize) {
    if (m_fp == 0) return false;

    if (access(m_filename, R_OK) == -1) return false;

    //初始化buff
    memset(buff, 0, sizeof(readsize));

    if (fgets(buff, readsize, m_fp) == 0) return false;

    return true;
}

//fgets,一个字节读文件
int CFILE::Fgetc() {
    if (m_fp == 0) return EOF;

    return fgetc(m_fp);
}

//写文件
void CFILE::Fprintf(const char *write) {
    if (m_fp == 0) return;

    if (access(m_filename, W_OK) == -1) return;

    fprintf(m_fp, write);

    if (m_buffenable == false) fflush(m_fp);
}

//写文件
void CFILE::Fprintf(const char *format, ...) {
    if (m_fp == 0) return;

    if (access(m_filename, W_OK) == -1) return;

    va_list va;
    va_start(va, format);

    fprintf(m_fp, format);

    va_end(va);

    if (m_buffenable == false) fflush(m_fp);
}

//一个字节写文件
void CFILE::Fputc(const int c) {
    if (m_fp == 0) return;

    fputc(c, m_fp);
    if (m_buffenable == false) fflush(m_fp);
}

//获取文件大小
long CFILE::GetFileSize() {
    if (m_fp == 0) return 0;

    if (access(m_filename, R_OK) == -1) return 0;

    //将文件指针移动到文件尾
    fseek(m_fp, 0, SEEK_END);
    long size = ftell(m_fp);

    //还原文件指针
    rewind(m_fp);
    return size;
}

//析构函数
CFILE::~CFILE() {
    fclose(m_fp);
    m_fp = 0;
    m_buffenable = true;
    memset(m_filename, 0, sizeof(m_filename));
}
注意:记得在CMakeList中添加

2.创建Java工具类,定义文件拆分和合并的native方法

package com.aruba.ndkapplication;

/**
 * 文件拆分和合并
 */
public class FileDiffUtils {
    //拆分文件
    public static native void diff(String filePath, String partPath, int num);

    //合并文件
    public static native void merge(String mergeFilePath, String partPath, int num);
}

3.以动态注册的方式连接native方法

//文件拆分和合并
static const JNINativeMethod gMethodsFileDiff[] = {
        {
                "diff",  "(Ljava/lang/String;Ljava/lang/String;I)V", (void *) native_diff
        },
        {
                "merge", "(Ljava/lang/String;Ljava/lang/String;I)V", (void *) native_merge
        }
};

//注册文件拆分和合并
static int registerNativesFileDiff(JNIEnv *env) {
    LOGI("registerNatives begin");
    jclass clazz;
    //找到java的类
    clazz = env->FindClass("com/aruba/ndkapplication/FileDiffUtils");

    if (clazz == NULL) {
        LOGI("clazz is null");
        return JNI_FALSE;
    }

    if (env->RegisterNatives(clazz, gMethodsFileDiff, NELEM(gMethodsFileDiff)) < 0) {
        LOGI("RegisterNatives error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}
JNI_OnLoad方法中调用
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGI("jni_OnLoad begin");

    JNIEnv *env = NULL;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGI("ERROR: GetEnv failed\n");
        return -1;
    }

    assert(env != NULL);

    registerNatives(env);
    registerNativesFileDiff(env);

    return JNI_VERSION_1_4;
}

4.实现文件拆分和合并方法

//二进制一个个字节写文件
void putPartFilec(JNIEnv *env, const long size, const char *partPath, CFILE *readFile) {
    CFILE partFile;
    //打开拆分文件
    if (!partFile.Open(partPath, "w")) {
        jclass newExc = env->FindClass("java/lang/Exception");
        char info[500];
        memset(info, 0, 500);
        sprintf(info, "can not open :%s", partPath);
        if (newExc != NULL) env->ThrowNew(newExc, info);
        env->DeleteLocalRef(newExc);

        return;
    }

    for (int j = 0; j < size; j++) {
        int c = readFile->Fgetc();
        partFile.Fputc(c);
    }
}

//拆分文件
JNIEXPORT void JNICALL
native_diff(JNIEnv *env, jclass type, jstring filePath, jstring partPath, jint num) {
    //读取的文件
    const char *readPath = env->GetStringUTFChars(filePath, NULL);
    //拆分文件
    const char *wirtePath = env->GetStringUTFChars(partPath, NULL);

    //读文件
    CFILE readFile;
    if (!readFile.Open(readPath, "r")) {
        jclass newExc = env->FindClass("java/lang/Exception");
        char info[500];
        memset(info, 0, 500);
        sprintf(info, "can not open :%s", filePath);
        if (newExc != NULL) env->ThrowNew(newExc, info);
        env->DeleteLocalRef(newExc);

        env->ReleaseStringUTFChars(partPath, wirtePath);
        env->ReleaseStringUTFChars(filePath, readPath);
        return;
    }

    //生成拆分文件绝对路径数组
    char partPaths[num][500];
    memset(partPaths, 0, sizeof(partPaths));
    for (int i = 0; i < num; i++) {
        sprintf(partPaths[i], wirtePath, i);
    }

    //计算每个文件大小
    long fileSize = readFile.GetFileSize();
    //整除的情况,均分
    if (fileSize % num == 0) {
        //每个拆分文件的大小
        long partFileSize = fileSize / num;

        //循环写入文件
        for (int i = 0; i < num; i++) {
            putPartFilec(env, partFileSize, partPaths[i], &readFile);
        }
    } else {
        //前(num - 1)个拆分文件的大小
        long partFileSize = fileSize / num ;
        //循环写入文件
        for (int i = 0; i < num - 1; i++) {
            putPartFilec(env, partFileSize, partPaths[i], &readFile);
        }

        //第num个拆分文件的大小
        partFileSize = partFileSize + fileSize % num;
        putPartFilec(env, partFileSize, partPaths[num - 1], &readFile);
    }

    env->ReleaseStringUTFChars(partPath, wirtePath);
    env->ReleaseStringUTFChars(filePath, readPath);
}

//合并文件
JNIEXPORT void JNICALL
native_merge(JNIEnv *env, jclass type, jstring mergeFilePath, jstring partPath, jint num) {
    //写入的合并文件
    const char *wirtePath = env->GetStringUTFChars(mergeFilePath, NULL);
    //拆分文件
    const char *readPath = env->GetStringUTFChars(partPath, NULL);

    //生成拆分文件绝对路径数组
    char partPaths[num][500];
    memset(partPaths, 0, sizeof(partPaths));
    for (int i = 0; i < num; i++) {
        sprintf(partPaths[i], readPath, i);
    }

    //写合并文件
    CFILE mergeFile = new CFILE(false);
    if (!mergeFile.Open(wirtePath, "w")) {
        jclass newExc = env->FindClass("java/lang/Exception");
        char info[500];
        memset(info, 0, 500);
        sprintf(info, "can not open :%s", mergeFilePath);
        if (newExc != NULL) env->ThrowNew(newExc, info);
        env->DeleteLocalRef(newExc);

        env->ReleaseStringUTFChars(mergeFilePath, wirtePath);
        env->ReleaseStringUTFChars(partPath, readPath);
        return;
    }

    for (int i = 0; i < num; i++) {
        CFILE partFile;
        partFile.Open(partPaths[i], "r");
        long partFileSize = partFile.GetFileSize();
        for (int j = 0; j < partFileSize; j++) {
            mergeFile.Fputc(partFile.Fgetc());
        }
    }

    env->ReleaseStringUTFChars(mergeFilePath, wirtePath);
    env->ReleaseStringUTFChars(partPath, readPath);
}

5.最后在activity中调用,实现效果,记得添加读写权限

        Button btn_click = findViewById(R.id.btn_click);
        btn_click.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FileDiffUtils.diff(Environment.getExternalStorageDirectory() + File.separator + "test.jpg", Environment.getExternalStorageDirectory() + File.separator + "test_%d.jpg", 4);
            }
        });

        Button btn_click2 = findViewById(R.id.btn_click2);
        btn_click2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FileDiffUtils.merge(Environment.getExternalStorageDirectory() + File.separator + "test_merge.jpg", Environment.getExternalStorageDirectory() + File.separator + "test_%d.jpg", 4);
            }
        });
demo地址:https://gitee.com/aruba/NDKApplication.git
上一篇下一篇

猜你喜欢

热点阅读