要点####
- 文件数据的读取相较数据库的存储较快
- Android系统对单个应用的内存有限制
- 数据库操作中对数据的批插入比单个插入效率更高
- 合理利用多线程能够大大提高工作效率
背景####
项目有一个需求就是将文本文件解析,然后保存到数据库中。最初的思考方式是采用读取文件一行数据(按行存储一个实体变量数据),解析数据为实体类,然后将该实体类存储至数据库中;该方法在测试过程中有一个致命缺点:处理时间长。
分析####
将上述流程转化为比较容易理解的抽象图,如下:
将子公司货物(单行数据)快速打包成货物(解析)并运送至交接位置处(内存),然后交由速度较慢的运送方式将货物运送至总部(数据库)。该方式为顺序流程,当货物运送至总部后才会再次从子公司重新开始运送货物。该方式有几个缺点:
- 顺序流程增加了总时间:数据插入时间+数据的处理时间,如果采用多线程将会大幅度减少总时间
- 数据的单个插入增加了数据插入时间
优化####
通过分析将流程优化,优化后的抽象图如下:
该流程分为两部分:
- 将子公司货物(单行数据)快速打包成货物(解析)并运送至仓库(内存队列);
- 从仓库中取出合适数量的货物(批量数据)运送至总部(数据库)
该流程利用了多线程将流程分为两部分——数据存储与数据解析,数据存储的速度较慢,因此最后总时间的计算是以数据存储为依据,也就是数据插入时间。
另一方面,程序使用了仓库,也就是队列的方式优化了数据的交接方式,将数据存储与数据解析分离,他们只通过队列才产生耦合。但是因为Android对内存的限制,所以需要合理设计仓库的大小,以防出现内存溢出。
注意:合理的设计数据解析与数据存储流程对队列的数据读写速度。
Code####
通过上述的代码分析,应该对整体的流程有一个大概的思路,现在我们来看一下代码:
- 队列的定义与初始化:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
- 主要流程代码:
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(path))));
String line = null;
//初始化数据存储线程
WriteDB writeDB = new WriteDB();
new Thread(writeDB).start();
//读取文本文件,并解析
while ((line = reader.readLine())!=null){
//通过该函数解析数据
parase(line);
//查询队列中的数据量,当数据为8000条时暂停数据解析线程
if(queue.size() == 8000){
Thread.currentThread().sleep(2*1000);
}
}
reader.close();
//告知数据存储线程读取线程结束
writeDB.stop();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
- 数据解析流程:
private void parase(String s) throws Exception{
//根据规则解析单行数据,并转化为实体
String[] list = s.split("\\|");
BagInfo bag = new BagInfo();
bag.setBagID(list[0]);
bag.setPileID(list[1]);
bag.setTrayID(list[2]);
//存入队列中
queue.put(bag);
}
- 数据存储流程:
class WriteDB implements Runnable {
private boolean isRun = true;
@Override
public void run() {
//为了批量存储设置的列表,保存从队列中读取的数据
List<BagInfo> bagInfos = new ArrayList<>();
while (isRun || !queue.isEmpty()){
Object obj = null;
try {
obj = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
bagInfos.add((BagInfo) obj);
//当列表中的数据量达到2000时将数据批量存储至数据库中
if(bagInfos.size() == 2000){
DBService.getService().getBagInfoDao().insertInTx(bagInfos);
bagInfos.clear();
}
}
//处理当总数据量不是2000的整数倍时所剩余的数据
if(!bagInfos.isEmpty()){
DBService.getService().getBagInfoDao().insertInTx(bagInfos);
bagInfos.clear();
}
queue = null;
}
public void stop(){
isRun =false;
}
}
后台监测####
运行程序并监测后台,发现内存基本稳定在6M左右;测试5W多条的数据量总花费的时间为68s左右。监测图如下: