package com.github.netcomm.sponge;
import java.io.File;
import java.io.RandomAccessFile;
import com.github.netcomm.sponge.util.RAcsFile;
import com.github.netcomm.sponge.util.Utilities;
public class FilePersistence extends BasePersistence {
private String directory;
private RAcsFile theWriteDataFile;
private RAcsFile theReadDataFile;
private RAcsFile theFetchPosiFile;
private boolean forceToDisk = true;
private long curFetchPosi;
private final static String DataFile_Name = "dataFile.data";
private final static String FetchPosiFile_Name = "fetchPosiFile.data";
public FilePersistence(long maxByteArray_SzParm, int oneBatchWriteCntParm, int isCanReleaseResMaxTimeParm,
String directoryParm) throws Exception {
super(maxByteArray_SzParm, oneBatchWriteCntParm, isCanReleaseResMaxTimeParm);
directory = directoryParm;
if (!directoryParm.endsWith("/")) directory = directory + "/";
theWriteDataFile = new RAcsFile(directory + DataFile_Name);
theReadDataFile = new RAcsFile(directory + DataFile_Name, "r");
theFetchPosiFile = new RAcsFile(directory + FetchPosiFile_Name);
//初始化当前已经消费过的的位置
initCurFetchPosi();
//写数据文件的时候,要接着数据文件中已有的内容往后写. --> 定位写
theWriteDataFile.getDataFile().seek(theWriteDataFile.getFileLength());
//readOneBatch_MaxBytes = new byte[readOneBatch_MaxByteSz];
}
private void initCurFetchPosi() {
try {
//theFetchPosiFile中保存的是一个long类型,表示在数据文件中已经消费过的位置
if (theFetchPosiFile.getFileLength() == 8) {
curFetchPosi = theFetchPosiFile.getDataFile().readLong();
}
//fetchPosiFile保存的是数据文件中已经消费过的数据的位置 --> 定位读
//读数据的时候,这个位置会增加到读完的那个位置. 读完==消费过
theReadDataFile.getDataFile().seek(curFetchPosi);
//目前为止消费到的位置比要读取的文件的长度还少, 还没消费完数据文件. 即还有数据需要消费
if (curFetchPosi < theReadDataFile.getFileLength()) {
setHaveDataInPersistence(true);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 获取数据时比写数据要麻烦,要维护消费的位置.
* @return
* @throws Exception
*/
@Override
public byte[] doFetchOneBatchBytes() throws Exception {
byte[] tmpLengthByte = new byte[6];
int tmpReadCnt = theReadDataFile.getDataFile().read(tmpLengthByte);
if (tmpReadCnt == -1) return null;
//第二个字节后写入的是一批数据的大小(包括了magic head部分的6个字节).
int tmpByteLength = Utilities.getIntFromBytes(tmpLengthByte, 2);
byte[] tmpReadBytes = new byte[tmpByteLength];
//第六个字节后写入的是数组的每个元素的字节数组. ReadCnt表示读取的数组元素的数量
//起始现在就可以把tmpReadBytes返回了. 但是为什么还要处理FetchPositionFile?
tmpReadCnt = theReadDataFile.getDataFile().read(tmpReadBytes, 6, tmpByteLength - 6);
if (tmpReadCnt == -1) return null;
//读完一批数据之后, 将数据文件的消费位置右移到这批数据的末尾-->写到索引文件中!
curFetchPosi += tmpByteLength;
//将long类型的curFetchPosi转换为字节数组形式,覆写到索引文件中
byte[] tmpBytes = Utilities.getBytesFromLong(curFetchPosi);
//注意:每次fetch一批数据的时候,都是从0开始写一个long类型的数值,所以是覆盖!
theFetchPosiFile.getDataFile().seek(0);
theFetchPosiFile.getDataFile().write(tmpBytes);
theFetchPosiFile.getDataFile().getFD().sync();
return tmpReadBytes;
}
/**
* 持久化文件时,只需要将字节数组保存到数据文件中即可.
* @param writeBytesParm 要写入的数据
* @param offsetParm 起始位置
* @param lengthParm 写入多少长度的数据
* @throws Exception
*/
@Override
public void doWriteOneBatchBytes(byte[] writeBytesParm, int offsetParm, int lengthParm) throws Exception {
long tmpStartTime = System.currentTimeMillis();
RandomAccessFile file = theWriteDataFile.getDataFile();
file.write(writeBytesParm, offsetParm, lengthParm);
if (forceToDisk) file.getFD().sync();
System.out.println("一次写入耗时 " + (System.currentTimeMillis() - tmpStartTime));
}
@Override
public void destroy() throws Exception {
theWriteDataFile.close();
theReadDataFile.close();
}
@Override
public void doWriteOneBatchBytes(byte[] writeBytesParm) throws Exception {
doWriteOneBatchBytes(writeBytesParm, 0, writeBytesParm.length);
}
/**
* 释放资源. 资源是什么? 资源就是超过队列阈值后保存在持久化文件里的任务.
* 释放资源即要删除持久化文件.
* @throws Exception
*/
@Override
public void canReleaseRes() throws Exception {
if (curFetchPosi != 0) {
theWriteDataFile.destroy();
theReadDataFile.destroy();
theWriteDataFile = null;
theReadDataFile = null;
File tmpFile = new File(directory + DataFile_Name);
deleteFile(tmpFile);
curFetchPosi = 0;
theFetchPosiFile.getDataFile().seek(0);
theFetchPosiFile.getDataFile().write(Utilities.getBytesFromLong(curFetchPosi));
theFetchPosiFile.getDataFile().getFD().sync();
theWriteDataFile = new RAcsFile(directory + DataFile_Name);
theReadDataFile = new RAcsFile(directory + DataFile_Name, "r");
}
}
private boolean deleteFile(File fileToDelete) {
if (fileToDelete == null || !fileToDelete.exists()) {
return true;
}
boolean result = fileToDelete.delete();
return result;
}
}