万宁市网站建设_网站建设公司_版式布局_seo优化
2026/1/14 23:16:16 网站建设 项目流程

1. 在 MyBatis 的mapper.xml文件中,List 和 Set 的遍历方式是完全一样的

这是因为 MyBatis 的<foreach>标签在底层处理时,并不严格区分具体的集合类型(ListSet等),它统一将它们视为一个Iterable对象(或数组)进行处理。

核心机制

<foreach>标签的核心工作是:遍历一个可迭代的对象。无论是List还是Set,都实现了java.lang.Iterable接口,因此 MyBatis 可以用同一套逻辑来遍历它们。

使用示例与对比

假设你的 Mapper 接口方法如下:

// 参数是 ListList<User>selectUsersByIdList(List<Integer>idList);// 参数是 SetList<User>selectUsersByIdSet(Set<Integer>idSet);

mapper.xml中,对应的 SQL 写法是一模一样的:

<selectid="selectUsersByIdList"resultType="User">SELECT * FROM user WHERE id IN<foreachcollection="idList"item="id"open="("separator=","close=")">#{id}</foreach></select><selectid="selectUsersByIdSet"resultType="User">SELECT * FROM user WHERE id IN<foreachcollection="idSet"item="id"open="("separator=","close=")">#{id}</foreach></select>

它们的区别仅在于 Mapper 接口方法参数名不同(idListvsidSet),以及在 XML 中collection属性引用的名字不同。<foreach>标签的内部处理逻辑没有任何区别。

关键注意事项

虽然遍历方式相同,但有几个细节需要注意:

项目说明通用建议
参数名<foreach>中的collection属性值必须与接口方法中的参数名严格一致。使用明确的参数名,避免用listcollection等默认名。
参数唯一性当接口方法有且仅有一个集合参数时,在 XML 中可以直接用其参数名引用。如果方法有多个参数,务必使用@Param(“key”)注解显式命名。
有序性List是有序的,遍历出的顺序与存入顺序一致;Set(如HashSet)通常无序。如果在 SQL 中依赖遍历顺序,请务必使用List
去重List允许重复元素;Set会自动去重。如果希望 IN 语句中的条件自动去重,优先使用Set
空集合两者都可能为空。需在代码或 SQL 中处理空集合,否则可能导致 SQL 语法错误。在 Java 代码或 MyBatis 的<if>标签中判空是良好实践。

总结与建议

  • 遍历方式完全一样。无需因为ListSet而改变 XML 中的写法。
  • 选择依据:根据你的业务需求来选择:
    • 需要保持顺序允许重复,用List
    • 需要自动去重且不关心顺序,用Set

2. List.size() 和 Set.size()排除重复数据后是一样的

在 Java 中,idList.size()idSet.size()不一定一样。这取决于原始数据中是否有重复元素

简单来说:

  • 如果原始数据没有重复,它们的size()相等
  • 如果原始数据有重复List会保留所有重复项,而Set会自动去重,此时Listsize()大于Setsize()

核心区别对比

特性List(如ArrayList)Set(如HashSet)size()的影响
是否允许重复元素允许不允许(自动去重)决定size()是否相同的关键
顺序有序(保持插入顺序)通常无序(HashSetsize()无关
size()方法含义返回列表中元素的总数(包含重复项)返回集合中唯一元素的数量计算基数不同

示例说明

importjava.util.*;publicclassTest{publicstaticvoidmain(String[]args){// 原始数据有重复:1, 2, 2, 3List<Integer>idList=Arrays.asList(1,2,2,3);Set<Integer>idSet=newHashSet<>(idList);// 会自动去重System.out.println("List size: "+idList.size());// 输出:4System.out.println("Set size: "+idSet.size());// 输出:3System.out.println("size 是否相等: "+(idList.size()==idSet.size()));// 输出:false// 在MyBatis的SQL中,这会导致:// List -> WHERE id IN (1, 2, 2, 3) // 条件有4个,但重复条件可能多余// Set -> WHERE id IN (1, 2, 3) // 条件只有3个,是精确的唯一条件集合}}

在你的 MyBatis 场景下的影响

结合你之前关于 MyBatis 的问题,这个区别会产生直接的实际影响:

  • 使用List:如果传入的idList包含重复的用户ID,生成的 SQL 会是IN (1, 2, 2, 3)。虽然结果正确(数据库会对重复ID返回相同行,结果集不变),但SQL语句更长,可能影响解析效率,且不符合语义
  • 使用Set:如果传入的idSet(或先将List转为Set),生成的 SQL 会是IN (1, 2, 3)SQL更精简、语义更准确,是更推荐的做法。

最佳实践建议

  1. 数据一致性:如果业务上“ID集合不应重复”,建议在接口层直接使用Set类型,或在使用List前进行去重,从源头上保证数据语义。
  2. 性能考量:对于contains()操作(检查某个ID是否存在),HashSet(时间复杂度接近 O(1))的性能远高于ArrayList(O(n))。如果后续有此类操作,Set是更好选择。
  3. 明确选择
    • 需要保持顺序明确允许重复时,用List
    • 需要确保唯一性去重或进行快速查找时,用Set

所以,当你不确定传入的ID集合是否有重复时,如果你希望生成的SQL条件是最精简、准确的唯一值,在MyBatis的Mapper接口参数中直接使用Set类型是更安全、更语义化的选择

3. List去重,由Set可以优化

1.避免重复遍历(最优方案)

使用Collectors.toSet()直接收集到 Set,只需一次遍历:

// 方案1:使用 Collectors.toSet()longsectionCount=similarProjectVOS.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).collect(Collectors.toSet()).size();// 方案2:使用 toCollection 指定 HashSet(可控制初始容量)longsectionCount=similarProjectVOS.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).collect(Collectors.toCollection(HashSet::new)).size();

2.并行流优化(大数据量时)

当列表非常大(例如超过10万条)时,可考虑并行流:

longsectionCount=similarProjectVOS.parallelStream().map(PvModuleVO.SimilarProjectVO::getSectionId).collect(Collectors.toSet()).size();

3.根据实际情况选择数据结构

如果只需要判断是否有重复,不需要具体数量,用distinct().count() > 1即可:

booleanhasDuplicates=similarProjectVOS.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).distinct().count()>1;

📊 性能对比分析

方案时间复杂度空间复杂度适用场景
原方案.distinct().count()O(n)O(k)(k为去重后数量)小到中等数据集
优化方案.collect(Collectors.toSet()).size()O(n)O(k)推荐:所有场景
并行流方案O(n/p)(p为线程数)O(k)大数据集(>10万条)
传统 for 循环O(n)O(k)极致性能要求

💡 实际测试对比

// 性能测试代码示例List<PvModuleVO.SimilarProjectVO>data=generateTestData(100000);// 原方案longstart1=System.nanoTime();longcount1=data.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).distinct().count();longtime1=System.nanoTime()-start1;// 优化方案longstart2=System.nanoTime();longcount2=data.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).collect(Collectors.toSet()).size();longtime2=System.nanoTime()-start2;System.out.printf("原方案: %d 个, 耗时: %.2f ms%n",count1,time1/1e6);System.out.printf("优化方案: %d 个, 耗时: %.2f ms%n",count2,time2/1e6);

🔍 进一步优化建议

  1. 源头去重:如果similarProjectVOS的数据来自数据库查询,考虑在 SQL 层面直接去重计数:

    SELECTCOUNT(DISTINCTsection_id)FROM...WHERE...
  2. 考虑空值处理:如果sectionId可能为 null,需要处理:

    longsectionCount=similarProjectVOS.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).filter(Objects::nonNull)// 过滤 null 值.collect(Collectors.toSet()).size();
  3. 预分配容量:如果知道大概的去重后数量,可以优化 HashSet 初始容量:

    intestimatedSize=similarProjectVOS.size()/2;// 预估容量longsectionCount=similarProjectVOS.stream().map(PvModuleVO.SimilarProjectVO::getSectionId).collect(Collectors.toCollection(()->newHashSet<>(estimatedSize))).size();

🎯 总结推荐

最佳实践:使用.collect(Collectors.toSet()).size(),原因:

  1. 语义更清晰:明确表达"获取不重复的集合"
  2. 性能更优:避免了distinct()的中间状态维护
  3. 代码简洁:一行完成,易于阅读

如果你的数据量特别大(百万级以上),或者这是性能关键路径,建议先进行基准测试,选择最适合你场景的方案。

4. 打印效果是一样的

简单来说,最直观的区别是:List 严格按插入顺序保留所有元素(包括重复项);而典型的 Set(如 HashSet)不保证顺序且自动去重。它们的toString()输出格式通常也不同(List 是[a, b, c],Set 是[a, c, b]等)。

核心区别对比

特性List(如ArrayList)Set(如HashSet)对打印输出的影响
顺序性严格保持插入顺序通常不保证顺序HashSet基于哈希值)最常见区别:打印顺序可能不同
重复元素保留所有重复项自动去重(只保留一个)有重复数据时,List打印更长
toString()格式格式相同,都为[元素1, 元素2, ...]格式相同,都为[元素1, 元素2, ...]格式一样,但括号内内容不同

示例演示

importjava.util.*;publicclassPrintDemo{publicstaticvoidmain(String[]args){// 原始数据:有重复,注意插入顺序List<Integer>idList=newArrayList<>(Arrays.asList(3,1,2,1,4));Set<Integer>idSet=newHashSet<>(idList);// 用List构造Set会自动去重System.out.println("idList.toString() = "+idList);// 输出:idList.toString() = [3, 1, 2, 1, 4] ← 保持顺序和重复System.out.println("idSet.toString() = "+idSet);// 可能输出:idSet.toString() = [1, 2, 3, 4] ← 顺序不定,已去重// 也可能是:[3, 1, 2, 4] 等,取决于HashSet内部实现// 用迭代器/for循环打印,差异更明显System.out.print("遍历idList: ");for(Integerid:idList){System.out.print(id+" ");}// 输出:遍历idList: 3 1 2 1 4System.out.print("\n遍历idSet : ");for(Integerid:idSet){System.out.print(id+" ");}// 可能输出:遍历idSet : 1 2 3 4}}

三种关键场景分析

场景List 打印Set 打印输出是否“相同”
数据完全无重复[A, B, C][A, B, C][B, C, A]元素集合相同,但顺序可能不同
数据有重复[A, B, B, C][A, B, C]元素集合不同(数量、内容)
使用有序Set
(如TreeSet,LinkedHashSet)
[A, B, C]TreeSet: 排序后[A, B, C]
LinkedHashSet: 保持插入顺序[A, B, C]
可能相同(见下文)

特殊说明:如果你使用LinkedHashSet,它会保持插入顺序;如果使用TreeSet,它会按自然排序。对于无重复且顺序匹配的数据,它们的打印可能和 List“看起来一样”。

在 MyBatis 场景下的实际影响

回到你最初关心的 MyBatis 使用场景,这个差异会直接反映在生成的 SQL 中:

-- 假设原始数据:List<Integer> ids = Arrays.asList(3, 1, 2, 1, 4)-- 使用 List 参数WHEREidIN(3,1,2,1,4)-- 顺序固定,有重复-- 使用 HashSet 参数WHEREidIN(1,2,3,4)-- 顺序可能改变,已去重-- 使用 LinkedHashSet 参数(用 new LinkedHashSet<>(list) 构造)WHEREidIN(3,1,2,4)-- 保持原List顺序,但去重

实用建议

  1. 调试时:不要依赖Set(特别是HashSet)的toString()顺序来判断数据正确性。
  2. 需要顺序一致
    • 使用List
    • 或使用LinkedHashSet(去重但保序)
    • 或使用TreeSet(去重且排序)
  3. 在 MyBatis 中
    • 如果 SQL 条件顺序不影响结果(如IN子句),可忽略顺序差异。
    • 但如果需要稳定可预测的 SQL(如调试日志),建议在接口层统一转换为LinkedHashSetTreeSet

总结idListidSet打印出的数据通常不一样(顺序、重复项)。如果你希望它们“看起来一样”,需要确保数据无重复,并使用LinkedHashSetTreeSet来保序。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询