Compare commits

...

11 Commits

Author SHA1 Message Date
Looly
a6b1bebeac 修复ReUtil.replaceAll空指针问题(issue#IDPHVW@Gitee) 2026-02-09 09:14:27 +08:00
Looly
c2abe4c9d3 修复XmlUtil.xmlToBeanoption参数无效问题(issue#4226@Github) 2026-02-09 08:57:14 +08:00
Looly
84a6132c26 修复JschSessionPool回收导致的session未关闭问题(issue#4223@Github) 2026-02-03 15:41:42 +08:00
Looly
b3ec45a0ac 修复TransMap.computeIfAbsentmappingFunction处理不一致问题(issue#IDM6UR@Gitee) 2026-02-01 21:42:56 +08:00
Looly
dca189f700 修复MultiResource游标歧义问题(issue#IDNAOY@Gitee) 2026-02-01 21:32:50 +08:00
Looly
9c35fa9e3f 修复BufferUtilcopy歧义问题(issue#IDN097@Gitee) 2026-02-01 20:19:10 +08:00
Looly
c0bb9675b2 修复MultiResource游标歧义问题(issue#IDNAOY@Gitee) 2026-02-01 20:05:21 +08:00
Looly
7e0199c1a9 AppendableWriter增加checkNotClosed(issue#IDMZ5K@Gitee) 2026-02-01 19:35:00 +08:00
Looly
8d2c5b0449 DateUtil.normalize方法中正则预编译提升效率(pr#4221@Gitee) 2026-02-01 19:28:56 +08:00
Golden Looly
190bdf2585 Merge pull request #4221 from dingqianwen/v5-master
提取并预编译日期时间正则提高性能
2026-02-01 19:26:04 +08:00
qwding
47479e9262 提取并预编译日期时间正则提高性能
提取DateUtil#normalize 方法中 [/.年月] 和 [时分秒] 为静态常量,避免重复编译正则提升效率。
2026-01-31 14:13:48 +08:00
11 changed files with 151 additions and 33 deletions

View File

@@ -1,17 +1,24 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
# 5.8.44(2026-01-27)
# 5.8.44(2026-02-09)
### 🐣新特性
* 【core 】 `NumberUtil.parseNumber`增加支持科学计数法pr#4211@Github
* 【captcha】 `AbstractCaptcha`增加`setStroke`方法支持线条粗细issue#IDJQ15@Gitee
* 【core 】 `BooleanUtil`新增 exactlyOneTrue 方法用于互斥条件校验issue#IDJQ15@Gitee
* 【core 】 `DateUtil.normalize`方法中正则预编译提升效率pr#4221@Gitee
* 【core 】 `AppendableWriter`增加checkNotClosedissue#IDMZ5K@Gitee
### 🐞Bug修复
* 【json 】 修复`JSONUtil.wrap`忽略错误问题issue#4210@Github
* 【http 】 修复`HttpUtil.normalizeParams `在极端输入下抛 StringIndexOutOfBoundsExceptionpr#4216@Github
* 【extra 】 修复`MailAccount.setAuth`参数与field不一致问题issue#4217@Github
* 【core 】 修复`TransMap.computeIfAbsent`mappingFunction处理不一致问题issue#IDM6UR@Gitee
* 【core 】 修复`MultiResource`游标歧义问题issue#IDNAOY@Gitee
* 【core 】 修复`BufferUtil`copy歧义问题issue#IDN097@Gitee
* 【core 】 修复`JschSessionPool`回收导致的session未关闭问题issue#4223@Github
* 【core 】 修复`XmlUtil.xmlToBean`option参数无效问题issue#4226@Github
* 【core 】 修复`ReUtil.replaceAll`空指针问题issue#IDPHVW@Gitee
-------------------------------------------------------------------------------------------------------------
# 5.8.43(2026-01-04)

View File

@@ -25,6 +25,7 @@ import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -44,6 +45,16 @@ public class DateUtil extends CalendarUtil {
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", // 月份
"gmt", "ut", "utc", "est", "edt", "cst", "cdt", "mst", "mdt", "pst", "pdt"// 时间标准
};
/**
* 匹配日期分隔符(正斜杠、点、年、月),用于统一替换为横杠
*/
private static final Pattern DATE_SEPARATOR_PATTERN = Pattern.compile("[/.年月]");
/**
* 匹配时间单位(时、分、秒),用于统一替换为冒号 匹配时间单位(时、分、秒),用于统一替换为冒号
*/
private static final Pattern TIME_UNIT_PATTERN = Pattern.compile("[时分秒]");
/**
* 当前时间,转换为{@link DateTime}对象
@@ -2408,14 +2419,14 @@ public class DateUtil extends CalendarUtil {
final StringBuilder builder = StrUtil.builder();
// 日期部分("\"、"/"、"."、"年"、"月"都替换为"-"
String datePart = dateAndTime.get(0).replaceAll("[/.年月]", "-");
String datePart = DATE_SEPARATOR_PATTERN.matcher(dateAndTime.get(0)).replaceAll("-");
datePart = StrUtil.removeSuffix(datePart, "");
builder.append(datePart);
// 时间部分
if (size == 2) {
builder.append(' ');
String timePart = dateAndTime.get(1).replaceAll("[时分秒]", ":");
String timePart = TIME_UNIT_PATTERN.matcher(dateAndTime.get(1)).replaceAll(":");
timePart = StrUtil.removeSuffix(timePart, ":");
//将ISO8601中的逗号替换为.
timePart = timePart.replace(',', '.');

View File

@@ -66,11 +66,13 @@ public class AppendableWriter extends Writer implements Appendable {
@Override
public void write(final String str) throws IOException {
checkNotClosed();
appendable.append(str);
}
@Override
public void write(final char[] cbuf) throws IOException {
checkNotClosed();
appendable.append(CharBuffer.wrap(cbuf));
}

View File

@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* {@link ByteBuffer} 工具类<br>
@@ -26,7 +27,7 @@ public class BufferUtil {
* @return 新的ByteBuffer
*/
public static ByteBuffer copy(ByteBuffer src, int start, int end) {
return copy(src, ByteBuffer.allocate(end - start));
return ByteBuffer.wrap(Arrays.copyOfRange(src.array(), start, end));
}
/**

View File

@@ -24,7 +24,10 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
private static final long serialVersionUID = 1L;
private final List<Resource> resources;
private int cursor;
/**
* 游标
*/
private int cursor = -1;
/**
* 构造
@@ -50,42 +53,42 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
@Override
public String getName() {
return resources.get(cursor).getName();
return resources.get(getValidCursor()).getName();
}
@Override
public URL getUrl() {
return resources.get(cursor).getUrl();
return resources.get(getValidCursor()).getUrl();
}
@Override
public InputStream getStream() {
return resources.get(cursor).getStream();
return resources.get(getValidCursor()).getStream();
}
@Override
public boolean isModified() {
return resources.get(cursor).isModified();
return resources.get(getValidCursor()).isModified();
}
@Override
public BufferedReader getReader(Charset charset) {
return resources.get(cursor).getReader(charset);
return resources.get(getValidCursor()).getReader(charset);
}
@Override
public String readStr(Charset charset) throws IORuntimeException {
return resources.get(cursor).readStr(charset);
return resources.get(getValidCursor()).readStr(charset);
}
@Override
public String readUtf8Str() throws IORuntimeException {
return resources.get(cursor).readUtf8Str();
return resources.get(getValidCursor()).readUtf8Str();
}
@Override
public byte[] readBytes() throws IORuntimeException {
return resources.get(cursor).readBytes();
return resources.get(getValidCursor()).readBytes();
}
@Override
@@ -95,12 +98,12 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
@Override
public boolean hasNext() {
return cursor < resources.size();
return getValidCursor() < resources.size();
}
@Override
public synchronized Resource next() {
if (cursor >= resources.size()) {
if (!hasNext()) {
throw new ConcurrentModificationException();
}
this.cursor++;
@@ -109,14 +112,14 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
@Override
public void remove() {
this.resources.remove(this.cursor);
this.resources.remove(getValidCursor());
}
/**
* 重置游标
*/
public synchronized void reset() {
this.cursor = 0;
this.cursor = -1;
}
/**
@@ -129,4 +132,12 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
return this;
}
/**
* 获取当前有效游标位置的资源
*
* @return 资源
*/
private int getValidCursor() {
return Math.max(cursor, 0);
}
}

View File

@@ -87,17 +87,17 @@ public abstract class TransMap<K, V> extends MapWrapper<K, V> {
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v)));
return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(k, customValue(v)));
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return super.compute(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v)));
return super.compute(customKey(key), (k, v) -> remappingFunction.apply(k, customValue(v)));
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2)));
return super.merge(customKey(key), customValue(value), remappingFunction);
}
@Override

View File

@@ -4,10 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.comparator.LengthComparator;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.lang.*;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.lang.mutable.MutableObj;
@@ -216,7 +213,7 @@ public class ReUtil {
* @param pattern 编译后的正则模式
* @param content 被匹配的内容
* @param withGroup0 是否包括分组0此分组表示全匹配的信息
* @param findAll 是否查找所有匹配到的内容,{@code false}表示只读取第一个匹配到的内容
* @param findAll 是否查找所有匹配到的内容,{@code false}表示只读取第一个匹配到的内容
* @return 匹配后得到的字符串数组按照分组顺序依次列出未匹配到返回空列表任何一个参数为null返回null
* @since 4.0.13
*/
@@ -234,7 +231,7 @@ public class ReUtil {
result.add(matcher.group(i));
}
if(false == findAll){
if (false == findAll) {
break;
}
}
@@ -885,7 +882,8 @@ public class ReUtil {
String replacement = replacementTemplate;
for (final String var : varNums) {
final int group = Integer.parseInt(var);
replacement = replacement.replace("$" + var, matcher.group(group));
//replacement = replacement.replace("$" + var, matcher.group(group));
replacement = StrUtil.replace(replacement, "$" + var, matcher.group(group));
}
matcher.appendReplacement(sb, escape(replacement));
result = matcher.find();

View File

@@ -1022,7 +1022,7 @@ public class XmlUtil {
final String nodeName = CollUtil.getFirst(map.keySet());
if (simpleName.equalsIgnoreCase(nodeName)) {
// 只有key和bean的名称匹配时才做单一对象转换
return BeanUtil.toBean(map.get(nodeName), bean);
return BeanUtil.toBean(map.get(nodeName), bean, copyOptions);
}
}
return BeanUtil.toBean(map, bean, copyOptions);

View File

@@ -1,13 +1,14 @@
package cn.hutool.core.io;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
/**
* BufferUtil单元测试
@@ -81,4 +82,84 @@ public class BufferUtilTest {
// 再次调用工具类转换,输出结果应该不变
assertEquals(originalText, StrUtil.str(buffer, StandardCharsets.UTF_8));
}
/**
* 测试正常范围内的拷贝功能
*/
@Test
public void copyNormalRangeTest() {
// 准备测试数据
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
// 执行拷贝操作从索引1到4不包含4即拷贝BCD
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 1, 4);
// 验证结果
final byte[] resultArray = new byte[3];
resultBuffer.get(resultArray);
assertArrayEquals(new byte[]{66, 67, 68}, resultArray); // BCD
}
/**
* 测试从开头开始拷贝
*/
@Test
public void copyFromStartTest() {
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
// 从索引0拷贝到3即拷贝ABC
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 0, 3);
final byte[] resultArray = new byte[3];
resultBuffer.get(resultArray);
assertArrayEquals(new byte[]{65, 66, 67}, resultArray); // ABC
}
/**
* 测试拷贝到末尾
*/
@Test
public void copyToEndTest() {
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
// 从索引3拷贝到末尾即拷贝DEF
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 3, 6);
final byte[] resultArray = new byte[3];
resultBuffer.get(resultArray);
assertArrayEquals(new byte[]{68, 69, 70}, resultArray); // DEF
}
/**
* 测试空拷贝start等于end
*/
@Test
public void copyEmptyRangeTest() {
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
// 拷贝相同起始和结束位置,应该得到空数组
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 2, 2);
assertEquals(0, resultBuffer.remaining()); // 应该为空
}
/**
* 测试整个数组的拷贝
*/
@Test
public void copyFullRangeTest() {
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
// 拷贝整个数组
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 0, 6);
final byte[] resultArray = new byte[6];
resultBuffer.get(resultArray);
assertArrayEquals(originalData, resultArray);
}
}

View File

@@ -244,4 +244,10 @@ public class ReUtilTest {
s = ReUtil.get(PatternPool.EMAIL, mail, 0);
assertEquals("a.b@Hutool.cn", s);
}
@Test
void issueIDPHVWTest(){
final String s = ReUtil.replaceAll("2 倾斜摄影成果", "(^\\d+(\\.\\d+)*)(\\s)(((.*?)(DEM|DOM)?)([|\\(](.*?)[|\\)])?$)", "$1$3$5($9)");
assertEquals("2 倾斜摄影成果()", s);
}
}

View File

@@ -1,6 +1,7 @@
package cn.hutool.extra.ssh;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.Session;
@@ -22,7 +23,7 @@ public enum JschSessionPool {
/**
* SSH会话池keyhostvalueSession对象
*/
private final SimpleCache<String, Session> cache = new SimpleCache<>();
private final SimpleCache<String, Session> cache = new SimpleCache<>(new SafeConcurrentHashMap<>());
/**
* 获取Session不存在返回null