要點####
- 文件數(shù)據(jù)的讀取相較數(shù)據(jù)庫的存儲較快
- Android系統(tǒng)對單個應(yīng)用的內(nèi)存有限制
- 數(shù)據(jù)庫操作中對數(shù)據(jù)的批插入比單個插入效率更高
- 合理利用多線程能夠大大提高工作效率
背景####
項目有一個需求就是將文本文件解析歹垫,然后保存到數(shù)據(jù)庫中镀迂。最初的思考方式是采用讀取文件一行數(shù)據(jù)(按行存儲一個實體變量數(shù)據(jù))同窘,解析數(shù)據(jù)為實體類辉阶,然后將該實體類存儲至數(shù)據(jù)庫中沃但;該方法在測試過程中有一個致命缺點:處理時間長可款。
分析####
將上述流程轉(zhuǎn)化為比較容易理解的抽象圖,如下:
將子公司貨物(單行數(shù)據(jù))快速打包成貨物(解析)并運送至交接位置處(內(nèi)存)逼庞,然后交由速度較慢的運送方式將貨物運送至總部(數(shù)據(jù)庫)蛇更。該方式為順序流程,當貨物運送至總部后才會再次從子公司重新開始運送貨物赛糟。該方式有幾個缺點:
- 順序流程增加了總時間:數(shù)據(jù)插入時間+數(shù)據(jù)的處理時間派任,如果采用多線程將會大幅度減少總時間
- 數(shù)據(jù)的單個插入增加了數(shù)據(jù)插入時間
優(yōu)化####
通過分析將流程優(yōu)化,優(yōu)化后的抽象圖如下:
該流程分為兩部分:
- 將子公司貨物(單行數(shù)據(jù))快速打包成貨物(解析)并運送至倉庫(內(nèi)存隊列)璧南;
- 從倉庫中取出合適數(shù)量的貨物(批量數(shù)據(jù))運送至總部(數(shù)據(jù)庫)
??該流程利用了多線程將流程分為兩部分——數(shù)據(jù)存儲與數(shù)據(jù)解析掌逛,數(shù)據(jù)存儲的速度較慢,因此最后總時間的計算是以數(shù)據(jù)存儲為依據(jù)司倚,也就是數(shù)據(jù)插入時間豆混。
??另一方面,程序使用了倉庫动知,也就是隊列的方式優(yōu)化了數(shù)據(jù)的交接方式皿伺,將數(shù)據(jù)存儲與數(shù)據(jù)解析分離,他們只通過隊列才產(chǎn)生耦合盒粮。但是因為Android對內(nèi)存的限制鸵鸥,所以需要合理設(shè)計倉庫的大小,以防出現(xiàn)內(nèi)存溢出丹皱。
注意:合理的設(shè)計數(shù)據(jù)解析與數(shù)據(jù)存儲流程對隊列的數(shù)據(jù)讀寫速度妒穴。
Code####
??通過上述的代碼分析,應(yīng)該對整體的流程有一個大概的思路摊崭,現(xiàn)在我們來看一下代碼:
- 隊列的定義與初始化:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
- 主要流程代碼:
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(path))));
String line = null;
//初始化數(shù)據(jù)存儲線程
WriteDB writeDB = new WriteDB();
new Thread(writeDB).start();
//讀取文本文件讼油,并解析
while ((line = reader.readLine())!=null){
//通過該函數(shù)解析數(shù)據(jù)
parase(line);
//查詢隊列中的數(shù)據(jù)量,當數(shù)據(jù)為8000條時暫停數(shù)據(jù)解析線程
if(queue.size() == 8000){
Thread.currentThread().sleep(2*1000);
}
}
reader.close();
//告知數(shù)據(jù)存儲線程讀取線程結(jié)束
writeDB.stop();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
- 數(shù)據(jù)解析流程:
private void parase(String s) throws Exception{
//根據(jù)規(guī)則解析單行數(shù)據(jù)爽室,并轉(zhuǎn)化為實體
String[] list = s.split("\\|");
BagInfo bag = new BagInfo();
bag.setBagID(list[0]);
bag.setPileID(list[1]);
bag.setTrayID(list[2]);
//存入隊列中
queue.put(bag);
}
- 數(shù)據(jù)存儲流程:
class WriteDB implements Runnable {
private boolean isRun = true;
@Override
public void run() {
//為了批量存儲設(shè)置的列表汁讼,保存從隊列中讀取的數(shù)據(jù)
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);
//當列表中的數(shù)據(jù)量達到2000時將數(shù)據(jù)批量存儲至數(shù)據(jù)庫中
if(bagInfos.size() == 2000){
DBService.getService().getBagInfoDao().insertInTx(bagInfos);
bagInfos.clear();
}
}
//處理當總數(shù)據(jù)量不是2000的整數(shù)倍時所剩余的數(shù)據(jù)
if(!bagInfos.isEmpty()){
DBService.getService().getBagInfoDao().insertInTx(bagInfos);
bagInfos.clear();
}
queue = null;
}
public void stop(){
isRun =false;
}
}
后臺監(jiān)測####
??運行程序并監(jiān)測后臺,發(fā)現(xiàn)內(nèi)存基本穩(wěn)定在6M左右阔墩;測試5W多條的數(shù)據(jù)量總花費的時間為68s左右嘿架。監(jiān)測圖如下: