Map的containskey方法使用哈希算法查找key是否存在,運算時間是常數;
List的contains方法是將元素在列表中遍歷,運算時間和列表長度有關。
我使用兩種不同SQL語句獲取兩種不同類型的結果集進行比較,發現兩者差別很明顯。
名稱 | 類型 | 比較方法 | 耗時 |
兩個含35,7282數據的map對比 | map | containsKey | 101ms |
兩個含36,13962數據的list對比 | list | contains | 46s455ms |
至於Map包含的數據量略少於map,是因為存在重復key,map把它過濾掉了,這在結果集比較時有一小段是缺乏證明的,需要用另外的手段再驗證。
代碼:
package com.ufo.leftjoin; import java.security.MessageDigest; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.log4j.Logger; public class SqlComparator { private static Logger log = Logger.getLogger(SqlComparator.class); public void compare() { Connection conn = null; Statement stmt = null; try { Class.forName(DBParam.Driver).newInstance(); Properties pro = new Properties(); pro.setProperty("user", DBParam.User); pro.setProperty("password", DBParam.Pswd); conn = DriverManager.getConnection(DBParam.DbUrl, pro); stmt = conn.createStatement(); //compareUsingMap(stmt); compareUsingList(stmt); } catch (Exception e) { System.out.print(e.getMessage()); e.printStackTrace(); } finally { try { stmt.close(); conn.close(); } catch (SQLException e) { log.error("Can't close stmt/conn because of " + e.getMessage()); } } } private void compareUsingMap(Statement stmt) throws SQLException { long startMs = System.currentTimeMillis(); Map<String,DhItem> leftMap=getMapFrom(getLeftjoinSql(),stmt); long endMs = System.currentTimeMillis(); log.info("It takes "+ms2DHMS(startMs,endMs)+" to get leftMap."); int leftCount=leftMap.size(); log.info("There are "+toEastNumFormat(leftCount)+" records in leftMap."); startMs = System.currentTimeMillis(); Map<String,DhItem> notexistMap=getMapFrom(getNotExistSql(),stmt); endMs = System.currentTimeMillis(); log.info("It takes "+ms2DHMS(startMs,endMs)+" to get notexistMap."); int notexistCount=notexistMap.size(); log.info("There are "+toEastNumFormat(notexistCount)+" records in notexistMap."); startMs = System.currentTimeMillis(); List<DhItem> onlyInInnerLs=new ArrayList<DhItem>(); int count=0; for(String key:notexistMap.keySet()) { if(leftMap.containsKey(key)) { count++; }else { DhItem dhItem=notexistMap.get(key); onlyInInnerLs.add(dhItem); } } log.info("There are "+toEastNumFormat(count)+" records in two maps."); log.info("There are "+toEastNumFormat(onlyInInnerLs.size())+" records only in notexistMap."); endMs = System.currentTimeMillis(); log.info("It takes "+ms2DHMS(startMs,endMs)+" to complete comparison using map."); } private void compareUsingList(Statement stmt) throws SQLException { long startMs = System.currentTimeMillis(); List<DhItem> leftList=getListFrom(getLeftjoinSql(),stmt); long endMs = System.currentTimeMillis(); log.info("It takes "+ms2DHMS(startMs,endMs)+" to get leftList."); int leftCount=leftList.size(); log.info("There are "+toEastNumFormat(leftCount)+" records in leftList."); startMs = System.currentTimeMillis(); List<DhItem> notexistList=getListFrom(getNotExistSql(),stmt); endMs = System.currentTimeMillis(); log.info("It takes "+ms2DHMS(startMs,endMs)+" to get notexistList."); int notexistCount=notexistList.size(); log.info("There are "+toEastNumFormat(notexistCount)+" records in notexistList."); startMs = System.currentTimeMillis(); List<DhItem> onlyInInnerLs=new ArrayList<DhItem>(); int count=0; for(DhItem item:notexistList) { if(leftList.contains(item)) { count++; }else { onlyInInnerLs.add(item); } } log.info("There are "+toEastNumFormat(count)+" records in two lists."); log.info("There are "+toEastNumFormat(onlyInInnerLs.size())+" records only in notexistList."); endMs = System.currentTimeMillis(); log.info("It takes "+ms2DHMS(startMs,endMs)+" to complete comparison using list."); } private List<DhItem> getListFrom(String sql,Statement stmt) throws SQLException { List<DhItem> list=new ArrayList<DhItem>(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { DhItem dhItem=new DhItem(); dhItem.order_no=rs.getString("order_no"); dhItem.shipper_code=rs.getString("shipper_code"); dhItem.vehicle_name=rs.getString("vehicle_name"); dhItem.vehicle_code=rs.getString("vehicle_code"); dhItem.reason_name_mobile=rs.getString("reason_name_mobile"); dhItem.status_name_mobile=rs.getString("status_name_mobile"); list.add(dhItem); } return list; } private Map<String,DhItem> getMapFrom(String sql,Statement stmt) throws SQLException { Map<String,DhItem> map=new HashMap<String,DhItem>(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { DhItem dhItem=new DhItem(); dhItem.order_no=rs.getString("order_no"); dhItem.shipper_code=rs.getString("shipper_code"); dhItem.vehicle_name=rs.getString("vehicle_name"); dhItem.vehicle_code=rs.getString("vehicle_code"); dhItem.reason_name_mobile=rs.getString("reason_name_mobile"); dhItem.status_name_mobile=rs.getString("status_name_mobile"); map.put(toMD5(dhItem.toString()), dhItem); } return map; } private String getLeftjoinSql() { StringBuilder sb = new StringBuilder(); sb.append(" SELECT "); sb.append(" DH1.ORDER_NO, "); sb.append(" DH1.SHIPPER_CODE , "); sb.append(" DH1.VEHICLE_NAME, "); sb.append(" DH1.VEHICLE_CODE , "); sb.append(" DH1.REASON_NAME_MOBILE, "); sb.append(" DH1.STATUS_NAME_MOBILE "); sb.append(" from "); sb.append(" DELIVERY_HISTORY DH1 "); sb.append(" left JOIN DELIVERY_HISTORY DH2 on "); sb.append(" DH1.SHIPPER_CODE = DH2.SHIPPER_CODE "); sb.append(" and DH1.ORDER_NO = DH2.ORDER_NO "); sb.append(" and DH2.UPDATED_DATETIME > DH1.UPDATED_DATETIME "); sb.append(" where DH2.UPDATED_DATETIME IS NULL "); sb.append(" and DH1.DISABLED_FLG = 0 "); String sql = sb.toString(); return sql; } private String getInnerSql() { StringBuilder sb = new StringBuilder(); sb.append(" select "); sb.append(" DH1.ORDER_NO, "); sb.append(" DH1.SHIPPER_CODE , "); sb.append(" DH1.VEHICLE_NAME, "); sb.append(" DH1.VEHICLE_CODE , "); sb.append(" DH1.REASON_NAME_MOBILE, "); sb.append(" DH1.STATUS_NAME_MOBILE "); sb.append(" from "); sb.append(" DELIVERY_HISTORY dh1 , "); sb.append(" (select SHIPPER_CODE,ORDER_NO,max(UPDATED_DATETIME) as utime from DELIVERY_HISTORY "); sb.append(" group by SHIPPER_CODE,ORDER_NO) dh2 "); sb.append(" where "); sb.append(" dh1.SHIPPER_CODE=dh2.SHIPPER_CODE and "); sb.append(" dh1.ORDER_NO=dh2.ORDER_NO and "); sb.append(" dh1.UPDATED_DATETIME=dh2.utime and "); sb.append(" dh1.DISABLED_FLG='0' "); String sql = sb.toString(); return sql; } private String getNotExistSql() { StringBuilder sb = new StringBuilder(); sb.append(" select "); sb.append(" a.ORDER_NO, "); sb.append(" a.SHIPPER_CODE , "); sb.append(" a.VEHICLE_NAME, "); sb.append(" a.VEHICLE_CODE , "); sb.append(" a.REASON_NAME_MOBILE, "); sb.append(" a.STATUS_NAME_MOBILE ,"); sb.append(" a.UPDATED_DATETIME"); sb.append(" from DELIVERY_HISTORY a"); sb.append(" where not exists(select 1 "); sb.append(" from DELIVERY_HISTORY b"); sb.append(" where b.SHIPPER_CODE=a.SHIPPER_CODE and b.ORDER_NO=a.ORDER_NO and b.UPDATED_DATETIME>a.UPDATED_DATETIME)"); sb.append(" and a.DISABLED_FLG=0 "); String sql = sb.toString(); return sql; } // 將整數在萬分位以逗號分隔表示 public static String toEastNumFormat(long number) { DecimalFormat df = new DecimalFormat("#,####"); return df.format(number); } /** * change seconds to DayHourMinuteSecond format * * @param startMs * @param endMs * @return */ private static String ms2DHMS(long startMs, long endMs) { String retval = null; long secondCount = (endMs - startMs) / 1000; String ms = (endMs - startMs) % 1000 + "ms"; long days = secondCount / (60 * 60 * 24); long hours = (secondCount % (60 * 60 * 24)) / (60 * 60); long minutes = (secondCount % (60 * 60)) / 60; long seconds = secondCount % 60; if (days > 0) { retval = days + "d" + hours + "h" + minutes + "m" + seconds + "s"; } else if (hours > 0) { retval = hours + "h" + minutes + "m" + seconds + "s"; } else if (minutes > 0) { retval = minutes + "m" + seconds + "s"; } else { retval = seconds + "s"; } return retval + ms; } public static String toMD5(String key) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; try { byte[] btInput = key.getBytes(); // 獲得MD5摘要算法的 MessageDigest 對象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字節更新摘要 mdInst.update(btInput); // 獲得密文 byte[] md = mdInst.digest(); // 把密文轉換成十六進制的字符串形式 int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { return null; } } public static void main(String[] args) { SqlComparator sc=new SqlComparator(); sc.compare(); } }
輸出:
2019-12-25 10:31:17,997 INFO [main] - It takes 2m36s81ms to get leftMap. 2019-12-25 10:31:18,002 INFO [main] - There are 35,7282 records in leftMap. 2019-12-25 10:33:57,147 INFO [main] - It takes 2m39s145ms to get notexistMap. 2019-12-25 10:33:57,147 INFO [main] - There are 35,7282 records in notexistMap. 2019-12-25 10:33:57,247 INFO [main] - There are 35,7282 records in two maps. 2019-12-25 10:33:57,247 INFO [main] - There are 0 records only in notexistMap. 2019-12-25 10:33:57,248 INFO [main] - It takes 0s101ms to complete comparison using map. 2019-12-25 10:44:22,695 INFO [main] - It takes 2m36s52ms to get leftList. 2019-12-25 10:44:22,700 INFO [main] - There are 36,1396 records in leftList. 2019-12-25 10:46:55,335 INFO [main] - It takes 2m32s634ms to get notexistList. 2019-12-25 10:46:55,335 INFO [main] - There are 36,1396 records in notexistList. 2019-12-25 10:47:41,789 INFO [main] - There are 0 records in two lists. 2019-12-25 10:47:41,790 INFO [main] - There are 36,1396 records only in notexistList. 2019-12-25 10:47:41,790 INFO [main] - It takes 46s455ms to complete comparison using list.
--END-- 2019-12-25 11:45