Merge remote-tracking branch 'origin/v5-dev' into v5-dev

This commit is contained in:
achao
2022-02-07 20:55:09 +08:00
53 changed files with 1229 additions and 182 deletions

View File

@@ -2,12 +2,28 @@
# 🚀Changelog # 🚀Changelog
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.21 (2022-01-21) # 5.7.21 (2022-02-07)
### 🐣新特性 ### 🐣新特性
* 【extra 】 增加jetbrick模板支持 * 【extra 】 增加jetbrick模板支持
* 【extra 】 EmojiUtil增加方法pr#519@Gitee
* 【core 】 DateUtil 添加两个日期是否同一周方法pr#516@Gitee
* 【db 】 新增条件组用于处理复杂的where条件pr#514@Gitee
* 【core 】 新增LocalDateTimeUtil.weekOfYearissue#I4RWXC@Gitee
* 【core 】 Month增加toJdkMonth、getValueBaseOne
* 【core 】 CsvWriter修改规则去除末尾多余换行符issue#I4RSQY@Gitee
* 【core 】 DateUtil增加rangeFunc和rangeConsumeissue#I4RSQY@Gitee
* 【core 】 DateTime增加setUseJdkToStringStyle方法
* 【core 】 CharSequenceUtil增加replace重载(issue#2122@Github)
* 【core 】 IntMap和LongMap使用位运算快速求解取余运算(pr#2123@Github)
* 【core 】 新增通用builder类GenericBuilder(pr#526@Gitee)
* 【core 】 新增copySafely方法与mkdirsSafely方法(pr#527@Gitee)
### 🐞Bug修复 ### 🐞Bug修复
* 【core 】 修复ChineseDate农历获取正月出现数组越界BUGissue#2112@Github * 【core 】 修复ChineseDate农历获取正月出现数组越界BUGissue#2112@Github
* 【extra 】 修复EmojiUtil.toHtmlHex()方法pr#519@Gitee
* 【system 】 修复CpuInfo.getUsed()方法issue#2116@Github
* 【dfa 】 修复密集匹配和贪婪匹配冲突问题issue#2126@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.20 (2022-01-20) # 5.7.20 (2022-01-20)

View File

@@ -7,5 +7,5 @@ echo ' / /_/ // / / // __// __ \ / __ \ / / '
echo ' / __ // /_/ // /_ / /_/ // /_/ // / ' echo ' / __ // /_/ // /_ / /_/ // /_/ // / '
echo '/_/ /_/ \____/ \__/ \____/ \____//_/ ' echo '/_/ /_/ \____/ \__/ \____/ \____//_/ '
echo '' echo ''
echo '-----------http://hutool.cn/------------' echo '-----------https://hutool.cn/-----------'
echo '========================================' echo '========================================'

View File

@@ -32,21 +32,21 @@ public class IntMap implements BitMap, Serializable {
@Override @Override
public void add(long i) { public void add(long i) {
int r = (int) (i / BitMap.MACHINE32); int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32); int c = (int) (i & (BitMap.MACHINE32 - 1));
ints[r] = ints[r] | (1 << c); ints[r] = ints[r] | (1 << c);
} }
@Override @Override
public boolean contains(long i) { public boolean contains(long i) {
int r = (int) (i / BitMap.MACHINE32); int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32); int c = (int) (i & (BitMap.MACHINE32 - 1));
return ((ints[r] >>> c) & 1) == 1; return ((ints[r] >>> c) & 1) == 1;
} }
@Override @Override
public void remove(long i) { public void remove(long i) {
int r = (int) (i / BitMap.MACHINE32); int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32); int c = (int) (i & (BitMap.MACHINE32 - 1));
ints[r] &= ~(1 << c); ints[r] &= ~(1 << c);
} }

View File

@@ -32,21 +32,21 @@ public class LongMap implements BitMap, Serializable {
@Override @Override
public void add(long i) { public void add(long i) {
int r = (int) (i / BitMap.MACHINE64); int r = (int) (i / BitMap.MACHINE64);
long c = i % BitMap.MACHINE64; long c = i & (BitMap.MACHINE64 - 1);
longs[r] = longs[r] | (1L << c); longs[r] = longs[r] | (1L << c);
} }
@Override @Override
public boolean contains(long i) { public boolean contains(long i) {
int r = (int) (i / BitMap.MACHINE64); int r = (int) (i / BitMap.MACHINE64);
long c = i % BitMap.MACHINE64; long c = i & (BitMap.MACHINE64 - 1);
return ((longs[r] >>> c) & 1) == 1; return ((longs[r] >>> c) & 1) == 1;
} }
@Override @Override
public void remove(long i) { public void remove(long i) {
int r = (int) (i / BitMap.MACHINE64); int r = (int) (i / BitMap.MACHINE64);
long c = i % BitMap.MACHINE64; long c = i & (BitMap.MACHINE64 - 1);
longs[r] &= ~(1L << c); longs[r] &= ~(1L << c);
} }

View File

@@ -6,8 +6,8 @@ import cn.hutool.core.lang.func.Func0;
/** /**
* Bean属性缓存<br> * Bean属性缓存<br>
* 缓存用于防止多次反射造成的性能问题 * 缓存用于防止多次反射造成的性能问题
* @author Looly
* *
* @author Looly
*/ */
public enum BeanDescCache { public enum BeanDescCache {
INSTANCE; INSTANCE;
@@ -16,12 +16,22 @@ public enum BeanDescCache {
/** /**
* 获得属性名和{@link BeanDesc}Map映射 * 获得属性名和{@link BeanDesc}Map映射
*
* @param beanClass Bean的类 * @param beanClass Bean的类
* @param supplier 对象不存在时创建对象的函数 * @param supplier 对象不存在时创建对象的函数
* @return 属性名和{@link BeanDesc}映射 * @return 属性名和{@link BeanDesc}映射
* @since 5.4.2 * @since 5.4.2
*/ */
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier){ public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier) {
return bdCache.get(beanClass, supplier); return bdCache.get(beanClass, supplier);
} }
/**
* 清空全局的Bean属性缓存
*
* @since 5.7.21
*/
public void clear() {
this.bdCache.clear();
}
} }

View File

@@ -56,6 +56,16 @@ public enum BeanInfoCache {
getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap); getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap);
} }
/**
* 清空缓存
*
* @since 5.7.21
*/
public void clear() {
this.pdCache.clear();
this.ignoreCasePdCache.clear();
}
/** /**
* 根据是否忽略字段名的大小写返回不用Cache对象 * 根据是否忽略字段名的大小写返回不用Cache对象
* *

View File

@@ -0,0 +1,208 @@
package cn.hutool.core.builder;
import cn.hutool.core.lang.func.Supplier1;
import cn.hutool.core.lang.func.Supplier2;
import cn.hutool.core.lang.func.Supplier3;
import cn.hutool.core.lang.func.Supplier4;
import cn.hutool.core.lang.func.Supplier5;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* <p>通用Builder</p>
* 参考: <a href="https://blog.csdn.net/weixin_43935907/article/details/105003719">一看就会的java8通用Builder</a>
* <p>使用方法如下:</p>
* <pre>
* Box box = GenericBuilder
* .of(Box::new)
* .with(Box::setId, 1024L)
* .with(Box::setTitle, "Hello World!")
* .with(Box::setLength, 9)
* .with(Box::setWidth, 8)
* .with(Box::setHeight, 7)
* .build();
*
* </pre>
*
* <p> 我们也可以对已创建的对象进行修改:</p>
* <pre>
* Box boxModified = GenericBuilder
* .of(() -&gt; box)
* .with(Box::setTitle, "Hello Friend!")
* .with(Box::setLength, 3)
* .with(Box::setWidth, 4)
* .with(Box::setHeight, 5)
* .build();
* </pre>
* <p> 我们还可以对这样调用有参构造,这对于创建一些在有参构造中包含初始化函数的对象是有意义的:</p>
* <pre>
* Box box1 = GenericBuilder
* .of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
* .with(Box::alis)
* .build();
* </pre>
* <p>注意本工具类支持调用的方法的参数数量不超过1个更多的参数不利于阅读和维护。</p>
*
* @author TomXin
* @since 5.7.21
*/
public class GenericBuilder<T> implements Builder<T> {
/**
* 实例化器
*/
private final Supplier<T> instant;
/**
* 修改器列表
*/
private final List<Consumer<T>> modifiers = new ArrayList<>();
/**
* 构造
*
* @param instant 实例化器
*/
public GenericBuilder(Supplier<T> instant) {
this.instant = instant;
}
/**
* 通过无参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param <T> 目标类型
* @return GenericBuilder对象
*/
public static <T> GenericBuilder<T> of(Supplier<T> instant) {
return new GenericBuilder<>(instant);
}
/**
* 通过1参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param <T> 目标类型
* @param <P1> 参数一类型
* @return GenericBuilder对象
*/
public static <T, P1> GenericBuilder<T> of(Supplier1<T, P1> instant, P1 p1) {
return of(instant.toSupplier(p1));
}
/**
* 通过2参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @return GenericBuilder对象
*/
public static <T, P1, P2> GenericBuilder<T> of(Supplier2<T, P1, P2> instant, P1 p1, P2 p2) {
return of(instant.toSupplier(p1, p2));
}
/**
* 通过3参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @return GenericBuilder对象
*/
public static <T, P1, P2, P3> GenericBuilder<T> of(Supplier3<T, P1, P2, P3> instant, P1 p1, P2 p2, P3 p3) {
return of(instant.toSupplier(p1, p2, p3));
}
/**
* 通过4参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @param <P4> 参数四类型
* @return GenericBuilder对象
*/
public static <T, P1, P2, P3, P4> GenericBuilder<T> of(Supplier4<T, P1, P2, P3, P4> instant, P1 p1, P2 p2, P3 p3, P4 p4) {
return of(instant.toSupplier(p1, p2, p3, p4));
}
/**
* 通过5参数实例化器创建GenericBuilder
*
* @param instant 实例化器
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param p5 参数五
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @param <P4> 参数四类型
* @param <P5> 参数五类型
* @return GenericBuilder对象
*/
public static <T, P1, P2, P3, P4, P5> GenericBuilder<T> of(Supplier5<T, P1, P2, P3, P4, P5> instant, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
return of(instant.toSupplier(p1, p2, p3, p4, p5));
}
/**
* 调用无参数方法
*
* @param consumer 无参数Consumer
* @return GenericBuilder对象
*/
public GenericBuilder<T> with(Consumer<T> consumer) {
modifiers.add(consumer);
return this;
}
/**
* 调用1参数方法
*
* @param <P1> 参数一类型
* @param consumer 1参数Consumer一般为Setter方法引用
* @param p1 参数一
* @return GenericBuilder对象
*/
public <P1> GenericBuilder<T> with(BiConsumer<T, P1> consumer, P1 p1) {
modifiers.add(instant -> consumer.accept(instant, p1));
return this;
}
/**
* 构建
*
* @return 目标对象
*/
@Override
public T build() {
T value = instant.get();
modifiers.forEach(modifier -> modifier.accept(value));
modifiers.clear();
return value;
}
}

View File

@@ -132,7 +132,7 @@ public class CalendarUtil {
} }
/** /**
* 获取秒级别的开始时间,即忽略毫秒部分 * 修改秒级别的开始时间,即忽略毫秒部分
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -143,7 +143,7 @@ public class CalendarUtil {
} }
/** /**
* 获取秒级别的结束时间即毫秒设置为999 * 修改秒级别的结束时间即毫秒设置为999
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -154,7 +154,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某小时的开始时间 * 修改某小时的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -164,7 +164,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某小时的结束时间 * 修改某小时的结束时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -174,7 +174,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某分钟的开始时间 * 修改某分钟的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -184,7 +184,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某分钟的结束时间 * 修改某分钟的结束时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -194,7 +194,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某天的开始时间 * 修改某天的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -204,7 +204,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某天的结束时间 * 修改某天的结束时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -214,7 +214,7 @@ public class CalendarUtil {
} }
/** /**
* 获取给定日期当前周的开始时间,周一定为一周的开始时间 * 修改给定日期当前周的开始时间,周一定为一周的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -224,7 +224,7 @@ public class CalendarUtil {
} }
/** /**
* 获取给定日期当前周的开始时间 * 修改给定日期当前周的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @param isMondayAsFirstDay 是否周一做为一周的第一天false表示周日做为第一天 * @param isMondayAsFirstDay 是否周一做为一周的第一天false表示周日做为第一天
@@ -238,7 +238,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某周的结束时间,周日定为一周的结束 * 修改某周的结束时间,周日定为一周的结束
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -248,7 +248,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某周的结束时间 * 修改某周的结束时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @param isSundayAsLastDay 是否周日做为一周的最后一天false表示周六做为最后一天 * @param isSundayAsLastDay 是否周日做为一周的最后一天false表示周六做为最后一天
@@ -261,7 +261,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某月的开始时间 * 修改某月的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -271,7 +271,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某月的结束时间 * 修改某月的结束时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -281,7 +281,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某季度的开始时间 * 修改某季度的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -313,7 +313,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某年的开始时间 * 修改某年的开始时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -323,7 +323,7 @@ public class CalendarUtil {
} }
/** /**
* 获取某年的结束时间 * 修改某年的结束时间
* *
* @param calendar 日期 {@link Calendar} * @param calendar 日期 {@link Calendar}
* @return {@link Calendar} * @return {@link Calendar}
@@ -348,6 +348,40 @@ public class CalendarUtil {
cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA);
} }
/**
* 比较两个日期是否为同一周
*
* @param cal1 日期1
* @param cal2 日期2
* @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日
* @return 是否为同一周
* @since 5.7.21
*/
public static boolean isSameWeek(Calendar cal1, Calendar cal2, boolean isMon) {
if (cal1 == null || cal2 == null) {
throw new IllegalArgumentException("The date must not be null");
}
// 防止比较前修改原始Calendar对象
cal1 = (Calendar) cal1.clone();
cal2 = (Calendar) cal2.clone();
// 把所传日期设置为其当前周的第一天
// 比较设置后的两个日期是否是同一天true 代表同一周
if (isMon) {
cal1.setFirstDayOfWeek(Calendar.MONDAY);
cal1.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
cal2.setFirstDayOfWeek(Calendar.MONDAY);
cal2.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
} else {
cal1.setFirstDayOfWeek(Calendar.SUNDAY);
cal1.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
cal2.setFirstDayOfWeek(Calendar.SUNDAY);
cal2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
}
return isSameDay(cal1, cal2);
}
/** /**
* 比较两个日期是否为同一月 * 比较两个日期是否为同一月
* *

View File

@@ -24,13 +24,30 @@ import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
* 包装java.util.Date * 包装{@link Date}<br>
* 此类继承了{@link Date},并提供扩展方法,如时区等。<br>
* 此类重写了父类的{@code toString()}方法,返回值为"yyyy-MM-dd HH:mm:ss"格式
* *
* @author xiaoleilu * @author xiaoleilu
*/ */
public class DateTime extends Date { public class DateTime extends Date {
private static final long serialVersionUID = -5395712593979185936L; private static final long serialVersionUID = -5395712593979185936L;
private static boolean useJdkToStringStyle = false;
/**
* 设置全局的,是否使用{@link Date}默认的toString()格式<br>
* 如果为{@code true}则调用toString()时返回"EEE MMM dd HH:mm:ss zzz yyyy"格式,<br>
* 如果为{@code false},则返回"yyyy-MM-dd HH:mm:ss"<br>
* 默认为{@code false}
*
* @param customUseJdkToStringStyle 是否使用{@link Date}默认的toString()格式
* @since 5.7.21
*/
public static void setUseJdkToStringStyle(boolean customUseJdkToStringStyle){
useJdkToStringStyle = customUseJdkToStringStyle;
}
/** /**
* 是否可变对象 * 是否可变对象
*/ */
@@ -932,13 +949,18 @@ public class DateTime extends Date {
// -------------------------------------------------------------------- toString start // -------------------------------------------------------------------- toString start
/** /**
* 转为"yyyy-MM-dd HH:mm:ss" 格式字符串<br> * 转为字符串,如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区<br>
* 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区 * 可以调用{@link DateTime#setUseJdkToStringStyle(boolean)} 方法自定义默认的风格<br>
* 如果{@link #useJdkToStringStyle}为{@code true},返回"EEE MMM dd HH:mm:ss zzz yyyy"格式,<br>
* 如果为{@code false},则返回"yyyy-MM-dd HH:mm:ss"
* *
* @return "yyyy-MM-dd HH:mm:ss" 格式字符串 * @return 格式字符串
*/ */
@Override @Override
public String toString() { public String toString() {
if(useJdkToStringStyle){
return super.toString();
}
return toString(this.timeZone); return toString(this.timeZone);
} }

View File

@@ -20,7 +20,9 @@ import java.time.LocalDateTime;
import java.time.Year; import java.time.Year;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@@ -28,6 +30,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/** /**
* 时间工具类 * 时间工具类
@@ -1586,6 +1590,21 @@ public class DateUtil extends CalendarUtil {
return CalendarUtil.isSameDay(calendar(date1), calendar(date2)); return CalendarUtil.isSameDay(calendar(date1), calendar(date2));
} }
/**
* 比较两个日期是否为同一周
*
* @param date1 日期1
* @param date2 日期2
* @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日
* @return 是否为同一周
*/
public static boolean isSameWeek(final Date date1, final Date date2, boolean isMon) {
if (date1 == null || date2 == null) {
throw new IllegalArgumentException("The date must not be null");
}
return CalendarUtil.isSameWeek(calendar(date1), calendar(date2), isMon);
}
/** /**
* 比较两个日期是否为同一月 * 比较两个日期是否为同一月
* *
@@ -1870,6 +1889,44 @@ public class DateUtil extends CalendarUtil {
return new DateRange(start, end, unit); return new DateRange(start, end, unit);
} }
/**
* 按日期范围遍历,执行 function
*
* @param start 起始日期时间(包括)
* @param end 结束日期时间
* @param unit 步进单位
* @param func 每次遍历要执行的 function
* @param <T> Date经过函数处理结果类型
* @return 结果列表
* @since 5.7.21
*/
public static <T> List<T> rangeFunc(Date start, Date end, final DateField unit, Function<Date, T> func) {
if (start == null || end == null || start.after(end)) {
return Collections.emptyList();
}
ArrayList<T> list = new ArrayList<>();
for(DateTime date : range(start, end, unit)){
list.add(func.apply(date));
}
return list;
}
/**
* 按日期范围遍历,执行 consumer
*
* @param start 起始日期时间(包括)
* @param end 结束日期时间
* @param unit 步进单位
* @param consumer 每次遍历要执行的 consumer
* @since 5.7.21
*/
public static void rangeConsume(Date start, Date end, final DateField unit, Consumer<Date> consumer) {
if (start == null || end == null || start.after(end)) {
return;
}
range(start, end, unit).forEach(consumer);
}
/** /**
* 创建日期范围生成器 * 创建日期范围生成器
* *
@@ -1878,7 +1935,7 @@ public class DateUtil extends CalendarUtil {
* @param unit 步进单位 * @param unit 步进单位
* @return {@link DateRange} * @return {@link DateRange}
*/ */
public static List<DateTime> rangeToList(Date start, Date end, final DateField unit) { public static List<DateTime> rangeToList(Date start, Date end, DateField unit) {
return CollUtil.newArrayList((Iterable<DateTime>) range(start, end, unit)); return CollUtil.newArrayList((Iterable<DateTime>) range(start, end, unit));
} }

View File

@@ -22,6 +22,7 @@ import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal; import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.time.temporal.WeekFields;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
@@ -562,4 +563,21 @@ public class LocalDateTimeUtil {
return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime); return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime);
} }
/**
* 获得指定日期是所在年份的第几周,如:
* <ul>
* <li>如果一年的第一天是星期一,则第一周从第一天开始,没有零周</li>
* <li>如果一年的第二天是星期一,则第一周从第二天开始,而第一天在零周</li>
* <li>如果一年中的第4天是星期一则第1周从第4周开始第1至第3周在零周开始</li>
* <li>如果一年中的第5天是星期一则第二周从第5周开始第1至第4周在第1周</li>
* </ul>
*
*
* @param date 日期({@link LocalDate} 或者 {@link LocalDateTime}等)
* @return 所在年的第几周
* @since 5.7.21
*/
public static int weekOfYear(TemporalAccessor date){
return TemporalAccessorUtil.get(date, WeekFields.ISO.weekOfYear());
}
} }

View File

@@ -1,5 +1,7 @@
package cn.hutool.core.date; package cn.hutool.core.date;
import cn.hutool.core.lang.Assert;
import java.util.Calendar; import java.util.Calendar;
/** /**
@@ -93,14 +95,27 @@ public enum Month {
} }
/** /**
* 获取{@link Calendar}中的对应值 * 获取{@link Calendar}中的对应值<br>
* 此值从0开始即0表示一月
* *
* @return {@link Calendar}中的对应 * @return {@link Calendar}中的对应月份值从0开始计数
*/ */
public int getValue() { public int getValue() {
return this.value; return this.value;
} }
/**
* 获取月份值,此值与{@link java.time.Month}对应<br>
* 此值从1开始即1表示一月
*
* @return 月份值,对应{@link java.time.Month}从1开始计数
* @since 5.7.21
*/
public int getValueBaseOne() {
Assert.isFalse(this == UNDECIMBER, "Unsupported UNDECIMBER Field");
return getValue() + 1;
}
/** /**
* 获取此月份最后一天的值不支持的月份例如UNDECIMBER返回-1 * 获取此月份最后一天的值不支持的月份例如UNDECIMBER返回-1
* *
@@ -165,17 +180,28 @@ public enum Month {
/** /**
* 获得指定月的最后一天 * 获得指定月的最后一天
*
* @param month 月份从0开始 * @param month 月份从0开始
* @param isLeapYear 是否为闰年,闰年只对二月有影响 * @param isLeapYear 是否为闰年,闰年只对二月有影响
* @return 最后一天可能为28,29,30,31 * @return 最后一天可能为28,29,30,31
* @since 5.4.7 * @since 5.4.7
*/ */
public static int getLastDay(int month, boolean isLeapYear){ public static int getLastDay(int month, boolean isLeapYear) {
int lastDay = DAYS_OF_MONTH[month]; int lastDay = DAYS_OF_MONTH[month];
if (isLeapYear && Calendar.FEBRUARY == month){ if (isLeapYear && Calendar.FEBRUARY == month) {
// 二月 // 二月
lastDay += 1; lastDay += 1;
} }
return lastDay; return lastDay;
} }
/**
* 转换为{@link java.time.Month}
*
* @return {@link java.time.Month}
* @since 5.7.21
*/
public java.time.Month toJdkMonth() {
return java.time.Month.of(getValueBaseOne());
}
} }

View File

@@ -51,7 +51,7 @@ public class CheckedUtil {
* @param expression Lambda表达式 * @param expression Lambda表达式
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型 * @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func * @return {@link FuncRt}
*/ */
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression) { public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression) {
return uncheck(expression, new RuntimeException()); return uncheck(expression, new RuntimeException());
@@ -63,7 +63,7 @@ public class CheckedUtil {
* *
* @param expression 运行时传入的参数类型 * @param expression 运行时传入的参数类型
* @param <R> 最终返回的数据类型 * @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func0 * @return {@link Func0Rt}
*/ */
public static <R> Func0Rt<R> uncheck(Func0<R> expression) { public static <R> Func0Rt<R> uncheck(Func0<R> expression) {
return uncheck(expression, new RuntimeException()); return uncheck(expression, new RuntimeException());
@@ -76,7 +76,7 @@ public class CheckedUtil {
* @param expression 运行时传入的参数类型 * @param expression 运行时传入的参数类型
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型 * @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func1 * @return {@link Func1Rt}
*/ */
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression) { public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression) {
return uncheck(expression, new RuntimeException()); return uncheck(expression, new RuntimeException());
@@ -89,7 +89,7 @@ public class CheckedUtil {
* *
* @param expression 运行时传入的参数类型 * @param expression 运行时传入的参数类型
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc * @return {@link VoidFuncRt}
*/ */
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression) { public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression) {
return uncheck(expression, new RuntimeException()); return uncheck(expression, new RuntimeException());
@@ -100,7 +100,7 @@ public class CheckedUtil {
* 如此一来代码中就不用显示的try-catch转化成运行时异常 * 如此一来代码中就不用显示的try-catch转化成运行时异常
* *
* @param expression 运行时传入的参数类型 * @param expression 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc0 * @return {@link VoidFunc0Rt}
*/ */
public static VoidFunc0Rt uncheck(VoidFunc0 expression) { public static VoidFunc0Rt uncheck(VoidFunc0 expression) {
return uncheck(expression, new RuntimeException()); return uncheck(expression, new RuntimeException());
@@ -112,7 +112,7 @@ public class CheckedUtil {
* *
* @param expression 运行时传入的参数类型 * @param expression 运行时传入的参数类型
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc1 * @return {@link VoidFunc1Rt}
*/ */
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression) { public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression) {
return uncheck(expression, new RuntimeException()); return uncheck(expression, new RuntimeException());
@@ -127,7 +127,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常 * @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型 * @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func * @return {@link FuncRt}
*/ */
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression, RuntimeException rte) { public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null"); Objects.requireNonNull(expression, "expression can not be null");
@@ -152,7 +152,7 @@ public class CheckedUtil {
* @param expression Lambda表达式 * @param expression Lambda表达式
* @param rte 期望抛出的运行时异常 * @param rte 期望抛出的运行时异常
* @param <R> 最终返回的数据类型 * @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func0 * @return {@link Func0Rt}
*/ */
public static <R> Func0Rt<R> uncheck(Func0<R> expression, RuntimeException rte) { public static <R> Func0Rt<R> uncheck(Func0<R> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null"); Objects.requireNonNull(expression, "expression can not be null");
@@ -178,7 +178,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常 * @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型 * @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func1 * @return {@link Func1Rt}
*/ */
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression, RuntimeException rte) { public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null"); Objects.requireNonNull(expression, "expression can not be null");
@@ -203,7 +203,7 @@ public class CheckedUtil {
* @param expression Lambda表达式 * @param expression Lambda表达式
* @param rte 期望抛出的运行时异常 * @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc * @return {@link VoidFuncRt}
*/ */
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression, RuntimeException rte) { public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null"); Objects.requireNonNull(expression, "expression can not be null");
@@ -228,7 +228,7 @@ public class CheckedUtil {
* *
* @param expression Lambda表达式 * @param expression Lambda表达式
* @param rte 期望抛出的运行时异常 * @param rte 期望抛出的运行时异常
* @return cn.hutool.core.lang.func.VoidFunc0 * @return {@link VoidFunc0Rt}
*/ */
public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) { public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null"); Objects.requireNonNull(expression, "expression can not be null");
@@ -254,7 +254,7 @@ public class CheckedUtil {
* @param expression Lambda表达式 * @param expression Lambda表达式
* @param rte 期望抛出的运行时异常 * @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型 * @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc1 * @return {@link VoidFunc1Rt}
*/ */
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression, RuntimeException rte) { public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null"); Objects.requireNonNull(expression, "expression can not be null");

View File

@@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.unit.DataSizeUtil; import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
@@ -526,24 +527,39 @@ public class FileUtil extends PathUtil {
/** /**
* 计算目录或文件的总大小<br> * 计算目录或文件的总大小<br>
* 当给定对象为文件时,直接调用 {@link File#length()}<br> * 当给定对象为文件时,直接调用 {@link File#length()}<br>
* 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回 * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回<br>
* 此方法不包括目录本身的占用空间大小。
* *
* @param file 目录或文件,null或者文件不存在返回0 * @param file 目录或文件,null或者文件不存在返回0
* @return 总大小bytes长度 * @return 总大小bytes长度
*/ */
public static long size(File file) { public static long size(File file) {
return size(file, false);
}
/**
* 计算目录或文件的总大小<br>
* 当给定对象为文件时,直接调用 {@link File#length()}<br>
* 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
*
* @param file 目录或文件,null或者文件不存在返回0
* @param includeDirSize 是否包括每层目录本身的大小
* @return 总大小bytes长度
* @since 5.7.21
*/
public static long size(File file, boolean includeDirSize) {
if (null == file || false == file.exists() || isSymlink(file)) { if (null == file || false == file.exists() || isSymlink(file)) {
return 0; return 0;
} }
if (file.isDirectory()) { if (file.isDirectory()) {
long size = 0L; long size = includeDirSize ? file.length() : 0;
File[] subFiles = file.listFiles(); File[] subFiles = file.listFiles();
if (ArrayUtil.isEmpty(subFiles)) { if (ArrayUtil.isEmpty(subFiles)) {
return 0L;// empty directory return 0L;// empty directory
} }
for (File subFile : subFiles) { for (File subFile : subFiles) {
size += size(subFile); size += size(subFile, includeDirSize);
} }
return size; return size;
} else { } else {
@@ -811,7 +827,7 @@ public class FileUtil extends PathUtil {
/** /**
* 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹<br> * 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹<br>
* 此方法不对File对象类型做判断如果File不存在无法判断其类型 * 此方法不对File对象类型做判断如果File不存在无法判断其类型<br>
* *
* @param dir 目录 * @param dir 目录
* @return 创建的目录 * @return 创建的目录
@@ -821,12 +837,48 @@ public class FileUtil extends PathUtil {
return null; return null;
} }
if (false == dir.exists()) { if (false == dir.exists()) {
//noinspection ResultOfMethodCallIgnored mkdirsSafely(dir, 5, 1);
dir.mkdirs();
} }
return dir; return dir;
} }
/**
* 安全地级联创建目录 (确保并发环境下能创建成功)
*
* <pre>
* 并发环境下,假设 test 目录不存在如果线程A mkdirs "test/A" 目录线程B mkdirs "test/B"目录,
* 其中一个线程可能会失败,进而导致以下代码抛出 FileNotFoundException 异常
*
* file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中,返回 false
* file.createNewFile(); // 抛出 IO 异常,因为该线程无法感知到父目录已被创建
* </pre>
*
* @param dir 待创建的目录
* @param tryCount 最大尝试次数
* @param sleepMillis 线程等待的毫秒数
* @return true表示创建成功false表示创建失败
* @since 5.7.21
* @author z8g
*/
public static boolean mkdirsSafely(File dir, int tryCount, long sleepMillis) {
if (dir == null) {
return false;
}
if (dir.isDirectory()) {
return true;
}
for (int i = 1; i <= tryCount; i++) { // 高并发场景下,可以看到 i 处于 1 ~ 3 之间
// 如果文件已存在,也会返回 false所以该值不能作为是否能创建的依据因此不对其进行处理
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
if (dir.exists()) {
return true;
}
ThreadUtil.sleep(sleepMillis);
}
return dir.exists();
}
/** /**
* 创建临时文件<br> * 创建临时文件<br>
* 创建后的文件名为 prefix[Randon].tmp * 创建后的文件名为 prefix[Randon].tmp

View File

@@ -87,12 +87,49 @@ public class NioUtil {
Assert.notNull(outChannel, "Out channel is null!"); Assert.notNull(outChannel, "Out channel is null!");
try { try {
return inChannel.transferTo(0, inChannel.size(), outChannel); return copySafely(inChannel, outChannel);
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
} }
/**
* 文件拷贝实现
*
* <pre>
* FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
* 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
* 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes不处理将导致文件内容缺失
*
* // 错误写法dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
* try (InputStream srcStream = zipFile.getInputStream(zipEntry);
* ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
* FileOutputStream fos = new FileOutputStream(saveFile);
* FileChannel dstChannel = fos.getChannel()) {
* dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
* }
* </pre>
*
* @param inChannel 输入通道
* @param outChannel 输出通道
* @return 输入通道的字节数
* @throws IOException 发生IO错误
* @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c
* @author z8g
* @since 5.7.21
*/
private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException {
final long totalBytes = inChannel.size();
for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失
final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数
pos += writeBytes;
remaining -= writeBytes;
}
return totalBytes;
}
/** /**
* 拷贝流使用NIO不会关闭channel * 拷贝流使用NIO不会关闭channel
* *

View File

@@ -0,0 +1,32 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 1参数Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier1<T, P1> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @return 目标对象
*/
T get(P1 p1);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1) {
return () -> get(p1);
}
}

View File

@@ -0,0 +1,36 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 两个参数的Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @param <P2> 参数二 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier2<T, P1, P2> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @return 目标对象
*/
T get(P1 p1, P2 p2);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2) {
return () -> get(p1, p2);
}
}

View File

@@ -0,0 +1,39 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 3参数Supplier
*
* @param <T> 目标类型
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier3<T, P1, P2, P3> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @return 目标对象
*/
T get(P1 p1, P2 p2, P3 p3);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @param p3 参数3
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3) {
return () -> get(p1, p2, p3);
}
}

View File

@@ -0,0 +1,42 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 4参数Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @param <P2> 参数二 类型
* @param <P3> 参数三 类型
* @param <P4> 参数四 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier4<T, P1, P2, P3, P4> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @return 目标对象
*/
T get(P1 p1, P2 p2, P3 p3, P4 p4);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @param p3 参数3
* @param p4 参数4
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3, P4 p4) {
return () -> get(p1, p2, p3, p4);
}
}

View File

@@ -0,0 +1,45 @@
package cn.hutool.core.lang.func;
import java.util.function.Supplier;
/**
* 5参数Supplier
*
* @param <T> 目标 类型
* @param <P1> 参数一 类型
* @param <P2> 参数二 类型
* @param <P3> 参数三 类型
* @param <P4> 参数四 类型
* @param <P5> 参数五 类型
* @author TomXin
* @since 5.7.21
*/
@FunctionalInterface
public interface Supplier5<T, P1, P2, P3, P4, P5> {
/**
* 生成实例的方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
* @param p4 参数四
* @param p5 参数五
* @return 目标对象
*/
T get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5);
/**
* 将带有参数的Supplier转换为无参{@link Supplier}
*
* @param p1 参数1
* @param p2 参数2
* @param p3 参数3
* @param p4 参数4
* @param p5 参数5
* @return {@link Supplier}
*/
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
return () -> get(p1, p2, p3, p4, p5);
}
}

View File

@@ -3601,6 +3601,46 @@ public class CharSequenceUtil {
return stringBuilder.toString(); return stringBuilder.toString();
} }
/**
* 替换指定字符串的指定区间内字符为指定字符串,字符串只重复一次<br>
* 此方法使用{@link String#codePoints()}完成拆分替换
*
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @param replacedStr 被替换的字符串
* @return 替换后的字符串
* @since 3.2.1
*/
public static String replace(CharSequence str, int startInclude, int endExclude, CharSequence replacedStr) {
if (isEmpty(str)) {
return str(str);
}
final String originalStr = str(str);
int[] strCodePoints = originalStr.codePoints().toArray();
final int strLength = strCodePoints.length;
if (startInclude > strLength) {
return originalStr;
}
if (endExclude > strLength) {
endExclude = strLength;
}
if (startInclude > endExclude) {
// 如果起始位置大于结束位置,不替换
return originalStr;
}
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < startInclude; i++) {
stringBuilder.append(new String(strCodePoints, i, 1));
}
stringBuilder.append(replacedStr);
for (int i = endExclude; i < strLength; i++) {
stringBuilder.append(new String(strCodePoints, i, 1));
}
return stringBuilder.toString();
}
/** /**
* 替换所有正则匹配的文本,并使用自定义函数决定如何替换<br> * 替换所有正则匹配的文本,并使用自定义函数决定如何替换<br>
* replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。 * replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。

View File

@@ -1,6 +1,7 @@
package cn.hutool.core.text.csv; package cn.hutool.core.text.csv;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
@@ -45,6 +46,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* 是否处于新行开始 * 是否处于新行开始
*/ */
private boolean newline = true; private boolean newline = true;
/**
* 是否首行即CSV开始的位置当初始化时默认为true一旦写入内容为false
*/
private boolean isFirstLine = true;
// --------------------------------------------------------------------------------------------------- Constructor start // --------------------------------------------------------------------------------------------------- Constructor start
@@ -183,13 +188,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public CsvWriter write(String[]... lines) throws IORuntimeException { public CsvWriter write(String[]... lines) throws IORuntimeException {
if (ArrayUtil.isNotEmpty(lines)) { return write(new ArrayIter<>(lines));
for (final String[] values : lines) {
appendLine(values);
}
flush();
}
return this;
} }
/** /**
@@ -320,9 +319,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
public CsvWriter writeComment(String comment) { public CsvWriter writeComment(String comment) {
Assert.notNull(this.config.commentCharacter, "Comment is disable!"); Assert.notNull(this.config.commentCharacter, "Comment is disable!");
try { try {
if(isFirstLine){
// 首行不补换行符
isFirstLine = false;
}else {
writer.write(config.lineDelimiter);
}
writer.write(this.config.commentCharacter); writer.write(this.config.commentCharacter);
writer.write(comment); writer.write(comment);
writer.write(config.lineDelimiter);
newline = true; newline = true;
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
@@ -366,12 +370,17 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @param fields 字段列表 ({@code null} 值会被做为空值追加) * @param fields 字段列表 ({@code null} 值会被做为空值追加)
* @throws IOException IO异常 * @throws IOException IO异常
*/ */
private void doAppendLine(final String... fields) throws IOException { private void doAppendLine(String... fields) throws IOException {
if (null != fields) { if (null != fields) {
if(isFirstLine){
// 首行不补换行符
isFirstLine = false;
}else {
writer.write(config.lineDelimiter);
}
for (String field : fields) { for (String field : fields) {
appendField(field); appendField(field);
} }
writer.write(config.lineDelimiter);
newline = true; newline = true;
} }
} }

View File

@@ -0,0 +1,93 @@
package cn.hutool.core.builder;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.junit.Assert;
import org.junit.Test;
/**
* {@link GenericBuilder} 单元测试类
*
* @author TomXin
*/
public class GenericBuilderTest {
@Test
public void test() {
Box box = GenericBuilder
.of(Box::new)
.with(Box::setId, 1024L)
.with(Box::setTitle, "Hello World!")
.with(Box::setLength, 9)
.with(Box::setWidth, 8)
.with(Box::setHeight, 7)
.build();
Assert.assertEquals(1024L, box.getId().longValue());
Assert.assertEquals("Hello World!", box.getTitle());
Assert.assertEquals(9, box.getLength().intValue());
Assert.assertEquals(8, box.getWidth().intValue());
Assert.assertEquals(7, box.getHeight().intValue());
// 对象修改
Box boxModified = GenericBuilder
.of(() -> box)
.with(Box::setTitle, "Hello Friend!")
.with(Box::setLength, 3)
.with(Box::setWidth, 4)
.with(Box::setHeight, 5)
.build();
Assert.assertEquals(1024L, boxModified.getId().longValue());
Assert.assertEquals("Hello Friend!", box.getTitle());
Assert.assertEquals(3, boxModified.getLength().intValue());
Assert.assertEquals(4, boxModified.getWidth().intValue());
Assert.assertEquals(5, boxModified.getHeight().intValue());
// 多参数构造
Box box1 = GenericBuilder
.of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
.with(Box::alis)
.build();
Assert.assertEquals(2048L, box1.getId().longValue());
Assert.assertEquals("Hello Partner!", box1.getTitle());
Assert.assertEquals(222, box1.getLength().intValue());
Assert.assertEquals(333, box1.getWidth().intValue());
Assert.assertEquals(444, box1.getHeight().intValue());
}
@Getter
@Setter
@ToString
@Accessors(chain = true)
public static class Box {
private Long id;
private String title;
private Integer length;
private Integer width;
private Integer height;
private String titleAlias;
public Box() {
}
public Box(Long id, String title, Integer length, Integer width, Integer height) {
this.id = id;
this.title = title;
this.length = length;
this.width = width;
this.height = height;
}
public void alis() {
if (StrUtil.isNotBlank(this.title)) {
this.titleAlias = "TomXin:\"" + title + "\"";
}
}
}
}

View File

@@ -19,7 +19,7 @@ import java.util.Map;
public class ListUtilTest { public class ListUtilTest {
@Test @Test
public void splitTest(){ public void splitTest() {
List<List<Object>> lists = ListUtil.split(null, 3); List<List<Object>> lists = ListUtil.split(null, 3);
Assert.assertEquals(ListUtil.empty(), lists); Assert.assertEquals(ListUtil.empty(), lists);
@@ -60,7 +60,7 @@ public class ListUtilTest {
} }
@Test @Test
public void splitAvgTest(){ public void splitAvgTest() {
List<List<Object>> lists = ListUtil.splitAvg(null, 3); List<List<Object>> lists = ListUtil.splitAvg(null, 3);
Assert.assertEquals(ListUtil.empty(), lists); Assert.assertEquals(ListUtil.empty(), lists);
@@ -80,13 +80,13 @@ public class ListUtilTest {
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void splitAvgNotZero(){ public void splitAvgNotZero() {
// limit不能小于等于0 // limit不能小于等于0
ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0); ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0);
} }
@Test @Test
public void editTest(){ public void editTest() {
List<String> a = ListUtil.toLinkedList("1", "2", "3"); List<String> a = ListUtil.toLinkedList("1", "2", "3");
final List<String> filter = (List<String>) CollUtil.edit(a, str -> "edit" + str); final List<String> filter = (List<String>) CollUtil.edit(a, str -> "edit" + str);
Assert.assertEquals("edit1", filter.get(0)); Assert.assertEquals("edit1", filter.get(0));
@@ -104,7 +104,7 @@ public class ListUtilTest {
} }
@Test @Test
public void pageTest(){ public void pageTest() {
List<Integer> a = ListUtil.toLinkedList(1, 2, 3,4,5); List<Integer> a = ListUtil.toLinkedList(1, 2, 3,4,5);
PageUtil.setFirstPageNo(1); PageUtil.setFirstPageNo(1);
@@ -167,10 +167,13 @@ public class ListUtilTest {
Assert.assertArrayEquals(new int[]{}, pageListData.get(0).stream().mapToInt(Integer::valueOf).toArray()); Assert.assertArrayEquals(new int[]{}, pageListData.get(0).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray()); Assert.assertArrayEquals(new int[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray()); Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray());
// 恢复默认值,避免影响其他测试用例
PageUtil.setFirstPageNo(0);
} }
@Test @Test
public void subTest(){ public void subTest() {
final List<Integer> of = ListUtil.of(1, 2, 3, 4); final List<Integer> of = ListUtil.of(1, 2, 3, 4);
final List<Integer> sub = ListUtil.sub(of, 2, 4); final List<Integer> sub = ListUtil.sub(of, 2, 4);
sub.remove(0); sub.remove(0);
@@ -181,10 +184,10 @@ public class ListUtilTest {
} }
@Test @Test
public void sortByPropertyTest(){ public void sortByPropertyTest() {
@Data @Data
@AllArgsConstructor @AllArgsConstructor
class TestBean{ class TestBean {
private int order; private int order;
private String name; private String name;
} }

View File

@@ -1,15 +1,14 @@
package cn.hutool.core.convert; package cn.hutool.core.convert;
import cn.hutool.core.date.DateUtil;
import org.junit.Assert;
import org.junit.Test;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
import org.junit.Assert;
import org.junit.Test;
import cn.hutool.core.date.DateUtil;
public class DateConvertTest { public class DateConvertTest {
@Test @Test
@@ -28,7 +27,7 @@ public class DateConvertTest {
int dateLong = -1497600000; int dateLong = -1497600000;
Date value = Convert.toDate(dateLong); Date value = Convert.toDate(dateLong);
Assert.assertNotNull(value); Assert.assertNotNull(value);
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString()); Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString().replace("GMT+08:00", "CST"));
final java.sql.Date sqlDate = Convert.convert(java.sql.Date.class, dateLong); final java.sql.Date sqlDate = Convert.convert(java.sql.Date.class, dateLong);
Assert.assertNotNull(sqlDate); Assert.assertNotNull(sqlDate);

View File

@@ -692,8 +692,11 @@ public class DateUtilTest {
@Test @Test
public void parseCSTTest() { public void parseCSTTest() {
String dateStr = "Wed Sep 16 11:26:23 CST 2009"; String dateStr = "Wed Sep 16 11:26:23 CST 2009";
Console.log(TimeZone.getDefault().getDisplayName());
SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US);
// Asia/Shanghai是以地区命名的地区标准时在中国叫CST因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
final DateTime parse = DateUtil.parse(dateStr, sdf); final DateTime parse = DateUtil.parse(dateStr, sdf);
DateTime dateTime = DateUtil.parseCST(dateStr); DateTime dateTime = DateUtil.parseCST(dateStr);
@@ -1004,4 +1007,17 @@ public class DateUtilTest {
final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER); final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER);
Assert.assertEquals("2021-12-01 00:00:00", parse.toString()); Assert.assertEquals("2021-12-01 00:00:00", parse.toString());
} }
@Test
public void isSameWeekTest() {
// 周六与周日比较
final boolean isSameWeek = DateUtil.isSameWeek(DateTime.of("2022-01-01", "yyyy-MM-dd"), DateTime.of("2022-01-02", "yyyy-MM-dd"), true);
Assert.assertTrue(isSameWeek);
// 周日与周一比较
final boolean isSameWeek1 = DateUtil.isSameWeek(DateTime.of("2022-01-02", "yyyy-MM-dd"), DateTime.of("2022-01-03", "yyyy-MM-dd"), false);
Assert.assertTrue(isSameWeek1);
// 跨月比较
final boolean isSameWeek2 = DateUtil.isSameWeek(DateTime.of("2021-12-29", "yyyy-MM-dd"), DateTime.of("2022-01-01", "yyyy-MM-dd"), true);
Assert.assertTrue(isSameWeek2);
}
} }

View File

@@ -197,4 +197,24 @@ public class LocalDateTimeUtilTest {
Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime)); Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime));
Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime)); Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime));
} }
@Test
public void weekOfYearTest(){
LocalDate date1 = LocalDate.of(2021, 12, 31);
final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
Assert.assertEquals(52, weekOfYear1);
final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
Assert.assertEquals(52, weekOfYear2);
}
@Test
public void weekOfYearTest2(){
LocalDate date1 = LocalDate.of(2022, 1, 31);
final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
Assert.assertEquals(5, weekOfYear1);
final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
Assert.assertEquals(5, weekOfYear2);
}
} }

View File

@@ -37,4 +37,15 @@ public class MonthTest {
lastDay = Month.of(Calendar.DECEMBER).getLastDay(true); lastDay = Month.of(Calendar.DECEMBER).getLastDay(true);
Assert.assertEquals(31, lastDay); Assert.assertEquals(31, lastDay);
} }
@Test
public void toJdkMonthTest(){
final java.time.Month month = Month.AUGUST.toJdkMonth();
Assert.assertEquals(java.time.Month.AUGUST, month);
}
@Test(expected = IllegalArgumentException.class)
public void toJdkMonthTest2(){
Month.UNDECIMBER.toJdkMonth();
}
} }

View File

@@ -21,11 +21,8 @@ public class CheckedUtilTest {
@Test @Test
public void sleepTest() { public void sleepTest() {
VoidFunc0 func = () -> Thread.sleep(1000L); VoidFunc0 func = () -> Thread.sleep(1000L);
func.callWithRuntimeException(); func.callWithRuntimeException();
} }
@@ -39,7 +36,6 @@ public class CheckedUtilTest {
} catch (Exception re) { } catch (Exception re) {
Assert.assertTrue(re instanceof RuntimeException); Assert.assertTrue(re instanceof RuntimeException);
} }
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")

View File

@@ -36,6 +36,32 @@ public class RangeTest {
Assert.assertFalse(range.hasNext()); Assert.assertFalse(range.hasNext());
} }
@Test
public void dateRangeFuncTest() {
DateTime start = DateUtil.parse("2021-01-01");
DateTime end = DateUtil.parse("2021-01-03");
List<Integer> dayOfMonthList = DateUtil.rangeFunc(start, end, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
Assert.assertArrayEquals(dayOfMonthList.toArray(new Integer[]{}), new Integer[]{1, 2, 3});
List<Integer> dayOfMonthList2 = DateUtil.rangeFunc(null, null, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
Assert.assertArrayEquals(dayOfMonthList2.toArray(new Integer[]{}), new Integer[]{});
}
@Test
public void dateRangeConsumeTest() {
DateTime start = DateUtil.parse("2021-01-01");
DateTime end = DateUtil.parse("2021-01-03");
StringBuilder sb = new StringBuilder();
DateUtil.rangeConsume(start, end, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
Assert.assertEquals(sb.toString(), "1#2#3#");
StringBuilder sb2 = new StringBuilder();
DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
Assert.assertEquals(sb2.toString(), "");
}
@Test @Test
public void dateRangeTest2() { public void dateRangeTest2() {
DateTime start = DateUtil.parse("2021-01-31"); DateTime start = DateUtil.parse("2021-01-31");

View File

@@ -22,6 +22,13 @@ public class CharSequenceUtilTest {
Assert.assertEquals(replace, result); Assert.assertEquals(replace, result);
} }
@Test
public void replaceByStrTest(){
String replace = "SSM15930297701BeryAllen";
String result = CharSequenceUtil.replace(replace, 5, 12, "***");
Assert.assertEquals("SSM15***01BeryAllen", result);
}
@Test @Test
public void addPrefixIfNotTest(){ public void addPrefixIfNotTest(){
String str = "hutool"; String str = "hutool";

View File

@@ -12,4 +12,10 @@ public class NamingCaseTest {
.set("customerNickV2", "customer_nick_v2") .set("customerNickV2", "customer_nick_v2")
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key))); .forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key)));
} }
@Test
public void toUnderLineCaseTest2(){
final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime");
Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime);
}
} }

View File

@@ -19,6 +19,8 @@ public class CsvWriterTest {
CharsetUtil.CHARSET_GBK, false, csvWriteConfig); CharsetUtil.CHARSET_GBK, false, csvWriteConfig);
writer.writeHeaderLine("name", "gender", "address"); writer.writeHeaderLine("name", "gender", "address");
writer.writeLine("张三", "", "XX市XX区");
writer.writeLine("李四", "", "XX市XX区,01号");
writer.close(); writer.close();
} }
} }

View File

@@ -5,14 +5,13 @@ import org.junit.Test;
/** /**
* 分页单元测试 * 分页单元测试
* @author Looly
* *
* @author Looly
*/ */
public class PageUtilTest { public class PageUtilTest {
@Test @Test
public void transToStartEndTest(){ public void transToStartEndTest() {
PageUtil.setFirstPageNo(0);
int[] startEnd1 = PageUtil.transToStartEnd(0, 10); int[] startEnd1 = PageUtil.transToStartEnd(0, 10);
Assert.assertEquals(0, startEnd1[0]); Assert.assertEquals(0, startEnd1[0]);
Assert.assertEquals(10, startEnd1[1]); Assert.assertEquals(10, startEnd1[1]);
@@ -23,7 +22,7 @@ public class PageUtilTest {
} }
@Test @Test
public void totalPage(){ public void totalPage() {
int totalPage = PageUtil.totalPage(20, 3); int totalPage = PageUtil.totalPage(20, 3);
Assert.assertEquals(7, totalPage); Assert.assertEquals(7, totalPage);
} }
@@ -31,6 +30,6 @@ public class PageUtilTest {
@Test @Test
public void rainbowTest() { public void rainbowTest() {
int[] rainbow = PageUtil.rainbow(5, 20, 6); int[] rainbow = PageUtil.rainbow(5, 20, 6);
Assert.assertArrayEquals(new int[] {3, 4, 5, 6, 7, 8}, rainbow); Assert.assertArrayEquals(new int[]{3, 4, 5, 6, 7, 8}, rainbow);
} }
} }

View File

@@ -27,7 +27,7 @@
<sqlite.version>3.36.0.3</sqlite.version> <sqlite.version>3.36.0.3</sqlite.version>
<!-- 此处固定2.5.x支持到JDK8 --> <!-- 此处固定2.5.x支持到JDK8 -->
<hsqldb.version>2.5.2</hsqldb.version> <hsqldb.version>2.5.2</hsqldb.version>
<jedis.version>4.0.0</jedis.version> <jedis.version>4.1.1</jedis.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -81,7 +81,7 @@
<dependency> <dependency>
<groupId>com.github.chris2018998</groupId> <groupId>com.github.chris2018998</groupId>
<artifactId>beecp</artifactId> <artifactId>beecp</artifactId>
<version>3.3.0</version> <version>3.3.1</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
@@ -143,13 +143,13 @@
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version> <version>8.0.28</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>42.3.1</version> <version>42.3.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -167,13 +167,13 @@
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>2.0.206</version> <version>2.1.210</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ru.yandex.clickhouse</groupId> <groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId> <artifactId>clickhouse-jdbc</artifactId>
<version>0.3.1</version> <version>0.3.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,54 @@
package cn.hutool.db.sql;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import java.util.List;
/**
* 条件组<br>
* 用于构建复杂where条件
*
* @author tjh
* @since 5.7.21
*/
public class ConditionGroup extends Condition {
/**
* 条件列表
*/
private Condition[] conditions;
/**
* 追加条件
*
* @param conditions 条件列表
*/
public void addConditions(Condition... conditions) {
if (null == this.conditions) {
this.conditions = conditions;
} else {
this.conditions = ArrayUtil.addAll(this.conditions, conditions);
}
}
/**
* 将条件组转换为条件字符串,使用括号包裹,并回填占位符对应的参数值
*
* @param paramValues 参数列表,用于回填占位符对应参数值
* @return 条件字符串
*/
@Override
public String toString(List<Object> paramValues) {
if (ArrayUtil.isEmpty(conditions)) {
return StrUtil.EMPTY;
}
final StringBuilder conditionStrBuilder = StrUtil.builder();
conditionStrBuilder.append("(");
// 将组内的条件构造为SQL因为toString会进行递归处理所有的条件组
conditionStrBuilder.append(ConditionBuilder.of(this.conditions).build(paramValues));
conditionStrBuilder.append(")");
return conditionStrBuilder.toString();
}
}

View File

@@ -0,0 +1,28 @@
package cn.hutool.db.sql;
import cn.hutool.core.collection.ListUtil;
import org.junit.Assert;
import org.junit.Test;
public class ConditionGroupTest {
@Test
public void ConditionGroupToStringTest() {
Condition condition1 = new Condition("a", "A");
Condition condition2 = new Condition("b", "B");
condition2.setLinkOperator(LogicalOperator.OR);
Condition condition3 = new Condition("c", "C");
Condition condition4 = new Condition("d", "D");
ConditionGroup cg = new ConditionGroup();
cg.addConditions(condition1, condition2);
// 条件组嵌套情况
ConditionGroup cg2 = new ConditionGroup();
cg2.addConditions(cg, condition3);
final ConditionBuilder conditionBuilder = ConditionBuilder.of(cg2, condition4);
Assert.assertEquals("((a = ? OR b = ?) AND c = ?) AND d = ?", conditionBuilder.build());
Assert.assertEquals(ListUtil.of("A", "B", "C", "D"), conditionBuilder.getParamValues());
}
}

View File

@@ -17,11 +17,6 @@
<description>Hutool 基于DFA的关键词查找</description> <description>Hutool 基于DFA的关键词查找</description>
<dependencies> <dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId> <artifactId>hutool-json</artifactId>

View File

@@ -195,11 +195,21 @@ public final class SensitiveUtil {
*/ */
public static <T> T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) { public static <T> T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
String jsonText = JSONUtil.toJsonStr(bean); String jsonText = JSONUtil.toJsonStr(bean);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked") final Class<T> c = (Class<T>) bean.getClass();
final Class<T> c = (Class<T>) bean.getClass();
return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c); return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c);
} }
/**
* 处理过滤文本中的敏感词,默认替换成*
*
* @param text 文本
* @return 敏感词过滤处理后的文本
* @since 5.7.21
*/
public static String sensitiveFilter(String text) {
return sensitiveFilter(text, true, null);
}
/** /**
* 处理过滤文本中的敏感词,默认替换成* * 处理过滤文本中的敏感词,默认替换成*
* *
@@ -214,13 +224,14 @@ public final class SensitiveUtil {
} }
//敏感词过滤场景下,不需要密集匹配 //敏感词过滤场景下,不需要密集匹配
List<FoundWord> foundWordList = getFoundAllSensitive(text, false, isGreedMatch); List<FoundWord> foundWordList = getFoundAllSensitive(text, true, isGreedMatch);
if (CollUtil.isEmpty(foundWordList)) { if (CollUtil.isEmpty(foundWordList)) {
return text; return text;
} }
sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() { sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() {
} : sensitiveProcessor; } : sensitiveProcessor;
Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size());
final Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size(), 1);
foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord)); foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord));
int length = text.length(); int length = text.length();
StringBuilder textStringBuilder = new StringBuilder(); StringBuilder textStringBuilder = new StringBuilder();

View File

@@ -3,7 +3,6 @@ package cn.hutool.dfa;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.Filter;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import java.util.ArrayList; import java.util.ArrayList;
@@ -247,15 +246,15 @@ public class WordTree extends HashMap<Character, WordTree> {
List<FoundWord> foundWords = new ArrayList<>(); List<FoundWord> foundWords = new ArrayList<>();
WordTree current = this; WordTree current = this;
int length = text.length(); final int length = text.length();
final Filter<Character> charFilter = this.charFilter; final Filter<Character> charFilter = this.charFilter;
//存放查找到的字符缓存。完整出现一个词时加到findedWords中否则清空 //存放查找到的字符缓存。完整出现一个词时加到findedWords中否则清空
final StrBuilder wordBuffer = StrUtil.strBuilder(); final StringBuilder wordBuffer = StrUtil.builder();
final StrBuilder keyBuffer = StrUtil.strBuilder(); final StringBuilder keyBuffer = StrUtil.builder();
char currentChar; char currentChar;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
wordBuffer.reset(); wordBuffer.setLength(0);
keyBuffer.reset(); keyBuffer.setLength(0);
for (int j = i; j < length; j++) { for (int j = i; j < length; j++) {
currentChar = text.charAt(j); currentChar = text.charAt(j);
// Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar); // Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar);
@@ -284,6 +283,7 @@ public class WordTree extends HashMap<Character, WordTree> {
if (false == isDensityMatch) { if (false == isDensityMatch) {
//如果非密度匹配,跳过匹配到的词 //如果非密度匹配,跳过匹配到的词
i = j; i = j;
break;
} }
if (false == isGreedMatch) { if (false == isGreedMatch) {
//如果懒惰匹配(非贪婪匹配)。当遇到第一个结尾标记就结束本轮匹配 //如果懒惰匹配(非贪婪匹配)。当遇到第一个结尾标记就结束本轮匹配

View File

@@ -47,7 +47,7 @@ public class DfaTest {
} }
/** /**
* 贪婪匹配原则测试 * 贪婪非密集匹配原则测试
*/ */
@Test @Test
public void greedMatchTest() { public void greedMatchTest() {
@@ -56,15 +56,15 @@ public class DfaTest {
// ----------------------------------------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------------------------------------
// 情况三:匹配到最长关键词,跳过已经匹配的关键词 // 情况三:匹配到最长关键词,跳过已经匹配的关键词
// 匹配到【大】,由于到最长匹配,因此【土豆】接着被匹配 // 匹配到【大】,由于非密集匹配,因此从下一个字符开始查找,匹配到【土豆】接着被匹配
// 由于【大土豆】被匹配,【土豆】被跳过,由于【刚出锅】被匹配,【出锅】被跳过 // 由于【刚出锅】被匹配,由于非密集匹配,【出锅】被跳过
List<String> matchAll = tree.matchAll(text, -1, false, true); List<String> matchAll = tree.matchAll(text, -1, false, true);
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "土^豆", "刚出锅")); Assert.assertEquals(matchAll, CollUtil.newArrayList("", "土^豆", "刚出锅"));
} }
/** /**
* 密集匹配原则(最匹配)和贪婪匹配原则测试 * 密集匹配原则(最匹配)和贪婪匹配原则测试
*/ */
@Test @Test
public void densityAndGreedMatchTest() { public void densityAndGreedMatchTest() {
@@ -80,6 +80,29 @@ public class DfaTest {
} }
@Test
public void densityAndGreedMatchTest2(){
WordTree tree = new WordTree();
tree.addWord("");
tree.addWord("赵阿");
tree.addWord("赵阿三");
final List<FoundWord> result = tree.matchAllWords("赵阿三在做什么", -1, true, true);
Assert.assertEquals(3, result.size());
Assert.assertEquals("", result.get(0).getWord());
Assert.assertEquals(0, result.get(0).getStartIndex().intValue());
Assert.assertEquals(0, result.get(0).getEndIndex().intValue());
Assert.assertEquals("赵阿", result.get(1).getWord());
Assert.assertEquals(0, result.get(1).getStartIndex().intValue());
Assert.assertEquals(1, result.get(1).getEndIndex().intValue());
Assert.assertEquals("赵阿三", result.get(2).getWord());
Assert.assertEquals(0, result.get(2).getStartIndex().intValue());
Assert.assertEquals(2, result.get(2).getEndIndex().intValue());
}
/** /**
* 停顿词测试 * 停顿词测试
*/ */

View File

@@ -1,5 +1,7 @@
package cn.hutool.dfa; package cn.hutool.dfa;
import cn.hutool.core.collection.ListUtil;
import lombok.Data;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@@ -24,25 +26,17 @@ public class SensitiveUtilTest {
Assert.assertEquals(bean.getStr(), "我有一颗$*******的"); Assert.assertEquals(bean.getStr(), "我有一颗$*******的");
} }
@Data
public static class TestBean { public static class TestBean {
private String str; private String str;
private Integer num; private Integer num;
public String getStr() {
return str;
} }
public void setStr(String str) { @Test
this.str = str; public void issue2126(){
} SensitiveUtil.init(ListUtil.of("", "赵阿", "赵阿三"));
public Integer getNum() { String result = SensitiveUtil.sensitiveFilter("赵阿三在做什么。", true, null);
return num; Assert.assertEquals("***在做什么。", result);
} }
public void setNum(Integer num) {
this.num = num;
}
}
} }

View File

@@ -23,7 +23,7 @@
<rythm.version>1.4.1</rythm.version> <rythm.version>1.4.1</rythm.version>
<freemarker.version>2.3.31</freemarker.version> <freemarker.version>2.3.31</freemarker.version>
<enjoy.version>4.9.16</enjoy.version> <enjoy.version>4.9.16</enjoy.version>
<thymeleaf.version>3.0.14.RELEASE</thymeleaf.version> <thymeleaf.version>3.0.15.RELEASE</thymeleaf.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>
<jsch.version>0.1.55</jsch.version> <jsch.version>0.1.55</jsch.version>
<sshj.version>0.32.0</sshj.version> <sshj.version>0.32.0</sshj.version>
@@ -31,7 +31,7 @@
<net.version>3.8.0</net.version> <net.version>3.8.0</net.version>
<emoji-java.version>5.1.1</emoji-java.version> <emoji-java.version>5.1.1</emoji-java.version>
<servlet-api.version>4.0.1</servlet-api.version> <servlet-api.version>4.0.1</servlet-api.version>
<spring-boot.version>2.6.2</spring-boot.version> <spring-boot.version>2.6.3</spring-boot.version>
<cglib.version>3.3.0</cglib.version> <cglib.version>3.3.0</cglib.version>
</properties> </properties>
@@ -340,7 +340,7 @@
<dependency> <dependency>
<groupId>com.github.houbb</groupId> <groupId>com.github.houbb</groupId>
<artifactId>pinyin</artifactId> <artifactId>pinyin</artifactId>
<version>0.2.2</version> <version>0.3.1</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@@ -446,7 +446,7 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId> <artifactId>spring-expression</artifactId>
<version>5.3.14</version> <version>5.3.15</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>

View File

@@ -126,11 +126,11 @@ public class EmojiUtil {
* @return 替换后的字符串 * @return 替换后的字符串
*/ */
public static String toHtmlHex(String str) { public static String toHtmlHex(String str) {
return EmojiParser.parseToHtmlHexadecimal(str); return toHtml(str, true);
} }
/** /**
* 将字符串中的Unicode Emoji字符转换为HTML表现形式 * 将字符串中的Unicode Emoji字符转换为HTML表现形式Hex方式
* <p> * <p>
* 例如:<code>👦🏿</code> 转换为 <code>&amp;#128102;</code> * 例如:<code>👦🏿</code> 转换为 <code>&amp;#128102;</code>
* *
@@ -138,7 +138,24 @@ public class EmojiUtil {
* @return 替换后的字符串 * @return 替换后的字符串
*/ */
public static String toHtml(String str) { public static String toHtml(String str) {
return EmojiParser.parseToHtmlHexadecimal(str); return toHtml(str, false);
}
/**
* 将字符串中的Unicode Emoji字符转换为HTML表现形式例如
* <pre>
* 如果为hex形式<code>👦🏿</code> 转换为 <code>&amp;#x1f466;</code>
* 否则,<code>👦🏿</code> 转换为 <code>&amp;#128102;</code>
* </pre>
*
* @param str 包含Emoji Unicode字符的字符串
* @param isHex 是否hex形式
* @return 替换后的字符串
* @since 5.7.21
*/
public static String toHtml(String str, boolean isHex) {
return isHex ? EmojiParser.parseToHtmlHexadecimal(str) :
EmojiParser.parseToHtmlDecimal(str);
} }
/** /**

View File

@@ -94,4 +94,6 @@ public class QrCodeUtilTest {
final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create()); final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create());
Assert.assertNotNull(image); Assert.assertNotNull(image);
} }
} }

View File

@@ -249,22 +249,20 @@ public class JSONWriter extends Writer {
* 此方法输出的值不包装引号。 * 此方法输出的值不包装引号。
* *
* @param number 数字 * @param number 数字
* @return this
*/ */
private JSONWriter writeNumberValue(Number number) { private void writeNumberValue(Number number) {
// since 5.6.2可配置是否去除末尾多余0例如如果为true,5.0返回5 // since 5.6.2可配置是否去除末尾多余0例如如果为true,5.0返回5
final boolean isStripTrailingZeros = null == config || config.isStripTrailingZeros(); final boolean isStripTrailingZeros = null == config || config.isStripTrailingZeros();
return writeRaw(NumberUtil.toStr(number, isStripTrailingZeros)); writeRaw(NumberUtil.toStr(number, isStripTrailingZeros));
} }
/** /**
* 写出Boolean值直接写出true或false,不适用引号包装 * 写出Boolean值直接写出true或false,不适用引号包装
* *
* @param value Boolean值 * @param value Boolean值
* @return this
*/ */
private JSONWriter writeBooleanValue(Boolean value) { private void writeBooleanValue(Boolean value) {
return writeRaw(value.toString()); writeRaw(value.toString());
} }
/** /**
@@ -273,9 +271,8 @@ public class JSONWriter extends Writer {
* 如果toJSONString()返回null调用toString()方法并使用双引号包装。 * 如果toJSONString()返回null调用toString()方法并使用双引号包装。
* *
* @param jsonString {@link JSONString} * @param jsonString {@link JSONString}
* @return this
*/ */
private JSONWriter writeJSONStringValue(JSONString jsonString) { private void writeJSONStringValue(JSONString jsonString) {
String valueStr; String valueStr;
try { try {
valueStr = jsonString.toJSONString(); valueStr = jsonString.toJSONString();
@@ -287,7 +284,6 @@ public class JSONWriter extends Writer {
} else { } else {
writeStrValue(jsonString.toString()); writeStrValue(jsonString.toString());
} }
return this;
} }
/** /**
@@ -297,30 +293,26 @@ public class JSONWriter extends Writer {
* JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠
* *
* @param csq 字符串 * @param csq 字符串
* @return this
*/ */
private JSONWriter writeStrValue(String csq) { private void writeStrValue(String csq) {
try { try {
JSONUtil.quote(csq, writer); JSONUtil.quote(csq, writer);
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
return this;
} }
/** /**
* 写出空格 * 写出空格
* *
* @param count 空格数 * @param count 空格数
* @return this
*/ */
private JSONWriter writeSpace(int count) { private void writeSpace(int count) {
if (indentFactor > 0) { if (indentFactor > 0) {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
writeRaw(CharUtil.SPACE); writeRaw(CharUtil.SPACE);
} }
} }
return this;
} }
/** /**

View File

@@ -19,7 +19,7 @@ public class Issue677Test {
final String jsonStr = JSONUtil.toJsonStr(dto); final String jsonStr = JSONUtil.toJsonStr(dto);
final AuditResultDto auditResultDto = JSONUtil.toBean(jsonStr, AuditResultDto.class); final AuditResultDto auditResultDto = JSONUtil.toBean(jsonStr, AuditResultDto.class);
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", auditResultDto.getDate().toString()); Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", auditResultDto.getDate().toString().replace("GMT+08:00", "CST"));
} }
@Data @Data

View File

@@ -24,7 +24,7 @@
<log4j2.version>2.17.1</log4j2.version> <log4j2.version>2.17.1</log4j2.version>
<commons-logging.version>1.2</commons-logging.version> <commons-logging.version>1.2</commons-logging.version>
<tinylog.version>1.3.6</tinylog.version> <tinylog.version>1.3.6</tinylog.version>
<jboss-logging.version>3.4.2.Final</jboss-logging.version> <jboss-logging.version>3.4.3.Final</jboss-logging.version>
<logtube.version>0.43.0</logtube.version> <logtube.version>0.43.0</logtube.version>
</properties> </properties>

View File

@@ -1,6 +1,6 @@
/** /**
* Hutool-log只是一个日志的通用门面功能类似于Slf4j。<br> * Hutool-log只是一个日志的通用门面功能类似于Slf4j。<br>
* 根据加入ClassPath中的jar包动态检测日志实现的方式使日志使用更加便利灵活。 * 根据加入ClassPath中的jar包动态检测日志实现的方式使日志使用更加便利灵活。
* *
* @author looly * @author looly
* *

View File

@@ -45,7 +45,7 @@
<dependency> <dependency>
<groupId>org.ofdrw</groupId> <groupId>org.ofdrw</groupId>
<artifactId>ofdrw-full</artifactId> <artifactId>ofdrw-full</artifactId>
<version>1.17.6</version> <version>1.17.8</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>

View File

@@ -30,7 +30,7 @@
<dependency> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
<version>6.0.0</version> <version>6.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -1,5 +1,6 @@
package cn.hutool.system.oshi; package cn.hutool.system.oshi;
import cn.hutool.core.util.NumberUtil;
import oshi.hardware.CentralProcessor; import oshi.hardware.CentralProcessor;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@@ -31,7 +32,7 @@ public class CpuInfo {
/** /**
* CPU用户使用率 * CPU用户使用率
*/ */
private double used; private double user;
/** /**
* CPU当前等待率 * CPU当前等待率
@@ -75,16 +76,16 @@ public class CpuInfo {
* @param cpuNum CPU核心数 * @param cpuNum CPU核心数
* @param toTal CPU总的使用率 * @param toTal CPU总的使用率
* @param sys CPU系统使用率 * @param sys CPU系统使用率
* @param used CPU用户使用率 * @param user CPU用户使用率
* @param wait CPU当前等待率 * @param wait CPU当前等待率
* @param free CPU当前空闲率 * @param free CPU当前空闲率
* @param cpuModel CPU型号信息 * @param cpuModel CPU型号信息
*/ */
public CpuInfo(Integer cpuNum, double toTal, double sys, double used, double wait, double free, String cpuModel) { public CpuInfo(Integer cpuNum, double toTal, double sys, double user, double wait, double free, String cpuModel) {
this.cpuNum = cpuNum; this.cpuNum = cpuNum;
this.toTal = toTal; this.toTal = toTal;
this.sys = sys; this.sys = sys;
this.used = used; this.user = user;
this.wait = wait; this.wait = wait;
this.free = free; this.free = free;
this.cpuModel = cpuModel; this.cpuModel = cpuModel;
@@ -114,12 +115,12 @@ public class CpuInfo {
this.sys = sys; this.sys = sys;
} }
public double getUsed() { public double getUser() {
return used; return user;
} }
public void setUsed(double used) { public void setUser(double user) {
this.used = used; this.user = user;
} }
public double getWait() { public double getWait() {
@@ -154,16 +155,25 @@ public class CpuInfo {
this.ticks = ticks; this.ticks = ticks;
} }
/**
* 获取用户+系统的总的CPU使用率
*
* @return 总CPU使用率
*/
public double getUsed() {
return NumberUtil.sub(100, this.free);
}
@Override @Override
public String toString() { public String toString() {
return "CpuInfo{" + return "CpuInfo{" +
"CPU核心数=" + cpuNum + "CPU核心数=" + cpuNum +
", CPU总的使用率=" + toTal + ", CPU总的使用率=" + toTal +
", CPU系统使用率=" + sys + ", CPU系统使用率=" + sys +
", CPU用户使用率=" + used + ", CPU用户使用率=" + user +
", CPU当前等待率=" + wait + ", CPU当前等待率=" + wait +
", CPU当前空闲率=" + free + ", CPU当前空闲率=" + free +
", CPU利用率=" + LOAD_FORMAT.format(100 - free) + ", CPU利用率=" + getUsed() +
", CPU型号信息='" + cpuModel + '\'' + ", CPU型号信息='" + cpuModel + '\'' +
'}'; '}';
} }
@@ -185,7 +195,7 @@ public class CpuInfo {
final long totalCpu = ticks.totalCpu(); final long totalCpu = ticks.totalCpu();
this.toTal = totalCpu; this.toTal = totalCpu;
this.sys = formatDouble(ticks.cSys, totalCpu); this.sys = formatDouble(ticks.cSys, totalCpu);
this.used = formatDouble(ticks.user, totalCpu); this.user = formatDouble(ticks.user, totalCpu);
this.wait = formatDouble(ticks.ioWait, totalCpu); this.wait = formatDouble(ticks.ioWait, totalCpu);
this.free = formatDouble(ticks.idle, totalCpu); this.free = formatDouble(ticks.idle, totalCpu);
} }

View File

@@ -1,8 +1,10 @@
package cn.hutool.system; package cn.hutool.system;
import cn.hutool.core.lang.Console;
import cn.hutool.system.oshi.CpuInfo; import cn.hutool.system.oshi.CpuInfo;
import cn.hutool.system.oshi.OshiUtil; import cn.hutool.system.oshi.OshiUtil;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import oshi.software.os.OSProcess; import oshi.software.os.OSProcess;
@@ -28,4 +30,12 @@ public class OshiTest {
final OSProcess currentProcess = OshiUtil.getCurrentProcess(); final OSProcess currentProcess = OshiUtil.getCurrentProcess();
Assert.assertEquals("java", currentProcess.getName()); Assert.assertEquals("java", currentProcess.getName());
} }
@Test
@Ignore
public void getUsedTest(){
while (true){
Console.log(OshiUtil.getCpuInfo().getUsed());
}
}
} }