背景
正在完善的安卓日志工具,输出日志文件采用mmap方式输出,参考的biaoShow/TestDemoKotlin,映射不存在文件的时候没有问题。当原本存在一个文件,由于mmap要求4096整数倍为一页,文件大小原本上次映射时就有了一页大,但里面内容才几个字符,后面通过常规方式读取文件大小出来就是那个4096整数倍了,虽然也能追加写日志,但这样下去就浪费了太多空间。
解决思路
映射前先读取文件内容一次,读取失败就是不存在,成功就获取到了一个字符串,这个字符串长度就是实际内容大小。
至于获取文件大小,stat()、fstat()、lseek()等方式获取的确实是文件大小,但是里面内容是没填满的。
打个比方就是mmap映射按整桶来,每次装东西在桶里,重新映射的时候桶数(文件大小)没错,但里面有没装满的其实还能装,就需要得到实际装了多少才行。
代码
因为只需要会用,实现这个文件写入功能,我是根据参考代码来的:
extern "C"
jstring
Java_com_biao_testdemokotlin_Jni_read(JNIEnv *env, jobject clazz, jstring path) {
try {
std::string file_path = jstring2str(env, path);
// 打开文件
int fd = open(file_path.c_str(), O_RDONLY);
LOGI("read fd = %d\n", fd);
// 读取文件长度
int len = lseek(fd, 0, SEEK_END);
LOGI("len = %d", len);
// 建立内存映射
char *addr = (char *) mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
char *data = new char[len];// data用于保存读取的数据
memcpy(data, addr, len);// 复制过来
close(fd);
munmap(addr, len); // 解除映射
jstring js = char2jstring(env, data);
delete[]data;//释放内存空间
return js;
} catch (...) {
return NULL;
}
}
这是读取文件,建立映射时需要文件大小,最初思路就是通过这种方式获取具体字符串再得到字符串长度,但实际用下来总有奇奇怪怪的问题。
于是想到返璞归真,直接读取对应文件的内容得到大小:
#include <fstream>
...
int file_size;
std::ifstream file (file_path, std::ios::in|std::ios::binary|std::ios::ate);
if (file.is_open())
{
file_size = (long)file.tellg();
char *contents = new char [file_size];
file.seekg (0, std::ios::beg);
file.read (contents, file_size);
file.close();
std::string str = contents;
//LOGI("str = %s", str.c_str());
file_size = str.length();
delete [] contents;
}
...
之后再用这个数值去判断是不是需要添加文件的大小来达成追加文本写入以及确定写入的具体位置。
此外
根据参考代码里,写入时获取待写入内容字符串长度是用的:int32_t m_size = env->GetStringLength(text);
。这个实际用下来,会出现中文乱码问题,还有含有中文时,换行也没法写入,数字和英文就正常。换成:int32_t m_size = writ_text.length();
解决问题。