mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
Merge remote-tracking branch 'upstream/v5-dev' into v5-dev
This commit is contained in:
commit
211413f789
@ -3,7 +3,7 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.2 (2020-11-20)
|
||||
# 5.5.2 (2020-11-25)
|
||||
|
||||
### 新特性
|
||||
* 【crypto 】 KeyUtil增加重载,AES构造增加重载(issue#I25NNZ@Gitee)
|
||||
@ -12,12 +12,17 @@
|
||||
* 【extra 】 新增Rhino表达式执行引擎(pr#1229@Github)
|
||||
* 【crypto 】 增加判空(issue#1230@Github)
|
||||
* 【core 】 xml.setXmlStandalone(true)格式优化(pr#1234@Github)
|
||||
* 【core 】 AnnotationUtil增加setValue方法(pr#1250@Github)
|
||||
* 【core 】 ZipUtil增加get方法
|
||||
* 【cache 】 对CacheObj等变量使用volatile关键字
|
||||
* 【core 】 Base64增加encodeWithoutPadding方法(issue#I26J16@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github)
|
||||
* 【core 】 修复Calculator.conversion单个数字越界问题(issue#1222@Github)
|
||||
* 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题(issue#1225@Github)
|
||||
* 【core 】 修复HexUtil.format问题(issue#I268XT@Gitee)
|
||||
* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题(issue#1251@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -6,6 +6,7 @@ import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
/**
|
||||
@ -44,11 +45,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
/**
|
||||
* 命中数
|
||||
*/
|
||||
protected int hitCount;
|
||||
protected AtomicLong hitCount;
|
||||
/**
|
||||
* 丢失数
|
||||
*/
|
||||
protected int missCount;
|
||||
protected AtomicLong missCount;
|
||||
|
||||
// ---------------------------------------------------------------- put start
|
||||
@Override
|
||||
@ -113,15 +114,15 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
/**
|
||||
* @return 命中数
|
||||
*/
|
||||
public int getHitCount() {
|
||||
return hitCount;
|
||||
public long getHitCount() {
|
||||
return hitCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 丢失数
|
||||
*/
|
||||
public int getMissCount() {
|
||||
return missCount;
|
||||
public long getMissCount() {
|
||||
return missCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -157,15 +158,15 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
// 不存在或已移除
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
if (null == co) {
|
||||
missCount++;
|
||||
missCount.getAndIncrement();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (co.isExpired()) {
|
||||
missCount++;
|
||||
missCount.getAndIncrement();
|
||||
} else{
|
||||
// 命中
|
||||
hitCount++;
|
||||
hitCount.getAndIncrement();
|
||||
return co.get(isUpdateLastAccess);
|
||||
}
|
||||
} finally {
|
||||
@ -318,7 +319,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
final CacheObj<K, V> co = cacheMap.remove(key);
|
||||
if (withMissCount) {
|
||||
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
|
||||
this.missCount++;
|
||||
this.missCount.getAndIncrement();
|
||||
}
|
||||
return co;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 缓存对象
|
||||
@ -16,9 +17,9 @@ public class CacheObj<K, V> implements Serializable{
|
||||
protected final V obj;
|
||||
|
||||
/** 上次访问时间 */
|
||||
private long lastAccess;
|
||||
private volatile long lastAccess;
|
||||
/** 访问次数 */
|
||||
protected long accessCount;
|
||||
protected AtomicLong accessCount;
|
||||
/** 对象存活时长,0表示永久存活*/
|
||||
private final long ttl;
|
||||
|
||||
@ -61,7 +62,7 @@ public class CacheObj<K, V> implements Serializable{
|
||||
if(isUpdateLastAccess) {
|
||||
lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
accessCount++;
|
||||
accessCount.getAndIncrement();
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
|
@ -69,21 +69,20 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
|
||||
}
|
||||
|
||||
//找出访问最少的对象
|
||||
if (comin == null || co.accessCount < comin.accessCount) {
|
||||
if (comin == null || co.accessCount.get() < comin.accessCount.get()) {
|
||||
comin = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 减少所有对象访问量,并清除减少后为0的访问对象
|
||||
if (isFull() && comin != null) {
|
||||
long minAccessCount = comin.accessCount;
|
||||
long minAccessCount = comin.accessCount.get();
|
||||
|
||||
values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co1;
|
||||
while (values.hasNext()) {
|
||||
co1 = values.next();
|
||||
co1.accessCount -= minAccessCount;
|
||||
if (co1.accessCount <= 0) {
|
||||
if (co1.accessCount.addAndGet(-minAccessCount) <= 0) {
|
||||
values.remove();
|
||||
onRemove(co1.key, co1.obj);
|
||||
count++;
|
||||
|
@ -13,6 +13,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -200,4 +201,18 @@ public class AnnotationUtil {
|
||||
public static boolean isInherited(Class<? extends Annotation> annotationType) {
|
||||
return annotationType.isAnnotationPresent(Inherited.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置新的注解的属性(字段)值
|
||||
*
|
||||
* @param annotation 注解对象
|
||||
* @param annotationField 注解属性(字段)名称
|
||||
* @param value 要更新的属性值
|
||||
* @since 5.5.2
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static void setValue(Annotation annotation, String annotationField, Object value) {
|
||||
final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
|
||||
memberValues.put(annotationField, value);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package cn.hutool.core.codec;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@ -75,6 +76,17 @@ public class Base64 {
|
||||
return encode(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,不进行padding(末尾不会填充'=')
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @return 被加密后的字符串
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static String encodeWithoutPadding(CharSequence source, String charset) {
|
||||
return encodeWithoutPadding(StrUtil.bytes(source, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全
|
||||
*
|
||||
@ -120,6 +132,17 @@ public class Base64 {
|
||||
return Base64Encoder.encode(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,不进行padding(末尾不会填充'=')
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @return 被加密后的字符串
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static String encodeWithoutPadding(byte[] source) {
|
||||
return java.util.Base64.getEncoder().withoutPadding().encodeToString(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全的
|
||||
*
|
||||
@ -175,60 +198,6 @@ public class Base64 {
|
||||
return Base64Encoder.encodeUrlSafe(FileUtil.readBytes(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
* @deprecated 编码参数无意义,作废
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encode(byte[] source, String charset) {
|
||||
return Base64Encoder.encode(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全的
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
* @since 3.0.6
|
||||
* @deprecated 编码参数无意义,作废
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encodeUrlSafe(byte[] source, String charset) {
|
||||
return Base64Encoder.encodeUrlSafe(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
* @deprecated 编码参数无意义,作废
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encode(byte[] source, Charset charset) {
|
||||
return Base64Encoder.encode(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64编码,URL安全的
|
||||
*
|
||||
* @param source 被编码的base64字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
* @since 3.0.6
|
||||
* @deprecated 编码参数无意义,作废
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encodeUrlSafe(byte[] source, Charset charset) {
|
||||
return Base64Encoder.encodeUrlSafe(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码为Base64<br>
|
||||
* 如果isMultiLine为<code>true</code>,则每76个字符一个换行符,否则在一行显示
|
||||
|
@ -819,10 +819,10 @@ public class DateUtil extends CalendarUtil {
|
||||
int length = utcString.length();
|
||||
if (StrUtil.contains(utcString, 'Z')) {
|
||||
if (length == DatePattern.UTC_PATTERN.length() - 4) {
|
||||
// 格式类似:2018-09-13T05:34:31Z
|
||||
// 格式类似:2018-09-13T05:34:31Z,-4表示减去4个单引号的长度
|
||||
return parse(utcString, DatePattern.UTC_FORMAT);
|
||||
} else if (length == DatePattern.UTC_MS_PATTERN.length() - 4) {
|
||||
// 格式类似:2018-09-13T05:34:31.999Z
|
||||
// 格式类似:2018-09-13T05:34:31.999Z,-4表示减去4个单引号的长度
|
||||
return parse(utcString, DatePattern.UTC_MS_FORMAT);
|
||||
}
|
||||
} else {
|
||||
|
@ -1972,7 +1972,7 @@ public class NumberUtil {
|
||||
* @param number A Number
|
||||
* @return A String.
|
||||
*/
|
||||
public static String toStr(Number number) {
|
||||
public static String toStr(Number number) {
|
||||
Assert.notNull(number, "Number is null !");
|
||||
|
||||
// BigDecimal单独处理,使用非科学计数法
|
||||
|
@ -459,13 +459,12 @@ public class ZipUtil {
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.8
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException {
|
||||
if(outFile.exists() && outFile.isFile()){
|
||||
throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath());
|
||||
}
|
||||
try {
|
||||
final Enumeration<ZipEntry> em = (Enumeration<ZipEntry>) zipFile.entries();
|
||||
final Enumeration<? extends ZipEntry> em = zipFile.entries();
|
||||
ZipEntry zipEntry;
|
||||
File outItemFile;
|
||||
while (em.hasMoreElements()) {
|
||||
@ -487,6 +486,40 @@ public class ZipUtil {
|
||||
return outFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩包中的指定文件流
|
||||
* @param zipFile 压缩文件
|
||||
* @param path 需要提取文件的文件名或路径
|
||||
* @return 压缩文件流,如果未找到返回{@code null}
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static InputStream get(File zipFile, Charset charset, String path){
|
||||
try {
|
||||
return get(new ZipFile(zipFile, charset), path);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩包中的指定文件流
|
||||
* @param zipFile 压缩文件
|
||||
* @param path 需要提取文件的文件名或路径
|
||||
* @return 压缩文件流,如果未找到返回{@code null}
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static InputStream get(ZipFile zipFile, String path){
|
||||
final ZipEntry entry = zipFile.getEntry(path);
|
||||
if(null != entry){
|
||||
try {
|
||||
return zipFile.getInputStream(entry);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压<br>
|
||||
* ZIP条目不使用高速缓冲。
|
||||
@ -1024,15 +1057,9 @@ public class ZipUtil {
|
||||
throw new UtilException(StrUtil.format("File [{}] not exist!", srcFile.getAbsolutePath()));
|
||||
}
|
||||
|
||||
try {
|
||||
final File parentFile = zipFile.getCanonicalFile().getParentFile();
|
||||
// 压缩文件不能位于被压缩的目录内
|
||||
if (srcFile.isDirectory() && parentFile.getCanonicalPath().contains(srcFile.getCanonicalPath())) {
|
||||
throw new UtilException("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getCanonicalPath(), srcFile.getCanonicalPath());
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new UtilException(e);
|
||||
// 压缩文件不能位于被压缩的目录内
|
||||
if(srcFile.isDirectory() && FileUtil.isSub(srcFile, zipFile.getParentFile())){
|
||||
throw new UtilException("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getPath(), srcFile.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -377,4 +377,11 @@ public class FileUtilTest {
|
||||
String mimeType = FileUtil.getMimeType("test2Write.jpg");
|
||||
Assert.assertEquals("image/jpeg", mimeType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSubTest() {
|
||||
File file = new File("d:/test");
|
||||
File file2 = new File("d:/test2/aaa");
|
||||
Assert.assertFalse(FileUtil.isSub(file, file2));
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ public class BCrypt {
|
||||
private static byte char64(char x) {
|
||||
if ((int) x > index_64.length)
|
||||
return -1;
|
||||
return index_64[(int) x];
|
||||
return index_64[x];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -446,8 +446,7 @@ public class JschUtil {
|
||||
/**
|
||||
* 执行Shell命令
|
||||
* <p>
|
||||
* 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭channel,不会产生阻塞。<br>
|
||||
* 此方法返回数据中可能
|
||||
* 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭channel,不会产生阻塞。
|
||||
* </p>
|
||||
*
|
||||
* @param session Session会话
|
||||
|
@ -20,7 +20,7 @@ public class JSONUtilTest {
|
||||
* 出现语法错误时报错,检查解析\x字符时是否会导致死循环异常
|
||||
*/
|
||||
@Test(expected = JSONException.class)
|
||||
public void parseTest(){
|
||||
public void parseTest() {
|
||||
JSONArray jsonArray = JSONUtil.parseArray("[{\"a\":\"a\\x]");
|
||||
Console.log(jsonArray);
|
||||
}
|
||||
@ -29,7 +29,7 @@ public class JSONUtilTest {
|
||||
* 数字解析为JSONArray报错
|
||||
*/
|
||||
@Test(expected = JSONException.class)
|
||||
public void parseNumberTest(){
|
||||
public void parseNumberTest() {
|
||||
JSONArray json = JSONUtil.parseArray(123L);
|
||||
Console.log(json);
|
||||
}
|
||||
@ -38,7 +38,7 @@ public class JSONUtilTest {
|
||||
* 数字解析为JSONObject忽略
|
||||
*/
|
||||
@Test
|
||||
public void parseNumberTest2(){
|
||||
public void parseNumberTest2() {
|
||||
JSONObject json = JSONUtil.parseObj(123L);
|
||||
Assert.assertEquals(new JSONObject(), json);
|
||||
}
|
||||
@ -156,11 +156,18 @@ public class JSONUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleTest(){
|
||||
public void doubleTest() {
|
||||
String json = "{\"test\": 12.00}";
|
||||
final JSONObject jsonObject = JSONUtil.parseObj(json);
|
||||
//noinspection BigDecimalMethodWithoutRoundingCalled
|
||||
Assert.assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseObjTest() {
|
||||
final JSONObject jsonObject = JSONUtil.parseObj("{\n" +
|
||||
" \"test\": \"\\\\地库地库\",\n" +
|
||||
"}");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user