最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func1
+ * @return {@link Func1Rt}
*/
public static Func1Rt
uncheck(Func1
expression) {
return uncheck(expression, new RuntimeException());
@@ -89,7 +89,7 @@ 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());
@@ -100,7 +100,7 @@ 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());
@@ -112,7 +112,7 @@ 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());
@@ -127,7 +127,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常
* @param
运行时传入的参数类型
* @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func
+ * @return {@link FuncRt}
*/
public static FuncRt
uncheck(Func
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -152,7 +152,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func0
+ * @return {@link Func0Rt}
*/
public static Func0Rt uncheck(Func0 expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -178,7 +178,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常
* @param 运行时传入的参数类型
* @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func1
+ * @return {@link Func1Rt}
*/
public static Func1Rt
uncheck(Func1
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -203,7 +203,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc
+ * @return {@link VoidFuncRt}
*/
public static
VoidFuncRt
uncheck(VoidFunc
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -228,7 +228,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");
@@ -254,7 +254,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc1
+ * @return {@link VoidFunc1Rt}
*/
public static
VoidFunc1Rt
uncheck(VoidFunc1
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
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..72eb18ca6 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;
@@ -526,24 +527,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 {
@@ -811,7 +827,7 @@ public class FileUtil extends PathUtil {
/**
* 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹
- * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
+ * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
*
* @param dir 目录
* @return 创建的目录
@@ -821,12 +837,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
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/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/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/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/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java
index 82821897d..e41e9b84f 100644
--- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java
@@ -8,12 +8,15 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import java.util.List;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
import java.util.stream.Collector;
+import java.util.stream.Collectors;
/**
* 可变的汇聚操作{@link Collector} 相关工具封装
@@ -140,6 +143,18 @@ public class CollectorUtil {
return groupingBy(classifier, HashMap::new, downstream);
}
+ /**
+ * 提供对null值友好的groupingBy操作的{@link Collector}实现
+ *
+ * @param classifier 分组依据
+ * @param 实体类型
+ * @param 实体中的分组依据对应类型,也是Map中key的类型
+ * @return {@link Collector}
+ */
+ public static Collector>>
+ groupingBy(Function super T, ? extends K> classifier) {
+ return groupingBy(classifier, Collectors.toList());
+ }
/**
* 对null友好的 toMap 操作的 {@link Collector}实现,默认使用HashMap
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
index 2d7bcad89..4a308c14d 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
@@ -3601,6 +3601,46 @@ public class CharSequenceUtil {
return stringBuilder.toString();
}
+ /**
+ * 替换指定字符串的指定区间内字符为指定字符串,字符串只重复一次
+ * 此方法使用{@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();
+ }
+
/**
* 替换所有正则匹配的文本,并使用自定义函数决定如何替换
* replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java
index 878011082..5c386214e 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java
@@ -1,6 +1,7 @@
package cn.hutool.core.text.csv;
import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
@@ -45,6 +46,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* 是否处于新行开始
*/
private boolean newline = true;
+ /**
+ * 是否首行,即CSV开始的位置,当初始化时默认为true,一旦写入内容,为false
+ */
+ private boolean isFirstLine = true;
// --------------------------------------------------------------------------------------------------- Constructor start
@@ -183,13 +188,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @throws IORuntimeException IO异常
*/
public CsvWriter write(String[]... lines) throws IORuntimeException {
- if (ArrayUtil.isNotEmpty(lines)) {
- for (final String[] values : lines) {
- appendLine(values);
- }
- flush();
- }
- return this;
+ return write(new ArrayIter<>(lines));
}
/**
@@ -320,9 +319,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
public CsvWriter writeComment(String comment) {
Assert.notNull(this.config.commentCharacter, "Comment is disable!");
try {
+ if(isFirstLine){
+ // 首行不补换行符
+ isFirstLine = false;
+ }else {
+ writer.write(config.lineDelimiter);
+ }
writer.write(this.config.commentCharacter);
writer.write(comment);
- writer.write(config.lineDelimiter);
newline = true;
} catch (IOException e) {
throw new IORuntimeException(e);
@@ -366,12 +370,17 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @param fields 字段列表 ({@code null} 值会被做为空值追加)
* @throws IOException IO异常
*/
- private void doAppendLine(final String... fields) throws IOException {
+ private void doAppendLine(String... fields) throws IOException {
if (null != fields) {
+ if(isFirstLine){
+ // 首行不补换行符
+ isFirstLine = false;
+ }else {
+ writer.write(config.lineDelimiter);
+ }
for (String field : fields) {
appendField(field);
}
- writer.write(config.lineDelimiter);
newline = true;
}
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java
index bc6af864b..7863d5c06 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java
@@ -1,7 +1,8 @@
/**
- * 提供CSV文件读写的封装,入口为CsvUtil
+ * 提供CSV文件读写的封装,入口为CsvUtil
+ * 规范见:https://datatracker.ietf.org/doc/html/rfc4180
*
* @author looly
*
*/
-package cn.hutool.core.text.csv;
\ No newline at end of file
+package cn.hutool.core.text.csv;
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java
index f20d26914..46f988c65 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java
@@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder;
*/
public class ByteUtil {
- public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN;
+ public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN;
+ /**
+ * CPU的字节序
+ */
+ public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
/**
* int转byte
@@ -130,16 +134,29 @@ public class ByteUtil {
* @return int值
*/
public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) {
+ return bytesToInt(bytes, 0, byteOrder);
+ }
+
+ /**
+ * byte[]转int值
+ * 自定义端序
+ *
+ * @param bytes byte数组
+ * @param byteOrder 端序
+ * @return int值
+ * @since 5.7.21
+ */
+ public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) {
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
- return bytes[0] & 0xFF | //
- (bytes[1] & 0xFF) << 8 | //
- (bytes[2] & 0xFF) << 16 | //
- (bytes[3] & 0xFF) << 24; //
+ return bytes[start] & 0xFF | //
+ (bytes[1 + start] & 0xFF) << 8 | //
+ (bytes[2 + start] & 0xFF) << 16 | //
+ (bytes[3 + start] & 0xFF) << 24; //
} else {
- return bytes[3] & 0xFF | //
- (bytes[2] & 0xFF) << 8 | //
- (bytes[1] & 0xFF) << 16 | //
- (bytes[0] & 0xFF) << 24; //
+ return bytes[3 + start] & 0xFF | //
+ (bytes[2 + start] & 0xFF) << 8 | //
+ (bytes[1 + start] & 0xFF) << 16 | //
+ (bytes[start] & 0xFF) << 24; //
}
}
@@ -243,16 +260,31 @@ public class ByteUtil {
* @return long值
*/
public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) {
+ return bytesToLong(bytes, 0, byteOrder);
+ }
+
+ /**
+ * byte数组转long
+ * 自定义端序
+ * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
+ *
+ * @param bytes byte数组
+ * @param start 计算数组开始位置
+ * @param byteOrder 端序
+ * @return long值
+ * @since 5.7.21
+ */
+ public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) {
long values = 0;
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
for (int i = (Long.BYTES - 1); i >= 0; i--) {
values <<= Byte.SIZE;
- values |= (bytes[i] & 0xff);
+ values |= (bytes[i + start] & 0xff);
}
} else {
for (int i = 0; i < Long.BYTES; i++) {
values <<= Byte.SIZE;
- values |= (bytes[i] & 0xff);
+ values |= (bytes[i + start] & 0xff);
}
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java
index 97ee1537b..bafe37995 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java
@@ -1,6 +1,7 @@
package cn.hutool.core.util;
import cn.hutool.core.lang.hash.CityHash;
+import cn.hutool.core.lang.hash.MetroHash;
import cn.hutool.core.lang.hash.MurmurHash;
import cn.hutool.core.lang.hash.Number128;
@@ -386,7 +387,7 @@ public class HashUtil {
if (ucChar <= 'Z' && ucChar >= 'A') {
ucChar = (char) (ucChar + 32);
}
- hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216;
+ hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216;
}
} else {
for (i = 1; i <= 96; i++) {
@@ -394,7 +395,7 @@ public class HashUtil {
if (ucChar <= 'Z' && ucChar >= 'A') {
ucChar = (char) (ucChar + 32);
}
- hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216;
+ hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216;
}
}
if (hash < 0) {
@@ -545,4 +546,46 @@ public class HashUtil {
public static long[] cityHash128(byte[] data, Number128 seed) {
return CityHash.hash128(data, seed).getLongArray();
}
+
+ /**
+ * MetroHash 算法64-bit实现
+ *
+ * @param data 数据
+ * @param seed 种子
+ * @return hash值
+ */
+ public static long metroHash64(byte[] data, long seed) {
+ return MetroHash.hash64(data, seed);
+ }
+
+ /**
+ * MetroHash 算法64-bit实现
+ *
+ * @param data 数据
+ * @return hash值
+ */
+ public static long metroHash64(byte[] data) {
+ return MetroHash.hash64(data);
+ }
+
+ /**
+ * MetroHash 算法128-bit实现
+ *
+ * @param data 数据
+ * @param seed 种子
+ * @return hash值,long[0]:低位,long[1]:高位
+ */
+ public static long[] metroHash128(byte[] data, long seed) {
+ return MetroHash.hash128(data,seed).getLongArray();
+ }
+
+ /**
+ * MetroHash 算法128-bit实现
+ *
+ * @param data 数据
+ * @return hash值,long[0]:低位,long[1]:高位
+ */
+ public static long[] metroHash128(byte[] data) {
+ return MetroHash.hash128(data).getLongArray();
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java
index 724312695..fde460ff2 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java
@@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil {
*/
public static final String WAR_URL_SEPARATOR = "*/";
+ /**
+ * 将{@link URI}转换为{@link URL}
+ *
+ * @param uri {@link URI}
+ * @return URL对象
+ * @see URI#toURL()
+ * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出
+ * @since 5.7.21
+ */
+ public static URL url(URI uri) throws UtilException{
+ if(null == uri){
+ return null;
+ }
+ try {
+ return uri.toURL();
+ } catch (MalformedURLException e) {
+ throw new UtilException(e);
+ }
+ }
+
/**
* 通过一个字符串形式的URL地址创建URL对象
*
@@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil {
* @since 4.1.1
*/
public static URL url(String url, URLStreamHandler handler) {
- Assert.notNull(url, "URL must not be null");
+ if(null == url){
+ return null;
+ }
// 兼容Spring的ClassPath路径
if (url.startsWith(CLASSPATH_URL_PREFIX)) {
@@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil {
* @since 5.5.2
*/
public static URI getStringURI(CharSequence content) {
+ if(null == content){
+ return null;
+ }
final String contentStr = StrUtil.addPrefixIfNot(content, "string:///");
return URI.create(contentStr);
}
@@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil {
* @since 3.0.9
*/
public static boolean isFileURL(URL url) {
+ Assert.notNull(url, "URL must be not null");
String protocol = url.getProtocol();
return (URL_PROTOCOL_FILE.equals(protocol) || //
URL_PROTOCOL_VFSFILE.equals(protocol) || //
@@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil {
* @return 是否为jar包URL
*/
public static boolean isJarURL(URL url) {
+ Assert.notNull(url, "URL must be not null");
final String protocol = url.getProtocol();
return (URL_PROTOCOL_JAR.equals(protocol) || //
URL_PROTOCOL_ZIP.equals(protocol) || //
@@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil {
* @since 4.1
*/
public static boolean isJarFileURL(URL url) {
+ Assert.notNull(url, "URL must be not null");
return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && //
url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT));
}
@@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil {
* @since 3.2.1
*/
public static InputStream getStream(URL url) {
- Assert.notNull(url);
+ Assert.notNull(url, "URL must be not null");
try {
return url.openStream();
} catch (IOException e) {
diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java
new file mode 100644
index 000000000..84f6d4581
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java
@@ -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 + "\"";
+ }
+ }
+ }
+
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java
index 912df5904..5cecd5de3 100644
--- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java
@@ -19,7 +19,7 @@ import java.util.Map;
public class ListUtilTest {
@Test
- public void splitTest(){
+ public void splitTest() {
List> lists = ListUtil.split(null, 3);
Assert.assertEquals(ListUtil.empty(), lists);
@@ -60,7 +60,7 @@ public class ListUtilTest {
}
@Test
- public void splitAvgTest(){
+ public void splitAvgTest() {
List> lists = ListUtil.splitAvg(null, 3);
Assert.assertEquals(ListUtil.empty(), lists);
@@ -80,13 +80,13 @@ public class ListUtilTest {
}
@Test(expected = IllegalArgumentException.class)
- public void splitAvgNotZero(){
+ public void splitAvgNotZero() {
// limit不能小于等于0
ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0);
}
@Test
- public void editTest(){
+ public void editTest() {
List a = ListUtil.toLinkedList("1", "2", "3");
final List filter = (List) CollUtil.edit(a, str -> "edit" + str);
Assert.assertEquals("edit1", filter.get(0));
@@ -104,7 +104,7 @@ public class ListUtilTest {
}
@Test
- public void pageTest(){
+ public void pageTest() {
List a = ListUtil.toLinkedList(1, 2, 3,4,5);
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[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray());
+
+ // 恢复默认值,避免影响其他测试用例
+ PageUtil.setFirstPageNo(0);
}
@Test
- public void subTest(){
+ public void subTest() {
final List of = ListUtil.of(1, 2, 3, 4);
final List sub = ListUtil.sub(of, 2, 4);
sub.remove(0);
@@ -181,10 +184,10 @@ public class ListUtilTest {
}
@Test
- public void sortByPropertyTest(){
+ public void sortByPropertyTest() {
@Data
@AllArgsConstructor
- class TestBean{
+ class TestBean {
private int order;
private String name;
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java
index f7ffeac24..e3815d7c0 100644
--- a/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java
@@ -1,15 +1,14 @@
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
-import org.junit.Assert;
-import org.junit.Test;
-
-import cn.hutool.core.date.DateUtil;
-
public class DateConvertTest {
@Test
@@ -28,7 +27,7 @@ public class DateConvertTest {
int dateLong = -1497600000;
Date value = Convert.toDate(dateLong);
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);
Assert.assertNotNull(sqlDate);
@@ -53,18 +52,18 @@ public class DateConvertTest {
java.sql.Date value2 = Convert.convert(java.sql.Date.class, timeLong);
Assert.assertEquals(timeLong, value2.getTime());
}
-
+
@Test
public void toLocalDateTimeTest() {
Date src = new Date();
-
+
LocalDateTime ldt = Convert.toLocalDateTime(src);
Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src));
-
+
Timestamp ts = Timestamp.from(src.toInstant());
ldt = Convert.toLocalDateTime(ts);
Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src));
-
+
String str = "2020-12-12 12:12:12.0";
ldt = Convert.toLocalDateTime(str);
Assert.assertEquals(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")), str);
diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java
index f9bec8251..5dc83a714 100644
--- a/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java
@@ -175,6 +175,26 @@ public class NumberChineseFormatterTest {
Assert.assertEquals("零点零伍", f1);
}
+ @Test
+ public void formatSimpleTest() {
+ String f1 = NumberChineseFormatter.formatSimple(1_2345);
+ Assert.assertEquals("1.23万", f1);
+ f1 = NumberChineseFormatter.formatSimple(-5_5555);
+ Assert.assertEquals("-5.56万", f1);
+ f1 = NumberChineseFormatter.formatSimple(1_2345_6789);
+ Assert.assertEquals("1.23亿", f1);
+ f1 = NumberChineseFormatter.formatSimple(-5_5555_5555);
+ Assert.assertEquals("-5.56亿", f1);
+ f1 = NumberChineseFormatter.formatSimple(1_2345_6789_1011L);
+ Assert.assertEquals("1.23万亿", f1);
+ f1 = NumberChineseFormatter.formatSimple(-5_5555_5555_5555L);
+ Assert.assertEquals("-5.56万亿", f1);
+ f1 = NumberChineseFormatter.formatSimple(123);
+ Assert.assertEquals("123", f1);
+ f1 = NumberChineseFormatter.formatSimple(-123);
+ Assert.assertEquals("-123", f1);
+ }
+
@Test
public void digitToChineseTest() {
String digitToChinese = Convert.digitToChinese(12_4124_1241_2421.12);
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java
index da29455fb..e2d86d1e9 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java
@@ -105,4 +105,12 @@ public class ChineseDateTest {
Assert.assertEquals("戊申猴年 五月初五", c1.toString());
Assert.assertEquals("戊申猴年 闰五月初五", c2.toString());
}
+
+ @Test
+ public void getChineseMonthTest2(){
+ //https://github.com/dromara/hutool/issues/2112
+ ChineseDate springFestival = new ChineseDate(DateUtil.parseDate("2022-02-01"));
+ final String chineseMonth = springFestival.getChineseMonth();
+ Assert.assertEquals("一月", chineseMonth);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java
index 24e7321a8..8b9dd7500 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java
@@ -3,7 +3,6 @@ package cn.hutool.core.date;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.BetweenFormatter.Level;
import cn.hutool.core.date.format.FastDateFormat;
-import cn.hutool.core.lang.Console;
import cn.hutool.core.util.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -694,6 +693,8 @@ public class DateUtilTest {
String dateStr = "Wed Sep 16 11:26:23 CST 2009";
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);
DateTime dateTime = DateUtil.parseCST(dateStr);
@@ -992,11 +993,13 @@ public class DateUtilTest {
@Test
public void parseSingleMonthAndDayTest() {
- final DateTime parse = DateUtil.parse("2021-1-1");
+ DateTime parse = DateUtil.parse("2021-1-1");
Assert.assertNotNull(parse);
Assert.assertEquals("2021-01-01 00:00:00", parse.toString());
- Console.log(DateUtil.parse("2021-1-22 00:00:00"));
+ parse = DateUtil.parse("2021-1-22 00:00:00");
+ Assert.assertNotNull(parse);
+ Assert.assertEquals("2021-01-22 00:00:00", parse.toString());
}
@Test
@@ -1004,4 +1007,17 @@ public class DateUtilTest {
final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER);
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);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java
index 8f35c59a5..57d6eb717 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java
@@ -197,4 +197,24 @@ public class LocalDateTimeUtilTest {
Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,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);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java b/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java
index dce94bb09..7a7929f39 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java
@@ -37,4 +37,15 @@ public class MonthTest {
lastDay = Month.of(Calendar.DECEMBER).getLastDay(true);
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();
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java
index fbfe82cb2..35be0f241 100644
--- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java
@@ -21,11 +21,8 @@ public class CheckedUtilTest {
@Test
public void sleepTest() {
-
VoidFunc0 func = () -> Thread.sleep(1000L);
func.callWithRuntimeException();
-
-
}
@@ -39,7 +36,6 @@ public class CheckedUtilTest {
} catch (Exception re) {
Assert.assertTrue(re instanceof RuntimeException);
}
-
}
@SuppressWarnings("ConstantConditions")
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java
index c08e5dbb1..b10b115eb 100644
--- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java
@@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateRange;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -12,8 +13,8 @@ import java.util.NoSuchElementException;
/**
* {@link Range} 单元测试
- * @author Looly
*
+ * @author Looly
*/
public class RangeTest {
@@ -36,6 +37,32 @@ public class RangeTest {
Assert.assertFalse(range.hasNext());
}
+ @Test
+ public void dateRangeFuncTest() {
+ DateTime start = DateUtil.parse("2021-01-01");
+ DateTime end = DateUtil.parse("2021-01-03");
+
+ List 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 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 -> sb2.append(DateTime.of(a).dayOfMonth()).append("#"));
+ Assert.assertEquals(sb2.toString(), StrUtil.EMPTY);
+ }
+
@Test
public void dateRangeTest2() {
DateTime start = DateUtil.parse("2021-01-31");
@@ -84,7 +111,7 @@ public class RangeTest {
}
@Test
- public void rangeDayOfYearTest(){
+ public void rangeDayOfYearTest() {
DateTime start = DateUtil.parse("2017-01-01");
DateTime end = DateUtil.parse("2017-01-05");
@@ -109,4 +136,39 @@ public class RangeTest {
Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0));
Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1));
}
+
+
+ @Test
+ public void rangeContains() {
+ // 开始区间
+ DateTime start = DateUtil.parse("2017-01-01");
+ DateTime end = DateUtil.parse("2017-01-31");
+ DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR);
+ // 结束区间
+ DateTime start1 = DateUtil.parse("2017-01-31");
+ DateTime end1 = DateUtil.parse("2017-02-02");
+ DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR);
+ // 交集
+ List dateTimes = DateUtil.rangeContains(startRange, endRange);
+ Assert.assertEquals(1, dateTimes.size());
+ Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0));
+ }
+
+ @Test
+ public void rangeNotContains() {
+ // 开始区间
+ DateTime start = DateUtil.parse("2017-01-01");
+ DateTime end = DateUtil.parse("2017-01-30");
+ DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR);
+ // 结束区间
+ DateTime start1 = DateUtil.parse("2017-01-01");
+ DateTime end1 = DateUtil.parse("2017-01-31");
+ DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR);
+ // 差集
+ List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange);
+
+ Assert.assertEquals(1, dateTimes1.size());
+ Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0));
+ }
+
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java
new file mode 100755
index 000000000..d253ffa2e
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java
@@ -0,0 +1,36 @@
+package cn.hutool.core.lang.hash;
+
+import cn.hutool.core.util.StrUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CityHashTest {
+
+ @Test
+ public void hash32Test() {
+ int hv = CityHash.hash32(StrUtil.utf8Bytes("你"));
+ Assert.assertEquals(1290029860, hv);
+
+ hv = CityHash.hash32(StrUtil.utf8Bytes("你好"));
+ Assert.assertEquals(1374181357, hv);
+
+ hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴"));
+ Assert.assertEquals(1475516842, hv);
+ hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
+ Assert.assertEquals(0x51020cae, hv);
+ }
+
+ @Test
+ public void hash64Test() {
+ long hv = CityHash.hash64(StrUtil.utf8Bytes("你"));
+ Assert.assertEquals(-4296898700418225525L, hv);
+
+ hv = CityHash.hash64(StrUtil.utf8Bytes("你好"));
+ Assert.assertEquals(-4294276205456761303L, hv);
+
+ hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴"));
+ Assert.assertEquals(272351505337503793L, hv);
+ hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
+ Assert.assertEquals(-8234735310919228703L, hv);
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java
new file mode 100644
index 000000000..5577a1bb5
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java
@@ -0,0 +1,93 @@
+package cn.hutool.core.lang.hash;
+
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * https://gitee.com/dromara/hutool/pulls/532
+ */
+public class MetroHashTest {
+
+ @Test
+ public void testEmpty() {
+ Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0)));
+ }
+
+ @Test
+ public void metroHash64Test() {
+ byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8);
+ final long hash64 = MetroHash.hash64(str);
+ Assert.assertEquals(62920234463891865L, hash64);
+ }
+
+ @Test
+ public void metroHash128Test() {
+ byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8);
+ final long[] hash128 = MetroHash.hash128(str).getLongArray();
+ Assert.assertEquals(4956592424592439349L, hash128[0]);
+ Assert.assertEquals(6301214698325086246L, hash128[1]);
+ }
+
+ /**
+ * 数据量越大 MetroHash 优势越明显,
+ */
+ @Test
+ @Ignore
+ public void bulkHashing64Test() {
+ String[] strArray = getRandomStringArray();
+ long startCity = System.currentTimeMillis();
+ for (String s : strArray) {
+ CityHash.hash64(s.getBytes());
+ }
+ long endCity = System.currentTimeMillis();
+
+ long startMetro = System.currentTimeMillis();
+ for (String s : strArray) {
+ MetroHash.hash64(StrUtil.utf8Bytes(s));
+ }
+ long endMetro = System.currentTimeMillis();
+
+ System.out.println("metroHash =============" + (endMetro - startMetro));
+ System.out.println("cityHash =============" + (endCity - startCity));
+ }
+
+
+ /**
+ * 数据量越大 MetroHash 优势越明显,
+ */
+ @Test
+ @Ignore
+ public void bulkHashing128Test() {
+ String[] strArray = getRandomStringArray();
+ long startCity = System.currentTimeMillis();
+ for (String s : strArray) {
+ CityHash.hash128(s.getBytes());
+ }
+ long endCity = System.currentTimeMillis();
+
+ long startMetro = System.currentTimeMillis();
+ for (String s : strArray) {
+ MetroHash.hash128(StrUtil.utf8Bytes(s));
+ }
+ long endMetro = System.currentTimeMillis();
+
+ System.out.println("metroHash =============" + (endMetro - startMetro));
+ System.out.println("cityHash =============" + (endCity - startCity));
+ }
+
+
+ private static String[] getRandomStringArray() {
+ String[] result = new String[10000000];
+ int index = 0;
+ while (index < 10000000) {
+ result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64));
+ }
+ return result;
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java
new file mode 100755
index 000000000..fcf946b5e
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java
@@ -0,0 +1,36 @@
+package cn.hutool.core.lang.hash;
+
+import cn.hutool.core.util.StrUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MurMurHashTest {
+
+ @Test
+ public void hash32Test() {
+ int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你"));
+ Assert.assertEquals(222142701, hv);
+
+ hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好"));
+ Assert.assertEquals(1188098267, hv);
+
+ hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴"));
+ Assert.assertEquals(-1898490321, hv);
+ hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
+ Assert.assertEquals(-1713131054, hv);
+ }
+
+ @Test
+ public void hash64Test() {
+ long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你"));
+ Assert.assertEquals(-1349759534971957051L, hv);
+
+ hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好"));
+ Assert.assertEquals(-7563732748897304996L, hv);
+
+ hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴"));
+ Assert.assertEquals(-766658210119995316L, hv);
+ hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
+ Assert.assertEquals(-7469283059271653317L, hv);
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
index 764167662..d6a1e290d 100644
--- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
@@ -348,4 +348,19 @@ public class UrlBuilderTest {
builder.setFragment(builder.getFragment() + "?timestamp=1640391380204");
Assert.assertEquals("https://www.hutool.cn/#/a/b?timestamp=1640391380204", builder.toString());
}
+
+ @Test
+ public void paramWithPlusTest(){
+ String url = "http://127.0.0.1/?" +
+ "Expires=1642734164&" +
+ "security-token=CAIS+AF1q6Ft5B2yfSjIr5fYEeju1b1ggpPee2KGpjlgQtdfl43urjz2IHtKdXRvBu8Xs" +
+ "/4wnmxX7f4YlqB6T55OSAmcNZEoPwKpT4zmMeT7oMWQweEurv" +
+ "/MQBqyaXPS2MvVfJ+OLrf0ceusbFbpjzJ6xaCAGxypQ12iN+/m6" +
+ "/Ngdc9FHHPPD1x8CcxROxFppeIDKHLVLozNCBPxhXfKB0ca0WgVy0EHsPnvm5DNs0uH1AKjkbRM9r6ceMb0M5NeW75kSMqw0eBMca7M7TVd8RAi9t0t1" +
+ "/IVpGiY4YDAWQYLv0rda7DOltFiMkpla7MmXqlft+hzcgeQY0pc" +
+ "/RqAAYRYVCBiyuzAexSiDiJX1VqWljg4jYp1sdyv3HpV3sXVcf6VH6AN9ot5YNTw4JNO0aNpLpLm93rRMrOKIOsve+OmNyZ4HS7qHQKt1qp7HY1A" +
+ "/wGhJstkAoGQt+CHSMwVdIx3bVT1+ZYnJdM/oIQ/90afw4EEEQaRE51Z0rQC7z8d";
+ final String build = UrlBuilder.of(url).build();
+ Assert.assertEquals(url, build);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
index 18b84edb2..07afd2b4a 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
@@ -22,6 +22,13 @@ public class CharSequenceUtilTest {
Assert.assertEquals(replace, result);
}
+ @Test
+ public void replaceByStrTest(){
+ String replace = "SSM15930297701BeryAllen";
+ String result = CharSequenceUtil.replace(replace, 5, 12, "***");
+ Assert.assertEquals("SSM15***01BeryAllen", result);
+ }
+
@Test
public void addPrefixIfNotTest(){
String str = "hutool";
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java
index a8800578a..62545d934 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java
@@ -12,4 +12,10 @@ public class NamingCaseTest {
.set("customerNickV2", "customer_nick_v2")
.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);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java
index e1a27b206..e8fc851e8 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java
@@ -8,7 +8,7 @@ import org.junit.Test;
import java.io.StringReader;
public class CsvParserTest {
-
+
@Test
public void parseTest1() {
StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc");
@@ -18,7 +18,7 @@ public class CsvParserTest {
Assert.assertEquals("b\"bba\"", row.getRawList().get(1));
IoUtil.close(parser);
}
-
+
@Test
public void parseTest2() {
StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc");
@@ -28,7 +28,7 @@ public class CsvParserTest {
Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1));
IoUtil.close(parser);
}
-
+
@Test
public void parseTest3() {
StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc");
@@ -38,7 +38,7 @@ public class CsvParserTest {
Assert.assertEquals("bba", row.getRawList().get(1));
IoUtil.close(parser);
}
-
+
@Test
public void parseTest4() {
StringReader reader = StrUtil.getReader("aaa,\"\",ccc");
@@ -48,4 +48,16 @@ public class CsvParserTest {
Assert.assertEquals("", row.getRawList().get(1));
IoUtil.close(parser);
}
+
+ @Test
+ public void parseEscapeTest(){
+ // https://datatracker.ietf.org/doc/html/rfc4180#section-2
+ // 第七条规则
+ StringReader reader = StrUtil.getReader("\"b\"\"bb\"");
+ CsvParser parser = new CsvParser(reader, null);
+ CsvRow row = parser.nextRow();
+ Assert.assertNotNull(row);
+ Assert.assertEquals(1, row.size());
+ Assert.assertEquals("b\"bb", row.get(0));
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java
index 18203196c..e79c9b784 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java
@@ -19,6 +19,8 @@ public class CsvWriterTest {
CharsetUtil.CHARSET_GBK, false, csvWriteConfig);
writer.writeHeaderLine("name", "gender", "address");
+ writer.writeLine("张三", "男", "XX市XX区");
+ writer.writeLine("李四", "男", "XX市XX区,01号");
writer.close();
}
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java
index b68b95ce9..ba7121f53 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java
@@ -10,9 +10,16 @@ public class ByteUtilTest {
@Test
public void intAndBytesLittleEndianTest() {
// 测试 int 转小端序 byte 数组
- int int1 = RandomUtil.randomInt();
+ int int1 = RandomUtil.randomInt((Integer.MAX_VALUE));
+
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(int1);
+ byte[] bytesIntFromBuffer = buffer.array();
byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN);
+ Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt);
+
int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN);
Assert.assertEquals(int1, int2);
@@ -28,8 +35,14 @@ public class ByteUtilTest {
@Test
public void intAndBytesBigEndianTest() {
// 测试 int 转大端序 byte 数组
- int int2 = RandomUtil.randomInt();
+ int int2 = RandomUtil.randomInt(Integer.MAX_VALUE);
+
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
+ buffer.putInt(int2);
+ byte[] bytesIntFromBuffer = buffer.array();
+
byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN);
+ Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt);
// 测试大端序 byte 数组转 int
int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN);
@@ -39,9 +52,16 @@ public class ByteUtilTest {
@Test
public void longAndBytesLittleEndianTest() {
// 测试 long 转 byte 数组
- long long1 = 2223;
+ long long1 = RandomUtil.randomLong(Long.MAX_VALUE);
+
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putLong(long1);
+ byte[] bytesLongFromBuffer = buffer.array();
byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN);
+ Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong);
+
long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN);
Assert.assertEquals(long1, long2);
@@ -57,11 +77,16 @@ public class ByteUtilTest {
@Test
public void longAndBytesBigEndianTest() {
// 测试大端序 long 转 byte 数组
- long long1 = 2223;
+ long long1 = RandomUtil.randomLong(Long.MAX_VALUE);
+
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.putLong(long1);
+ byte[] bytesLongFromBuffer = buffer.array();
byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN);
- long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN);
+ Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong);
+ long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN);
Assert.assertEquals(long1, long2);
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java
index 46e6e606b..f382b8711 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java
@@ -5,32 +5,31 @@ import org.junit.Test;
/**
* 分页单元测试
- * @author Looly
*
+ * @author Looly
*/
public class PageUtilTest {
-
+
@Test
- public void transToStartEndTest(){
- PageUtil.setFirstPageNo(0);
+ public void transToStartEndTest() {
int[] startEnd1 = PageUtil.transToStartEnd(0, 10);
Assert.assertEquals(0, startEnd1[0]);
Assert.assertEquals(10, startEnd1[1]);
-
+
int[] startEnd2 = PageUtil.transToStartEnd(1, 10);
Assert.assertEquals(10, startEnd2[0]);
Assert.assertEquals(20, startEnd2[1]);
}
-
+
@Test
- public void totalPage(){
+ public void totalPage() {
int totalPage = PageUtil.totalPage(20, 3);
Assert.assertEquals(7, totalPage);
}
-
+
@Test
public void rainbowTest() {
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);
}
}
diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml
index f0929496c..df16039ae 100644
--- a/hutool-cron/pom.xml
+++ b/hutool-cron/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.20
+ 5.7.21
hutool-cron
diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml
index 7105bd7f5..f7763706e 100644
--- a/hutool-crypto/pom.xml
+++ b/hutool-crypto/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.20
+ 5.7.21
hutool-crypto
diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
index 4d6d83222..6db487dac 100644
--- a/hutool-db/pom.xml
+++ b/hutool-db/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.20
+ 5.7.21
hutool-db
@@ -27,7 +27,7 @@
3.36.0.3
2.5.2
- 4.0.0
+ 4.1.1
@@ -81,7 +81,7 @@
com.github.chris2018998
beecp
- 3.3.0
+ 3.3.1
slf4j-api
@@ -143,13 +143,13 @@
mysql
mysql-connector-java
- 8.0.27
+ 8.0.28
test
org.postgresql
postgresql
- 42.3.1
+ 42.3.2
test
@@ -167,13 +167,13 @@
com.h2database
h2
- 2.0.206
+ 2.1.210
test
ru.yandex.clickhouse
clickhouse-jdbc
- 0.3.1
+ 0.3.2
test
diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java
index ecc7a4371..4f11a56bc 100644
--- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java
+++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java
@@ -1,5 +1,6 @@
package cn.hutool.db.ds.c3p0;
+import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.ds.AbstractDSFactory;
@@ -12,13 +13,13 @@ import java.beans.PropertyVetoException;
/**
* Druid数据源工厂类
- *
+ *
* @author Looly
*
*/
public class C3p0DSFactory extends AbstractDSFactory {
private static final long serialVersionUID = -6090788225842047281L;
-
+
public static final String DS_NAME = "C3P0";
/**
@@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory {
/**
* 构造
- *
+ *
* @param setting 配置
*/
public C3p0DSFactory(Setting setting) {
super(DS_NAME, ComboPooledDataSource.class, setting);
}
-
+
@Override
protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) {
final ComboPooledDataSource ds = new ComboPooledDataSource();
- ds.setJdbcUrl(jdbcUrl);
- try {
- ds.setDriverClass(driver);
- } catch (PropertyVetoException e) {
- throw new DbRuntimeException(e);
- }
- ds.setUser(user);
- ds.setPassword(pass);
// remarks等特殊配置,since 5.3.8
final Props connProps = new Props();
@@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory {
connProps.setProperty(key, connValue);
}
}
- ds.setProperties(connProps);
+ if(MapUtil.isNotEmpty(connProps)){
+ ds.setProperties(connProps);
+ }
+
+ ds.setJdbcUrl(jdbcUrl);
+ try {
+ ds.setDriverClass(driver);
+ } catch (PropertyVetoException e) {
+ throw new DbRuntimeException(e);
+ }
+ ds.setUser(user);
+ ds.setPassword(pass);
// 注入属性
poolSetting.toBean(ds);
diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java
new file mode 100644
index 000000000..fb33305cd
--- /dev/null
+++ b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java
@@ -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;
+
+/**
+ * 条件组
+ * 用于构建复杂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