From 27142127b40bc03c96446bde99ff4645b2241b5b Mon Sep 17 00:00:00 2001 From: gqh Date: Sat, 19 Apr 2025 23:48:12 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E9=87=8D=E8=BD=BDsubtractToList?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=8C=E6=8F=90=E4=BE=9BisLinked=E9=80=89?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/collection/CollUtil.java | 43 ++- .../hutool/core/collection/CollUtilTest.java | 355 ++++++++++++++++++ 2 files changed, 394 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index fe03757c6..0fc4adbf8 100755 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -395,22 +395,57 @@ public class CollUtil { * @since 5.3.5 */ public static List subtractToList(Collection coll1, Collection coll2) { + return subtractToList(coll1, coll2, true); + } + /** + * 计算集合的单差集,即只返回【集合1】中有,但是【集合2】中没有的元素 + * 只要【集合1】中的某个元素在【集合2】中存在(equals和hashcode),就会被排除 + * + *
+	 * 示例:
+	 * 1. subtractToList([null, null, null, null], [null, null]) → []
+	 * 2. subtractToList([null, null, null, null], [null, null, "c"]) → []
+	 * 3. subtractToList(["a", "b", "c"], ["a", "b", "c"]) → []
+	 * 4. subtractToList([], ["a", "b", "c"]) → []
+	 * 5. subtractToList(["a", "b", "c"], []) → ["a", "b", "c"]
+	 * 6. subtractToList(["a", "a", "b", "b", "c", "c", "d"], ["b", "c"]) → ["a", "a", "d"]
+	 * 7. subtractToList(["a", null, "b"], ["a", "c"]) → [null, "b"]
+	 * 8. subtractToList(["a", "b", "c"], ["d", "e", "f"]) → ["a", "b", "c"]
+	 * 9. subtractToList(["a", "a", "b", "b", "c"], ["d", "e", "f"]) → ["a", "a", "b", "b", "c"]
+	 * 
+ * + * @param coll1 集合1,需要计算差集的源集合 + * @param coll2 集合2,需要从集合1中排除的元素所在集合 + * @param isLinked 返回的集合类型是否是LinkedList,{@code true}返回{@link LinkedList},{@code false}返回{@link ArrayList} + * @param 元素类型 + * @return 单差集结果。当【集合1】为空时返回空列表;当【集合2】为空时返回集合1的拷贝;否则返回【集合1】中排除【集合2】中所有元素后的结果 + */ + public static List subtractToList(Collection coll1, Collection coll2, boolean isLinked) { if (isEmpty(coll1)) { return ListUtil.empty(); } + if (isEmpty(coll2)) { - return ListUtil.list(true, coll1); + return ListUtil.list(isLinked, coll1); } - //将被交数用链表储存,防止因为频繁扩容影响性能 - final List result = new LinkedList<>(); + /* + 返回的集合最大不会超过 coll1.size + 所以这里创建 ArrayList 时 initialCapacity 给的是 coll1.size + 这样做可以避免频繁扩容 + */ + final List result = isLinked + ? new LinkedList<>() + : new ArrayList<>(coll1.size()); + Set set = new HashSet<>(coll2); for (T t : coll1) { - if (false == set.contains(t)) { + if (!set.contains(t)) { result.add(t); } } + return result; } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index de32a7829..5615a97aa 100755 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -845,6 +845,361 @@ public class CollUtilTest { assertEquals(1L, (long) result.get(0)); } + @Test + public void subtractToListAllNullTest() { + final List list1 = new ArrayList<>(); + list1.add(null); + list1.add(null); + list1.add(null); + list1.add(null); + + final List list2 = new ArrayList<>(); + list2.add(null); + list2.add(null); + + final List result1 = CollUtil.subtractToList(list1, list2); + assertTrue(result1.isEmpty()); + assertNotSame(result1, list1); + + list2.add("c"); + final List result2 = CollUtil.subtractToList(list1, list2); + assertTrue(result2.isEmpty()); + assertNotSame(result2, list1); + } + + @Test + public void subtractToListEmptyTest() { + // 测试第一个集合为空的情况 + final List list1 = Collections.emptyList(); + final List list2 = Arrays.asList("a", "b", "c"); + + // 第一个集合为空时应返回空列表 + final List result1 = CollUtil.subtractToList(list1, list2); + assertTrue(result1.isEmpty()); + + // 测试第二个集合为空的情况 + final List list3 = Arrays.asList("a", "b", "c"); + final List list4 = Collections.emptyList(); + + // 第二个集合为空时应返回第一个集合的拷贝 + final List result2 = CollUtil.subtractToList(list3, list4); + assertEquals(3, result2.size()); + assertEquals(list3, result2); + assertNotSame(list3, result2); + } + + @Test + public void subtractToListDuplicateTest() { + // 测试第一个集合中有重复元素的情况 + final List list1 = Arrays.asList("a", "a", "b", "b", "c", "c", "d"); + final List list2 = Arrays.asList("b", "c"); + + // 应该返回所有不在第二个集合中的元素,包括重复的 + final List result = CollUtil.subtractToList(list1, list2); + assertEquals(3, result.size()); + assertEquals("a", result.get(0)); + assertEquals("a", result.get(1)); + assertEquals("d", result.get(2)); + } + + @Test + public void subtractToListNoCommonElementsTest() { + // 测试集合1和集合2不包含相同元素的情况 + final List list1 = Arrays.asList("a", "b", "c"); + final List list2 = Arrays.asList("d", "e", "f"); + + // 期望结果:返回集合1的完整拷贝 + final List result = CollUtil.subtractToList(list1, list2); + assertEquals(3, result.size()); + assertEquals("a", result.get(0)); + assertEquals("b", result.get(1)); + assertEquals("c", result.get(2)); + assertEquals(list1, result); + assertNotSame(list1, result); // 确保返回的是拷贝而不是原始引用 + + // 测试集合1中有重复元素的情况 + final List list3 = Arrays.asList("a", "a", "b", "b", "c"); + final List list4 = Arrays.asList("d", "e", "f"); + + // 期望结果:返回集合1的完整拷贝,包括重复元素 + final List result2 = CollUtil.subtractToList(list3, list4); + assertEquals(5, result2.size()); + assertEquals("a", result2.get(0)); + assertEquals("a", result2.get(1)); + assertEquals("b", result2.get(2)); + assertEquals("b", result2.get(3)); + assertEquals("c", result2.get(4)); + assertEquals(list3, result2); + assertNotSame(list3, result2); + + // 测试不同类型的元素但确保两个集合的泛型类型一致 + final List list5 = Arrays.asList(1, 2, 3); + final List list6 = Arrays.asList(4, 5, 6); + + // 期望结果:返回集合1的完整拷贝 + final List result3 = CollUtil.subtractToList(list5, list6); + assertEquals(3, result3.size()); + assertEquals(Integer.valueOf(1), result3.get(0)); + assertEquals(Integer.valueOf(2), result3.get(1)); + assertEquals(Integer.valueOf(3), result3.get(2)); + assertEquals(list5, result3); + assertNotSame(list5, result3); + } + + @Test + public void subtractToListWithLinkedTest() { + // 测试指定返回LinkedList的情况 + final List list1 = Arrays.asList(1, 2, 3, 4, 5); + final List list2 = Arrays.asList(2, 4); + + // 使用LinkedList + final List result1 = CollUtil.subtractToList(list1, list2, true); + assertInstanceOf(LinkedList.class, result1); + assertEquals(3, result1.size()); + assertEquals(Arrays.asList(1, 3, 5), result1); + + // 使用ArrayList + final List result2 = CollUtil.subtractToList(list1, list2, false); + assertInstanceOf(ArrayList.class, result2); + assertEquals(3, result2.size()); + assertEquals(Arrays.asList(1, 3, 5), result2); + } + + @Test + public void subtractToListWithNullElementsTest() { + // 测试包含null元素的情况 + final List list1 = new ArrayList<>(); + list1.add("a"); + list1.add(null); + list1.add("b"); + + final List list2 = Arrays.asList("a", "c"); + + // null元素不在list2中,应该保留 + final List result = CollUtil.subtractToList(list1, list2); + assertEquals(2, result.size()); + assertNull(result.get(0)); + assertEquals("b", result.get(1)); + } + + @Test + public void subtractToListLargeCollectionTest() { + // 测试大集合性能 + final int size = 10000; + final List list1 = new ArrayList<>(size); + final List list2 = new ArrayList<>(size / 2); + + // 构建测试数据,list1 size为 10000,元素为 [0, 9999] + for (int i = 0; i < size; i++) { + list1.add(i); + } + + // list2 size 为 5000,元素为 0, 2, 4, 6, 8, ..., 9996, 9998 + for (int i = 0; i < size / 2; i++) { + list2.add(i * 2); // 偶数 + } + + // 记录开始时间 + long startTime = System.currentTimeMillis(); + + // 执行操作 + final List result = CollUtil.subtractToList(list1, list2); + + // 记录结束时间 + long endTime = System.currentTimeMillis(); + + // 验证结果 - 应该只包含奇数 + assertEquals(size / 2, result.size()); + for (Integer num : result) { + assertEquals(1, num % 2); + } + + // 输出性能指标 + System.out.println("Large collection subtractToList took: " + (endTime - startTime) + "ms for " + size + " elements"); + } + + @Test + public void subtractToListPerformanceComparisonTest() { + // 比较不同实现方式的性能 + final int list1Size = 100000, list2Size = 1000; + final List list1 = new ArrayList<>(list1Size); + final List list2 = new ArrayList<>(list2Size); + + // 构建测试数据,list1 size 为 100000 + // 元素为 [0, 99999] + for (int i = 0; i < list1Size; i++) { + list1.add(i); + } + + // [0, 9999] + for (int i = 0; i < list2Size; i++) { + list2.add(i); + } + + // 测试LinkedList性能 + long startTime1 = System.currentTimeMillis(); + final List result1 = CollUtil.subtractToList(list1, list2, true); + long endTime1 = System.currentTimeMillis(); + long linkedListTime = endTime1 - startTime1; + + // 测试ArrayList性能 + long startTime2 = System.currentTimeMillis(); + final List result2 = CollUtil.subtractToList(list1, list2, false); + long endTime2 = System.currentTimeMillis(); + long arrayListTime = endTime2 - startTime2; + + // 验证结果相同 + assertEquals(result1.size(), result2.size()); + assertEquals(list1Size - list2Size, result1.size()); + + // 输出性能比较 + System.out.println("subtractToList performance comparison for " + list1Size + " elements:"); + System.out.println("LinkedList implementation: " + linkedListTime + "ms"); + System.out.println("ArrayList implementation: " + arrayListTime + "ms"); + } + + @Test + public void subtractToListPreservesOrderTest() { + // 测试确保保留原始集合的顺序 + final List list1 = Arrays.asList("c", "a", "d", "b", "e"); + final List list2 = Arrays.asList("a", "e"); + + // 减去后应该保持原顺序:c, d, b + final List result = CollUtil.subtractToList(list1, list2); + assertEquals(3, result.size()); + assertEquals("c", result.get(0)); + assertEquals("d", result.get(1)); + assertEquals("b", result.get(2)); + } + + @Test + public void subtractToListTypeTest() { + // 测试默认返回LinkedList的特性(旧版本特性) + final List list1 = Arrays.asList(1, 2, 3, 4, 5); + final List list2 = Arrays.asList(2, 4); + + // 不指定类型时,旧版本默认使用LinkedList + final List result = CollUtil.subtractToList(list1, list2); + assertInstanceOf(LinkedList.class, result); + assertEquals(3, result.size()); + assertEquals(Arrays.asList(1, 3, 5), result); + } + + @Test + public void subtractToListNullElementsComparisonTest() { + // 测试对null元素处理的一致性 + final ArrayList list1 = new ArrayList<>(); + list1.add(null); + list1.add("a"); + list1.add(null); + list1.add("b"); + + final ArrayList list2 = new ArrayList<>(); + list2.add("a"); + + // 默认调用(旧版行为) + final List result1 = CollUtil.subtractToList(list1, list2); + // 指定LinkedList(模拟旧版) + final List result2 = CollUtil.subtractToList(list1, list2, true); + // 指定ArrayList(新版特性) + final List result3 = CollUtil.subtractToList(list1, list2, false); + + // 验证三种结果一致, 都应该是 [null, null, "b"] + assertEquals(3, result1.size()); + assertEquals(3, result2.size()); + assertEquals(3, result3.size()); + + // 都应该包含null元素和 "b" + assertTrue(result1.contains(null)); + assertTrue(result1.contains("b")); + assertFalse(result1.contains("a")); + + // 旧版实现和新版保持空元素顺序一致性 + assertNull(result1.get(0)); + assertNull(result1.get(1)); + assertEquals("b", result1.get(2)); + + // 结果应该相同 + assertEquals(result1, result2); + assertEquals(result1, result3); + assertEquals(result1.toString(), result3.toString()); + } + + @Test + public void subtractToListWithCustomObjectsTest() { + // 测试自定义对象的情况 + final Person p1 = new Person("张三", 20, "male", 1); + final Person p2 = new Person("李四", 21, "female", 2); + final Person p3 = new Person("王五", 22, "male", 3); + final Person p4 = new Person("赵六", 23, "female", 4); + + final List list1 = Arrays.asList(p1, p2, p3, p4); + final List list2 = Arrays.asList(p2, p4); + + // 减去后应该只剩下p1和p3 + final List result = CollUtil.subtractToList(list1, list2); + assertEquals(2, result.size()); + assertEquals("张三", result.get(0).getName()); + assertEquals(20, result.get(0).getAge()); + assertEquals("male", result.get(0).getGender()); + assertEquals(1, result.get(0).getId()); + + assertEquals("王五", result.get(1).getName()); + assertEquals(22, result.get(1).getAge()); + assertEquals("male", result.get(1).getGender()); + assertEquals(3, result.get(1).getId()); + } + + @Test + public void subtractToListSameObjectsTest() { + // 测试两个集合有完全相同对象的情况 + final List list1 = Arrays.asList("a", "b", "c"); + final List list2 = Arrays.asList("a", "b", "c"); + + // 减去后应该为空列表 + final List result = CollUtil.subtractToList(list1, list2); + assertTrue(result.isEmpty()); + + // 验证结果类型 + assertInstanceOf(LinkedList.class, result); + assertNotSame(result, list1); + } + + @Test + public void subtractToListCollectionImplementationTest() { + // 测试非List集合实现的情况 + final Set set1 = new HashSet<>(Arrays.asList("a", "b", "c", "d")); + final Set set2 = new LinkedHashSet<>(Arrays.asList("b", "d")); + + // 减去后应该只剩下a和c + final List result = CollUtil.subtractToList(set1, set2); + assertEquals(2, result.size()); + assertTrue(result.contains("a")); + assertTrue(result.contains("c")); + + // 验证结果类型 + assertInstanceOf(LinkedList.class, result); + } + + @Test + public void subtractToListConsistencyTest() { + // 测试subtractToList与subtract方法的一致性 + final List list1 = Arrays.asList("a", "b", "c", "d", "e"); + final List list2 = Arrays.asList("b", "d"); + + // 使用subtract方法 + final Collection subtractResult = CollUtil.subtract(list1, list2); + // 使用subtractToList方法 + final List subtractToListResult = CollUtil.subtractToList(list1, list2); + + // 虽然实现类型不同,但内容应该一致 + assertEquals(new HashSet<>(subtractResult), new HashSet<>(subtractToListResult)); + assertEquals(3, subtractToListResult.size()); + assertTrue(subtractToListResult.contains("a")); + assertTrue(subtractToListResult.contains("c")); + assertTrue(subtractToListResult.contains("e")); + } + @Test public void sortComparableTest() { final List of = ListUtil.toList("a", "c", "b"); From 9eda35076f266f38a1ae24cd5fb56de58119a790 Mon Sep 17 00:00:00 2001 From: gqh Date: Mon, 21 Apr 2025 09:53:42 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E6=9B=B4=E6=AD=A3Issue3705Test#wri?= =?UTF-8?q?teTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/cn/hutool/core/text/csv/Issue3705Test.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java index efe05ac34..8763f0ba8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java @@ -1,6 +1,6 @@ package cn.hutool.core.text.csv; -import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -19,8 +19,10 @@ public class Issue3705Test { csvWriter.flush(); } + String lineSeparator = new String(new char[]{CharUtil.CR, CharUtil.LF}); + Assertions.assertEquals( - "\"2024-08-20 14:24:35,\"" + FileUtil.getLineSeparator() + "最后一行", + "\"2024-08-20 14:24:35,\"" + lineSeparator + "最后一行", stringWriter.toString()); } } From ab0f7a06191756f7d23c86da0df0948db6b8cf5e Mon Sep 17 00:00:00 2001 From: gqh Date: Mon, 21 Apr 2025 11:06:04 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=9B=B4=E6=AD=A3FileUtilTest#smbP?= =?UTF-8?q?athTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 0b1de97d1..ca998ec26 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -7,6 +7,8 @@ import cn.hutool.core.util.CharsetUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; import java.io.File; import java.nio.file.Path; @@ -58,6 +60,7 @@ public class FileUtilTest { } @Test + @EnabledOnOs(OS.WINDOWS) public void smbPathTest() { final String smbPath = "\\\\192.168.1.1\\share\\rc-source"; final String parseSmbPath = FileUtil.getAbsolutePath(smbPath);