最近和老師做項目,用到了兩個集合:空氣品質pm和氣象資料meteo,pm包含1500萬條資料,meteo包含300萬條資料。
目的是要找出這其中時間“time”屬效能夠相對應的所有資料。
例如:pm和meteo都有2013-12-01 12:00:00這一時刻的資料,其中pm有920條這一時刻的空氣品質資料(因為有很多不同的空氣品質監測網站),meteo有350條這一時刻的氣象資料(因為有很多不同的氣象站),那就把這些資料保留或者存入各自的新集合。
如果在2013-12-02 12:00:00這一時刻,pm有這一時刻的空氣品質資料,但是meteo中沒有這一天的氣象資料,那麼pm中這一時刻的資料就沒有什麼用了。因為我們要求:針對同一時刻,兩邊的資料缺一不可。
然後開始使用mongodb操作,想法比較簡單:
(1) 設定時間格式,方便後面把字串String轉換成Date對象。
//設定時間格式和時區SimpleDateFormat df = new SimpleDateFormat(("yyyy-MM-dd HH:mm:ss"));df.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT")));
Tips: MongoDB在把時間Date對象存入資料庫的時候,會自動儲存為GMT標準時間,它預設你的原始時間資料是你的當地時間(不好意思我不知道它是根據什麼識別的,根據伺服器位址或是什麼),反正就是會自動識別到你當地時區,然後轉化成GMT時間。比如我們都在中國:你的原始.txt檔案裡的時間是2013-12-01 10:00:00,當你直接使用java存入mongodb資料庫的時候,它會自動減去8個小時,變成2013-12-01 02:00:00。
但是我的未經處理資料本來就是以GMT時間給我的,並且我也希望以GMT標準時間儲存它們,不是北京時。所以如果再把GMT給我減去8個小時,我的資料就不知道變成什麼時間了。所以一定要設定好TimeZone哦。
(2)設定開始和結束的時刻startDate和endDate:
Date startDate = df.parse("2013-12-01 00:00:00");Date endDate = df.parse("2015-12-31 23:00:00");
Tips: MongoDB中的時間最好使用Date()Object Storage Service,我們的未經處理資料處理的時候,時間也是使用JavaScript的Date()Object Storage Service到mongodb的。另外,我這裡命名雖然是xxxDate,其即時間是精確到時刻的哦。
(3)設定迴圈變數eachDate:
Calendar eachDate = Calendar.getInstance();eachDate.setTime(startDate);
這裡使用到了Calendar類,因為Calendar類提供了自動增加年份、月份、天數、小時的add(int filed, int amount)方法。
比如2013-12-31使用Calendar的add()方法加一天,自動會變成2014-01-01,不會變成2013-12-32這樣。加一小時同理,在2013-12-31 23:00:00的基礎上加一小時,會變成2014-01-01 00:00:00,很方便。
(4)開始日期的迴圈:
使用while,當startDate一直在endDate的前面,就一直去做詳細的尋找匹配(詳細操作這裡不討論,在下面討論)。
while(eachDate.getTime().before(endDate)){ eachDate.add(Calendar.HOUR, 1); //按小時增減迴圈 Date date = eachDate.getTime(); //詳細的匹配操作下面詳細討論,上面兩句只是寫明如何進行時間迴圈 //相信如果你清楚我的問題是什麼,自己也會有自己的想法,希望一起討論,小弟還是個新手呢。}
Tips: eachDate是Calendar對象,所以要調用其getTime()方法,返回一個Date對象,再使用Date對象的before()或者after()方法判斷迴圈。
(5)重要,開始討論while裡面的語句吧。
我最起初的想法很簡單,設定好了eachDate的迴圈,直接使用eachDate去分別在pm和meteo兩個表查詢符合這個時間的所有文檔。
/*設定mongodb的遊標cursor,必須*/BasicDBObject queryObject = new BasicDBObject();queryObject.put("time", date);DBCursor queryCursorMeteo = dbMeteoCollection.find(queryObject);DBCursor queryCursorPM = dbPMCollection.find(queryObject);/*若都包含此天,則把對應的文檔分別插入各自新表中*/if ((queryCursorMeteo.hasNext()) && (queryCursorPM.hasNext())) { //判斷是否都有這一天,如有則繼續下面的 while (queryCursorMeteo.hasNext()) { dbMeteoSameTimeCollection.insert(queryCursorMeteo.next()); //存入meteo庫的meteo_data_same_time集合 } while (queryCursorPM.hasNext()) { dbPMSameTimeCollection.insert(queryCursorPM.next()); //存入pmdata庫的pm_data_same_time集合 }}
想法單純入如我,直接根據eachDate去兩個集合中查,如果均查到了(也就是上面的if判斷),則用while把所查到的所有文檔存入新的集合中去。沒有做任何的處理,沒有索引,沒有排序,沒有批量插入,沒有,什麼都沒有,寶寶心累了所以什麼都沒有。
這樣簡單的想法的代價就是:時間開銷巨大。。面對1500w和300w的兩個集合,這個代碼跑了我電腦14個小時。寶寶真是蠢斃了。
(6)好了,寶寶要開始最佳化代碼了。
兩個想法:
1,在(5)的基礎上,增加對於時間的索引,索引的代價是在原、集合中插入會多用時間,但是我不在原集合中插入,而是插入到新集合中。所以我對原集合的處理只是查詢,而索引可以大大大大的節省時間;然後插入到新集合的時候不使用while一條一條插入,而是使用批量插入。
2,對原始兩個集合進行按照時間排序,設定兩個變數indexPm和i分別指向pm和meteo,外層迴圈使用資料量較小的meteo的指標i。比較pm的時間和meteo的時間是否相等,如果相等,pm指標後移一次。虛擬碼如下:
//先對兩組資料排序,可以用mongodb的sort命令int indexPm = 0;for(int i = 0; i < dateLength; i ++){ while( date at indexPm of collection pm == date at i of collection climate){ indexPm ++; //就是 pm的資料的cursor向前一個 } while(date at indexPm of collection pm != date at i+1 of collection climate){ delete current data of pm; indexPm ++; }}