最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func1
+ * @return {@link Func1Rt}
*/
public static Func1Rt
uncheck(Func1
expression) {
- return uncheck(expression, new RuntimeException());
+ return uncheck(expression, RuntimeException::new);
}
@@ -89,10 +89,10 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc
+ * @return {@link VoidFuncRt}
*/
public static
VoidFuncRt
uncheck(VoidFunc
expression) {
- return uncheck(expression, new RuntimeException());
+ return uncheck(expression, RuntimeException::new);
}
/**
@@ -100,10 +100,10 @@ public class CheckedUtil {
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
* @param expression 运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc0
+ * @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression) {
- return uncheck(expression, new RuntimeException());
+ return uncheck(expression, RuntimeException::new);
}
/**
@@ -112,110 +112,106 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc1
+ * @return {@link VoidFunc1Rt}
*/
public static
VoidFunc1Rt
uncheck(VoidFunc1
expression) {
- return uncheck(expression, new RuntimeException());
+ return uncheck(expression, RuntimeException::new);
}
/**
- * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常
+ * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
- * @param expression Lambda表达式
- * @param rte 期望抛出的运行时异常
- * @param
运行时传入的参数类型
- * @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func
+ * @param expression Lambda表达式
+ * @param rteSupplier 转化运行时异常的表达式
+ * @param 运行时传入的参数类型
+ * @param 最终返回的数据类型
+ * @return {@link FuncRt}
*/
- public static FuncRt
uncheck(Func
expression, RuntimeException rte) {
+ public static
FuncRt
uncheck(Func
expression, Supplier1 rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
return expression.call(t);
} catch (Exception e) {
- if (rte == null) {
+ if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
- rte.initCause(e);
- throw rte;
+ throw rteSupplier.get(e);
}
}
};
}
/**
- * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常
+ * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
- * @param expression Lambda表达式
- * @param rte 期望抛出的运行时异常
- * @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func0
+ * @param expression Lambda表达式
+ * @param rteSupplier 转化运行时异常的表达式
+ * @param 最终返回的数据类型
+ * @return {@link Func0Rt}
*/
- public static Func0Rt uncheck(Func0 expression, RuntimeException rte) {
+ public static Func0Rt uncheck(Func0 expression, Supplier1 rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return () -> {
try {
return expression.call();
} catch (Exception e) {
- if (rte == null) {
+ if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
- rte.initCause(e);
- throw rte;
+ throw rteSupplier.get(e);
}
}
};
}
/**
- * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常
+ * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
- * @param expression Lambda表达式
- * @param rte 期望抛出的运行时异常
- * @param 运行时传入的参数类型
- * @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func1
+ * @param expression Lambda表达式
+ * @param rteSupplier 转化运行时异常的表达式
+ * @param 运行时传入的参数类型
+ * @param 最终返回的数据类型
+ * @return {@link Func1Rt}
*/
- public static Func1Rt
uncheck(Func1
expression, RuntimeException rte) {
+ public static
Func1Rt
uncheck(Func1
expression, Supplier1 rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
return expression.call(t);
} catch (Exception e) {
- if (rte == null) {
+ if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
- rte.initCause(e);
- throw rte;
+ throw rteSupplier.get(e);
}
}
};
}
/**
- * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常
+ * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
- * @param expression Lambda表达式
- * @param rte 期望抛出的运行时异常
- * @param 运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc
+ * @param expression Lambda表达式
+ * @param rteSupplier 转化运行时异常的表达式
+ * @param
运行时传入的参数类型
+ * @return {@link VoidFuncRt}
*/
- public static
VoidFuncRt
uncheck(VoidFunc
expression, RuntimeException rte) {
+ public static
VoidFuncRt
uncheck(VoidFunc
expression, Supplier1 rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
expression.call(t);
} catch (Exception e) {
- if (rte == null) {
+ if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
- rte.initCause(e);
- throw rte;
+ throw rteSupplier.get(e);
}
}
};
@@ -228,7 +224,7 @@ public class CheckedUtil {
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
- * @return cn.hutool.core.lang.func.VoidFunc0
+ * @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -246,27 +242,48 @@ public class CheckedUtil {
};
}
+ /**
+ * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
+ * 如此一来,代码中就不用显示的try-catch转化成运行时异常
+ *
+ * @param expression Lambda表达式
+ * @param rteSupplier 转化运行时异常的表达式
+ * @return {@link VoidFunc0Rt}
+ */
+ public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) {
+ Objects.requireNonNull(expression, "expression can not be null");
+ return () -> {
+ try {
+ expression.call();
+ } catch (Exception e) {
+ if (rteSupplier == null) {
+ throw new RuntimeException(e);
+ } else {
+ throw rteSupplier.get(e);
+ }
+ }
+ };
+ }
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
- * @param expression Lambda表达式
- * @param rte 期望抛出的运行时异常
- * @param 运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc1
+ * @param expression Lambda表达式
+ * @param rteSupplier 转化运行时异常的表达式
+ * @param
运行时传入的参数类型
+ * @return {@link VoidFunc1Rt}
*/
- public static
VoidFunc1Rt
uncheck(VoidFunc1
expression, RuntimeException rte) {
+ public static
VoidFunc1Rt
uncheck(VoidFunc1
expression, Supplier1 rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
expression.call(t);
} catch (Exception e) {
- if (rte == null) {
+ if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
- rte.initCause(e);
- throw rte;
+ throw rteSupplier.get(e);
}
}
};
@@ -274,27 +291,33 @@ public class CheckedUtil {
public interface FuncRt extends Func
{
@SuppressWarnings("unchecked")
+ @Override
R call(P... parameters) throws RuntimeException;
}
public interface Func0Rt extends Func0 {
+ @Override
R call() throws RuntimeException;
}
public interface Func1Rt extends Func1
{
+ @Override
R call(P parameter) throws RuntimeException;
}
public interface VoidFuncRt
extends VoidFunc
{
@SuppressWarnings("unchecked")
+ @Override
void call(P... parameters) throws RuntimeException;
}
public interface VoidFunc0Rt extends VoidFunc0 {
+ @Override
void call() throws RuntimeException;
}
public interface VoidFunc1Rt
extends VoidFunc1
{
+ @Override
void call(P parameter) throws RuntimeException;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
index 3d4b80858..e0622f7fc 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
@@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
@@ -32,6 +33,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
@@ -40,7 +42,11 @@ import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
-import java.nio.file.*;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -526,24 +532,39 @@ public class FileUtil extends PathUtil {
/**
* 计算目录或文件的总大小
* 当给定对象为文件时,直接调用 {@link File#length()}
- * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ * 此方法不包括目录本身的占用空间大小。
*
* @param file 目录或文件,null或者文件不存在返回0
* @return 总大小,bytes长度
*/
public static long size(File file) {
+ return size(file, false);
+ }
+
+ /**
+ * 计算目录或文件的总大小
+ * 当给定对象为文件时,直接调用 {@link File#length()}
+ * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ *
+ * @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)) {
return 0;
}
if (file.isDirectory()) {
- long size = 0L;
+ long size = includeDirSize ? file.length() : 0;
File[] subFiles = file.listFiles();
if (ArrayUtil.isEmpty(subFiles)) {
return 0L;// empty directory
}
for (File subFile : subFiles) {
- size += size(subFile);
+ size += size(subFile, includeDirSize);
}
return size;
} else {
@@ -551,6 +572,31 @@ public class FileUtil extends PathUtil {
}
}
+ /**
+ * 计算文件的总行数
+ * 读取文件采用系统默认编码,一般乱码不会造成行数错误。
+ *
+ * @param file 文件
+ * @return 该文件总行数
+ * @since 5.7.22
+ */
+ public static int getTotalLines(File file) {
+ if (false == isFile(file)) {
+ throw new IORuntimeException("Input must be a File");
+ }
+ try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) {
+ // 设置起始为1
+ lineNumberReader.setLineNumber(1);
+ // 跳过文件中内容
+ //noinspection ResultOfMethodCallIgnored
+ lineNumberReader.skip(Long.MAX_VALUE);
+ // 获取当前行号
+ return lineNumberReader.getLineNumber();
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+
/**
* 给定文件或目录的最后修改时间是否晚于给定时间
*
@@ -811,7 +857,7 @@ public class FileUtil extends PathUtil {
/**
* 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹
- * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
+ * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
*
* @param dir 目录
* @return 创建的目录
@@ -821,12 +867,48 @@ public class FileUtil extends PathUtil {
return null;
}
if (false == dir.exists()) {
- //noinspection ResultOfMethodCallIgnored
- dir.mkdirs();
+ mkdirsSafely(dir, 5, 1);
}
return dir;
}
+ /**
+ * 安全地级联创建目录 (确保并发环境下能创建成功)
+ *
+ *
+ * 并发环境下,假设 test 目录不存在,如果线程A mkdirs "test/A" 目录,线程B mkdirs "test/B"目录,
+ * 其中一个线程可能会失败,进而导致以下代码抛出 FileNotFoundException 异常
+ *
+ * file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中,返回 false
+ * file.createNewFile(); // 抛出 IO 异常,因为该线程无法感知到父目录已被创建
+ *
+ *
+ * @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();
+ }
+
/**
* 创建临时文件
* 创建后的文件名为 prefix[Randon].tmp
@@ -839,6 +921,54 @@ public class FileUtil extends PathUtil {
return createTempFile("hutool", null, dir, true);
}
+ /**
+ * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。
+ * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。
+ * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"};
+ * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。
+ * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。
+ * @return 临时文件
+ * @throws IORuntimeException IO异常
+ * @since 5.7.22
+ */
+ public static File createTempFile() throws IORuntimeException {
+ return createTempFile("hutool", null, null, true);
+ }
+
+ /**
+ * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。
+ * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。
+ * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"};
+ * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。
+ * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。
+ * @param suffix 后缀,如果null则使用默认.tmp
+ * @param isReCreat 是否重新创建文件(删掉原来的,创建新的)
+ * @return 临时文件
+ * @throws IORuntimeException IO异常
+ * @since 5.7.22
+ */
+ public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException {
+ return createTempFile("hutool", suffix, null, isReCreat);
+ }
+
+ /**
+ * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。
+ * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。
+ * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"};
+ * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。
+ * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。
+ *
+ * @param prefix 前缀,至少3个字符
+ * @param suffix 后缀,如果null则使用默认.tmp
+ * @param isReCreat 是否重新创建文件(删掉原来的,创建新的)
+ * @return 临时文件
+ * @throws IORuntimeException IO异常
+ * @since 5.7.22
+ */
+ public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException {
+ return createTempFile(prefix, suffix, null, isReCreat);
+ }
+
/**
* 创建临时文件
* 创建后的文件名为 prefix[Randon].tmp
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
index 2db343457..806105051 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
@@ -87,12 +87,49 @@ public class NioUtil {
Assert.notNull(outChannel, "Out channel is null!");
try {
- return inChannel.transferTo(0, inChannel.size(), outChannel);
+ return copySafely(inChannel, outChannel);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
+ /**
+ * 文件拷贝实现
+ *
+ *
+ * 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());
+ * }
+ *
+ *
+ * @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
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java b/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java
index 0743ed5e3..26126a658 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java
@@ -17,7 +17,7 @@ public class CRC16 implements Checksum, Serializable {
private final CRC16Checksum crc16;
- public CRC16(){
+ public CRC16() {
this(new CRC16IBM());
}
@@ -26,10 +26,31 @@ public class CRC16 implements Checksum, Serializable {
*
* @param crc16Checksum {@link CRC16Checksum} 实现
*/
- public CRC16(CRC16Checksum crc16Checksum){
+ public CRC16(CRC16Checksum crc16Checksum) {
this.crc16 = crc16Checksum;
}
+ /**
+ * 获取16进制的CRC16值
+ *
+ * @return 16进制的CRC16值
+ * @since 5.7.22
+ */
+ public String getHexValue() {
+ return this.crc16.getHexValue();
+ }
+
+ /**
+ * 获取16进制的CRC16值
+ *
+ * @param isPadding 不足4位时,是否填充0以满足位数
+ * @return 16进制的CRC16值,4位
+ * @since 5.7.22
+ */
+ public String getHexValue(boolean isPadding) {
+ return crc16.getHexValue(isPadding);
+ }
+
@Override
public long getValue() {
return crc16.getValue();
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java
index cb4179b95..3fcdea20a 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java
@@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable {
private static final long serialVersionUID = 1L;
private final File file;
+ private final long lastModified;
private final String name;
// ----------------------------------------------------------------------- Constructor start
@@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable {
public FileResource(File file, String fileName) {
Assert.notNull(file, "File must be not null !");
this.file = file;
+ this.lastModified = file.lastModified();
this.name = ObjectUtil.defaultIfNull(fileName, file::getName);
}
@@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable {
return this.file;
}
+ @Override
+ public boolean isModified() {
+ return this.lastModified != file.lastModified();
+ }
+
/**
* 返回路径
* @return 返回URL路径
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java
index 8464aa808..608265bce 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java
@@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator
+ * 一般用于文件类资源,检查文件是否被修改过。
+ *
+ * @return 是否变更
+ * @since 5.7.21
+ */
+ default boolean isModified(){
+ return false;
+ }
+
/**
* 将资源内容写出到流,不关闭输出流,但是关闭资源流
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java
index c6b18ac44..49b7d90ec 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java
@@ -1,9 +1,10 @@
package cn.hutool.core.io.resource;
-import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.EnumerationIter;
+import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.lang.Filter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.StrUtil;
@@ -21,7 +22,6 @@ import java.util.List;
* Resource资源工具类
*
* @author Looly
- *
*/
public class ResourceUtil {
@@ -40,7 +40,7 @@ public class ResourceUtil {
* 读取Classpath下的资源为字符串
*
* @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath)
- * @param charset 编码
+ * @param charset 编码
* @return 资源内容
* @since 3.1.1
*/
@@ -102,7 +102,7 @@ public class ResourceUtil {
* 从ClassPath资源中获取{@link BufferedReader}
*
* @param resource ClassPath资源
- * @param charset 编码
+ * @param charset 编码
* @return {@link InputStream}
* @since 3.1.2
*/
@@ -139,13 +139,24 @@ public class ResourceUtil {
* @return 资源列表
*/
public static List getResources(String resource) {
- final Enumeration resources;
- try {
- resources = ClassLoaderUtil.getClassLoader().getResources(resource);
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
- return CollUtil.newArrayList(resources);
+ return getResources(resource, null);
+ }
+
+ /**
+ * 获取指定路径下的资源列表
+ * 路径格式必须为目录格式,用/分隔,例如:
+ *
+ *
+ * config/a
+ * spring/xml
+ *
+ *
+ * @param resource 资源路径
+ * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素
+ * @return 资源列表
+ */
+ public static List getResources(String resource, Filter filter) {
+ return IterUtil.filterToList(getResourceIter(resource), filter);
}
/**
@@ -174,7 +185,7 @@ public class ResourceUtil {
/**
* 获得资源相对路径对应的URL
*
- * @param resource 资源相对路径,{@code null}和""都表示classpath根路径
+ * @param resource 资源相对路径,{@code null}和""都表示classpath根路径
* @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath
* @return {@link URL}
*/
@@ -192,8 +203,8 @@ public class ResourceUtil {
* @since 3.2.1
*/
public static Resource getResourceObj(String path) {
- if(StrUtil.isNotBlank(path)) {
- if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) {
+ if (StrUtil.isNotBlank(path)) {
+ if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) {
return new FileResource(path);
}
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java
index fd6b23044..21f24b2ef 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java
@@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
+import java.net.URI;
import java.net.URL;
/**
@@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{
private static final long serialVersionUID = 1L;
protected URL url;
+ private long lastModified = 0;
protected String name;
//-------------------------------------------------------------------------------------- Constructor start
+ /**
+ * 构造
+ * @param uri URI
+ * @since 5.7.21
+ */
+ public UrlResource(URI uri) {
+ this(URLUtil.url(uri), null);
+ }
+
/**
* 构造
* @param url URL
@@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{
*/
public UrlResource(URL url, String name) {
this.url = url;
+ if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){
+ this.lastModified = FileUtil.file(url).lastModified();
+ }
this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null));
}
@@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{
return URLUtil.getStream(url);
}
+ @Override
+ public boolean isModified() {
+ // lastModified == 0表示此资源非文件资源
+ return (0 != this.lastModified) && this.lastModified != getFile().lastModified();
+ }
+
/**
* 获得File
* @return {@link File}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java
new file mode 100755
index 000000000..2295d97c2
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java
@@ -0,0 +1,107 @@
+package cn.hutool.core.io.resource;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ClassLoaderUtil;
+import cn.hutool.core.util.ReflectUtil;
+
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+
+/**
+ * VFS资源封装
+ * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+
+ * 参考:org.springframework.core.io.VfsUtils
+ *
+ * @author looly, Spring
+ * @since 5.7.21
+ */
+public class VfsResource implements Resource {
+ private static final String VFS3_PKG = "org.jboss.vfs.";
+
+ private static final Method VIRTUAL_FILE_METHOD_EXISTS;
+ private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM;
+ private static final Method VIRTUAL_FILE_METHOD_GET_SIZE;
+ private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED;
+ private static final Method VIRTUAL_FILE_METHOD_TO_URL;
+ private static final Method VIRTUAL_FILE_METHOD_GET_NAME;
+
+ static {
+ Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile");
+ try {
+ VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists");
+ VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream");
+ VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize");
+ VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified");
+ VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL");
+ VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName");
+ } catch (NoSuchMethodException ex) {
+ throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex);
+ }
+ }
+
+ /**
+ * org.jboss.vfs.VirtualFile实例对象
+ */
+ private final Object virtualFile;
+ private final long lastModified;
+
+ /**
+ * 构造
+ *
+ * @param resource org.jboss.vfs.VirtualFile实例对象
+ */
+ public VfsResource(Object resource) {
+ Assert.notNull(resource, "VirtualFile must not be null");
+ this.virtualFile = resource;
+ this.lastModified = getLastModified();
+ }
+
+ /**
+ * VFS文件是否存在
+ *
+ * @return 文件是否存在
+ */
+ public boolean exists() {
+ return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS);
+ }
+
+ @Override
+ public String getName() {
+ return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME);
+ }
+
+ @Override
+ public URL getUrl() {
+ return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL);
+ }
+
+ @Override
+ public InputStream getStream() {
+ return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM);
+ }
+
+ @Override
+ public boolean isModified() {
+ return this.lastModified != getLastModified();
+ }
+
+ /**
+ * 获得VFS文件最后修改时间
+ *
+ * @return 最后修改时间
+ */
+ public long getLastModified() {
+ return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED);
+ }
+
+ /**
+ * 获取VFS文件大小
+ *
+ * @return VFS文件大小
+ */
+ public long size() {
+ return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE);
+ }
+
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java
new file mode 100644
index 000000000..b40a2c387
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java
@@ -0,0 +1,23 @@
+package cn.hutool.core.lang.func;
+
+/**
+ * 3参数Consumer
+ *
+ * @param 参数一类型
+ * @param 参数二类型
+ * @param 参数三类型
+ * @author TomXin
+ * @since 5.7.22
+ */
+@FunctionalInterface
+public interface Consumer3 {
+
+ /**
+ * 接收参数方法
+ *
+ * @param p1 参数一
+ * @param p2 参数二
+ * @param p3 参数三
+ */
+ void accept(P1 p1, P2 p2, P3 p3);
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java
new file mode 100755
index 000000000..376c91a2a
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java
@@ -0,0 +1,32 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 1参数Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier1 {
+ /**
+ * 生成实例的方法
+ *
+ * @param p1 参数一
+ * @return 目标对象
+ */
+ T get(P1 p1);
+
+ /**
+ * 将带有参数的Supplier转换为无参{@link Supplier}
+ *
+ * @param p1 参数1
+ * @return {@link Supplier}
+ */
+ default Supplier toSupplier(P1 p1) {
+ return () -> get(p1);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java
new file mode 100755
index 000000000..6d5e9ce80
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java
@@ -0,0 +1,36 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 两个参数的Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @param 参数二 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier2 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @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 toSupplier(P1 p1, P2 p2) {
+ return () -> get(p1, p2);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java
new file mode 100755
index 000000000..50324e55b
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java
@@ -0,0 +1,39 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 3参数Supplier
+ *
+ * @param 目标类型
+ * @param 参数一类型
+ * @param 参数二类型
+ * @param 参数三类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier3 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @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 toSupplier(P1 p1, P2 p2, P3 p3) {
+ return () -> get(p1, p2, p3);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java
new file mode 100755
index 000000000..c715b8693
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java
@@ -0,0 +1,42 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 4参数Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @param 参数二 类型
+ * @param 参数三 类型
+ * @param 参数四 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier4 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @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 toSupplier(P1 p1, P2 p2, P3 p3, P4 p4) {
+ return () -> get(p1, p2, p3, p4);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java
new file mode 100755
index 000000000..121a2ce9c
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java
@@ -0,0 +1,45 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 5参数Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @param 参数二 类型
+ * @param 参数三 类型
+ * @param 参数四 类型
+ * @param 参数五 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier5 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @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 toSupplier(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
+ return () -> get(p1, p2, p3, p4, p5);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java
index aad2da056..b5558ea74 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java
@@ -1,5 +1,7 @@
package cn.hutool.core.lang.hash;
+import cn.hutool.core.util.ByteUtil;
+
import java.util.Arrays;
/**
@@ -140,11 +142,11 @@ public class CityHash {
len = (len - 1) & ~63;
int pos = 0;
do {
- x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
- y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
+ x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
+ y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
x ^= w.getHighValue();
y += v.getLowValue() + fetch64(data, pos + 40);
- z = rotate(z + w.getLowValue(), 33) * k1;
+ z = rotate64(z + w.getLowValue(), 33) * k1;
v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue());
w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16));
// swap z,x value
@@ -221,19 +223,19 @@ public class CityHash {
long x = seed.getLowValue();
long y = seed.getHighValue();
long z = len * k1;
- v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start));
- v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
- w.setLowValue(rotate(y + z, 35) * k1 + x);
- w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1);
+ v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start));
+ v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
+ w.setLowValue(rotate64(y + z, 35) * k1 + x);
+ w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1);
// This is the same inner loop as CityHash64(), manually unrolled.
int pos = start;
do {
- x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
- y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
+ x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
+ y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
x ^= w.getHighValue();
y += v.getLowValue() + fetch64(byteArray, pos + 40);
- z = rotate(z + w.getLowValue(), 33) * k1;
+ z = rotate64(z + w.getLowValue(), 33) * k1;
v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
@@ -241,11 +243,11 @@ public class CityHash {
x = z;
z = swapValue;
pos += 64;
- x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
- y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
+ x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
+ y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
x ^= w.getHighValue();
y += v.getLowValue() + fetch64(byteArray, pos + 40);
- z = rotate(z + w.getLowValue(), 33) * k1;
+ z = rotate64(z + w.getLowValue(), 33) * k1;
v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
swapValue = x;
@@ -254,16 +256,16 @@ public class CityHash {
pos += 64;
len -= 128;
} while (len >= 128);
- x += rotate(v.getLowValue() + z, 49) * k0;
- y = y * k0 + rotate(w.getHighValue(), 37);
- z = z * k0 + rotate(w.getLowValue(), 27);
+ x += rotate64(v.getLowValue() + z, 49) * k0;
+ y = y * k0 + rotate64(w.getHighValue(), 37);
+ z = z * k0 + rotate64(w.getLowValue(), 27);
w.setLowValue(w.getLowValue() * 9);
v.setLowValue(v.getLowValue() * k0);
// If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
for (int tail_done = 0; tail_done < len; ) {
tail_done += 32;
- y = rotate(x + y, 42) * k0 + v.getHighValue();
+ y = rotate64(x + y, 42) * k0 + v.getHighValue();
w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16));
x = x * k0 + w.getLowValue();
z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done);
@@ -321,8 +323,8 @@ public class CityHash {
long mul = k2 + len * 2L;
long a = fetch64(byteArray, 0) + k2;
long b = fetch64(byteArray, len - 8);
- long c = rotate(b, 37) * mul + a;
- long d = (rotate(a, 25) + b) * mul;
+ long c = rotate64(b, 37) * mul + a;
+ long d = (rotate64(a, 25) + b) * mul;
return hashLen16(c, d, mul);
}
if (len >= 4) {
@@ -349,8 +351,8 @@ public class CityHash {
long b = fetch64(byteArray, 8);
long c = fetch64(byteArray, len - 8) * mul;
long d = fetch64(byteArray, len - 16) * k2;
- return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d,
- a + rotate(b + k2, 18) + c, mul);
+ return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d,
+ a + rotate64(b + k2, 18) + c, mul);
}
private static long hashLen33to64(byte[] byteArray) {
@@ -364,10 +366,10 @@ public class CityHash {
long f = fetch64(byteArray, 24) * 9;
long g = fetch64(byteArray, len - 8);
long h = fetch64(byteArray, len - 16) * mul;
- long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9;
+ long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9;
long v = ((a + g) ^ d) + f + 1;
long w = Long.reverseBytes((u + v) * mul) + h;
- long x = rotate(e + f, 42) + c;
+ long x = rotate64(e + f, 42) + c;
long y = (Long.reverseBytes((v + w) * mul) + g) * mul;
long z = e + f + c;
a = Long.reverseBytes((x + z) * mul + y) + b;
@@ -375,37 +377,15 @@ public class CityHash {
return b + x;
}
- private static long loadUnaligned64(final byte[] byteArray, final int start) {
- long result = 0;
- OrderIter orderIter = new OrderIter(8);
- while (orderIter.hasNext()) {
- int next = orderIter.next();
- long value = (byteArray[next + start] & 0xffL) << (next * 8);
- result |= value;
- }
- return result;
- }
-
- private static int loadUnaligned32(final byte[] byteArray, final int start) {
- int result = 0;
- OrderIter orderIter = new OrderIter(4);
- while (orderIter.hasNext()) {
- int next = orderIter.next();
- int value = (byteArray[next + start] & 0xff) << (next * 8);
- result |= value;
- }
- return result;
- }
-
- private static long fetch64(byte[] byteArray, final int start) {
- return loadUnaligned64(byteArray, start);
+ private static long fetch64(byte[] byteArray, int start) {
+ return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN);
}
private static int fetch32(byte[] byteArray, final int start) {
- return loadUnaligned32(byteArray, start);
+ return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN);
}
- private static long rotate(long val, int shift) {
+ private static long rotate64(long val, int shift) {
// Avoid shifting by 64: doing so yields an undefined result.
return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift)));
}
@@ -465,11 +445,11 @@ public class CityHash {
private static Number128 weakHashLen32WithSeeds(
long w, long x, long y, long z, long a, long b) {
a += w;
- b = rotate(b + a + z, 21);
+ b = rotate64(b + a + z, 21);
long c = a;
a += x;
a += y;
- b += rotate(a, 44);
+ b += rotate64(a, 44);
return new Number128(a + z, b + c);
}
@@ -515,24 +495,5 @@ public class CityHash {
b = hashLen16(d, b);
return new Number128(a ^ b, hashLen16(b, a));
}
-
- private static class OrderIter {
- private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
-
- private final int size;
- private int index;
-
- OrderIter(int size) {
- this.size = size;
- }
-
- boolean hasNext() {
- return index < size;
- }
-
- int next() {
- return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++);
- }
- }
//------------------------------------------------------------------------------------------------------- Private method end
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java
new file mode 100644
index 000000000..bbf40c882
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java
@@ -0,0 +1,217 @@
+package cn.hutool.core.lang.hash;
+
+import cn.hutool.core.util.ByteUtil;
+
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。
+ * 除了卓越的性能外,他们还以算法生成而著称。
+ *
+ *
+ * 官方实现:https://github.com/jandrewrogers/MetroHash
+ * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
+ * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/
+ * @author li
+ */
+public class MetroHash {
+
+ /**
+ * hash64 种子加盐
+ */
+ private final static long k0_64 = 0xD6D018F5;
+ private final static long k1_64 = 0xA2AA033B;
+ private final static long k2_64 = 0x62992FC1;
+ private final static long k3_64 = 0x30BC5B29;
+
+ /**
+ * hash128 种子加盐
+ */
+ private final static long k0_128 = 0xC83A91E1;
+ private final static long k1_128 = 0x8648DBDB;
+ private final static long k2_128 = 0x7BDEC03B;
+ private final static long k3_128 = 0x2F5870A5;
+
+ public static long hash64(byte[] data) {
+ return hash64(data, 1337);
+ }
+
+ public static Number128 hash128(byte[] data) {
+ return hash128(data, 1337);
+ }
+
+ public static long hash64(byte[] data, long seed) {
+ byte[] buffer = data;
+ long hash = (seed + k2_64) * k0_64;
+
+ long v0, v1, v2, v3;
+ v0 = hash;
+ v1 = hash;
+ v2 = hash;
+ v3 = hash;
+
+ if (buffer.length >= 32) {
+
+ while (buffer.length >= 32) {
+ v0 += littleEndian64(buffer, 0) * k0_64;
+ v0 = rotateLeft64(v0, -29) + v2;
+ v1 += littleEndian64(buffer, 8) * k1_64;
+ v1 = rotateLeft64(v1, -29) + v3;
+ v2 += littleEndian64(buffer, 24) * k2_64;
+ v2 = rotateLeft64(v2, -29) + v0;
+ v3 += littleEndian64(buffer, 32) * k3_64;
+ v3 = rotateLeft64(v3, -29) + v1;
+ buffer = Arrays.copyOfRange(buffer, 32, buffer.length);
+ }
+
+ v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64;
+ v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64;
+ v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64;
+ v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64;
+ hash += v0 ^ v1;
+ }
+
+ if (buffer.length >= 16) {
+ v0 = hash + littleEndian64(buffer, 0) * k2_64;
+ v0 = rotateLeft64(v0, -29) * k3_64;
+ v1 = hash + littleEndian64(buffer, 8) * k2_64;
+ v1 = rotateLeft64(v1, -29) * k3_64;
+ v0 ^= rotateLeft64(v0 * k0_64, -21) + v1;
+ v1 ^= rotateLeft64(v1 * k3_64, -21) + v0;
+ hash += v1;
+ buffer = Arrays.copyOfRange(buffer, 16, buffer.length);
+ }
+
+ if (buffer.length >= 8) {
+ hash += littleEndian64(buffer, 0) * k3_64;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ hash ^= rotateLeft64(hash, -55) * k1_64;
+ }
+
+ if (buffer.length >= 4) {
+ hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64;
+ hash ^= rotateLeft64(hash, -26) * k1_64;
+ buffer = Arrays.copyOfRange(buffer, 4, buffer.length);
+ }
+
+ if (buffer.length >= 2) {
+ hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64;
+ buffer = Arrays.copyOfRange(buffer, 2, buffer.length);
+ hash ^= rotateLeft64(hash, -48) * k1_64;
+ }
+
+ if (buffer.length >= 1) {
+ hash += (long) buffer[0] * k3_64;
+ hash ^= rotateLeft64(hash, -38) * k1_64;
+ }
+
+ hash ^= rotateLeft64(hash, -28);
+ hash *= k0_64;
+ hash ^= rotateLeft64(hash, -29);
+
+ return hash;
+ }
+
+ public static Number128 hash128(byte[] data, long seed) {
+ byte[] buffer = data;
+
+ long v0, v1, v2, v3;
+
+ v0 = (seed - k0_128) * k3_128;
+ v1 = (seed + k1_128) * k2_128;
+
+ if (buffer.length >= 32) {
+ v2 = (seed + k0_128) * k2_128;
+ v3 = (seed - k1_128) * k3_128;
+
+ while (buffer.length >= 32) {
+ v0 += littleEndian64(buffer, 0) * k0_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v0 = rotateRight(v0, 29) + v2;
+ v1 += littleEndian64(buffer, 0) * k1_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v1 = rotateRight(v1, 29) + v3;
+ v2 += littleEndian64(buffer, 0) * k2_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v2 = rotateRight(v2, 29) + v0;
+ v3 = littleEndian64(buffer, 0) * k3_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v3 = rotateRight(v3, 29) + v1;
+ }
+
+ v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128;
+ v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128;
+ v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128;
+ v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128;
+ }
+
+ if (buffer.length >= 16) {
+ v0 += littleEndian64(buffer, 0) * k2_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v0 = rotateRight(v0, 33) * k3_128;
+ v1 += littleEndian64(buffer, 0) * k2_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v1 = rotateRight(v1, 33) * k3_128;
+ v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128;
+ v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128;
+ }
+
+ if (buffer.length >= 8) {
+ v0 += littleEndian64(buffer, 0) * k2_128;
+ buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
+ v0 = rotateRight(v0, 33) * k3_128;
+ v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128;
+ }
+
+ if (buffer.length >= 4) {
+ v1 += (long) littleEndian32(buffer) * k2_128;
+ buffer = Arrays.copyOfRange(buffer, 4, buffer.length);
+ v1 = rotateRight(v1, 33) * k3_128;
+ v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128;
+ }
+
+ if (buffer.length >= 2) {
+ v0 += (long) littleEndian16(buffer) * k2_128;
+ buffer = Arrays.copyOfRange(buffer, 2, buffer.length);
+ v0 = rotateRight(v0, 33) * k3_128;
+ v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128;
+ }
+
+ if (buffer.length >= 1) {
+ v1 += (long) buffer[0] * k2_128;
+ v1 = rotateRight(v1, 33) * k3_128;
+ v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128;
+ }
+
+ v0 += rotateRight((v0 * k0_128) + v1, 13);
+ v1 += rotateRight((v1 * k1_128) + v0, 37);
+ v0 += rotateRight((v0 * k2_128) + v1, 13);
+ v1 += rotateRight((v1 * k3_128) + v0, 37);
+
+ return new Number128(v0, v1);
+ }
+
+
+ private static long littleEndian64(byte[] b, int start) {
+ return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN);
+ }
+
+ private static int littleEndian32(byte[] b) {
+ return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24;
+ }
+
+ private static int littleEndian16(byte[] b) {
+ return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN);
+ }
+
+ private static long rotateLeft64(long x, int k) {
+ int n = 64;
+ int s = k & (n - 1);
+ return x << s | x >> (n - s);
+ }
+
+ private static long rotateRight(long val, int shift) {
+ return (val >> shift) | (val << (64 - shift));
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java
index b3dd26961..94826602b 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java
@@ -1,9 +1,11 @@
package cn.hutool.core.lang.hash;
+import cn.hutool.core.util.ByteUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
+import java.nio.ByteOrder;
import java.nio.charset.Charset;
/**
@@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{
private static final int DEFAULT_SEED = 0;
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
+ private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN;
/**
* Murmur3 32-bit Hash值计算
@@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{
// body
for (int i = 0; i < nblocks; i++) {
- int i_4 = i << 2;
- int k = (data[i_4] & 0xff) //
- | ((data[i_4 + 1] & 0xff) << 8) //
- | ((data[i_4 + 2] & 0xff) << 16) //
- | ((data[i_4 + 3] & 0xff) << 24);
+ int i4 = i << 2;
+ int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER);
// mix functions
k *= C1_32;
@@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{
// body
for (int i = 0; i < nblocks; i++) {
final int i8 = i << 3;
- long k = ((long) data[i8] & 0xff) //
- | (((long) data[i8 + 1] & 0xff) << 8) //
- | (((long) data[i8 + 2] & 0xff) << 16) //
- | (((long) data[i8 + 3] & 0xff) << 24) //
- | (((long) data[i8 + 4] & 0xff) << 32)//
- | (((long) data[i8 + 5] & 0xff) << 40) //
- | (((long) data[i8 + 6] & 0xff) << 48) //
- | (((long) data[i8 + 7] & 0xff) << 56);
+ long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER);
// mix functions
k *= C1;
@@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{
// body
for (int i = 0; i < nblocks; i++) {
final int i16 = i << 4;
- long k1 = ((long) data[i16] & 0xff) //
- | (((long) data[i16 + 1] & 0xff) << 8) //
- | (((long) data[i16 + 2] & 0xff) << 16) //
- | (((long) data[i16 + 3] & 0xff) << 24) //
- | (((long) data[i16 + 4] & 0xff) << 32) //
- | (((long) data[i16 + 5] & 0xff) << 40) //
- | (((long) data[i16 + 6] & 0xff) << 48) //
- | (((long) data[i16 + 7] & 0xff) << 56);
-
- long k2 = ((long) data[i16 + 8] & 0xff) //
- | (((long) data[i16 + 9] & 0xff) << 8) //
- | (((long) data[i16 + 10] & 0xff) << 16) //
- | (((long) data[i16 + 11] & 0xff) << 24) //
- | (((long) data[i16 + 12] & 0xff) << 32) //
- | (((long) data[i16 + 13] & 0xff) << 40) //
- | (((long) data[i16 + 14] & 0xff) << 48) //
- | (((long) data[i16 + 15] & 0xff) << 56);
+ long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER);
+ long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER);
// mix functions for k1
k1 *= C1;
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java
index 4f6301095..12e0a8c35 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java
@@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash;
* @author hexiufeng
* @since 5.2.5
*/
-public class Number128 extends Number{
+public class Number128 extends Number {
private static final long serialVersionUID = 1L;
private long lowValue;
@@ -23,22 +23,47 @@ public class Number128 extends Number{
this.highValue = highValue;
}
+ /**
+ * 获取低位值
+ *
+ * @return 地位值
+ */
public long getLowValue() {
return lowValue;
}
- public long getHighValue() {
- return highValue;
- }
-
+ /**
+ * 设置低位值
+ *
+ * @param lowValue 低位值
+ */
public void setLowValue(long lowValue) {
this.lowValue = lowValue;
}
+ /**
+ * 获取高位值
+ *
+ * @return 高位值
+ */
+ public long getHighValue() {
+ return highValue;
+ }
+
+ /**
+ * 设置高位值
+ *
+ * @param hiValue 高位值
+ */
public void setHighValue(long hiValue) {
this.highValue = hiValue;
}
+ /**
+ * 获取高低位数组,long[0]:低位,long[1]:高位
+ *
+ * @return 高低位数组,long[0]:低位,long[1]:高位
+ */
public long[] getLongArray() {
return new long[]{lowValue, highValue};
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java
index 37af3f94a..ebc1e0c13 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java
@@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> {
}
final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false);
- List> rootTreeList = CollUtil.newArrayList();
E parentId;
for (Tree node : eTreeMap.values()) {
if (null == node) {
@@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> {
parentId = node.getParentId();
if (ObjectUtil.equals(this.root.getId(), parentId)) {
this.root.addChildren(node);
- rootTreeList.add(node);
continue;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java
new file mode 100755
index 000000000..7f8b065f6
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java
@@ -0,0 +1,73 @@
+package cn.hutool.core.map;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * 忽略大小写的{@link TreeMap}
+ * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖
+ *
+ * @author Looly
+ *
+ * @param 键类型
+ * @param 值类型
+ * @since 3.3.1
+ */
+public class CaseInsensitiveTreeMap extends CustomKeyMap {
+ private static final long serialVersionUID = 4043263744224569870L;
+
+ // ------------------------------------------------------------------------- Constructor start
+ /**
+ * 构造
+ */
+ public CaseInsensitiveTreeMap() {
+ this((Comparator super K>) null);
+ }
+
+ /**
+ * 构造
+ *
+ * @param m Map
+ * @since 3.1.2
+ */
+ public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) {
+ this();
+ this.putAll(m);
+ }
+
+ /**
+ * 构造
+ *
+ * @param m Map
+ * @since 3.1.2
+ */
+ public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) {
+ super(new TreeMap(m));
+ }
+
+ /**
+ * 构造
+ *
+ * @param comparator 比较器,{@code null}表示使用默认比较器
+ */
+ public CaseInsensitiveTreeMap(Comparator super K> comparator) {
+ super(new TreeMap<>(comparator));
+ }
+ // ------------------------------------------------------------------------- Constructor end
+
+ /**
+ * 将Key转为小写
+ *
+ * @param key KEY
+ * @return 小写KEY
+ */
+ @Override
+ protected Object customKey(Object key) {
+ if (key instanceof CharSequence) {
+ key = key.toString().toLowerCase();
+ }
+ return key;
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java
index a8409e6d0..5840d9204 100644
--- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java
@@ -1,6 +1,8 @@
package cn.hutool.core.map;
-import java.io.Serializable;
+
+import cn.hutool.core.builder.Builder;
+
import java.util.Map;
import java.util.function.Supplier;
@@ -11,7 +13,7 @@ import java.util.function.Supplier;
* @param Value类型
* @since 3.1.1
*/
-public class MapBuilder implements Serializable {
+public class MapBuilder implements Builder