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