Compare commits

...

13 Commits

Author SHA1 Message Date
Looly
87a6bdeaaa 优化PropDesc缓存注解判断,提升性能(pr#1335@Gitee) 2025-04-26 13:20:40 +08:00
Looly
3ff79b3f23 !1335 优化BeanUtil.copyToList拷贝较大数据量的性能问题
Merge pull request !1335 from IzayoiYurin/v5-dev
2025-04-26 05:17:24 +00:00
Looly
9015c3ea0a AbstractFtp增加rename方法(issue#IC3PMI@Gitee) 2025-04-26 11:52:17 +08:00
Looly
6d8764eb03 TemplateConfig增加setUseCache方法(issue#IC3JRY@Gitee) 2025-04-26 11:31:22 +08:00
Looly
92d3eaeead fix changelog 2025-04-24 09:18:46 +08:00
Looly
49426fd6ea add test 2025-04-21 19:12:09 +08:00
Looly
fd049cef71 重载subtractToList方法,提供isLinked选项(pr#3923@Github) 2025-04-21 18:22:42 +08:00
Golden Looly
0479a8e831 Merge pull request #3923 from guqihao-7/v5-dev
feat: 重载subtractToList方法,提供isLinked选项
2025-04-21 18:17:41 +08:00
Golden Looly
78b75f4283 Merge branch 'v5-dev' into v5-dev 2025-04-21 18:16:32 +08:00
gqh
ab0f7a0619 feat: 更正FileUtilTest#smbPathTest 2025-04-21 11:06:04 +08:00
gqh
9eda35076f feat: 更正Issue3705Test#writeTest 2025-04-21 09:53:42 +08:00
gqh
27142127b4 feat: 重载subtractToList方法,提供isLinked选项 2025-04-19 23:48:12 +08:00
IzayoiYurin
bff3ddc2f1 优化BeanUtil.copyToList拷贝较大数据量的性能问题 2025-04-16 21:34:54 +08:00
18 changed files with 577 additions and 23 deletions

View File

@@ -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

View File

@@ -181,7 +181,8 @@ public class BeanDesc implements Serializable {
prop.setter = propIgnoreCase.setter;
}
}
// 所有属性完成填充后的初始化逻辑
prop.initialize();
return prop;
}

View File

@@ -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);
}
/**

View File

@@ -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));
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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");

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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
/**

View File

@@ -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客户端对象
*

View File

@@ -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);
}
}
/**
* 获取远程文件
*

View File

@@ -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);

View File

@@ -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;
}
/**
* 资源加载方式枚举
*

View File

@@ -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:

View File

@@ -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()) {