本工程应用于一款安卓白板程序,因有读写速度需求,传统的序列化到磁盘的方式读写速度太慢,因此想到了利用C的特性写一个高速缓存,并继承实现了自己的inputStream和outputStream,用于暂存大规模多叉树和大体积对象。本工程的序列化对象的数据均使用一个整数作为标记进行区分,有需要的朋友可以修改本工程的实现以支持字符串标记。
本工程的JNI部分:
一、首先是MakeFile:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -lm -llog
LOCAL_MODULE := JNIArrayMemmapUtil
LOCAL_SRC_FILES := ArrayMemmapUtil.c
include $(BUILD_SHARED_LIBRARY)
二、本工程的C工程文件 ArrayMemmapUtil.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include <string.h>
#include<jni.h>
#include<android/log.h>
#include <stdint.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "native-activity", __VA_ARGS__))
#define new(Class) (Class*)malloc(sizeof(Class))
//行单元节点
typedef struct Node Node;
struct Node {
jbyte content;
Node *next;
};
typedef struct NodeHead NodeHead;
struct NodeHead {
jbyte content;
Node *next;
Node *last;
};
//列单元结点
typedef struct NodeList NodeList;
struct NodeList {
NodeHead* nodeHead;
NodeList* next;
jint tag;
jint size;
};
NodeList* nodeList = NULL;
/**用于创建序列化了的对象的jint数组
tag 列标记
dataItem 保存的数据**/
void Java_com_cjz_image_ArrayHeap_outputData (JNIEnv *env, jobject obj, jint tag, jint dataItem) {
NodeList* listCursor = NULL; //找表游标
static NodeList* targetList = NULL; //利用static实现表缓存,减少读写同一个表时的读写次数
if(nodeList == NULL){ //还没有列,创建一个,而且列标记就用传入的
nodeList = new(NodeList);
nodeList->tag = tag;
nodeList->nodeHead = NULL;
nodeList->next = NULL;
nodeList->size = 0;
}
if(targetList == NULL || targetList->tag != tag) { //如果已经缓存了正确的表就不遍历
//查找nodeList中有没有对应tag的数据表
listCursor = nodeList;
while(listCursor != NULL){
if(listCursor->tag == tag){
targetList = listCursor;
break;
}
listCursor = listCursor->next;
}
//如果找不到,就在表中新建一个
if(targetList == NULL || targetList->tag != tag) {
listCursor = nodeList;
while(listCursor->next != NULL){
//遍历到最后一个有效结点
listCursor = listCursor->next;
}
listCursor->next = new(NodeList);
listCursor = listCursor->next;
listCursor->tag = tag;
listCursor->nodeHead = NULL;
listCursor->next = NULL;
listCursor->size = 0;
targetList = listCursor;
}
}
//写入行数据
if(targetList->nodeHead == NULL) {
targetList->nodeHead = new(NodeHead);
targetList->nodeHead->next = NULL;
targetList->nodeHead->last = (Node*) targetList->nodeHead;
}
targetList->nodeHead->last->content = (jbyte) (dataItem & 0xFF);
// LOGI("input data:%d\n", targetList->nodeHead->last->content);
targetList->nodeHead->last->next = new(Node);
targetList->nodeHead->last->next->next = NULL;
targetList->nodeHead->last = targetList->nodeHead->last->next;
targetList->size++;
}
jint Java_com_cjz_image_ArrayHeap_read(JNIEnv *env, jobject obj, jint tag) {
NodeList* listCursor = NULL;
static NodeList* targetList = NULL;
if(targetList == NULL || targetList->tag != tag){ //如果已经缓存了正确的表就不遍历
//查找nodeList中有没有对应tag的数据表
listCursor = nodeList;
while(listCursor != NULL){
if(listCursor->tag == tag){
targetList = listCursor;
break;
}
listCursor = listCursor->next;
}
}
if(targetList == NULL){
return -1;
}
if(targetList->nodeHead == NULL)
return -1;
if(targetList->nodeHead->next == NULL){
free(targetList->nodeHead);
targetList->nodeHead = NULL;
return -1;
}
jbyte data = targetList->nodeHead->content;
NodeHead* beDelNode = targetList->nodeHead;
if(targetList->nodeHead->next != NULL){
NodeHead* newNodeHead = new(NodeHead);
memcpy(newNodeHead, targetList->nodeHead->next, sizeof(NodeHead));
newNodeHead->last = beDelNode->last;
free(targetList->nodeHead->next);
targetList->nodeHead = newNodeHead;
}
else
targetList->nodeHead = NULL;
free(beDelNode);
targetList->size--;
return (data & 0xFF);
}
/**读指定位置数据**/
jint Java_com_cjz_image_ArrayHeap_readPos(JNIEnv *env, jobject obj, jint tag, jint pos) {
NodeList* listCursor = NULL;
static NodeList* targetList = NULL;
static jint lastPos = 0;
static Node* lastNode = NULL;
Node* cursor = NULL;
jint i;
if(targetList == NULL || targetList->tag != tag){ //如果已经缓存了正确的表就不遍历
//查找nodeList中有没有对应tag的数据表
listCursor = nodeList;
while(listCursor != NULL){
if(listCursor->tag == tag){
targetList = listCursor;
break;
}
listCursor = listCursor->next;
}
}
if(targetList == NULL){
return -1;
}
if(targetList->nodeHead == NULL)
return -1;
//找目标地址的数据:
//如果地址不在附近就重新遍历
if(lastPos == 0 || lastNode == NULL || pos - lastPos < 0) {
cursor = (Node*) targetList->nodeHead;
lastPos = pos;
while(pos-- > 0) {
if(cursor->next != NULL)
cursor = cursor->next;
else
return -1;
}
lastNode = cursor;
return (lastNode->content & 0xFF);
} else { //否则后面附近的话就用上次记录的位置偏移过去,节约时间
jint offset = pos - lastPos;
lastPos += offset;
cursor = lastNode;
while(offset-- > 0) {
if(cursor->next != NULL)
cursor = cursor->next;
else
return -1;
}
lastNode = cursor;
return (lastNode->content & 0xFF);
}
}
void clearProc(Node* head) {
Node* cursor = head;
while(cursor != NULL) {
Node* next = cursor->next;
free(cursor);
cursor = next;
}
}
jint Java_com_cjz_image_ArrayHeap_getSize(JNIEnv *env, jobject obj, jint tag){
NodeList* listCursor = nodeList;
while(listCursor != NULL){
if(listCursor->tag == tag){
return listCursor->size;
}
listCursor = listCursor->next;
}
return -1;
}
void Java_com_cjz_image_ArrayHeap_clear(JNIEnv *env, jobject obj, jint tag) {
NodeList* listCursor = nodeList;
while(listCursor != NULL){
if(listCursor->tag == tag){
Node* head = (Node*) listCursor->nodeHead;
if(head != NULL){
clearProc(head);
listCursor->nodeHead = NULL;
}
return;
}
listCursor = listCursor->next;
}
}
三、数据结构关系:
使用ndk-build工具编译后,生成.so文件,复制到目标工程的app\libs文件夹中,然后app的build.gradle中加入:
sourceSets参数
在Android Stuido中添加包:com.cjz.image。
并粘贴一个jni十字链表使用类ArrayHeap,和如下inputStream和outputStream实现即可使用本功能 :
ArrayHeap:
package com.cjz.image;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Created by cjz on 2018/6/25.
*/
public class ArrayHeap {
static {
System.loadLibrary("JNIArrayMemmapUtil");
}
// public static Map<Integer, Queue<Integer>> fakeDatas = new HashMap<>();
private static native void outputData(int tag, int dataItem);
private static native int read(int tag);
private static native int readPos(int tag, int pos);
public static native void clear(int tag);
public static native int getSize(int tag);
public static void output(int tag, int dataItem){
// Log.i("output", "tag:" + tag + ", dataItem:" + dataItem);
outputData(tag, dataItem);
// if(fakeDatas.get(tag) == null){
// fakeDatas.put(tag, new LinkedBlockingQueue<Integer>());
// fakeDatas.get(tag).add(dataItem);
// } else {
// fakeDatas.get(tag).add(dataItem);
// }
}
public static int readData(int tag){
return read(tag);
// return fakeDatas.get(tag).poll();
}
public static int readData(int tag, int position) {
return readPos(tag, position);
}
}
ArrayOutputStream类:
package com.cjz.image;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
/**
* Created by cjz on 2018/6/25.
*/
public class ArrayOutputStream extends OutputStream {
private final int tag;
public ArrayOutputStream(int tag){
this.tag = tag;
}
@Override
public void write(int b) throws IOException {
// Log.i("ArrayOutputStream", "write(int b)");
ArrayHeap.output(tag, b);
}
}
ArrayInputStream类:
package com.cjz.image;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
public
class ArrayInputStream extends InputStream {
private int tag;
// protected byte buf[];
protected int pos;
protected int mark = 0;
protected int count;
public ArrayInputStream(int tag) {
this.tag = tag;
Log.i("ArrayHeap.getSize(tag)", ArrayHeap.getSize(tag) + "");
this.pos = 0;
this.count = ArrayHeap.getSize(tag);
}
public ArrayInputStream(byte buf[], int offset, int length) {
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
public synchronized int read() {
return (pos < count) ? (ArrayHeap.readData(tag, pos++) & 0xff) : -1;
}
public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
if (pos >= count) {
return -1;
}
int avail = count - pos;
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
// System.arraycopy(buf, pos, b, off, len);
for(int i = 0; i < len; i++){
b[off + i] = (byte) (ArrayHeap.readData(tag, pos++) & 0xff);
}
// pos += len;
return len;
}
public synchronized long skip(long n) {
Log.i("ArrayInputStream", "skip(long n)");
long k = count - pos;
if (n < k) {
k = n < 0 ? 0 : n;
}
pos += k;
return k;
}
public synchronized int available() {
Log.i("ArrayInputStream", "available");
return count - pos;
}
public boolean markSupported() {
Log.i("ArrayInputStream", "markSupported");
return true;
}
public void mark(int readAheadLimit) {
Log.i("ArrayInputStream", "readAheadLimit");
mark = pos;
}
public synchronized void reset() {
Log.i("ArrayInputStream", "reset");
pos = mark;
}
public void close() throws IOException {
ArrayHeap.clear(tag);
}
}
至此,本工程导入即可完成。
使用例子代码片段:
导入一个大对象到JNI管控的内存区中:
保存之前的内存占用量:
保存之后的内存占用量:
再从中读出对象:
可以明显地看到使用JNI保存序列化对象或者大块的数据除了可以提供高速读写的能力之外,还可以规避JVM OOM的危险。