mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
Compare commits
13 Commits
a2cc1e1587
...
87a6bdeaaa
Author | SHA1 | Date | |
---|---|---|---|
|
87a6bdeaaa | ||
|
3ff79b3f23 | ||
|
9015c3ea0a | ||
|
6d8764eb03 | ||
|
92d3eaeead | ||
|
49426fd6ea | ||
|
fd049cef71 | ||
|
0479a8e831 | ||
|
78b75f4283 | ||
|
ab0f7a0619 | ||
|
9eda35076f | ||
|
27142127b4 | ||
|
bff3ddc2f1 |
@@ -2,7 +2,7 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.38(2025-04-17)
|
||||
# 5.8.38(2025-04-26)
|
||||
|
||||
### 🐣新特性
|
||||
* 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee)
|
||||
@@ -10,6 +10,10 @@
|
||||
* 【crypto 】 增加`Argon2`类,实现Argon2算法(issue#3890@Github)
|
||||
* 【core 】 `CharSequenceUtil`增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee)
|
||||
* 【core 】 增加分段锁实现`SegmentLock`(pr#1330@Gitee)
|
||||
* 【core 】 重载subtractToList方法,提供isLinked选项(pr#3923@Github)
|
||||
* 【extra 】 `TemplateConfig`增加`setUseCache`方法(issue#IC3JRY@Gitee)
|
||||
* 【extra 】 `AbstractFtp`增加`rename`方法(issue#IC3PMI@Gitee)
|
||||
* 【core 】 优化`PropDesc`缓存注解判断,提升性能(pr#1335@Gitee)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【setting】 修复`Setting`autoLoad可能的加载为空的问题(issue#3919@Github)
|
||||
@@ -22,7 +26,7 @@
|
||||
* 【core 】 `FileWriter`增加方法,可选是否追加换行符(issue#3858@Github)
|
||||
* 【core 】 `IdcardUtil`验证10位身份证兼容中英文括号(issue#IBP6T1@Gitee)
|
||||
* 【extra 】 `PinyinUtil`增加重载可选是否返回声调(pr#3875@Github)
|
||||
* 【http 】 `HttpBase`增加重载可选是否返回声调(pr#3883@Github)
|
||||
* 【extra 】 `PinyinEngine`增加重载可选是否返回声调(pr#3883@Github)
|
||||
* 【core 】 增加`VersionUtil`版本比较工具(pr#3876@Github)
|
||||
* 【db 】 增加GoldenDB识别(pr#3886@Github)
|
||||
* 【http 】 改进`UrlQuery`对无参URL增加判断识别(issue#IBRVE4@Gitee)
|
||||
|
@@ -181,7 +181,8 @@ public class BeanDesc implements Serializable {
|
||||
prop.setter = propIgnoreCase.setter;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有属性完成填充后的初始化逻辑
|
||||
prop.initialize();
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
@@ -36,6 +36,22 @@ public class PropDesc {
|
||||
* Setter方法
|
||||
*/
|
||||
protected Method setter;
|
||||
/**
|
||||
* get方法或字段上有无transient关键字和@Transient注解
|
||||
*/
|
||||
private boolean transientForGet;
|
||||
/**
|
||||
* set方法或字段上有无transient关键字和@Transient注解
|
||||
*/
|
||||
private boolean transientForSet;
|
||||
/**
|
||||
* 检查set方法和字段有无@PropIgnore注解
|
||||
*/
|
||||
private boolean ignoreGet;
|
||||
/**
|
||||
* 检查set方法和字段有无@PropIgnore注解
|
||||
*/
|
||||
private boolean ignoreSet;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
@@ -51,6 +67,21 @@ public class PropDesc {
|
||||
this.setter = ClassUtil.setAccessible(setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在对象的所有属性设置完成后,执行初始化逻辑。
|
||||
* <p>
|
||||
* 预先计算transient关键字和@Transient注解、{@link PropIgnore}注解信息<br>
|
||||
* 见:https://gitee.com/chinabugotech/hutool/pulls/1335
|
||||
*
|
||||
* @since 5.8.38
|
||||
*/
|
||||
public void initialize() {
|
||||
transientForGet = isTransientForGet();
|
||||
transientForSet = isTransientForSet();
|
||||
ignoreGet = isIgnoreGet();
|
||||
ignoreSet = isIgnoreSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名,如果存在Alias注解,读取注解的值作为名称
|
||||
*
|
||||
@@ -137,12 +168,12 @@ public class PropDesc {
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if (checkTransient && isTransientForGet()) {
|
||||
if (checkTransient && transientForGet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查@PropIgnore注解
|
||||
return false == isIgnoreGet();
|
||||
return false == ignoreGet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,12 +238,12 @@ public class PropDesc {
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if (checkTransient && isTransientForSet()) {
|
||||
if (checkTransient && transientForSet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查@PropIgnore注解
|
||||
return false == isIgnoreSet();
|
||||
return false == ignoreSet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +374,7 @@ public class PropDesc {
|
||||
*/
|
||||
private boolean isIgnoreSet() {
|
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
|
||||
|| AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class);
|
||||
|| AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,7 +389,7 @@ public class PropDesc {
|
||||
*/
|
||||
private boolean isIgnoreGet() {
|
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
|
||||
|| AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class);
|
||||
|| AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -9,6 +9,7 @@ import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.lang.func.LambdaUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
@@ -91,6 +92,14 @@ public class CopyOptions implements Serializable {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 快速处理简单值类型的转换
|
||||
if (type instanceof Class){
|
||||
Class<?> targetType = (Class<?>) type;
|
||||
if (ClassUtil.isSimpleValueType(targetType) && targetType.isInstance(value)) {
|
||||
return targetType.cast(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof IJSONTypeConverter) {
|
||||
return ((IJSONTypeConverter) value).toBean(ObjectUtil.defaultIfNull(type, Object.class));
|
||||
}
|
||||
|
@@ -395,22 +395,57 @@ public class CollUtil {
|
||||
* @since 5.3.5
|
||||
*/
|
||||
public static <T> List<T> subtractToList(Collection<T> coll1, Collection<T> coll2) {
|
||||
return subtractToList(coll1, coll2, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算集合的单差集,即只返回【集合1】中有,但是【集合2】中没有的元素
|
||||
* 只要【集合1】中的某个元素在【集合2】中存在(equals和hashcode),就会被排除
|
||||
*
|
||||
* <pre>
|
||||
* 示例:
|
||||
* 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"]
|
||||
* </pre>
|
||||
*
|
||||
* @param coll1 集合1,需要计算差集的源集合
|
||||
* @param coll2 集合2,需要从集合1中排除的元素所在集合
|
||||
* @param isLinked 返回的集合类型是否是LinkedList,{@code true}返回{@link LinkedList},{@code false}返回{@link ArrayList}
|
||||
* @param <T> 元素类型
|
||||
* @return 单差集结果。当【集合1】为空时返回空列表;当【集合2】为空时返回集合1的拷贝;否则返回【集合1】中排除【集合2】中所有元素后的结果
|
||||
*/
|
||||
public static <T> List<T> subtractToList(Collection<T> coll1, Collection<T> coll2, boolean isLinked) {
|
||||
if (isEmpty(coll1)) {
|
||||
return ListUtil.empty();
|
||||
}
|
||||
|
||||
if (isEmpty(coll2)) {
|
||||
return ListUtil.list(true, coll1);
|
||||
return ListUtil.list(isLinked, coll1);
|
||||
}
|
||||
|
||||
//将被交数用链表储存,防止因为频繁扩容影响性能
|
||||
final List<T> result = new LinkedList<>();
|
||||
/*
|
||||
返回的集合最大不会超过 coll1.size
|
||||
所以这里创建 ArrayList 时 initialCapacity 给的是 coll1.size
|
||||
这样做可以避免频繁扩容
|
||||
*/
|
||||
final List<T> result = isLinked
|
||||
? new LinkedList<>()
|
||||
: new ArrayList<>(coll1.size());
|
||||
|
||||
Set<T> set = new HashSet<>(coll2);
|
||||
for (T t : coll1) {
|
||||
if (false == set.contains(t)) {
|
||||
if (!set.contains(t)) {
|
||||
result.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,8 @@ import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.bean.copier.ValueProvider;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.date.StopWatch;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.map.MapBuilder;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
@@ -707,6 +709,37 @@ public class BeanUtilTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void copyLargeListTest(){
|
||||
final SubPerson person = new SubPerson();
|
||||
person.setName("测试A11");
|
||||
person.setAge(14);
|
||||
person.setOpenid("11213232");
|
||||
|
||||
person.setId(UUID.randomUUID());
|
||||
person.setSubName("sub名字");
|
||||
person.setSlow(true);
|
||||
person.setDate(LocalDateTime.now());
|
||||
person.setDate2(LocalDate.now());
|
||||
|
||||
final List<SubPerson> list = new ArrayList<>();
|
||||
CollUtil.padRight(list, 1000, person);
|
||||
|
||||
// 预先构建一次缓存,防止干扰
|
||||
BeanUtil.copyProperties(person, new SubPerson2());
|
||||
// org.springframework.beans.BeanUtils.copyProperties(new SubPerson(), new SubPerson2());
|
||||
|
||||
Console.log("copy bean size: {}\n", list.size());
|
||||
final StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start("BeanUtil#copyToList");
|
||||
List<SubPerson2> copyList = BeanUtil.copyToList(list, SubPerson2.class);
|
||||
// list.forEach(item -> org.springframework.beans.BeanUtils.copyProperties(item, new SubPerson2()));
|
||||
stopWatch.stop();
|
||||
Console.log(stopWatch.prettyPrint());
|
||||
assertEquals(copyList.size(),list.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void toMapTest() {
|
||||
// 测试转map的时候返回key
|
||||
|
@@ -845,6 +845,361 @@ public class CollUtilTest {
|
||||
assertEquals(1L, (long) result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtractToListAllNullTest() {
|
||||
final List<String> list1 = new ArrayList<>();
|
||||
list1.add(null);
|
||||
list1.add(null);
|
||||
list1.add(null);
|
||||
list1.add(null);
|
||||
|
||||
final List<String> list2 = new ArrayList<>();
|
||||
list2.add(null);
|
||||
list2.add(null);
|
||||
|
||||
final List<String> result1 = CollUtil.subtractToList(list1, list2);
|
||||
assertTrue(result1.isEmpty());
|
||||
assertNotSame(result1, list1);
|
||||
|
||||
list2.add("c");
|
||||
final List<String> result2 = CollUtil.subtractToList(list1, list2);
|
||||
assertTrue(result2.isEmpty());
|
||||
assertNotSame(result2, list1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtractToListEmptyTest() {
|
||||
// 测试第一个集合为空的情况
|
||||
final List<String> list1 = Collections.emptyList();
|
||||
final List<String> list2 = Arrays.asList("a", "b", "c");
|
||||
|
||||
// 第一个集合为空时应返回空列表
|
||||
final List<String> result1 = CollUtil.subtractToList(list1, list2);
|
||||
assertTrue(result1.isEmpty());
|
||||
|
||||
// 测试第二个集合为空的情况
|
||||
final List<String> list3 = Arrays.asList("a", "b", "c");
|
||||
final List<String> list4 = Collections.emptyList();
|
||||
|
||||
// 第二个集合为空时应返回第一个集合的拷贝
|
||||
final List<String> result2 = CollUtil.subtractToList(list3, list4);
|
||||
assertEquals(3, result2.size());
|
||||
assertEquals(list3, result2);
|
||||
assertNotSame(list3, result2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtractToListDuplicateTest() {
|
||||
// 测试第一个集合中有重复元素的情况
|
||||
final List<String> list1 = Arrays.asList("a", "a", "b", "b", "c", "c", "d");
|
||||
final List<String> list2 = Arrays.asList("b", "c");
|
||||
|
||||
// 应该返回所有不在第二个集合中的元素,包括重复的
|
||||
final List<String> 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<String> list1 = Arrays.asList("a", "b", "c");
|
||||
final List<String> list2 = Arrays.asList("d", "e", "f");
|
||||
|
||||
// 期望结果:返回集合1的完整拷贝
|
||||
final List<String> 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<String> list3 = Arrays.asList("a", "a", "b", "b", "c");
|
||||
final List<String> list4 = Arrays.asList("d", "e", "f");
|
||||
|
||||
// 期望结果:返回集合1的完整拷贝,包括重复元素
|
||||
final List<String> 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<Integer> list5 = Arrays.asList(1, 2, 3);
|
||||
final List<Integer> list6 = Arrays.asList(4, 5, 6);
|
||||
|
||||
// 期望结果:返回集合1的完整拷贝
|
||||
final List<Integer> 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<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
|
||||
final List<Integer> list2 = Arrays.asList(2, 4);
|
||||
|
||||
// 使用LinkedList
|
||||
final List<Integer> result1 = CollUtil.subtractToList(list1, list2, true);
|
||||
assertInstanceOf(LinkedList.class, result1);
|
||||
assertEquals(3, result1.size());
|
||||
assertEquals(Arrays.asList(1, 3, 5), result1);
|
||||
|
||||
// 使用ArrayList
|
||||
final List<Integer> 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<String> list1 = new ArrayList<>();
|
||||
list1.add("a");
|
||||
list1.add(null);
|
||||
list1.add("b");
|
||||
|
||||
final List<String> list2 = Arrays.asList("a", "c");
|
||||
|
||||
// null元素不在list2中,应该保留
|
||||
final List<String> 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<Integer> list1 = new ArrayList<>(size);
|
||||
final List<Integer> 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<Integer> 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<Integer> list1 = new ArrayList<>(list1Size);
|
||||
final List<Integer> 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<Integer> result1 = CollUtil.subtractToList(list1, list2, true);
|
||||
long endTime1 = System.currentTimeMillis();
|
||||
long linkedListTime = endTime1 - startTime1;
|
||||
|
||||
// 测试ArrayList性能
|
||||
long startTime2 = System.currentTimeMillis();
|
||||
final List<Integer> 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<String> list1 = Arrays.asList("c", "a", "d", "b", "e");
|
||||
final List<String> list2 = Arrays.asList("a", "e");
|
||||
|
||||
// 减去后应该保持原顺序:c, d, b
|
||||
final List<String> 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<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
|
||||
final List<Integer> list2 = Arrays.asList(2, 4);
|
||||
|
||||
// 不指定类型时,旧版本默认使用LinkedList
|
||||
final List<Integer> 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<String> list1 = new ArrayList<>();
|
||||
list1.add(null);
|
||||
list1.add("a");
|
||||
list1.add(null);
|
||||
list1.add("b");
|
||||
|
||||
final ArrayList<String> list2 = new ArrayList<>();
|
||||
list2.add("a");
|
||||
|
||||
// 默认调用(旧版行为)
|
||||
final List<String> result1 = CollUtil.subtractToList(list1, list2);
|
||||
// 指定LinkedList(模拟旧版)
|
||||
final List<String> result2 = CollUtil.subtractToList(list1, list2, true);
|
||||
// 指定ArrayList(新版特性)
|
||||
final List<String> 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<Person> list1 = Arrays.asList(p1, p2, p3, p4);
|
||||
final List<Person> list2 = Arrays.asList(p2, p4);
|
||||
|
||||
// 减去后应该只剩下p1和p3
|
||||
final List<Person> 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<String> list1 = Arrays.asList("a", "b", "c");
|
||||
final List<String> list2 = Arrays.asList("a", "b", "c");
|
||||
|
||||
// 减去后应该为空列表
|
||||
final List<String> result = CollUtil.subtractToList(list1, list2);
|
||||
assertTrue(result.isEmpty());
|
||||
|
||||
// 验证结果类型
|
||||
assertInstanceOf(LinkedList.class, result);
|
||||
assertNotSame(result, list1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtractToListCollectionImplementationTest() {
|
||||
// 测试非List集合实现的情况
|
||||
final Set<String> set1 = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
|
||||
final Set<String> set2 = new LinkedHashSet<>(Arrays.asList("b", "d"));
|
||||
|
||||
// 减去后应该只剩下a和c
|
||||
final List<String> 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<String> list1 = Arrays.asList("a", "b", "c", "d", "e");
|
||||
final List<String> list2 = Arrays.asList("b", "d");
|
||||
|
||||
// 使用subtract方法
|
||||
final Collection<String> subtractResult = CollUtil.subtract(list1, list2);
|
||||
// 使用subtractToList方法
|
||||
final List<String> 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<String> of = ListUtil.toList("a", "c", "b");
|
||||
|
@@ -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);
|
||||
|
@@ -1,15 +1,16 @@
|
||||
package cn.hutool.core.text;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class IssueI96LWHTest {
|
||||
@Test
|
||||
public void replaceTest() {
|
||||
String str = "\uD83D\uDC46最上方点击蓝字";
|
||||
Console.log(str.codePoints().toArray());
|
||||
Console.log(StrUtil.replaceByCodePoint(str, 3, 4, "下"));
|
||||
Console.log(new StringBuilder(str).replace(3, 4, "下"));
|
||||
Assertions.assertArrayEquals(new int[]{128070, 26368, 19978, 26041, 28857, 20987, 34013, 23383}, str.codePoints().toArray());
|
||||
// 这个方法里\uD83D\uDC46表示一个emoji表情,使用codePoint之后,一个表情表示一个字符,因此按照一个字符对
|
||||
Assertions.assertEquals("\uD83D\uDC46最上下点击蓝字", StrUtil.replaceByCodePoint(str, 3, 4, "下"));
|
||||
Assertions.assertEquals("\uD83D\uDC46最下方点击蓝字", new StringBuilder(str).replace(3, 4, "下").toString());
|
||||
}
|
||||
}
|
||||
|
@@ -19,8 +19,6 @@ public class Issue3705Test {
|
||||
}
|
||||
|
||||
// CsvWriteConfig中默认为`\r\n`
|
||||
Assertions.assertEquals(
|
||||
"\"2024-08-20 14:24:35,\"\r\n最后一行",
|
||||
stringWriter.toString());
|
||||
Assertions.assertEquals("\"2024-08-20 14:24:35,\"\r\n最后一行", stringWriter.toString());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,13 @@
|
||||
package cn.hutool.crypto.asymmetric;
|
||||
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class Issue3925Test {
|
||||
@Test
|
||||
void sm2Test() {
|
||||
final SM2 sm2 = new SM2();
|
||||
Assertions.assertThrows(DataLengthException.class, ()->sm2.encrypt("", KeyType.PublicKey));
|
||||
}
|
||||
}
|
@@ -250,6 +250,15 @@ public abstract class AbstractFtp implements Closeable {
|
||||
*/
|
||||
public abstract void recursiveDownloadFolder(String sourcePath, File destDir);
|
||||
|
||||
/**
|
||||
* 重命名文件/目录
|
||||
*
|
||||
* @param from 原路径
|
||||
* @param to 目标路径
|
||||
* @since 5.8.38
|
||||
*/
|
||||
public abstract void rename(String from, String to);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
|
@@ -726,6 +726,17 @@ public class Ftp extends AbstractFtp {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String from, String to) {
|
||||
try {
|
||||
if (!client.rename(from, to)) {
|
||||
throw new FtpException("rename [{}] to [{}] fail", from, to);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取FTPClient客户端对象
|
||||
*
|
||||
|
@@ -647,6 +647,15 @@ public class Sftp extends AbstractFtp {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String from, String to) {
|
||||
try {
|
||||
getClient().rename(from, to);
|
||||
} catch (SftpException e) {
|
||||
throw new JschRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取远程文件
|
||||
*
|
||||
|
@@ -210,6 +210,15 @@ public class SshjSftp extends AbstractFtp {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String from, String to) {
|
||||
try {
|
||||
sftp.rename(from, to);
|
||||
} catch (IOException e) {
|
||||
throw new FtpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IoUtil.close(this.session);
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package cn.hutool.extra.template;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Objects;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* 模板配置
|
||||
*
|
||||
@@ -34,6 +34,11 @@ public class TemplateConfig implements Serializable {
|
||||
*/
|
||||
private Class<? extends TemplateEngine> customEngine;
|
||||
|
||||
/**
|
||||
* 是否使用缓存
|
||||
*/
|
||||
private boolean useCache = true;
|
||||
|
||||
/**
|
||||
* 默认构造,使用UTF8编码,默认从ClassPath获取模板
|
||||
*/
|
||||
@@ -163,6 +168,28 @@ public class TemplateConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用缓存
|
||||
*
|
||||
* @return 是否使用缓存
|
||||
* @since 5.8.38
|
||||
*/
|
||||
public boolean isUseCache() {
|
||||
return useCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否使用缓存
|
||||
*
|
||||
* @param useCache 是否使用缓存
|
||||
* @return this
|
||||
* @since 5.8.38
|
||||
*/
|
||||
public TemplateConfig setUseCache(boolean useCache) {
|
||||
this.useCache = useCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源加载方式枚举
|
||||
*
|
||||
|
@@ -106,6 +106,7 @@ public class ThymeleafEngine implements TemplateEngine {
|
||||
classLoaderResolver.setCharacterEncoding(config.getCharsetStr());
|
||||
classLoaderResolver.setTemplateMode(TemplateMode.HTML);
|
||||
classLoaderResolver.setPrefix(StrUtil.addSuffixIfNot(config.getPath(), "/"));
|
||||
classLoaderResolver.setCacheable(config.isUseCache());
|
||||
resolver = classLoaderResolver;
|
||||
break;
|
||||
case FILE:
|
||||
@@ -113,6 +114,7 @@ public class ThymeleafEngine implements TemplateEngine {
|
||||
fileResolver.setCharacterEncoding(config.getCharsetStr());
|
||||
fileResolver.setTemplateMode(TemplateMode.HTML);
|
||||
fileResolver.setPrefix(StrUtil.addSuffixIfNot(config.getPath(), "/"));
|
||||
fileResolver.setCacheable(config.isUseCache());
|
||||
resolver = fileResolver;
|
||||
break;
|
||||
case WEB_ROOT:
|
||||
@@ -120,6 +122,7 @@ public class ThymeleafEngine implements TemplateEngine {
|
||||
webRootResolver.setCharacterEncoding(config.getCharsetStr());
|
||||
webRootResolver.setTemplateMode(TemplateMode.HTML);
|
||||
webRootResolver.setPrefix(StrUtil.addSuffixIfNot(FileUtil.getAbsolutePath(FileUtil.file(FileUtil.getWebRoot(), config.getPath())), "/"));
|
||||
webRootResolver.setCacheable(config.isUseCache());
|
||||
resolver = webRootResolver;
|
||||
break;
|
||||
case STRING:
|
||||
|
@@ -115,7 +115,10 @@ public class VelocityEngine implements TemplateEngine {
|
||||
final String charsetStr = config.getCharset().toString();
|
||||
ve.setProperty(Velocity.INPUT_ENCODING, charsetStr);
|
||||
// ve.setProperty(Velocity.OUTPUT_ENCODING, charsetStr);
|
||||
ve.setProperty(Velocity.FILE_RESOURCE_LOADER_CACHE, true); // 使用缓存
|
||||
if(config.isUseCache()){
|
||||
// issue#IC3JRY 可定制是否使用缓存
|
||||
ve.setProperty(Velocity.FILE_RESOURCE_LOADER_CACHE, true); // 使用缓存
|
||||
}
|
||||
|
||||
// loader
|
||||
switch (config.getResourceMode()) {
|
||||
|
Reference in New Issue
Block a user