抛出问题
假如有两组数据,这六个人去参加非诚勿扰,需要做一个配对。数据如下:
List<User> usersA = new ArrayList<>(); usersA.add(new User("1", "吕布", "10")); usersA.add(new User("2", "刘备", "11")); usersA.add(new User("3", "孙悟空", "12")); List<User> usersB = new ArrayList<>(); usersB.add(new User("10", "貂蝉", "1")); usersB.add(new User("11", "孙尚香", "2")); usersB.add(new User("12", "紫霞", "3"));
比如 usersA.add(new User("1", "吕布", "10"));
1是吕布自己的ID,10是配对方的ID。假设现在有个需求,需要返回一个结果集,返回“吕布-貂蝉”这样的数据。首先想到的肯定就是双层for,这简直就是理所当然的事情。
int loopCount = 0; for(User userA : usersA) { for(User userB : usersB) { loopCount++; if(userB.getUid().equals(userA.getCoupleId())) { System.out.println( String.format("%s-%s",userA.getUname(),userB.getUname())); } } }
结果:
吕布-貂蝉
刘备-孙尚香
孙悟空-紫霞
一共循环9次
逻辑清晰,简单易懂,完全没有问题。
优化1
一个最简单的优化思路就是,比如吕布已经找到了貂蝉,那么应该立刻跳出循环才对。因为吕布已经找到貂蝉了,就没必要再去找孙尚香。int loopCount = 0;
int loopCount = 0; for(User userA : usersA) { for(User userB : usersB) { loopCount++; if(userB.getUid().equals(userA.getCoupleId())) { System.out.println( String.format("%s-%s",userA.getUname(),userB.getUname())); break; } } }
加一个break就完事儿了。
吕布-貂蝉
刘备-孙尚香
孙悟空-紫霞
一共循环6次
从9次减少到6次,非常大的提升。
优化2
纳尼,还能优化?肯定的,比如吕布匹配到貂蝉后,下一个循环,刘备还是会匹配到貂蝉,这就不合理,所以不妨把匹配到的对象直接从List删去,就不会出现重复匹配的情况了。
非诚勿扰的规则也是,牵手成功后就直接下场了,不会继续留在舞台上。所以,加一个remove操作就很有必要。
for(User userA : usersA) { for(User userB : usersB) { loopCount++; if(userB.getUid().equals(userA.getCoupleId())) { System.out.println( String.format("%s-%s",userA.getUname(),userB.getUname())); usersB.remove(userB); break; } } }
我靠,直接到3次了,这肯定是最优解了吧?然而,并不是。因为List里面user的顺序是正好的,吕布对貂蝉,刘备对孙尚香,猴子对露娜。
可实际情况,哪有这么巧合的事情??
比如我随便打乱一下顺序,就不同了。
List<User> usersB = new ArrayList<>(); usersB.add(new User("12", "紫霞", "3")); usersB.add(new User("10", "貂蝉", "1")); usersB.add(new User("11", "孙尚香", "2"));
这样的结果就是循环5次,那如果数据更多呢?比如第一个数据要匹配到最后一个才对得上,那效率也就太低了。
优化3
虽然用for循环给人感觉是天经地义,但是不妨换个思路,男嘉宾为什么非要一个个去找呢,比如吕布,一眼扫过去看到貂蝉,直接牵走不就完事了吗?
所以,我们可以给女嘉宾建立索引,最简单的办法就是用Map。
int loopCount = 0; Map<String, String> usersMap = new LinkedHashMap<String, String> (); for (User userB : usersB) { loopCount++; usersMap.put(userB.getUid(), userB.getUname()); } for (User userA : usersA) { loopCount++; System.out.println(String.format("%s-%s", userA.getUname(), usersMap.get(userA.getCoupleId()))); }
结果:
吕布-貂蝉
刘备-孙尚香
孙悟空-紫霞
一共循环6次
假如两个列表的长度分别是m和n,那么不管其中的顺序如何,循环的深度永远是固定的m+n。虽然还是比不上上面的最佳情况n次,但是当m和n特别大的时候,效率就立刻突显出来了。
当然了,因为用了Map,牺牲了一点点内存,算是空间换时间吧。
实际应用场景
项目中的实际应用有很多,比如数据字典,很多项目会在SpringContext启动的时候,就把字典表加载到内存。当出现表字段需要做转换的时候,直接查表,效率非常可观。(和left join比起来)
比如,我有一个xml:
<grid id="grid0" class="com.service.UserService" method="getList" parameters="" resulttype="com.answer.entity.User"> <column id="uid"> 用户编号 </column> <column id="uname"> 用户名称 </column> <column id="role" datadic="role"> 角色 </column> </grid>
这是自定义的标签,可以通过反射,xml解析的方式做一个小型的解析框架。用到了jsoup,做一个测试类:
public class JspTest { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { Map<String, Map<String, Object>> datadics = new HashedMap(); Map<String, Object> roleDict = new HashedMap(); roleDict.put("1", "射手"); roleDict.put("2", "法师"); datadics.put("role", roleDict); File file = new File("D:\\eclipse-mars\\workspace2\\answer\\WebContent\\table.xml"); Document doc = Jsoup.parse(file, "UTF-8"); Elements grids = doc.getElementsByTag("grid"); List<String> colums = new ArrayList<String>(); List<List<Object>> rows = new ArrayList<List<Object>>(); for (Iterator<Element> iterator = grids.iterator(); iterator.hasNext(); ) { Element grid = (Element) iterator.next(); String className = grid.attr("class"); String method = grid.attr("method"); String resultType = grid.attr("resultType"); Class<?> clazz = Class.forName(className); Class resultClass = Class.forName(resultType); Method method2 = clazz.getMethod(method); List result = (List) method2.invoke(clazz.newInstance()); Elements columns = grid.getElementsByTag("column"); int index = 0; for (int i = 0; i < result.size(); i++) { Element e = (Element) columns.get(i); String colName = e.text(); colums.add(colName); List row = new ArrayList<>(); rows.add(row); Object object = result.get(index++); for (int j = 0; j < columns.size(); j++) { String id = columns.get(j).attr("id"); String DATADIC = columns.get(j).attr("DATADIC"); Map<String, Object> map = datadics.get(DATADIC); Object fieldValue = null; fieldValue = ReflectUtil.getFieldValue(object, id); if (map != null) { fieldValue = map.get(String.valueOf(fieldValue)); } rows.get(i).add(fieldValue); } } } StringBuffer sb = new StringBuffer("\n"); for (Iterator iterator = colums.iterator(); iterator.hasNext(); ) { String column = (String) iterator.next(); sb.append(""); } sb.append("\n"); for (Iterator iterator = rows.iterator(); iterator.hasNext(); ) { List<Object> row = (List<Object>) iterator.next(); sb.append(""); for (Iterator iterator2 = row.iterator(); iterator2.hasNext(); ) { Object string = iterator2.next(); sb.append(""); } sb.append(""); sb.append("\n"); } sb.append(""); sb.append(column); sb.append(""); sb.append(string); sb.append(""); grids.get(0).replaceWith(Jsoup.parse(sb.toString()).body()); System.out.println(doc); FileUtil.writeString(doc.toString(), new File("D:\\eclipse - mars\\workspace2\\answer\\WebContent\\table.html "), " GBK "); } }
最终生成一个html,效果如下:
其中,角色已经默认用字典转化了,很多框架内部应该是做了类似的事情。当然,主要是一些老的框架,毕竟现在还在用jsp标签的系统已经不多了。