Merge branch 'v6-dev' into v6-dev

This commit is contained in:
Golden Looly 2023-03-15 08:56:43 +08:00 committed by GitHub
commit 1f5700a57e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
491 changed files with 9218 additions and 8488 deletions

View File

@ -11,8 +11,8 @@ hutool版本 5.X.X请确保最新尝试是否还有问题
Console.log("报错了");
```
2. 堆栈信息
1. 堆栈信息
3. 测试涉及到的文件(注意脱密)
2. 测试涉及到的文件(注意脱密)
比如报错的Excel文件有问题的图片等。

View File

@ -11,8 +11,8 @@ hutool版本 5.X.X请确保最新尝试是否还有问题
Console.log("报错了");
```
2. 堆栈信息
1. 堆栈信息
3. 测试涉及到的文件(注意脱密)
2. 测试涉及到的文件(注意脱密)
比如报错的Excel文件有问题的图片等。

View File

@ -3,14 +3,13 @@
-------------------------------------------------------------------------------------------------------------
# 6.0.0.M1 (2022-10-09)
# 6.0.0.M1 (2023-03-10)
### 计划实现
* 【poi 】 PDF相关基于PdfBox
* 【poi 】 HTML、DOCX转换相关
* 【poi 】 Markdown相关如HTML转换等基于commonmark-java
* 【db 】 增加DDL封装
* 【poi 】 CellUtil.getCellIfMergedRegion考虑添加缓存支持增加最大和最小范围判断减少遍历
* 【http 】 公共代理和SSL验证
### ❌不兼容特性

View File

@ -1,5 +1,5 @@
<p align="center">
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
</p>
<p align="center">
<strong>🍬A set of tools that keep Java sweet.</strong>
@ -51,7 +51,7 @@
-------------------------------------------------------------------------------
## 📚Introduction
**Hutool** is a small but comprehensive library of Java tools, encapsulation by static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
**Hutool** is a small but comprehensive library of Java tools, achieved by encapsulation through static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
**Hutool** tools and methods from each user's crafted, it covers all aspects of the underlying code of Java development, it is a powerful tool for large project development to solve small problems, but also the efficiency of small projects;
@ -138,18 +138,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>6.0.0.M1</version>
<version>6.0.0.M2</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:6.0.0.M1'
implementation 'cn.hutool:hutool-all:6.0.0.M2'
```
## 📥Download
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M1/)
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M2/)
> 🔔note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
@ -176,10 +176,10 @@ cd ${hutool}
Hutool's source code is divided into two branches:
| branch | description |
|-----------|---------------------------------------------------------------|
| branch | description |
|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| v6-master | The master branch, the branch used by the release version, is the same as the jar committed to the central repository and does not receive any pr or modifications. |
| v6-dev | Development branch, which defaults to the next SNAPSHOT version, accepts modifications or pr |
| v6-dev | Development branch, which defaults to the next SNAPSHOT version, accepts modifications or pr |
### 🐞Provide feedback or suggestions on bugs

View File

@ -1,5 +1,5 @@
<p align="center">
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
</p>
<p align="center">
<strong>🍬A set of tools that keep Java sweet.</strong>
@ -141,21 +141,21 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>6.0.0.M1</version>
<version>6.0.0.M2</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:6.0.0.M1'
implementation 'cn.hutool:hutool-all:6.0.0.M2'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M1/)
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M2/)
> 🔔️注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。
@ -180,10 +180,10 @@ implementation 'cn.hutool:hutool-all:6.0.0.M1'
Hutool的源码分为两个分支功能如下
| 分支 | 作用 |
|-----------|---------------------------------------------------------------|
| 分支 | 作用 |
|-----------|--------------------------------------------|
| v6-master | 主分支release版本使用的分支与中央库提交的jar一致不接收任何pr或修改 |
| v6-dev | 开发分支默认为下个版本的SNAPSHOT版本接受修改或pr |
| v6-dev | 开发分支默认为下个版本的SNAPSHOT版本接受修改或pr |
### 🐞提供bug反馈或建议

View File

@ -3,7 +3,7 @@
## Supported Versions支持的版本
| Version | Supported |
|---------| ------------------ |
|---------|--------------------|
| 6.x.x | :white_check_mark: |
## Reporting a Vulnerability报告漏洞

View File

@ -12,7 +12,7 @@ echo "当前路径:${pwd}"
if [ -n "$1" ];then
new_version="$1"
old_version=`cat ${pwd}/bin/version.txt`
old_version=$(cat "${pwd}"/bin/version.txt)
echo "$old_version 替换为新版本 $new_version"
else
# 参数错误,退出
@ -20,16 +20,16 @@ else
exit
fi
if [ ! -n "$old_version" ]; then
if [ -z "$old_version" ]; then
echo "ERROR: 旧版本不存在请确认bin/version.txt中信息正确"
exit
fi
# 替换README.md中的版本
sed -i "s/${old_version}/${new_version}/g" $pwd/README.md
sed -i "s/${old_version}/${new_version}/g" $pwd/README-EN.md
sed -i "s/${old_version}/${new_version}/g" "$pwd"/README.md
sed -i "s/${old_version}/${new_version}/g" "$pwd"/README-EN.md
# 替换docs/js/version.js中的版本
sed -i "s/${old_version}/${new_version}/g" $pwd/docs/js/version.js
sed -i "s/${old_version}/${new_version}/g" "$pwd"/docs/js/version.js
# 保留新版本号
echo "$new_version" > $pwd/bin/version.txt
echo "$new_version" > "$pwd"/bin/version.txt

View File

@ -1 +1 @@
6.0.0.M1
6.0.0.M2

View File

@ -1 +1 @@
var version = '6.0.0.M1'
var version = '6.0.0.M2'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0.M1</version>
<version>6.0.0.M2</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -42,6 +42,9 @@ import java.util.Set;
*/
public class Hutool {
/**
* 作者贡献者
*/
public static final String AUTHOR = "Looly";
private Hutool() {

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0.M1</version>
<version>6.0.0.M2</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -1,5 +1,5 @@
<p align="center">
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
</p>
<p align="center">
<strong>🍬A set of tools that keep Java sweet.</strong>

View File

@ -9,23 +9,15 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>6.0.0.M1</version>
<version>6.0.0.M2</version>
</parent>
<artifactId>hutool-core</artifactId>
<name>${project.artifactId}</name>
<description>Hutool核心包括集合、字符串、Bean等工具</description>
<properties>
<Automatic-Module-Name>cn.hutool.core</Automatic-Module-Name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
</build>
</project>
</project>

View File

@ -703,7 +703,7 @@ public class AnnotatedElementUtil {
/**
* 清空相关缓存包括
* <ul>
* <li>{@link AnnotatedElementUtil}中的{@link AnnotatedElement}{@link AnnotationMapping}缓存</li>
* <li>{@code AnnotatedElementUtil}中的{@link AnnotatedElement}{@link AnnotationMapping}缓存</li>
* <li>{@link AnnotationUtil}中的{@link AnnotatedElement}上直接声明的注解缓存</li>
* <li>{@link RepeatableAnnotationCollector}中单例的注解属性缓存</li>
* </ul>

View File

@ -28,7 +28,7 @@ public interface AnnotationMapping<T extends Annotation> extends Annotation {
T getAnnotation();
/**
* 根据当前映射对象通过动态代理生成一个类型与被包装注解对象一致合成注解该注解相对原生注解
* 根据当前映射对象通过动态代理生成一个类型与被包装注解对象一致合成注解该注解相对原生注解
* <ul>
* <li>支持同注解内通过{@link Alias}构建的别名机制</li>
* <li>支持子注解对元注解的同名同类型属性覆盖机制</li>

View File

@ -9,6 +9,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.reflect.FieldUtil;
import cn.hutool.core.reflect.MethodUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
@ -21,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
@ -38,6 +40,11 @@ import java.util.stream.Stream;
*/
public class AnnotationUtil {
private static final String JDK_MEMBER_ATTRIBUTE = "memberValues";
private static final String SPRING_MEMBER_ATTRIBUTE = "valueCache";
private static final String HUTOOL_MEMBER_ATTRIBUTE = "valueCache";
private static final String SPRING_INVOCATION_HANDLER = "SynthesizedMergedAnnotationInvocationHandler";
/**
* 直接声明的注解缓存
*/
@ -323,9 +330,20 @@ public class AnnotationUtil {
* @param value 要更新的属性值
* @since 5.5.2
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static void setValue(final Annotation annotation, final String annotationField, final Object value) {
final Map memberValues = (Map) FieldUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
@SuppressWarnings("unchecked")
public static void setValue(
final Annotation annotation, final String annotationField, final Object value) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
String memberAttributeName = JDK_MEMBER_ATTRIBUTE;
// Spring合成注解
if (CharSequenceUtil.contains(invocationHandler.getClass().getName(), SPRING_INVOCATION_HANDLER)) {
memberAttributeName = SPRING_MEMBER_ATTRIBUTE;
}
// Hutool合成注解
else if (invocationHandler instanceof AnnotationMappingProxy) {
memberAttributeName = HUTOOL_MEMBER_ATTRIBUTE;
}
Map<String, Object> memberValues = (Map<String, Object>) FieldUtil.getFieldValue(invocationHandler, memberAttributeName);
memberValues.put(annotationField, value);
}

View File

@ -17,21 +17,21 @@ import java.util.stream.Stream;
*/
public class GenericAnnotationMapping implements AnnotationMapping<Annotation> {
private final Annotation annotation;
private final boolean isRoot;
private final Method[] attributes;
/**
* 创建一个通用注解包装类
*
* @param annotation 注解对象
* @param isRoot 是否根注解
* @return {@link GenericAnnotationMapping}实例
* @return {@code GenericAnnotationMapping}实例
*/
public static GenericAnnotationMapping create(final Annotation annotation, final boolean isRoot) {
return new GenericAnnotationMapping(annotation, isRoot);
}
private final Annotation annotation;
private final boolean isRoot;
private final Method[] attributes;
/**
* 创建一个通用注解包装类
*
@ -133,14 +133,14 @@ public class GenericAnnotationMapping implements AnnotationMapping<Annotation> {
* @return 是否
*/
@Override
public boolean equals(Object o) {
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GenericAnnotationMapping that = (GenericAnnotationMapping)o;
final GenericAnnotationMapping that = (GenericAnnotationMapping)o;
return isRoot == that.isRoot && annotation.equals(that.annotation);
}

View File

@ -25,7 +25,7 @@ public interface RepeatableAnnotationCollector {
/**
* 空实现
*
* @return {@link RepeatableAnnotationCollector}实例
* @return {@code RepeatableAnnotationCollector}实例
*/
static RepeatableAnnotationCollector none() {
return None.INSTANCE;
@ -47,7 +47,7 @@ public interface RepeatableAnnotationCollector {
* </code></pre>
* 解析任意{@code Annotation}注解对象则可以获得{@code value}属性中的{@code Item}注解对象
*
* @return {@link RepeatableAnnotationCollector}实例
* @return {@code RepeatableAnnotationCollector}实例
* @see Standard
*/
static RepeatableAnnotationCollector standard() {
@ -59,7 +59,7 @@ public interface RepeatableAnnotationCollector {
* 收集器将返回所有匹配的属性中的可重复注解
*
* @param predicate 是否为容纳可重复注解的属性的判断条件
* @return {@link RepeatableAnnotationCollector}实例
* @return {@code RepeatableAnnotationCollector}实例
*/
static RepeatableAnnotationCollector condition(final BiPredicate<Annotation, Method> predicate) {
return new Condition(predicate);
@ -80,7 +80,7 @@ public interface RepeatableAnnotationCollector {
* 则可以获得{@code items1}属性中的{@code Item1}注解对象
* 以及{@code items2}属性中的{@code Item2}注解对象
*
* @return {@link RepeatableAnnotationCollector}实例
* @return {@code RepeatableAnnotationCollector}实例
*/
static RepeatableAnnotationCollector full() {
return Full.INSTANCE;
@ -189,7 +189,7 @@ public interface RepeatableAnnotationCollector {
}
/**
* {@link RepeatableAnnotationCollector}的基本实现
* {@code RepeatableAnnotationCollector}的基本实现
*/
abstract class AbstractCollector implements RepeatableAnnotationCollector {
@ -218,7 +218,7 @@ public interface RepeatableAnnotationCollector {
* @return 容器注解中的可重复注解{@code annotation}不为容器注解则数组中仅有其本身一个对象
*/
@Override
public List<Annotation> getAllRepeatableAnnotations(Annotation annotation) {
public List<Annotation> getAllRepeatableAnnotations(final Annotation annotation) {
return find(annotation, null, true);
}
@ -261,7 +261,7 @@ public interface RepeatableAnnotationCollector {
final boolean isTarget = hasCondition && condition.test(source);
if (CollUtil.isEmpty(repeatableMethods) || isTarget) {
// 不是累加的则仅当正在处理的注解不为可重复注解时才记录
boolean shouldProcess = !accumulate && (!hasCondition || isTarget);
final boolean shouldProcess = !accumulate && (!hasCondition || isTarget);
if (shouldProcess) {
results.add(source);
}

View File

@ -145,10 +145,10 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
* <ul>
* <li>{@code annotation}已经被代理过时抛出</li>
* <li>{@code source}包装的注解对象与{@code annotation}相同时抛出</li>
* <li>{@code annotation}包装的注解对象类型为{@link ResolvedAnnotationMapping}时抛出</li>
* <li>{@code annotation}包装的注解对象类型为{@code ResolvedAnnotationMapping}时抛出</li>
* </ul>
*/
ResolvedAnnotationMapping(final ResolvedAnnotationMapping source, final Annotation annotation, boolean resolveAttribute) {
ResolvedAnnotationMapping(final ResolvedAnnotationMapping source, final Annotation annotation, final boolean resolveAttribute) {
Objects.requireNonNull(annotation);
Assert.isFalse(AnnotationMappingProxy.isProxied(annotation), "annotation has been proxied");
Assert.isFalse(annotation instanceof ResolvedAnnotationMapping, "annotation has been wrapped");
@ -254,7 +254,6 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
*
* @return 所需的注解{@link ResolvedAnnotationMapping#isResolved()}{@code false}则返回的是原始的注解对象
*/
@SuppressWarnings("unchecked")
@Override
public Annotation getResolvedAnnotation() {
if (!isResolved()) {
@ -399,7 +398,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
}
// 获取除自己外的全部子注解
final Deque<ResolvedAnnotationMapping> sources = new LinkedList<>();
Set<Class<? extends Annotation>> accessed = new HashSet<>();
final Set<Class<? extends Annotation>> accessed = new HashSet<>();
accessed.add(this.annotationType());
ResolvedAnnotationMapping sourceMapping = this.source;
while (Objects.nonNull(sourceMapping)) {
@ -444,7 +443,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
* 更新需要覆写的属性的相关映射关系若该属性存在别名则将别名的映射关系一并覆写
*/
private void overwriteAttribute(
final ResolvedAnnotationMapping overwriteMapping, final int overwriteIndex, final int targetIndex, boolean overwriteAliases) {
final ResolvedAnnotationMapping overwriteMapping, final int overwriteIndex, final int targetIndex, final boolean overwriteAliases) {
// 若目标属性已被覆写则不允许再次覆写
if (isOverwrittenAttribute(targetIndex)) {
return;
@ -463,7 +462,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
/**
* 判断该属性是否已被覆写
*/
private boolean isOverwrittenAttribute(int index) {
private boolean isOverwrittenAttribute(final int index) {
// 若属性未发生过解析则必然未被覆写
return NOT_FOUND_INDEX != resolvedAttributes[index]
// 若属性发生过解析且指向其他实例则说明已被覆写
@ -567,14 +566,14 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
* @return 是否
*/
@Override
public boolean equals(Object o) {
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ResolvedAnnotationMapping that = (ResolvedAnnotationMapping)o;
final ResolvedAnnotationMapping that = (ResolvedAnnotationMapping)o;
return resolved == that.resolved && annotation.equals(that.annotation);
}
@ -671,8 +670,8 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
/**
* 遍历下标
*/
void forEach(IntConsumer consumer) {
for (int index : indexes) {
void forEach(final IntConsumer consumer) {
for (final int index : indexes) {
consumer.accept(index);
}
}

View File

@ -116,8 +116,11 @@ public class BeanUtil {
if (ClassUtil.isNormalClass(clazz)) {
for (final Method method : clazz.getMethods()) {
if (method.getParameterCount() == 0) {
if (method.getName().startsWith("get") || method.getName().startsWith("is")) {
return true;
final String name = method.getName();
if (name.startsWith("get") || name.startsWith("is")) {
if(false == "getClass".equals(name)){
return true;
}
}
}
}

View File

@ -3,8 +3,8 @@ package cn.hutool.core.bean;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.annotation.PropIgnore;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.reflect.FieldUtil;
import cn.hutool.core.reflect.MethodUtil;
import cn.hutool.core.reflect.ModifierUtil;
import cn.hutool.core.reflect.ReflectUtil;
import cn.hutool.core.reflect.TypeUtil;
@ -153,7 +153,8 @@ public class PropDesc {
*/
public Object getValue(final Object bean) {
if (null != this.getter) {
return MethodUtil.invoke(bean, this.getter);
//return MethodUtil.invoke(bean, this.getter);
return LambdaUtil.buildGetter(this.getter).apply(bean);
} else if (ModifierUtil.isPublic(this.field)) {
return FieldUtil.getFieldValue(bean, this.field);
}
@ -224,7 +225,8 @@ public class PropDesc {
*/
public PropDesc setValue(final Object bean, final Object value) {
if (null != this.setter) {
MethodUtil.invoke(bean, this.setter, value);
//MethodUtil.invoke(bean, this.setter, value);
LambdaUtil.buildSetter(this.setter).accept(bean, value);
} else if (ModifierUtil.isPublic(this.field)) {
FieldUtil.setFieldValue(bean, this.field, value);
}

View File

@ -20,6 +20,13 @@ public abstract class AbsCopier<S, T> implements Copier<T> {
*/
protected final CopyOptions copyOptions;
/**
* 构造
*
* @param source 源对象
* @param target 目标对象
* @param copyOptions 拷贝选项
*/
public AbsCopier(final S source, final T target, final CopyOptions copyOptions) {
this.source = source;
this.target = target;

View File

@ -115,17 +115,6 @@ public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
// 转驼峰尝试查找
sKeyStr = StrUtil.toCamelCase(sKeyStr);
propDesc = targetPropDescMap.get(sKeyStr);
if(null != propDesc){
return propDesc;
}
// boolean类型参数名转换尝试查找
if(sKeyStr.startsWith("is")){
sKeyStr = StrUtil.removePreAndLowerFirst(sKeyStr, 2);
propDesc = targetPropDescMap.get(sKeyStr);
return propDesc;
}
return null;
return propDesc;
}
}

View File

@ -1,7 +1,7 @@
package cn.hutool.core.cache.file;
import cn.hutool.core.cache.Cache;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import java.io.File;

View File

@ -1,7 +1,7 @@
package cn.hutool.core.classloader;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.reflect.MethodUtil;

View File

@ -1,6 +1,6 @@
package cn.hutool.core.codec;
import cn.hutool.core.codec.BaseN.Base16Codec;
import cn.hutool.core.codec.binary.Base16Codec;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
@ -31,7 +31,7 @@ public class HexUtil {
* @return 是否为16进制
*/
public static boolean isHexNumber(final String value) {
if(StrUtil.startWith(value, '-')){
if (StrUtil.startWith(value, '-')) {
// issue#2875
return false;
}
@ -39,7 +39,7 @@ public class HexUtil {
if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
index += 2;
} else if (value.startsWith("#", index)) {
index ++;
index++;
}
try {
new BigInteger(value.substring(index), 16);
@ -253,7 +253,7 @@ public class HexUtil {
* 转换的字符串如果u后不足4位则前面用0填充例如
*
* <pre>
* '你' =\u4f60
* = &#92;u4f60
* </pre>
*
* @param value int值也可以是char
@ -278,7 +278,7 @@ public class HexUtil {
* 转换的字符串如果u后不足4位则前面用0填充例如
*
* <pre>
* '你' ='\u4f60'
* = &#92;u4f60
* </pre>
*
* @param ch char值

View File

@ -1,6 +1,6 @@
package cn.hutool.core.codec;
import cn.hutool.core.codec.BaseN.Base16Codec;
import cn.hutool.core.codec.binary.Base16Codec;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.text.StrUtil;

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.codec.Decoder;
import cn.hutool.core.codec.Encoder;
@ -81,7 +81,7 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* 转换的字符串如果u后不足4位则前面用0填充例如
*
* <pre>
* '你' ='\u4f60'
* =&#92;u4f60
* </pre>
*
* @param ch char值

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.text.StrUtil;

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.codec.Decoder;
import cn.hutool.core.codec.Encoder;
@ -7,7 +7,7 @@ import java.util.Arrays;
/**
* Base32 - encodes and decodes RFC4648 Base32<br>
* (see <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">https://datatracker.ietf.org/doc/html/rfc4648#section-6</a> )<br>
* (see <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">https://datatracker.ietf.org/doc/html/rfc4648#section-6</a> )<br>
* base32就是用322的5次方个特定ASCII码来表示256个ASCII码<br>
* 所以5个ASCII字符经过base32编码后会变为8个字符公约数为40长度增加3/5.不足8n用=补足<br>
* 根据RFC4648 Base32规范支持两种模式
@ -21,6 +21,9 @@ import java.util.Arrays;
*/
public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
/**
* 单例对象
*/
public static Base32Codec INSTANCE = new Base32Codec();
@Override
@ -66,7 +69,13 @@ public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequenc
private static final Character DEFAULT_PAD = '=';
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3};
/**
* 编码器
*/
public static final Base32Encoder ENCODER = new Base32Encoder(DEFAULT_ALPHABET, DEFAULT_PAD);
/**
* 16进制编码器
*/
public static final Base32Encoder HEX_ENCODER = new Base32Encoder(HEX_ALPHABET, DEFAULT_PAD);
private final char[] alphabet;
@ -142,7 +151,13 @@ public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequenc
public static class Base32Decoder implements Decoder<CharSequence, byte[]> {
private static final char BASE_CHAR = '0';
/**
* 解码器
*/
public static final Base32Decoder DECODER = new Base32Decoder(Base32Encoder.DEFAULT_ALPHABET);
/**
* 16进制解码器
*/
public static final Base32Decoder HEX_DECODER = new Base32Decoder(Base32Encoder.HEX_ALPHABET);
private final byte[] lookupTable;
@ -163,7 +178,7 @@ public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequenc
c = alphabet.charAt(i);
lookupTable[c - BASE_CHAR] = (byte) i;
// 支持小写字母解码
if(c >= 'A' && c <= 'Z'){
if (c >= 'A' && c <= 'Z') {
lookupTable[Character.toLowerCase(c) - BASE_CHAR] = (byte) i;
}
}

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.exceptions.ValidateException;

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.codec.Decoder;
import cn.hutool.core.codec.Encoder;
@ -15,6 +15,9 @@ import java.util.Arrays;
*/
public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
/**
* 单例
*/
public static Base58Codec INSTANCE = new Base58Codec();
/**
@ -48,6 +51,9 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
public static class Base58Encoder implements Encoder<byte[], String> {
private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
/**
* 编码器
*/
public static final Base58Encoder ENCODER = new Base58Encoder(DEFAULT_ALPHABET.toCharArray());
private final char[] alphabet;
@ -105,6 +111,9 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
*/
public static class Base58Decoder implements Decoder<CharSequence, byte[]> {
/**
* 解码器
*/
public static Base58Decoder DECODER = new Base58Decoder(Base58Encoder.DEFAULT_ALPHABET);
private final byte[] lookupTable;

View File

@ -1,6 +1,6 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.text.StrUtil;

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.codec.Decoder;
import cn.hutool.core.codec.Encoder;
@ -20,6 +20,9 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
private static final int STANDARD_BASE = 256;
private static final int TARGET_BASE = 62;
/**
* 单例
*/
public static Base62Codec INSTANCE = new Base62Codec();
/**
@ -102,7 +105,13 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
'U', 'V', 'W', 'X', 'Y', 'Z' //
};
/**
* GMP风格编码器
*/
public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP);
/**
* 反转风格即将GMP风格中的大小写做转换编码器
*/
public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED);
private final byte[] alphabet;
@ -130,7 +139,13 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
*/
public static class Base62Decoder implements Decoder<byte[], byte[]> {
/**
* GMP风格解码器
*/
public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP);
/**
* 反转风格即将GMP风格中的大小写做转换解码器
*/
public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED);
private final byte[] lookupTable;

View File

@ -1,6 +1,6 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.text.StrUtil;

View File

@ -1,4 +1,4 @@
package cn.hutool.core.codec.BaseN;
package cn.hutool.core.codec.binary;
import cn.hutool.core.codec.Decoder;
import cn.hutool.core.lang.mutable.MutableInt;
@ -13,6 +13,9 @@ import cn.hutool.core.util.ArrayUtil;
*/
public class Base64Decoder implements Decoder<byte[], byte[]> {
/**
* 单例对象
*/
public static Base64Decoder INSTANCE = new Base64Decoder();
private static final byte PADDING = -2;

View File

@ -0,0 +1,11 @@
/**
* BaseN编码解码提供将bytes和baseN的编码转换功能
* <ul>
* <li>Base16</li>
* <li>Base32</li>
* <li>Base58</li>
* <li>Base62</li>
* <li>Base64</li>
* </ul>
*/
package cn.hutool.core.codec.binary;

View File

@ -17,6 +17,9 @@ import java.util.Arrays;
* @since 5.2.5
*/
public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>{
/**
* 单例
*/
public static CityHash INSTANCE = new CityHash();
// Some primes between 2^63 and 2^64 for various uses.
@ -50,46 +53,46 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
// len > 24
int h = len, g = c1 * len, f = g;
int a0 = rotate32(fetch32(data, len - 4) * c1, 17) * c2;
int a1 = rotate32(fetch32(data, len - 8) * c1, 17) * c2;
int a2 = rotate32(fetch32(data, len - 16) * c1, 17) * c2;
int a3 = rotate32(fetch32(data, len - 12) * c1, 17) * c2;
int a4 = rotate32(fetch32(data, len - 20) * c1, 17) * c2;
int a0 = Integer.rotateRight(fetch32(data, len - 4) * c1, 17) * c2;
int a1 = Integer.rotateRight(fetch32(data, len - 8) * c1, 17) * c2;
int a2 = Integer.rotateRight(fetch32(data, len - 16) * c1, 17) * c2;
int a3 = Integer.rotateRight(fetch32(data, len - 12) * c1, 17) * c2;
int a4 = Integer.rotateRight(fetch32(data, len - 20) * c1, 17) * c2;
h ^= a0;
h = rotate32(h, 19);
h = Integer.rotateRight(h, 19);
h = h * 5 + 0xe6546b64;
h ^= a2;
h = rotate32(h, 19);
h = Integer.rotateRight(h, 19);
h = h * 5 + 0xe6546b64;
g ^= a1;
g = rotate32(g, 19);
g = Integer.rotateRight(g, 19);
g = g * 5 + 0xe6546b64;
g ^= a3;
g = rotate32(g, 19);
g = Integer.rotateRight(g, 19);
g = g * 5 + 0xe6546b64;
f += a4;
f = rotate32(f, 19);
f = Integer.rotateRight(f, 19);
f = f * 5 + 0xe6546b64;
int iters = (len - 1) / 20;
int pos = 0;
do {
a0 = rotate32(fetch32(data, pos) * c1, 17) * c2;
a0 = Integer.rotateRight(fetch32(data, pos) * c1, 17) * c2;
a1 = fetch32(data, pos + 4);
a2 = rotate32(fetch32(data, pos + 8) * c1, 17) * c2;
a3 = rotate32(fetch32(data, pos + 12) * c1, 17) * c2;
a2 = Integer.rotateRight(fetch32(data, pos + 8) * c1, 17) * c2;
a3 = Integer.rotateRight(fetch32(data, pos + 12) * c1, 17) * c2;
a4 = fetch32(data, pos + 16);
h ^= a0;
h = rotate32(h, 18);
h = Integer.rotateRight(h, 18);
h = h * 5 + 0xe6546b64;
f += a1;
f = rotate32(f, 19);
f = Integer.rotateRight(f, 19);
f = f * c1;
g += a2;
g = rotate32(g, 18);
g = Integer.rotateRight(g, 18);
g = g * 5 + 0xe6546b64;
h ^= a3 + a1;
h = rotate32(h, 19);
h = Integer.rotateRight(h, 19);
h = h * 5 + 0xe6546b64;
g ^= a4;
g = Integer.reverseBytes(g) * 5;
@ -104,16 +107,16 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
pos += 20;
} while (--iters != 0);
g = rotate32(g, 11) * c1;
g = rotate32(g, 17) * c1;
f = rotate32(f, 11) * c1;
f = rotate32(f, 17) * c1;
h = rotate32(h + g, 19);
g = Integer.rotateRight(g, 11) * c1;
g = Integer.rotateRight(g, 17) * c1;
f = Integer.rotateRight(f, 11) * c1;
f = Integer.rotateRight(f, 17) * c1;
h = Integer.rotateRight(h + g, 19);
h = h * 5 + 0xe6546b64;
h = rotate32(h, 17) * c1;
h = rotate32(h + f, 19);
h = Integer.rotateRight(h, 17) * c1;
h = Integer.rotateRight(h + f, 19);
h = h * 5 + 0xe6546b64;
h = rotate32(h, 17) * c1;
h = Integer.rotateRight(h, 17) * c1;
return h;
}
@ -149,11 +152,11 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
len = (len - 1) & ~63;
int pos = 0;
do {
x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
x = Long.rotateRight(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
y = Long.rotateRight(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
x ^= w.getHighValue();
y += v.getLowValue() + fetch64(data, pos + 40);
z = rotate64(z + w.getLowValue(), 33) * k1;
z = Long.rotateRight(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
@ -231,19 +234,19 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
long x = seed.getLowValue();
long y = seed.getHighValue();
long z = len * 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);
v.setLowValue(Long.rotateRight(y ^ k1, 49) * k1 + fetch64(byteArray, start));
v.setHighValue(Long.rotateRight(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
w.setLowValue(Long.rotateRight(y + z, 35) * k1 + x);
w.setHighValue(Long.rotateRight(x + fetch64(byteArray, start + 88), 53) * k1);
// This is the same inner loop as CityHash64(), manually unrolled.
int pos = start;
do {
x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
x = Long.rotateRight(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
y = Long.rotateRight(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
x ^= w.getHighValue();
y += v.getLowValue() + fetch64(byteArray, pos + 40);
z = rotate64(z + w.getLowValue(), 33) * k1;
z = Long.rotateRight(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));
@ -251,11 +254,11 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
x = z;
z = swapValue;
pos += 64;
x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
x = Long.rotateRight(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
y = Long.rotateRight(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
x ^= w.getHighValue();
y += v.getLowValue() + fetch64(byteArray, pos + 40);
z = rotate64(z + w.getLowValue(), 33) * k1;
z = Long.rotateRight(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;
@ -264,16 +267,16 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
pos += 64;
len -= 128;
} while (len >= 128);
x += rotate64(v.getLowValue() + z, 49) * k0;
y = y * k0 + rotate64(w.getHighValue(), 37);
z = z * k0 + rotate64(w.getLowValue(), 27);
x += Long.rotateRight(v.getLowValue() + z, 49) * k0;
y = y * k0 + Long.rotateRight(w.getHighValue(), 37);
z = z * k0 + Long.rotateRight(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 = rotate64(x + y, 42) * k0 + v.getHighValue();
y = Long.rotateRight(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);
@ -333,8 +336,8 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
final long mul = k2 + len * 2L;
final long a = fetch64(byteArray, 0) + k2;
final long b = fetch64(byteArray, len - 8);
final long c = rotate64(b, 37) * mul + a;
final long d = (rotate64(a, 25) + b) * mul;
final long c = Long.rotateRight(b, 37) * mul + a;
final long d = (Long.rotateRight(a, 25) + b) * mul;
return hashLen16(c, d, mul);
}
if (len >= 4) {
@ -361,8 +364,8 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
final long b = fetch64(byteArray, 8);
final long c = fetch64(byteArray, len - 8) * mul;
final long d = fetch64(byteArray, len - 16) * k2;
return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d,
a + rotate64(b + k2, 18) + c, mul);
return hashLen16(Long.rotateRight(a + b, 43) + Long.rotateRight(c, 30) + d,
a + Long.rotateRight(b + k2, 18) + c, mul);
}
private long hashLen33to64(final byte[] byteArray) {
@ -376,10 +379,10 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
final long f = fetch64(byteArray, 24) * 9;
final long g = fetch64(byteArray, len - 8);
final long h = fetch64(byteArray, len - 16) * mul;
final long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9;
final long u = Long.rotateRight(a + g, 43) + (Long.rotateRight(b, 30) + c) * 9;
final long v = ((a + g) ^ d) + f + 1;
final long w = Long.reverseBytes((u + v) * mul) + h;
final long x = rotate64(e + f, 42) + c;
final long x = Long.rotateRight(e + f, 42) + c;
final long y = (Long.reverseBytes((v + w) * mul) + g) * mul;
final long z = e + f + c;
a = Long.reverseBytes((x + z) * mul + y) + b;
@ -395,16 +398,6 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN);
}
private static long rotate64(final long val, final int shift) {
// Avoid shifting by 64: doing so yields an undefined result.
return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift)));
}
private static int rotate32(final int val, final int shift) {
// Avoid shifting by 32: doing so yields an undefined result.
return shift == 0 ? val : ((val >>> shift) | (val << (32 - shift)));
}
private static long hashLen16(final long u, final long v, final long mul) {
// Murmur-inspired hashing.
long a = (u ^ v) * mul;
@ -446,21 +439,21 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
private int mur(int a, int h) {
// Helper from Murmur3 for combining two 32-bit values.
a *= c1;
a = rotate32(a, 17);
a = Integer.rotateRight(a, 17);
a *= c2;
h ^= a;
h = rotate32(h, 19);
h = Integer.rotateRight(h, 19);
return h * 5 + 0xe6546b64;
}
private static Number128 weakHashLen32WithSeeds(
final long w, final long x, final long y, final long z, long a, long b) {
a += w;
b = rotate64(b + a + z, 21);
b = Long.rotateRight(b + a + z, 21);
final long c = a;
a += x;
a += y;
b += rotate64(a, 44);
b += Long.rotateRight(a, 44);
return new Number128(a + z, b + c);
}

View File

@ -1,6 +1,8 @@
package cn.hutool.core.codec.hash;
import cn.hutool.core.codec.Number128;
import cn.hutool.core.codec.hash.metro.MetroHash128;
import cn.hutool.core.codec.hash.metro.MetroHash64;
/**
* Hash算法大全<br>
@ -554,17 +556,7 @@ public class HashUtil {
* @return hash值
*/
public static long metroHash64(final byte[] data, final long seed) {
return MetroHash.INSTANCE.hash64(data, seed);
}
/**
* MetroHash 算法64-bit实现
*
* @param data 数据
* @return hash值
*/
public static long metroHash64(final byte[] data) {
return MetroHash.INSTANCE.hash64(data);
return MetroHash64.of(seed).hash64(data);
}
/**
@ -575,17 +567,7 @@ public class HashUtil {
* @return hash值long[0]低位long[1]高位
*/
public static long[] metroHash128(final byte[] data, final long seed) {
return MetroHash.INSTANCE.hash128(data, seed).getLongArray();
}
/**
* MetroHash 算法128-bit实现
*
* @param data 数据
* @return hash值long[0]低位long[1]高位
*/
public static long[] metroHash128(final byte[] data) {
return MetroHash.INSTANCE.hash128(data).getLongArray();
return MetroHash128.of(seed).hash128(data).getLongArray();
}
/**

View File

@ -1,237 +0,0 @@
package cn.hutool.core.codec.hash;
import cn.hutool.core.codec.Number128;
import cn.hutool.core.util.ByteUtil;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* Apache 发布的MetroHash算法是一组用于非加密用例的最先进的哈希函数
* 除了卓越的性能外他们还以算法生成而著称
*
* <p>
* 官方实现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 implements Hash64<byte[]>, Hash128<byte[]> {
public static MetroHash INSTANCE = new MetroHash();
@Override
public Number encode(final byte[] bytes) {
return hash64(bytes);
}
@Override
public long hash64(final byte[] data) {
return hash64(data, 1337);
}
/**
* 计算64位Hash值
*
* @param data 数据
* @param seed 种子
* @return hash64
*/
public long hash64(final byte[] data, final long seed) {
final long k0_64 = 0xD6D018F5;
final long k1_64 = 0xA2AA033B;
final long k2_64 = 0x62992FC1;
final long k3_64 = 0x30BC5B29;
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;
}
@Override
public Number128 hash128(final byte[] data) {
return hash128(data, 1337);
}
/**
* 计算128位hash值
*
* @param data 数据
* @param seed 种子
* @return hash128
*/
public Number128 hash128(final byte[] data, final long seed) {
final long k0_128 = 0xC83A91E1;
final long k1_128 = 0x8648DBDB;
final long k2_128 = 0x7BDEC03B;
final long k3_128 = 0x2F5870A5;
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);
}
// region =========== Private methods
private static long littleEndian64(final byte[] b, final int start) {
return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN);
}
private static int littleEndian32(final byte[] b) {
return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24;
}
private static int littleEndian16(final byte[] b) {
return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN);
}
private static long rotateLeft64(final long x, final int k) {
final int n = 64;
final int s = k & (n - 1);
return x << s | x >> (n - s);
}
private static long rotateRight(final long val, final int shift) {
return (val >> shift) | (val << (64 - shift));
}
// endregion =========== Private methods
}

View File

@ -21,6 +21,9 @@ import java.nio.charset.Charset;
* @since 4.3.3
*/
public class MurmurHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>{
/**
* 单例
*/
public static final MurmurHash INSTANCE = new MurmurHash();
// Constants for 32 bit variant

View File

@ -0,0 +1,71 @@
package cn.hutool.core.codec.hash.metro;
import java.nio.ByteBuffer;
/**
* Apache 发布的MetroHash算法抽象实现是一组用于非加密用例的最先进的哈希函数
* 除了卓越的性能外他们还以算法生成而著称
*
* <p>
* 官方实现https://github.com/jandrewrogers/MetroHash
* 官方文档http://www.jandrewrogers.com/2015/05/27/metrohash/
* 来自https://github.com/postamar/java-metrohash/
*
* @author Marius Posta
* @param <R> 返回值类型为this类型
*/
public abstract class AbstractMetroHash<R extends AbstractMetroHash<R>> implements MetroHash<R> {
final long seed;
long v0, v1, v2, v3;
long nChunks;
/**
* 使用指定种子构造
*
* @param seed 种子
*/
public AbstractMetroHash(final long seed) {
this.seed = seed;
reset();
}
@Override
public R apply(final ByteBuffer input) {
reset();
while (input.remaining() >= 32) {
partialApply32ByteChunk(input);
}
return partialApplyRemaining(input);
}
/**
* 从byteBuffer中计算32-byte块并更新hash状态
*
* @param partialInput byte buffer至少有32byte的数据
* @return this
*/
abstract R partialApply32ByteChunk(ByteBuffer partialInput);
/**
* 从byteBuffer中计算剩余bytes并更新hash状态
*
* @param partialInput byte buffer少于32byte的数据
* @return this
*/
abstract R partialApplyRemaining(ByteBuffer partialInput);
static long grab(final ByteBuffer bb, final int length) {
long result = bb.get() & 0xFFL;
for (int i = 1; i < length; i++) {
result |= (bb.get() & 0xFFL) << (i << 3);
}
return result;
}
static void writeLittleEndian(final long hash, final ByteBuffer output) {
for (int i = 0; i < 8; i++) {
output.put((byte) (hash >>> (i*8)));
}
}
}

View File

@ -0,0 +1,55 @@
package cn.hutool.core.codec.hash.metro;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Apache 发布的MetroHash算法接口是一组用于非加密用例的最先进的哈希函数
* 除了卓越的性能外他们还以算法生成而著称
*
* <p>
* 官方实现https://github.com/jandrewrogers/MetroHash
* 官方文档http://www.jandrewrogers.com/2015/05/27/metrohash/
* 来自https://github.com/postamar/java-metrohash/
*
* @param <R> 返回值类型为this类型
* @author Marius Posta
*/
public interface MetroHash<R extends MetroHash<R>> {
/**
* 创建 {@code MetroHash}对象
*
* @param seed 种子
* @param is128 是否128位
* @return {@code MetroHash}对象
*/
static MetroHash<?> of(final long seed, final boolean is128) {
return is128 ? new MetroHash128(seed) : new MetroHash64(seed);
}
/**
* 将给定的{@link ByteBuffer}中的数据追加计算hash值<br>
* 此方法会更新hash值状态
*
* @param input 内容
* @return this
*/
R apply(final ByteBuffer input);
/**
* 将结果hash值写出到{@link ByteBuffer}可选端序
*
* @param output 输出
* @param byteOrder 端序
* @return this
*/
R write(ByteBuffer output, final ByteOrder byteOrder);
/**
* 重置重置后可复用对象开启新的计算
*
* @return this
*/
R reset();
}

View File

@ -0,0 +1,164 @@
package cn.hutool.core.codec.hash.metro;
import cn.hutool.core.codec.Number128;
import cn.hutool.core.codec.hash.Hash128;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Apache 发布的MetroHash算法的128位实现是一组用于非加密用例的最先进的哈希函数
* 除了卓越的性能外他们还以算法生成而著称
*
* <p>
* 官方实现https://github.com/jandrewrogers/MetroHash
* 官方文档http://www.jandrewrogers.com/2015/05/27/metrohash/
* 来自https://github.com/postamar/java-metrohash/
* @author Marius Posta
*/
public class MetroHash128 extends AbstractMetroHash<MetroHash128> implements Hash128<byte[]> {
/**
* 创建 {@code MetroHash128}对象
*
* @param seed 种子
* @return {@code MetroHash128}对象
*/
public static MetroHash128 of(final long seed) {
return new MetroHash128(seed);
}
private static final long K0 = 0xC83A91E1L;
private static final long K1 = 0x8648DBDBL;
private static final long K2 = 0x7BDEC03BL;
private static final long K3 = 0x2F5870A5L;
/**
* 使用指定种子构造
*
* @param seed 种子
*/
public MetroHash128(final long seed) {
super(seed);
}
@Override
public MetroHash128 reset() {
v0 = (seed - K0) * K3;
v1 = (seed + K1) * K2;
v2 = (seed + K0) * K2;
v3 = (seed - K1) * K3;
nChunks = 0;
return this;
}
/**
* 获取结果hash值
*
* @return hash值
*/
public Number128 get() {
return new Number128(v1, v0);
}
@Override
public Number128 hash128(final byte[] bytes) {
return apply(ByteBuffer.wrap(bytes)).get();
}
@Override
public MetroHash128 write(final ByteBuffer output, final ByteOrder byteOrder) {
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
writeLittleEndian(v0, output);
writeLittleEndian(v1, output);
} else {
output.asLongBuffer().put(v1).put(v0);
}
return this;
}
@Override
MetroHash128 partialApply32ByteChunk(final ByteBuffer partialInput) {
assert partialInput.remaining() >= 32;
v0 += grab(partialInput, 8) * K0;
v0 = Long.rotateRight(v0, 29) + v2;
v1 += grab(partialInput, 8) * K1;
v1 = Long.rotateRight(v1, 29) + v3;
v2 += grab(partialInput, 8) * K2;
v2 = Long.rotateRight(v2, 29) + v0;
v3 += grab(partialInput, 8) * K3;
v3 = Long.rotateRight(v3, 29) + v1;
++nChunks;
return this;
}
@Override
MetroHash128 partialApplyRemaining(final ByteBuffer partialInput) {
assert partialInput.remaining() < 32;
if (nChunks > 0) {
metroHash128_32();
}
if (partialInput.remaining() >= 16) {
metroHash128_16(partialInput);
}
if (partialInput.remaining() >= 8) {
metroHash128_8(partialInput);
}
if (partialInput.remaining() >= 4) {
metroHash128_4(partialInput);
}
if (partialInput.remaining() >= 2) {
metroHash128_2(partialInput);
}
if (partialInput.remaining() >= 1) {
metroHash128_1(partialInput);
}
v0 += Long.rotateRight(v0 * K0 + v1, 13);
v1 += Long.rotateRight(v1 * K1 + v0, 37);
v0 += Long.rotateRight(v0 * K2 + v1, 13);
v1 += Long.rotateRight(v1 * K3 + v0, 37);
return this;
}
// region ----- private methods
private void metroHash128_32() {
v2 ^= Long.rotateRight((v0 + v3) * K0 + v1, 21) * K1;
v3 ^= Long.rotateRight((v1 + v2) * K1 + v0, 21) * K0;
v0 ^= Long.rotateRight((v0 + v2) * K0 + v3, 21) * K1;
v1 ^= Long.rotateRight((v1 + v3) * K1 + v2, 21) * K0;
}
private void metroHash128_16(final ByteBuffer bb) {
v0 += grab(bb, 8) * K2;
v0 = Long.rotateRight(v0, 33) * K3;
v1 += grab(bb, 8) * K2;
v1 = Long.rotateRight(v1, 33) * K3;
v0 ^= Long.rotateRight(v0 * K2 + v1, 45) * K1;
v1 ^= Long.rotateRight(v1 * K3 + v0, 45) * K0;
}
private void metroHash128_8(final ByteBuffer bb) {
v0 += grab(bb, 8) * K2;
v0 = Long.rotateRight(v0, 33) * K3;
v0 ^= Long.rotateRight(v0 * K2 + v1, 27) * K1;
}
private void metroHash128_4(final ByteBuffer bb) {
v1 += grab(bb, 4) * K2;
v1 = Long.rotateRight(v1, 33) * K3;
v1 ^= Long.rotateRight(v1 * K3 + v0, 46) * K0;
}
private void metroHash128_2(final ByteBuffer bb) {
v0 += grab(bb, 2) * K2;
v0 = Long.rotateRight(v0, 33) * K3;
v0 ^= Long.rotateRight(v0 * K2 + v1, 22) * K1;
}
private void metroHash128_1(final ByteBuffer bb) {
v1 += grab(bb, 1) * K2;
v1 = Long.rotateRight(v1, 33) * K3;
v1 ^= Long.rotateRight(v1 * K3 + v0, 58) * K0;
}
// endregion
}

View File

@ -0,0 +1,158 @@
package cn.hutool.core.codec.hash.metro;
import cn.hutool.core.codec.hash.Hash64;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Apache 发布的MetroHash算法的64位实现是一组用于非加密用例的最先进的哈希函数
* 除了卓越的性能外他们还以算法生成而著称
*
* <p>
* 官方实现https://github.com/jandrewrogers/MetroHash
* 官方文档http://www.jandrewrogers.com/2015/05/27/metrohash/
* 来自https://github.com/postamar/java-metrohash/
* @author Marius Posta
*/
public class MetroHash64 extends AbstractMetroHash<MetroHash64> implements Hash64<byte[]> {
/**
* 创建 {@code MetroHash64}对象
*
* @param seed 种子
* @return {@code MetroHash64}对象
*/
public static MetroHash64 of(final long seed) {
return new MetroHash64(seed);
}
private static final long K0 = 0xD6D018F5L;
private static final long K1 = 0xA2AA033BL;
private static final long K2 = 0x62992FC1L;
private static final long K3 = 0x30BC5B29L;
private long hash;
/**
* 使用指定种子构造
*
* @param seed 种子
*/
public MetroHash64(final long seed) {
super(seed);
}
@Override
public MetroHash64 reset() {
v0 = v1 = v2 = v3 = hash = (seed + K2) * K0;
nChunks = 0;
return this;
}
/**
* 获取计算结果hash值
*
* @return hash值
*/
public long get() {
return hash;
}
@Override
public long hash64(final byte[] bytes) {
return apply(ByteBuffer.wrap(bytes)).get();
}
@Override
public MetroHash64 write(final ByteBuffer output, final ByteOrder byteOrder) {
if(ByteOrder.LITTLE_ENDIAN == byteOrder){
writeLittleEndian(hash, output);
} else{
output.asLongBuffer().put(hash);
}
return this;
}
@Override
MetroHash64 partialApply32ByteChunk(final ByteBuffer partialInput) {
assert partialInput.remaining() >= 32;
v0 += grab(partialInput, 8) * K0;
v0 = Long.rotateRight(v0, 29) + v2;
v1 += grab(partialInput, 8) * K1;
v1 = Long.rotateRight(v1, 29) + v3;
v2 += grab(partialInput, 8) * K2;
v2 = Long.rotateRight(v2, 29) + v0;
v3 += grab(partialInput, 8) * K3;
v3 = Long.rotateRight(v3, 29) + v1;
++nChunks;
return this;
}
@Override
MetroHash64 partialApplyRemaining(final ByteBuffer partialInput) {
assert partialInput.remaining() < 32;
if (nChunks > 0) {
metroHash64_32();
}
if (partialInput.remaining() >= 16) {
metroHash64_16(partialInput);
}
if (partialInput.remaining() >= 8) {
metroHash64_8(partialInput);
}
if (partialInput.remaining() >= 4) {
metroHash64_4(partialInput);
}
if (partialInput.remaining() >= 2) {
metroHash64_2(partialInput);
}
if (partialInput.remaining() >= 1) {
metroHash64_1(partialInput);
}
hash ^= Long.rotateRight(hash, 28);
hash *= K0;
hash ^= Long.rotateRight(hash, 29);
return this;
}
// region ----- private methods
private void metroHash64_32() {
v2 ^= Long.rotateRight(((v0 + v3) * K0) + v1, 37) * K1;
v3 ^= Long.rotateRight(((v1 + v2) * K1) + v0, 37) * K0;
v0 ^= Long.rotateRight(((v0 + v2) * K0) + v3, 37) * K1;
v1 ^= Long.rotateRight(((v1 + v3) * K1) + v2, 37) * K0;
hash += v0 ^ v1;
}
private void metroHash64_16(final ByteBuffer bb) {
v0 = hash + grab(bb, 8) * K2;
v0 = Long.rotateRight(v0, 29) * K3;
v1 = hash + grab(bb, 8) * K2;
v1 = Long.rotateRight(v1, 29) * K3;
v0 ^= Long.rotateRight(v0 * K0, 21) + v1;
v1 ^= Long.rotateRight(v1 * K3, 21) + v0;
hash += v1;
}
private void metroHash64_8(final ByteBuffer bb) {
hash += grab(bb, 8) * K3;
hash ^= Long.rotateRight(hash, 55) * K1;
}
private void metroHash64_4(final ByteBuffer bb) {
hash += grab(bb, 4) * K3;
hash ^= Long.rotateRight(hash, 26) * K1;
}
private void metroHash64_2(final ByteBuffer bb) {
hash += grab(bb, 2) * K3;
hash ^= Long.rotateRight(hash, 48) * K1;
}
private void metroHash64_1(final ByteBuffer bb) {
hash += grab(bb, 1) * K3;
hash ^= Long.rotateRight(hash, 37) * K1;
}
// endregion
}

View File

@ -0,0 +1,8 @@
/**
* MetroHash算法实现<br>
* <p>
* 参考https://github.com/postamar/java-metrohash
*
* @author postamar, looly
*/
package cn.hutool.core.codec.hash.metro;

View File

@ -363,7 +363,7 @@ public class CollUtil {
* 例如集合1[a, b, c, c, c]集合2[a, b, c, c]<br>
* 结果[a, b, c]此结果中只保留了一个c
*
* @param <T> 集合元素类型
* @param <T> 集合元素类型
* @param colls 集合列表
* @return 交集的集合返回 {@link LinkedHashSet}
* @since 5.3.9
@ -1735,21 +1735,7 @@ public class CollUtil {
list.sort(comparator);
}
return page(pageNo, pageSize, list);
}
/**
* 对指定List分页取值
*
* @param <T> 集合元素类型
* @param pageNo 页码从0开始计数0表示第一页
* @param pageSize 每页的条目数
* @param list 列表
* @return 分页后的段落内容
* @since 4.1.20
*/
public static <T> List<T> page(final int pageNo, final int pageSize, final List<T> list) {
return ListUtil.page(pageNo, pageSize, list);
return ListUtil.page(list, pageNo, pageSize);
}
/**
@ -2015,7 +2001,7 @@ public class CollUtil {
}
/**
* 根据元素的指定字段分组非Bean都放在第一个分组中
* 根据元素的指定字段分组非Bean都放在第一个分组中
*
* @param <T> 元素类型
* @param collection 集合
@ -2023,8 +2009,24 @@ public class CollUtil {
* @return 分组列表
*/
public static <T> List<List<T>> groupByField(final Collection<T> collection, final String fieldName) {
return groupByFunc(collection, t -> BeanUtil.getFieldValue(t, fieldName));
}
/**
* 根据元素的指定字段值分组非Bean都放在第一个分组中<br>
* 例如{@code
* CollUtil.groupByFunc(list, TestBean::getAge)
* }
*
* @param <T> 元素类型
* @param collection 集合
* @param getter getter方法引用
* @return 分组列表
* @since 6.0.0
*/
public static <T> List<List<T>> groupByFunc(final Collection<T> collection, final Function<T, ?> getter) {
return group(collection, new Hash32<T>() {
private final List<Object> fieldNameList = new ArrayList<>();
private final List<Object> hashValList = new ArrayList<>();
@Override
public int hash32(final T t) {
@ -2032,14 +2034,13 @@ public class CollUtil {
// 非Bean放在同一子分组中
return 0;
}
final Object value = FieldUtil.getFieldValue(t, fieldName);
final int hash = fieldNameList.indexOf(value);
final Object value = getter.apply(t);
int hash = hashValList.indexOf(value);
if (hash < 0) {
fieldNameList.add(value);
return fieldNameList.size() - 1;
} else {
return hash;
hashValList.add(value);
hash = hashValList.size() - 1;
}
return hash;
}
});
}

View File

@ -8,20 +8,11 @@ import cn.hutool.core.collection.partition.RandomAccessPartition;
import cn.hutool.core.comparator.PinyinComparator;
import cn.hutool.core.comparator.PropertyComparator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.math.PageInfo;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.PageUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Predicate;
@ -34,7 +25,7 @@ import java.util.function.Predicate;
public class ListUtil {
/**
* 新建一个List<br>
* 新建一个{@link ArrayList}<br>
* 如果提供的初始化数组为空新建默认初始长度的List
*
* @param <T> 集合元素类型
@ -178,7 +169,7 @@ public class ListUtil {
/**
* 数组转为一个不可变List<br>
* 类似于Java9中的List.of<br>
* 不同于Arrays.asList此方法的原数组修改时并不会影响List
* 不同于Arrays.asList此方法不允许修改数组
*
* @param ts 对象
* @param <T> 对象类型
@ -254,42 +245,53 @@ public class ListUtil {
* 对指定List分页取值
*
* @param <T> 集合元素类型
* @param pageNo 页码第一页的页码取决于{@link PageUtil#getFirstPageNo()}默认0
* @param pageNo 页码第一页的页码从0开始
* @param pageSize 每页的条目数
* @param list 列表
* @return 分页后的段落内容
* @since 4.1.20
*/
public static <T> List<T> page(final int pageNo, final int pageSize, final List<T> list) {
public static <T> List<T> page(final List<T> list, final int pageNo, final int pageSize) {
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
return page(list, PageInfo.of(list.size(), pageSize)
.setFirstPageNo(0).setPageNo(pageNo));
}
/**
* 对指定List分页取值
*
* @param <T> 集合元素类型
* @param pageInfo 分页信息
* @param list 列表
* @return 分页后的段落内容
* @since 4.1.20
*/
public static <T> List<T> page(final List<T> list, final PageInfo pageInfo) {
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
final int resultSize = list.size();
// 每页条目数大于总数直接返回所有
if (resultSize <= pageSize) {
if (pageNo < (PageUtil.getFirstPageNo() + 1)) {
final int total = list.size();
final int pageSize = pageInfo.getPageSize();
// 不满一页
if (total <= pageSize) {
if (pageInfo.isFirstPage()) {
// 页码为1返回所有
return view(list);
} else {
// 越界直接返回空
return new ArrayList<>(0);
}
}
// 相乘可能会导致越界 临时用long
if (((long) (pageNo - PageUtil.getFirstPageNo()) * pageSize) > resultSize) {
if (pageInfo.getBeginIndex() > total) {
// 越界直接返回空
return new ArrayList<>(0);
}
final int[] startEnd = PageUtil.transToStartEnd(pageNo, pageSize);
if (startEnd[1] > resultSize) {
startEnd[1] = resultSize;
if (startEnd[0] > startEnd[1]) {
return new ArrayList<>(0);
}
}
return sub(list, startEnd[0], startEnd[1]);
return sub(list, pageInfo.getBeginIndex(), pageInfo.getEndIndexExclude());
}
/**
@ -307,16 +309,11 @@ public class ListUtil {
}
final int total = list.size();
final int totalPage = PageUtil.totalPage(total, pageSize);
for (int pageNo = PageUtil.getFirstPageNo(); pageNo < totalPage + PageUtil.getFirstPageNo(); pageNo++) {
// 获取当前页在列表中对应的起止序号
final int[] startEnd = PageUtil.transToStartEnd(pageNo, pageSize);
if (startEnd[1] > total) {
startEnd[1] = total;
}
final PageInfo pageInfo = PageInfo.of(total, pageSize);
while(pageInfo.isValidPage()){
// 返回数据
pageListConsumer.accept(sub(list, startEnd[0], startEnd[1]));
pageListConsumer.accept(sub(list, pageInfo.getBeginIndex(), pageInfo.getEndIndexExclude()));
pageInfo.nextPage();
}
}
@ -346,7 +343,7 @@ public class ListUtil {
if (CollUtil.isEmpty(list)) {
return list;
}
if(null == c){
if (null == c) {
c = Comparator.nullsFirst((Comparator<? super T>) Comparator.naturalOrder());
}
list.sort(c);
@ -386,6 +383,9 @@ public class ListUtil {
* @since 4.0.6
*/
public static <T> List<T> reverse(final List<T> list) {
if (CollUtil.isEmpty(list)) {
return list;
}
Collections.reverse(list);
return list;
}
@ -443,10 +443,10 @@ public class ListUtil {
/**
* 在指定位置设置元素当index小于List的长度时替换指定位置的值否则追加{@code paddingElement}直到到达index后设置值
*
* @param <T> 元素类型
* @param list List列表
* @param index 位置
* @param element 新元素
* @param <T> 元素类型
* @param list List列表
* @param index 位置
* @param element 新元素
* @param paddingElement 填充的值
* @return 原List
* @since 5.8.4
@ -468,29 +468,29 @@ public class ListUtil {
/**
* 截取集合的部分
*
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param start 开始位置包含
* @param end 结束位置不包含
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param begionInclude 开始位置包含
* @param endExclude 结束位置不包含
* @return 截取后的数组当开始位置超过最大时返回空的List
*/
public static <T> List<T> sub(final List<T> list, final int start, final int end) {
return sub(list, start, end, 1);
public static <T> List<T> sub(final List<T> list, final int begionInclude, final int endExclude) {
return sub(list, begionInclude, endExclude, 1);
}
/**
* 截取集合的部分<br>
* 此方法与{@link List#subList(int, int)} 不同在于子列表是新的副本操作子列表不会影响原列表
*
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param start 开始位置包含
* @param end 结束位置不包含
* @param step 步进
* @param <T> 集合元素类型
* @param list 被截取的数组
* @param begionInclude 开始位置包含
* @param endExclude 结束位置不包含
* @param step 步进
* @return 截取后的数组当开始位置超过最大时返回空的List
* @since 4.0.6
*/
public static <T> List<T> sub(final List<T> list, int start, int end, int step) {
public static <T> List<T> sub(final List<T> list, int begionInclude, int endExclude, int step) {
if (list == null) {
return null;
}
@ -500,25 +500,25 @@ public class ListUtil {
}
final int size = list.size();
if (start < 0) {
start += size;
if (begionInclude < 0) {
begionInclude += size;
}
if (end < 0) {
end += size;
if (endExclude < 0) {
endExclude += size;
}
if (start == size) {
if (begionInclude == size) {
return new ArrayList<>(0);
}
if (start > end) {
final int tmp = start;
start = end;
end = tmp;
if (begionInclude > endExclude) {
final int tmp = begionInclude;
begionInclude = endExclude;
endExclude = tmp;
}
if (end > size) {
if (start >= size) {
if (endExclude > size) {
if (begionInclude >= size) {
return new ArrayList<>(0);
}
end = size;
endExclude = size;
}
if (step < 1) {
@ -526,7 +526,7 @@ public class ListUtil {
}
final List<T> result = new ArrayList<>();
for (int i = start; i < end; i += step) {
for (int i = begionInclude; i < endExclude; i += step) {
result.add(list.get(i));
}
return result;
@ -716,8 +716,8 @@ public class ListUtil {
* 通过删除或替换现有元素或者原地添加新的元素来修改列表并以列表形式返回被修改的内容此方法不会改变原列表
* 类似js的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/splice">splice</a>函数
*
* @param <T> 元素类型
* @param list 列表
* @param <T> 元素类型
* @param list 列表
* @param start 指定修改的开始位置 0 计数, 可以为负数, -1代表最后一个元素
* @param deleteCount 删除个数必须是正整数
* @param items 放入的元素

View File

@ -95,6 +95,7 @@ public class LineIter extends ComputeIter<String> implements IterableIter<String
* @param line 需要验证的行
* @return 是否通过验证
*/
@SuppressWarnings("unused")
protected boolean isValidLine(final String line) {
return true;
}

View File

@ -3,13 +3,7 @@ package cn.hutool.core.comparator;
import cn.hutool.core.lang.Chain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.*;
/**
* 比较器链此链包装了多个比较器最终比较结果按照比较器顺序综合多个比较器结果<br>
@ -18,6 +12,7 @@ import java.util.Objects;
*
* @author looly
* @since 3.0.7
* @param <E> 被比较的对象
*/
public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<E>>, Comparator<E>, Serializable {
private static final long serialVersionUID = -2426725788913962429L;
@ -38,11 +33,11 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
//------------------------------------------------------------------------------------- Static method start
/**
* 构建 {@link ComparatorChain}
* 构建 {@code ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparator 比较器
* @return {@link ComparatorChain}
* @return {@code ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(final Comparator<E> comparator) {
@ -50,12 +45,12 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
}
/**
* 构建 {@link ComparatorChain}
* 构建 {@code ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparator 比较器
* @param reverse 是否反向
* @return {@link ComparatorChain}
* @return {@code ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(final Comparator<E> comparator, final boolean reverse) {
@ -63,11 +58,11 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
}
/**
* 构建 {@link ComparatorChain}
* 构建 {@code ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparators 比较器数组
* @return {@link ComparatorChain}
* @return {@code ComparatorChain}
* @since 5.4.3
*/
@SafeVarargs
@ -76,11 +71,11 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
}
/**
* 构建 {@link ComparatorChain}
* 构建 {@code ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparators 比较器列表
* @return {@link ComparatorChain}
* @return {@code ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(final List<Comparator<E>> comparators) {
@ -88,12 +83,12 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
}
/**
* 构建 {@link ComparatorChain}
* 构建 {@code ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparators 比较器列表
* @param bits {@link Comparator} 列表对应的排序boolean值true表示正序false反序
* @return {@link ComparatorChain}
* @return {@code ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(final List<Comparator<E>> comparators, final BitSet bits) {

View File

@ -1,34 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.text.StrUtil;
/**
* 编译异常
*
* @author looly
* @since 5.5.2
*/
public class CompilerException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CompilerException(final Throwable e) {
super(ExceptionUtil.getMessage(e), e);
}
public CompilerException(final String message) {
super(message);
}
public CompilerException(final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params));
}
public CompilerException(final String message, final Throwable throwable) {
super(message, throwable);
}
public CompilerException(final Throwable throwable, final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}

View File

@ -1,80 +0,0 @@
package cn.hutool.core.compiler;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* 源码编译工具类主要封装{@link JavaCompiler} 相关功能
*
* @author looly
* @since 5.5.2
*/
public class CompilerUtil {
/**
* java 编译器
*/
public static final JavaCompiler SYSTEM_COMPILER = ToolProvider.getSystemJavaCompiler();
/**
* 编译指定的源码文件
*
* @param sourceFiles 源码文件路径
* @return 0表示成功否则其他
*/
public static boolean compile(final String... sourceFiles) {
return 0 == SYSTEM_COMPILER.run(null, null, null, sourceFiles);
}
/**
* 获取{@link StandardJavaFileManager}
*
* @return {@link StandardJavaFileManager}
*/
public static StandardJavaFileManager getFileManager() {
return getFileManager(null);
}
/**
* 获取{@link StandardJavaFileManager}
*
* @param diagnosticListener 异常收集器
* @return {@link StandardJavaFileManager}
* @since 5.5.8
*/
public static StandardJavaFileManager getFileManager(final DiagnosticListener<? super JavaFileObject> diagnosticListener) {
return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null);
}
/**
* 新建编译任务
*
* @param fileManager {@link JavaFileManager}用于管理已经编译好的文件
* @param diagnosticListener 诊断监听
* @param options 选项例如 -cpXXX等
* @param compilationUnits 编译单元即需要编译的对象
* @return {@link JavaCompiler.CompilationTask}
*/
public static JavaCompiler.CompilationTask getTask(
final JavaFileManager fileManager,
final DiagnosticListener<? super JavaFileObject> diagnosticListener,
final Iterable<String> options,
final Iterable<? extends JavaFileObject> compilationUnits) {
return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits);
}
/**
* 获取{@link JavaSourceCompiler}
*
* @param parent {@link ClassLoader}
* @return {@link JavaSourceCompiler}
* @see JavaSourceCompiler#of(ClassLoader)
*/
public static JavaSourceCompiler getCompiler(final ClassLoader parent) {
return JavaSourceCompiler.of(parent);
}
}

View File

@ -1,26 +0,0 @@
package cn.hutool.core.compiler;
import javax.tools.DiagnosticCollector;
import java.util.List;
import java.util.stream.Collectors;
/**
* 诊断工具类
*
* @author looly
* @since 5.5.2
*/
public class DiagnosticUtil {
/**
* 获取{@link DiagnosticCollector}收集到的诊断信息以文本返回
*
* @param collector {@link DiagnosticCollector}
* @return 诊断消息
*/
public static String getMessages(final DiagnosticCollector<?> collector) {
final List<?> diagnostics = collector.getDiagnostics();
return diagnostics.stream().map(String::valueOf)
.collect(Collectors.joining(System.lineSeparator()));
}
}

View File

@ -1,78 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.io.resource.FileObjectResource;
import cn.hutool.core.classloader.ResourceClassLoader;
import cn.hutool.core.classloader.ClassLoaderUtil;
import cn.hutool.core.util.ObjUtil;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import java.util.HashMap;
import java.util.Map;
/**
* Java 字节码文件对象管理器
*
* <p>
* 正常我们使用javac命令编译源码时会将class文件写入到磁盘中但在运行时动态编译类不适合保存在磁盘中
* 我们采取此对象来管理运行时动态编译类生成的字节码
* </p>
*
* @author lzpeng
* @since 5.5.2
*/
class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
/**
* 存储java字节码文件对象映射
*/
private final Map<String, FileObjectResource> classFileObjectMap = new HashMap<>();
/**
* 加载动态编译生成类的父类加载器
*/
private final ClassLoader parent;
/**
* 构造
*
* @param parent 父类加载器
* @param fileManager 字节码文件管理器
*/
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) {
super(fileManager);
this.parent = ObjUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
}
/**
* 获得动态编译生成的类的类加载器
*
* @param location 源码位置
* @return 动态编译生成的类的类加载器
*/
@Override
public ClassLoader getClassLoader(final Location location) {
return new ResourceClassLoader<>(this.parent, this.classFileObjectMap);
}
/**
* 获得Java字节码文件对象
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象
*
* @param location 源码位置
* @param className 类名
* @param kind 文件类型
* @param sibling Java源码对象
* @return Java字节码文件对象
*/
@Override
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
final JavaFileObject javaFileObject = new JavaClassFileObject(className);
this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
return javaFileObject;
}
}

View File

@ -1,60 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.net.url.URLUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Java 字节码文件对象用于在内存中暂存class字节码从而可以在ClassLoader中动态加载
*
* @author lzpeng
* @since 5.5.2
*/
class JavaClassFileObject extends SimpleJavaFileObject {
/**
* 字节码输出流
*/
private final ByteArrayOutputStream byteArrayOutputStream;
/**
* 构造
*
* @param className 编译后的class文件的类名
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
*/
protected JavaClassFileObject(final String className) {
super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS);
this.byteArrayOutputStream = new ByteArrayOutputStream();
}
/**
* 获得字节码输入流
* 编译器编辑源码后我们将通过此输出流获得编译后的字节码以便运行时加载类
*
* @return 字节码输入流
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
*/
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
/**
* 获得字节码输出流
* 编译器编辑源码时会将编译结果输出到本输出流中
*
* @return 字节码输出流
*/
@Override
public OutputStream openOutputStream() {
return this.byteArrayOutputStream;
}
}

View File

@ -1,112 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.compress.ZipUtil;
import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.Resource;
import javax.tools.JavaFileObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;
/**
* {@link JavaFileObject} 相关工具类封装
*
* @author lzpeng, looly
* @since 5.5.2
*/
public class JavaFileObjectUtil {
/**
* 获取指定资源下的所有待编译的java源码文件并以{@link JavaFileObject}形式返回
* <ul>
* <li>如果资源为目录则遍历目录找到目录中的.java或者.jar等文件加载之</li>
* <li>如果资源为.jar或.zip等解压读取其中的.java文件加载之</li>
* <li>其他情况直接读取资源流并加载之</li>
* </ul>
*
* @param resource 资源可以为目录文件或流
* @return 所有待编译的 {@link JavaFileObject}
*/
public static List<JavaFileObject> getJavaFileObjects(final Resource resource) {
final List<JavaFileObject> result = new ArrayList<>();
if (resource instanceof FileResource) {
final File file = ((FileResource) resource).getFile();
result.addAll(JavaFileObjectUtil.getJavaFileObjects(file));
} else {
result.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
}
return result;
}
/**
* 获取指定文件下的所有待编译的java文件并以{@link JavaFileObject}形式返回
* <ul>
* <li>如果文件为目录则遍历目录找到目录中的.java或者.jar等文件加载之</li>
* <li>如果文件为.jar或.zip等解压读取其中的.java文件加载之</li>
* </ul>
*
* @param file 文件或目录文件支持.java.jar和.zip文件
* @return 所有待编译的 {@link JavaFileObject}
*/
public static List<JavaFileObject> getJavaFileObjects(final File file) {
final List<JavaFileObject> result = new ArrayList<>();
if (file.isDirectory()) {
FileUtil.walkFiles(file, (subFile) -> result.addAll(getJavaFileObjects(file)));
} else {
final String fileName = file.getName();
if (isJavaFile(fileName)) {
result.add(new JavaSourceFileObject(file.toURI()));
} else if (isJarOrZipFile(fileName)) {
result.addAll(getJavaFileObjectByZipOrJarFile(file));
}
}
return result;
}
/**
* 是否是jar zip 文件<br>
* 通过扩展名判定
*
* @param fileName 文件名
* @return 是否是jar zip 文件
*/
public static boolean isJarOrZipFile(final String fileName) {
return FileNameUtil.isType(fileName, "jar", "zip");
}
/**
* 是否是java文件<br>
* 通过扩展名判定
*
* @param fileName 文件名
* @return 是否是.java文件
*/
public static boolean isJavaFile(final String fileName) {
return FileNameUtil.isType(fileName, "java");
}
/**
* 通过zip包或jar包创建Java文件对象
*
* @param file 压缩文件
* @return Java文件对象
*/
private static List<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
final List<JavaFileObject> collection = new ArrayList<>();
final ZipFile zipFile = ZipUtil.toZipFile(file, null);
ZipUtil.read(zipFile, (zipEntry) -> {
final String name = zipEntry.getName();
if (isJavaFile(name)) {
collection.add(new JavaSourceFileObject(name, ZipUtil.getStream(zipFile, zipEntry)));
}
});
return collection;
}
}

View File

@ -1,234 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.classloader.ClassLoaderUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.io.resource.StringResource;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Java 源码编译器
* <p>通过此类可以动态编译java源码并加载到ClassLoader从而动态获取加载的类</p>
* <p>JavaSourceCompiler支持加载的源码类型包括</p>
* <ul>
* <li>源码文件</li>
* <li>源码文件源码字符串</li>
* </ul>
*
* <p>使用方法如下</p>
* <pre>
* ClassLoader classLoader = JavaSourceCompiler.create(null)
* .addSource(FileUtil.file("test-compile/b/B.java"))
* .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
* // 增加编译依赖的类库
* .addLibrary(libFile)
* .compile();
* Class&lt;?&gt; clazz = classLoader.loadClass("c.C");
* </pre>
*
* @author lzpeng
*/
public class JavaSourceCompiler {
/**
* 待编译的资源支持
*
* <ul>
* <li>源码字符串使用{@link StringResource}</li>
* <li>源码文件源码jar包或源码zip包亦或者文件夹使用{@link FileResource}</li>
* </ul>
* 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
*/
private final List<Resource> sourceList = new ArrayList<>();
/**
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
*/
private final List<File> libraryFileList = new ArrayList<>();
/**
* 编译类时使用的父类加载器
*/
private final ClassLoader parentClassLoader;
/**
* 创建Java源码编译器
*
* @param parent 父类加载器
* @return Java源码编译器
*/
public static JavaSourceCompiler of(final ClassLoader parent) {
return new JavaSourceCompiler(parent);
}
/**
* 构造
*
* @param parent 父类加载器null则使用默认类加载器
*/
private JavaSourceCompiler(final ClassLoader parent) {
this.parentClassLoader = ObjUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
}
/**
* 向编译器中加入待编译的资源<br>
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
*
* @param resources 待编译的资源支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(final Resource... resources) {
if (ArrayUtil.isNotEmpty(resources)) {
this.sourceList.addAll(Arrays.asList(resources));
}
return this;
}
/**
* 向编译器中加入待编译的文件<br>
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
*
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(final File... files) {
if (ArrayUtil.isNotEmpty(files)) {
for (final File file : files) {
this.sourceList.add(new FileResource(file));
}
}
return this;
}
/**
* 向编译器中加入待编译的源码Map
*
* @param sourceCodeMap 源码Map key: 类名 value 源码
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) {
sourceCodeMap.forEach(this::addSource);
}
return this;
}
/**
* 向编译器中加入待编译的源码
*
* @param className 类名
* @param sourceCode 源码
* @return Java文件编译器
*/
public JavaSourceCompiler addSource(final String className, final String sourceCode) {
if (className != null && sourceCode != null) {
this.sourceList.add(new StringResource(sourceCode, className));
}
return this;
}
/**
* 加入编译Java源码时所需要的jar包jar包中必须为字节码
*
* @param files 编译Java源码时所需要的jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addLibrary(final File... files) {
if (ArrayUtil.isNotEmpty(files)) {
this.libraryFileList.addAll(Arrays.asList(files));
}
return this;
}
/**
* 编译所有文件并返回类加载器
*
* @return 类加载器
*/
public ClassLoader compile() {
// 获得classPath
final List<File> classPath = getClassPath();
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
if (sourceList.isEmpty()) {
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
return ucl;
}
// 创建编译器
final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
// classpath
final List<String> options = new ArrayList<>();
if (false == classPath.isEmpty()) {
final List<String> cp = CollUtil.map(classPath, File::getAbsolutePath, true);
options.add("-cp");
options.add(CollUtil.join(cp, FileUtil.isWindows() ? ";" : ":"));
}
// 编译文件
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
try {
if (task.call()) {
// 加载编译后的类
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
}
} finally {
IoUtil.close(javaFileManager);
}
//编译失败,收集错误信息
throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector));
}
/**
* 获得编译源码时需要的classpath
*
* @return 编译源码时需要的classpath
*/
private List<File> getClassPath() {
final List<File> classPathFileList = new ArrayList<>();
for (final File file : libraryFileList) {
final List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
classPathFileList.addAll(jarOrZipFile);
if (file.isDirectory()) {
classPathFileList.add(file);
}
}
return classPathFileList;
}
/**
* 获得待编译的Java文件对象
*
* @return 待编译的Java文件对象
*/
private List<JavaFileObject> getJavaFileObject() {
final List<JavaFileObject> list = new ArrayList<>();
for (final Resource resource : this.sourceList) {
list.addAll(JavaFileObjectUtil.getJavaFileObjects(resource));
}
return list;
}
}

View File

@ -1,90 +0,0 @@
package cn.hutool.core.compiler;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.net.url.URLUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
/**
* Java 源码文件对象支持<br>
* <ol>
* <li>源文件通过文件的uri传入</li>
* <li>代码内容通过流传入</li>
* </ol>
*
* @author lzpeng
* @since 5.5.2
*/
class JavaSourceFileObject extends SimpleJavaFileObject {
/**
* 输入流
*/
private InputStream inputStream;
/**
* 构造支持File等路径类型的源码
*
* @param uri 需要编译的文件uri
*/
protected JavaSourceFileObject(final URI uri) {
super(uri, Kind.SOURCE);
}
/**
* 构造支持String类型的源码
*
* @param className 需要编译的类名
* @param code 需要编译的类源码
*/
protected JavaSourceFileObject(final String className, final String code, final Charset charset) {
this(className, IoUtil.toStream(code, charset));
}
/**
* 构造支持流中读取源码例如zip或网络等
*
* @param name 需要编译的文件名
* @param inputStream 输入流
*/
protected JavaSourceFileObject(final String name, final InputStream inputStream) {
this(URLUtil.getStringURI(name.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.SOURCE.extension));
this.inputStream = inputStream;
}
/**
* 获得类源码的输入流
*
* @return 类源码的输入流
* @throws IOException IO 异常
*/
@Override
public InputStream openInputStream() throws IOException {
if (inputStream == null) {
inputStream = toUri().toURL().openStream();
}
return new BufferedInputStream(inputStream);
}
/**
* 获得类源码
* 编译器编辑源码前会通过此方法获取类的源码
*
* @param ignoreEncodingErrors 是否忽略编码错误
* @return 需要编译的类的源码
* @throws IOException IO异常
*/
@Override
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
try(final InputStream in = openInputStream()){
return IoUtil.readUtf8(in);
}
}
}

View File

@ -1,6 +0,0 @@
/**
* 运行时编译java源码,动态从字符串或外部文件加载类
*
* @author : Lzpeng
*/
package cn.hutool.core.compiler;

View File

@ -1,7 +1,7 @@
package cn.hutool.core.compress;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.StrUtil;

View File

@ -2,9 +2,10 @@ package cn.hutool.core.compress;
import cn.hutool.core.collection.iter.EnumerationIter;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.file.FileSystemUtil;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.io.resource.Resource;
@ -172,7 +173,7 @@ public class ZipUtil {
* @throws UtilException IO异常
*/
public static File zip(final File srcFile, final Charset charset) throws UtilException {
final File zipFile = FileUtil.file(srcFile.getParentFile(), FileUtil.mainName(srcFile) + ".zip");
final File zipFile = FileUtil.file(srcFile.getParentFile(), FileNameUtil.mainName(srcFile) + ".zip");
zip(zipFile, charset, false, srcFile);
return zipFile;
}
@ -468,7 +469,7 @@ public class ZipUtil {
* @since 3.2.2
*/
public static File unzip(final File zipFile, final Charset charset) throws UtilException {
final File destDir = FileUtil.file(zipFile.getParentFile(), FileUtil.mainName(zipFile));
final File destDir = FileUtil.file(zipFile.getParentFile(), FileNameUtil.mainName(zipFile));
return unzip(zipFile, destDir, charset);
}

View File

@ -1,6 +1,6 @@
package cn.hutool.core.compress;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.Resource;
@ -100,6 +100,18 @@ public class ZipWriter implements Closeable {
return this;
}
/**
* 设置压缩方式
*
* @param method 压缩方式支持{@link ZipOutputStream#DEFLATED}{@link ZipOutputStream#STORED}
* @return this
* @since 6.0.0
*/
public ZipWriter setMethod(final int method) {
this.out.setMethod(method);
return this;
}
/**
* 获取原始的{@link ZipOutputStream}
*
@ -233,9 +245,10 @@ public class ZipWriter implements Closeable {
* @param filter 文件过滤器通过实现此接口自定义要过滤的文件过滤掉哪些文件或文件夹不加入压缩{@code null}表示不过滤
* @throws IORuntimeException IO异常
*/
private ZipWriter _add(final File file, final String srcRootDir, final FileFilter filter) throws IORuntimeException {
@SuppressWarnings("resource")
private void _add(final File file, final String srcRootDir, final FileFilter filter) throws IORuntimeException {
if (null == file || (null != filter && false == filter.accept(file))) {
return this;
return;
}
// 获取文件相对于压缩文件夹根目录的子路径
@ -256,7 +269,6 @@ public class ZipWriter implements Closeable {
// 如果是文件或其它符号则直接压缩该文件
putEntry(subPath, FileUtil.getInputStream(file));
}
return this;
}
/**

View File

@ -830,6 +830,7 @@ public class Convert {
* @param notConvertSet 不替换的字符集合
* @return 替换后的字符
*/
@SuppressWarnings("UnnecessaryUnicodeEscape")
public static String toDBC(final String text, final Set<Character> notConvertSet) {
if(StrUtil.isBlank(text)) {
return text;

View File

@ -1,6 +1,6 @@
package cn.hutool.core.convert.impl;
import cn.hutool.core.codec.BaseN.Base64;
import cn.hutool.core.codec.binary.Base64;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.AbstractConverter;
import cn.hutool.core.convert.Convert;
@ -24,6 +24,9 @@ import java.util.List;
public class ArrayConverter extends AbstractConverter {
private static final long serialVersionUID = 1L;
/**
* 单例
*/
public static final ArrayConverter INSTANCE = new ArrayConverter();
/**

View File

@ -3,6 +3,7 @@ package cn.hutool.core.convert.impl;
import cn.hutool.core.convert.AbstractConverter;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
@ -156,7 +157,15 @@ public class TemporalAccessorConverter extends AbstractConverter {
return IsoEra.of(Math.toIntExact(time));
}
return parseFromInstant(targetClass, Instant.ofEpochMilli(time), null);
final Instant instant;
if(GlobalCustomFormat.FORMAT_SECONDS.equals(this.format)){
// https://gitee.com/dromara/hutool/issues/I6IS5B
// Unix时间戳
instant = Instant.ofEpochSecond(time);
}else{
instant = Instant.ofEpochMilli(time);
}
return parseFromInstant(targetClass, instant, null);
}
/**

View File

@ -73,7 +73,7 @@ public class BetweenFormatter implements Serializable {
final int level = this.level.ordinal();
int levelCount = 0;
if (isLevelCountValid(levelCount) && 0 != day && level >= Level.DAY.ordinal()) {
if (isLevelCountValid(levelCount) && 0 != day) {
sb.append(day).append(Level.DAY.name);
levelCount++;
}

View File

@ -2,12 +2,12 @@ package cn.hutool.core.date;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.NumberChineseFormatter;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.date.format.parser.DateParser;
import cn.hutool.core.date.format.parser.FastDateParser;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.date.format.parser.PositionDateParser;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import javax.xml.datatype.XMLGregorianCalendar;
import java.text.ParsePosition;
@ -15,7 +15,6 @@ import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.TimeZone;
@ -74,7 +73,7 @@ public class CalendarUtil {
/**
* 转换为Calendar对象
*
* @param millis 时间戳
* @param millis 时间戳
* @param timeZone 时区
* @return Calendar对象
* @since 5.7.22
@ -105,6 +104,8 @@ public class CalendarUtil {
return Calendar.PM == calendar.get(Calendar.AM_PM);
}
// region ----- modify 时间修改
/**
* 修改日期为某个时间字段起始时间
*
@ -355,6 +356,7 @@ public class CalendarUtil {
public static Calendar endOfYear(final Calendar calendar) {
return ceiling(calendar, DateField.YEAR);
}
// endregion
/**
* 比较两个日期是否为同一天
@ -375,8 +377,8 @@ public class CalendarUtil {
/**
* 比较两个日期是否为同一周
*
* @param cal1 日期1
* @param cal2 日期2
* @param cal1 日期1
* @param cal2 日期2
* @param isMon 是否为周一国内第一天为星期一国外第一天为星期日
* @return 是否为同一周
* @since 5.7.21
@ -443,28 +445,6 @@ public class CalendarUtil {
return date1.getTimeInMillis() == date2.getTimeInMillis();
}
/**
* 获得指定日期区间内的年份和季度<br>
*
* @param startDate 起始日期包含
* @param endDate 结束日期包含
* @return 季度列表 元素类似于 20132
* @since 4.1.15
*/
public static LinkedHashSet<String> yearAndQuarter(long startDate, final long endDate) {
final LinkedHashSet<String> quarters = new LinkedHashSet<>();
final Calendar cal = calendar(startDate);
while (startDate <= endDate) {
// 如果开始时间超出结束时间让结束时间为开始时间处理完后结束循环
quarters.add(yearAndQuarter(cal));
cal.add(Calendar.MONTH, 3);
startDate = cal.getTimeInMillis();
}
return quarters;
}
/**
* 获得指定日期年份和季度<br>
* 格式[20131]表示2013年第一季度
@ -637,51 +617,7 @@ public class CalendarUtil {
return result.toString();
}
/**
* 计算相对于dateToCompare的年龄长用于计算指定生日在某年的年龄
*
* @param birthday 生日
* @param dateToCompare 需要对比的日期
* @return 年龄
*/
protected static int age(final long birthday, final long dateToCompare) {
if (birthday > dateToCompare) {
throw new IllegalArgumentException("Birthday is after dateToCompare!");
}
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(dateToCompare);
final int year = cal.get(Calendar.YEAR);
final int month = cal.get(Calendar.MONTH);
final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
cal.setTimeInMillis(birthday);
int age = year - cal.get(Calendar.YEAR);
//当前日期则为0岁
if (age == 0){
return 0;
}
final int monthBirth = cal.get(Calendar.MONTH);
if (month == monthBirth) {
final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);
final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
// issue#I6E6ZG法定生日当天不算年龄从第二天开始计算
if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth <= dayOfMonthBirth) {
// 如果生日在当月但是未达到生日当天的日期年龄减一
age--;
}
} else if (month < monthBirth) {
// 如果当前月份未达到生日的月份年龄计算减一
age--;
}
return age;
}
// region ----- parse
/**
* 通过给定的日期格式解析日期时间字符串<br>
* 传入的日期格式会逐个尝试直到解析成功返回{@link Calendar}对象否则抛出{@link DateException}异常
@ -782,4 +718,50 @@ public class CalendarUtil {
return parser.parse(StrUtil.str(str), new ParsePosition(0), calendar) ? calendar : null;
}
// endregion
/**
* 计算相对于dateToCompare的年龄长用于计算指定生日在某年的年龄
*
* @param birthday 生日
* @param dateToCompare 需要对比的日期
* @return 年龄
*/
protected static int age(final long birthday, final long dateToCompare) {
if (birthday > dateToCompare) {
throw new IllegalArgumentException("Birthday is after dateToCompare!");
}
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(dateToCompare);
final int year = cal.get(Calendar.YEAR);
final int month = cal.get(Calendar.MONTH);
final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
cal.setTimeInMillis(birthday);
int age = year - cal.get(Calendar.YEAR);
//当前日期则为0岁
if (age == 0) {
return 0;
}
final int monthBirth = cal.get(Calendar.MONTH);
if (month == monthBirth) {
final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);
final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
// issue#I6E6ZG法定生日当天不算年龄从第二天开始计算
if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth <= dayOfMonthBirth) {
// 如果生日在当月但是未达到生日当天的日期年龄减一
age--;
}
} else if (month < monthBirth) {
// 如果当前月份未达到生日的月份年龄计算减一
age--;
}
return age;
}
}

View File

@ -31,7 +31,7 @@ import java.util.regex.Pattern;
* <li>yyyy-MM-dd HH:mm:ss.SSS 示例2022-08-05 12:59:59.559</li>
* <li>yyyy-MM-dd HH:mm:ss.SSSZ 示例2022-08-05 12:59:59.559+0800东八区中国时区2022-08-05 04:59:59.559+0000冰岛0时区, 年月日 时分秒 毫秒 时区</li>
* <li>yyyy-MM-dd HH:mm:ss.SSSz 示例2022-08-05 12:59:59.559UTC世界标准时间=0时区2022-08-05T12:59:59.599GMT冰岛0时区2022-08-05T12:59:59.599CST东八区中国时区2022-08-23T03:45:00.599EDT美国东北纽约时间-0400 ,年月日 时分秒 毫秒 时区</li>
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 示例2022-08-05T12:59:59.559Z, 其中''单引号表示转义字符T:分隔符Z:一般UTC,0时区的时间含义</li>
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 示例2022-08-05T12:59:59.559Z, 其中''单引号表示转义字符T:分隔符Z:一般UTC,0时区的时间含义</li>
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSZ 示例2022-08-05T11:59:59.559+0800, 其中Z,表示时区</li>
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSX 示例2022-08-05T12:59:59.559+08, 其中X:两位时区+08表示东8区中国时区</li>
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSXX 示例2022-08-05T12:59:59.559+0800, 其中XX:四位时区</li>
@ -61,7 +61,7 @@ import java.util.regex.Pattern;
* 09:30 UTC表示为09:30ZT0930Z其中Z +00:00 的缩写意思是 UTC(零时分秒的偏移量).
* </p>
* <ul>
* <li>yyyy-MM-dd'T'HH:mm:ssZ</li>
* <li>yyyy-MM-dd'T'HH:mm:ss'Z'</li>
* <li>2022-08-23T15:20:46UTC</li>
* <li>2022-08-23T15:20:46 UTC</li>
* <li>2022-08-23T15:20:46+0000</li>
@ -189,15 +189,15 @@ public class DatePattern {
/**
* ISO8601日期时间格式精确到毫秒yyyy-MM-dd HH:mm:ss,SSS
*/
public static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
public static final String NORM_DATETIME_COMMA_MS_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
/**
* ISO8601日期时间格式精确到毫秒 {@link FastDateFormat}yyyy-MM-dd HH:mm:ss,SSS
*/
public static final FastDateFormat ISO8601_FORMAT = FastDateFormat.getInstance(ISO8601_PATTERN);
public static final FastDateFormat NORM_DATETIME_COMMA_MS_FORMAT = FastDateFormat.getInstance(NORM_DATETIME_COMMA_MS_PATTERN);
/**
* 标准日期格式 {@link DateTimeFormatter}yyyy-MM-dd HH:mm:ss,SSS
*/
public static final DateTimeFormatter ISO8601_FORMATTER = createFormatter(ISO8601_PATTERN);
public static final DateTimeFormatter NORM_DATETIME_COMMA_MS_FORMATTER = createFormatter(NORM_DATETIME_COMMA_MS_PATTERN);
/**
* 标准日期格式yyyy年MM月dd日
@ -286,8 +286,7 @@ public class DatePattern {
.toFormatter();
// endregion
// region Others
//================================================== Others ==================================================
// region ----- Others
/**
* HTTP头中日期时间格式EEE, dd MMM yyyy HH:mm:ss z
*/
@ -307,76 +306,78 @@ public class DatePattern {
public static final FastDateFormat JDK_DATETIME_FORMAT = FastDateFormat.getInstance(JDK_DATETIME_PATTERN, Locale.US);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ss
* ISO8601日期时间yyyy-MM-dd'T'HH:mm:ss<br>
* 按照ISO8601规范默认使用T分隔日期和时间末尾不加Z表示当地时区
*/
public static final String UTC_SIMPLE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
public static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss
* ISO8601日期时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss
*/
public static final FastDateFormat UTC_SIMPLE_FORMAT = FastDateFormat.getInstance(UTC_SIMPLE_PATTERN, TimeZone.getTimeZone("UTC"));
public static final FastDateFormat ISO8601_FORMAT = FastDateFormat.getInstance(ISO8601_PATTERN);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ss.SSS
*/
public static final String UTC_SIMPLE_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
public static final String ISO8601_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss.SSS
*/
public static final FastDateFormat UTC_SIMPLE_MS_FORMAT = FastDateFormat.getInstance(UTC_SIMPLE_MS_PATTERN, TimeZone.getTimeZone("UTC"));
public static final FastDateFormat ISO8601_MS_FORMAT = FastDateFormat.getInstance(ISO8601_MS_PATTERN);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ss'Z'
* UTC时间yyyy-MM-dd'T'HH:mm:ss'Z'<br>
* 按照ISO8601规范后缀加Z表示UTC时间
*/
public static final String UTC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss'Z'
* ISO8601时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss'Z'
*/
public static final FastDateFormat UTC_FORMAT = FastDateFormat.getInstance(UTC_PATTERN, TimeZone.getTimeZone("UTC"));
public static final FastDateFormat UTC_FORMAT = FastDateFormat.getInstance(UTC_PATTERN, ZoneUtil.ZONE_UTC);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ssZ
* ISO8601时间yyyy-MM-dd'T'HH:mm:ssZZ表示一个时间偏移+0800
*/
public static final String UTC_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
public static final String ISO8601_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ssZ
* ISO8601时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ssZZ表示一个时间偏移+0800
*/
public static final FastDateFormat UTC_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_WITH_ZONE_OFFSET_PATTERN, TimeZone.getTimeZone("UTC"));
public static final FastDateFormat ISO8601_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_WITH_ZONE_OFFSET_PATTERN);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ssXXX
* ISO8601时间yyyy-MM-dd'T'HH:mm:ssXXX
*/
public static final String UTC_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX";
public static final String ISO8601_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ssXXX
* ISO8601时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ssXXX
*/
public static final FastDateFormat UTC_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_WITH_XXX_OFFSET_PATTERN);
public static final FastDateFormat ISO8601_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_WITH_XXX_OFFSET_PATTERN);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
* ISO8601时间yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
*/
public static final String UTC_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
* ISO8601时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
*/
public static final FastDateFormat UTC_MS_FORMAT = FastDateFormat.getInstance(UTC_MS_PATTERN, TimeZone.getTimeZone("UTC"));
public static final FastDateFormat UTC_MS_FORMAT = FastDateFormat.getInstance(UTC_MS_PATTERN, ZoneUtil.ZONE_UTC);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ss.SSSZ
* ISO8601时间yyyy-MM-dd'T'HH:mm:ss.SSSZ
*/
public static final String UTC_MS_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public static final String ISO8601_MS_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss.SSSZ
* ISO8601时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss.SSSZ
*/
public static final FastDateFormat UTC_MS_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_MS_WITH_ZONE_OFFSET_PATTERN, TimeZone.getTimeZone("UTC"));
public static final FastDateFormat ISO8601_MS_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_MS_WITH_ZONE_OFFSET_PATTERN);
/**
* UTC时间yyyy-MM-dd'T'HH:mm:ss.SSSXXX
* ISO8601时间yyyy-MM-dd'T'HH:mm:ss.SSSXXX
*/
public static final String UTC_MS_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
public static final String ISO8601_MS_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
/**
* UTC时间{@link FastDateFormat}yyyy-MM-dd'T'HH:mm:ss.SSSXXX
*/
public static final FastDateFormat UTC_MS_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_MS_WITH_XXX_OFFSET_PATTERN);
public static final FastDateFormat ISO8601_MS_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_MS_WITH_XXX_OFFSET_PATTERN);
// endregion
/**

View File

@ -5,12 +5,7 @@ import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.date.format.DatePrinter;
import cn.hutool.core.date.format.FastDateFormat;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.date.format.parser.CSTDateParser;
import cn.hutool.core.date.format.parser.NormalDateParser;
import cn.hutool.core.date.format.parser.PositionDateParser;
import cn.hutool.core.date.format.parser.PureDateParser;
import cn.hutool.core.date.format.parser.TimeParser;
import cn.hutool.core.date.format.parser.UTCDateParser;
import cn.hutool.core.date.format.parser.*;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.math.NumberUtil;
import cn.hutool.core.regex.PatternPool;
@ -26,14 +21,7 @@ import java.time.LocalDateTime;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@ -513,21 +501,7 @@ public class DateUtil extends CalendarUtil {
return yearAndQuarter(calendar(date));
}
/**
* 获得指定日期区间内的年份和季节<br>
*
* @param startDate 起始日期包含
* @param endDate 结束日期包含
* @return 季度列表 元素类似于 20132
*/
public static LinkedHashSet<String> yearAndQuarter(final Date startDate, final Date endDate) {
if (startDate == null || endDate == null) {
return new LinkedHashSet<>(0);
}
return yearAndQuarter(startDate.getTime(), endDate.getTime());
}
// ------------------------------------ Format start ----------------------------------------------
// region ----- format
/**
* 格式化日期时间<br>
* 格式 yyyy-MM-dd HH:mm:ss
@ -696,10 +670,9 @@ public class DateUtil extends CalendarUtil {
return CalendarUtil.formatChineseDate(CalendarUtil.calendar(date), withTime);
}
// ------------------------------------ Format end ----------------------------------------------
// ------------------------------------ Parse start ----------------------------------------------
// endregion
// region ----- parse
/**
* 构建DateTime对象
*
@ -840,8 +813,8 @@ public class DateUtil extends CalendarUtil {
// Wed Aug 01 00:00:00 CST 2012
return CSTDateParser.INSTANCE.parse(dateStr);
} else if (StrUtil.contains(dateStr, 'T')) {
// UTC时间
return UTCDateParser.INSTANCE.parse(dateStr);
// ISO8601标准时间
return ISO8601DateParser.INSTANCE.parse(dateStr);
}
//标准日期格式包括单个数字的日期时间
@ -853,10 +826,9 @@ public class DateUtil extends CalendarUtil {
// 没有更多匹配的时间格式
throw new DateException("No format fit for date String [{}] !", dateStr);
}
// ------------------------------------ Parse end ----------------------------------------------
// ------------------------------------ Offset start ----------------------------------------------
// endregion
// region ----- offset
/**
* 修改日期为某个时间字段起始时间
*
@ -1096,7 +1068,6 @@ public class DateUtil extends CalendarUtil {
public static DateTime endOfYear(final Date date) {
return new DateTime(endOfYear(calendar(date)));
}
// --------------------------------------------------- Offset for now
/**
* 昨天
@ -1243,9 +1214,9 @@ public class DateUtil extends CalendarUtil {
public static DateTime offset(final Date date, final DateField dateField, final int offset) {
return dateNew(date).offset(dateField, offset);
}
// endregion
// ------------------------------------ Offset end ----------------------------------------------
// region ----- between
/**
* 判断两个日期相差的时长只保留绝对值
*
@ -1351,7 +1322,9 @@ public class DateUtil extends CalendarUtil {
public static long betweenYear(final Date beginDate, final Date endDate, final boolean isReset) {
return new DateBetween(beginDate, endDate).betweenYear(isReset);
}
// endregion
// region ----- formatBetween
/**
* 格式化日期间隔输出
*
@ -1397,6 +1370,7 @@ public class DateUtil extends CalendarUtil {
public static String formatBetween(final long betweenMs) {
return new BetweenFormatter(betweenMs, BetweenFormatter.Level.MILLISECOND).format();
}
// endregion
/**
* 当前日期是否在日期指定范围内<br>
@ -1992,7 +1966,7 @@ public class DateUtil extends CalendarUtil {
* @return 单位简写名称
* @since 5.7.16
*/
public static String getShotName(final TimeUnit unit) {
public static String getShortName(final TimeUnit unit) {
switch (unit) {
case NANOSECONDS:
return "ns";

View File

@ -3,23 +3,29 @@ package cn.hutool.core.date;
/**
* 季度枚举
*
* @author zhfish(https : / / github.com / zhfish)
* @see #Q1
* @see #Q2
* @see #Q3
* @see #Q4
*
* @author zhfish(https://github.com/zhfish)
*
*/
public enum Quarter {
/** 第一季度 */
/**
* 第一季度
*/
Q1(1),
/** 第二季度 */
/**
* 第二季度
*/
Q2(2),
/** 第三季度 */
/**
* 第三季度
*/
Q3(3),
/** 第四季度 */
/**
* 第四季度
*/
Q4(4);
// ---------------------------------------------------------------
@ -29,6 +35,11 @@ public enum Quarter {
this.value = value;
}
/**
* 获取季度值
*
* @return 季度值
*/
public int getValue() {
return this.value;
}
@ -36,13 +47,12 @@ public enum Quarter {
/**
* 季度int转换为Season枚举对象<br>
*
* @param intValue 季度int表示
* @return {@code Quarter}
* @see #Q1
* @see #Q2
* @see #Q3
* @see #Q4
*
* @param intValue 季度int表示
* @return {@link Quarter}
*/
public static Quarter of(final int intValue) {
switch (intValue) {

View File

@ -1,6 +1,6 @@
package cn.hutool.core.date;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.text.StrUtil;
import java.text.NumberFormat;
@ -352,7 +352,7 @@ public class StopWatch {
unit = TimeUnit.NANOSECONDS;
}
return StrUtil.format("StopWatch '{}': running time = {} {}",
this.id, getTotal(unit), DateUtil.getShotName(unit));
this.id, getTotal(unit), DateUtil.getShortName(unit));
}
/**
@ -382,20 +382,27 @@ public class StopWatch {
sb.append("No task info kept");
} else {
sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
sb.append(DateUtil.getShotName(unit)).append(" % Task name").append(FileUtil.getLineSeparator());
sb.append(DateUtil.getShortName(unit)).append(" % Task name").append(FileUtil.getLineSeparator());
sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
final NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(9);
nf.setGroupingUsed(false);
final NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(2);
pf.setGroupingUsed(false);
for (final TaskInfo task : getTaskInfo()) {
sb.append(nf.format(task.getTime(unit))).append(" ");
sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
final String taskTimeStr = nf.format(task.getTime(unit));
sb.append(taskTimeStr);
if(taskTimeStr.length() < 11){
sb.append(StrUtil.repeat(' ', 11 - taskTimeStr.length()));
}
final String percentStr = pf.format((double) task.getTimeNanos() / getTotalTimeNanos());
if(percentStr.length() < 4){
sb.append(StrUtil.repeat(' ', 4 - percentStr.length()));
}
sb.append(percentStr).append(" ");
sb.append(task.getTaskName()).append(FileUtil.getLineSeparator());
}
}

View File

@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit;
* System.currentTimeMillis()的调用比new一个普通对象要耗时的多具体耗时高出多少我还没测试过有人说是100倍左右
* System.currentTimeMillis()之所以慢是因为去跟系统打了一次交道
* 后台定时更新时钟JVM退出时线程自动回收
*
* <p>
* see <a href="http://git.oschina.net/yu120/sequence">http://git.oschina.net/yu120/sequence</a>
* @author lry, looly
*/

View File

@ -164,8 +164,6 @@ public class TemporalAccessorUtil extends TemporalUtil{
result = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant();
} else {
// issue#1891@Github
// Instant.from不能完成日期转换
//result = Instant.from(temporalAccessor);
result = toInstant(TimeUtil.of(temporalAccessor));
}

View File

@ -1,30 +1,18 @@
package cn.hutool.core.date;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalUnit;
import java.time.temporal.WeekFields;
import java.time.temporal.*;
import java.util.Date;
import java.util.TimeZone;
import java.util.function.Function;
/**
* JDK8+中的{@link java.time} 工具类封装
@ -35,11 +23,6 @@ import java.util.TimeZone;
* @since 6.0.0
*/
public class TimeUtil extends TemporalAccessorUtil {
/**
* UTC ZoneID
*/
public static final ZoneId ZONE_ID_UTC = ZoneId.of("UTC");
/**
* 当前时间默认时区
*
@ -65,7 +48,7 @@ public class TimeUtil extends TemporalAccessorUtil {
* @return {@link LocalDateTime}
*/
public static LocalDateTime ofUTC(final Instant instant) {
return of(instant, ZONE_ID_UTC);
return of(instant, ZoneUtil.ZONE_ID_UTC);
}
/**
@ -234,7 +217,7 @@ public class TimeUtil extends TemporalAccessorUtil {
/**
* 解析日期时间字符串为{@link LocalDateTime}格式支持日期时间日期时间<br>
* 如果formatter为{code null}则使用{@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}
* 如果formatter为{@code null}则使用{@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}
*
* @param text 日期时间字符串
* @param formatter 日期格式化器预定义的格式见{@link DateTimeFormatter}
@ -355,6 +338,17 @@ public class TimeUtil extends TemporalAccessorUtil {
return format(date, DatePattern.NORM_DATE_FORMATTER);
}
/**
* 格式化时间函数
*
* @param dateTimeFormatter {@link DateTimeFormatter}
* @return 格式化时间的函数
*/
public static Function<TemporalAccessor, String> formatFunc(final DateTimeFormatter dateTimeFormatter) {
return LambdaUtil.toFunction(TimeUtil::format, dateTimeFormatter);
}
/**
* 日期偏移,根据field不同加不同值偏移会修改传入的对象
*

View File

@ -11,6 +11,15 @@ import java.util.TimeZone;
*/
public class ZoneUtil {
/**
* UTC ZoneID
*/
public static final TimeZone ZONE_UTC = TimeZone.getTimeZone("UTC");
/**
* UTC TimeZone
*/
public static final ZoneId ZONE_ID_UTC = ZONE_UTC.toZoneId();
/**
* {@link ZoneId}转换为{@link TimeZone}{@code null}则返回系统默认值
*

View File

@ -573,17 +573,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mValue = value;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return mValue.length();
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
buffer.append(mValue);
@ -610,9 +604,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mValues = values;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
int max = 0;
@ -625,9 +616,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
return max;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
buffer.append(mValues[calendar.get(mField)]);
@ -651,25 +639,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mField = field;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return 4;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(mField));
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
if (value < 10) {
@ -697,25 +676,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
UnpaddedMonthField() {
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return 2;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
if (value < 10) {
@ -750,25 +720,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mSize = size;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return mSize;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(mField));
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
appendFullDigits(buffer, value, mSize);
@ -792,25 +753,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mField = field;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return 2;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(mField));
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
if (value < 100) {
@ -835,25 +787,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
TwoDigitYearField() {
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return 2;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
appendDigits(buffer, value);
@ -874,25 +817,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
TwoDigitMonthField() {
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return 2;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
appendDigits(buffer, value);
@ -916,17 +850,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mRule = rule;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return mRule.estimateLength();
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
int value = calendar.get(Calendar.HOUR);
@ -936,9 +864,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mRule.appendTo(buffer, value);
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final int value) throws IOException {
mRule.appendTo(buffer, value);
@ -962,17 +887,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mRule = rule;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return mRule.estimateLength();
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
int value = calendar.get(Calendar.HOUR_OF_DAY);
@ -982,9 +901,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mRule.appendTo(buffer, value);
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final int value) throws IOException {
mRule.appendTo(buffer, value);
@ -1103,9 +1019,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
// We have no access to the Calendar object that will be passed to
@ -1114,9 +1027,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
return Math.max(mStandard.length(), mDaylight.length());
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
final TimeZone zone = calendar.getTimeZone();
@ -1148,17 +1058,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mColon = colon;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return 5;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
@ -1227,17 +1131,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
this.length = length;
}
/**
* {@inheritDoc}
*/
@Override
public int estimateLength() {
return length;
}
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
@ -1298,17 +1196,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
mLocale = locale;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return (mStyle * 31 + mLocale.hashCode()) * 31 + mTimeZone.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {

View File

@ -18,7 +18,13 @@ import java.util.function.Function;
*/
public class GlobalCustomFormat {
/**
* 格式秒时间戳Unix时间戳
*/
public static final String FORMAT_SECONDS = "#sss";
/**
* 格式毫秒时间戳
*/
public static final String FORMAT_MILLISECONDS = "#SSS";
private static final Map<CharSequence, Function<Date, String>> formatterMap;

View File

@ -16,7 +16,10 @@ import cn.hutool.core.date.format.DefaultDateBasic;
* @since 6.0.0
*/
public class CSTDateParser extends DefaultDateBasic implements DateParser {
private static final long serialVersionUID = 1L;
/**
* 单例对象
*/
public static CSTDateParser INSTANCE = new CSTDateParser();
@Override

View File

@ -9,7 +9,7 @@ import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharUtil;
/**
* UTC日期字符串JDK的Date对象toString默认格式解析支持格式
* ISO8601日期字符串JDK的Date对象toString默认格式解析支持格式
* <ol>
* <li>yyyy-MM-dd'T'HH:mm:ss'Z'</li>
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</li>
@ -22,13 +22,13 @@ import cn.hutool.core.util.CharUtil;
* @author looly
* @since 6.0.0
*/
public class UTCDateParser extends DefaultDateBasic implements DateParser {
public class ISO8601DateParser extends DefaultDateBasic implements DateParser {
private static final long serialVersionUID = 1L;
/**
* 单例对象
*/
public static UTCDateParser INSTANCE = new UTCDateParser();
public static ISO8601DateParser INSTANCE = new ISO8601DateParser();
@Override
public DateTime parse(String source) {
@ -61,10 +61,10 @@ public class UTCDateParser extends DefaultDateBasic implements DateParser {
if (StrUtil.contains(source, CharUtil.DOT)) {
// 带毫秒格式类似2018-09-13T05:34:31.999+08:00
source = normalizeMillSeconds(source, ".", "+");
return new DateTime(source, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT);
return new DateTime(source, DatePattern.ISO8601_MS_WITH_XXX_OFFSET_FORMAT);
} else {
// 格式类似2018-09-13T05:34:31+08:00
return new DateTime(source, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT);
return new DateTime(source, DatePattern.ISO8601_WITH_XXX_OFFSET_FORMAT);
}
} else if(ReUtil.contains("-\\d{2}:?00", source)){
// Issue#2612类似 2022-09-14T23:59:00-08:00 或者 2022-09-14T23:59:00-0800
@ -78,22 +78,22 @@ public class UTCDateParser extends DefaultDateBasic implements DateParser {
if (StrUtil.contains(source, CharUtil.DOT)) {
// 带毫秒格式类似2018-09-13T05:34:31.999-08:00
source = normalizeMillSeconds(source, ".", "-");
return new DateTime(source, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT);
return new DateTime(source, DatePattern.ISO8601_MS_WITH_XXX_OFFSET_FORMAT);
} else {
// 格式类似2018-09-13T05:34:31-08:00
return new DateTime(source, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT);
return new DateTime(source, DatePattern.ISO8601_WITH_XXX_OFFSET_FORMAT);
}
} else {
if (length == DatePattern.UTC_SIMPLE_PATTERN.length() - 2) {
if (length == DatePattern.ISO8601_PATTERN.length() - 2) {
// 格式类似2018-09-13T05:34:31
return new DateTime(source, DatePattern.UTC_SIMPLE_FORMAT);
} else if (length == DatePattern.UTC_SIMPLE_PATTERN.length() - 5) {
return new DateTime(source, DatePattern.ISO8601_FORMAT);
} else if (length == DatePattern.ISO8601_PATTERN.length() - 5) {
// 格式类似2018-09-13T05:34
return new DateTime(source + ":00", DatePattern.UTC_SIMPLE_FORMAT);
return new DateTime(source + ":00", DatePattern.ISO8601_FORMAT);
} else if (StrUtil.contains(source, CharUtil.DOT)) {
// 可能为 2021-03-17T06:31:33.99
source = normalizeMillSeconds(source, ".", null);
return new DateTime(source, DatePattern.UTC_SIMPLE_MS_FORMAT);
return new DateTime(source, DatePattern.ISO8601_MS_FORMAT);
}
}
// 没有更多匹配的时间格式
@ -109,6 +109,7 @@ public class UTCDateParser extends DefaultDateBasic implements DateParser {
* @param after 毫秒部分的后一个字符
* @return 规范之后的毫秒部分
*/
@SuppressWarnings("SameParameterValue")
private static String normalizeMillSeconds(final String dateStr, final CharSequence before, final CharSequence after) {
if (StrUtil.isBlank(after)) {
final String millOrNaco = StrUtil.subPre(StrUtil.subAfter(dateStr, before, true), 3);

View File

@ -21,7 +21,11 @@ import cn.hutool.core.util.CharUtil;
* @since 6.0.0
*/
public class NormalDateParser extends DefaultDateBasic implements DateParser {
private static final long serialVersionUID = 1L;
/**
* 单例
*/
public static NormalDateParser INSTANCE = new NormalDateParser();
@Override

View File

@ -16,6 +16,7 @@ import java.util.Locale;
* @since 6.0.0
*/
public class PatternsDateParser extends DefaultDateBasic implements DateParser {
private static final long serialVersionUID = 1L;
/**
* 创建 PatternsDateParser

View File

@ -18,7 +18,11 @@ import cn.hutool.core.date.format.DefaultDateBasic;
* @since 6.0.0
*/
public class PureDateParser extends DefaultDateBasic implements DateParser {
private static final long serialVersionUID = 1L;
/**
* 单例
*/
public static PureDateParser INSTANCE = new PureDateParser();
@Override

View File

@ -17,7 +17,11 @@ import cn.hutool.core.text.StrUtil;
* @since 6.0.0
*/
public class TimeParser extends DefaultDateBasic implements DateParser {
private static final long serialVersionUID = 1L;
/**
* 单例
*/
public static TimeParser INSTANCE = new TimeParser();
@Override

View File

@ -6,28 +6,66 @@ package cn.hutool.core.exceptions;
* @author looly
*/
public class InvocationTargetRuntimeException extends UtilException {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param e 异常
*/
public InvocationTargetRuntimeException(final Throwable e) {
super(e);
}
/**
* 构造
*
* @param message 消息
*/
public InvocationTargetRuntimeException(final String message) {
super(message);
}
/**
* 构造
*
* @param messageTemplate 消息模板
* @param params 参数
*/
public InvocationTargetRuntimeException(final String messageTemplate, final Object... params) {
super(messageTemplate, params);
}
public InvocationTargetRuntimeException(final String message, final Throwable throwable) {
super(message, throwable);
/**
* 构造
*
* @param message 消息
* @param cause 被包装的子异常
*/
public InvocationTargetRuntimeException(final String message, final Throwable cause) {
super(message, cause);
}
public InvocationTargetRuntimeException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, throwable, enableSuppression, writableStackTrace);
/**
* 构造
*
* @param message 消息
* @param cause 被包装的子异常
* @param enableSuppression 是否启用抑制
* @param writableStackTrace 堆栈跟踪是否应该是可写的
*/
public InvocationTargetRuntimeException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public InvocationTargetRuntimeException(final Throwable throwable, final String messageTemplate, final Object... params) {
super(throwable, messageTemplate, params);
/**
* 构造
*
* @param cause 被包装的子异常
* @param messageTemplate 消息模板
* @param params 参数
*/
public InvocationTargetRuntimeException(final Throwable cause, final String messageTemplate, final Object... params) {
super(cause, messageTemplate, params);
}
}

View File

@ -10,8 +10,8 @@ import cn.hutool.core.text.StrUtil;
public class UtilException extends RuntimeException {
private static final long serialVersionUID = 8247610319171014183L;
public UtilException(final Throwable e) {
super(ExceptionUtil.getMessage(e), e);
public UtilException(final Throwable cause) {
super(ExceptionUtil.getMessage(cause), cause);
}
public UtilException(final String message) {
@ -22,15 +22,15 @@ public class UtilException extends RuntimeException {
super(StrUtil.format(messageTemplate, params));
}
public UtilException(final String message, final Throwable throwable) {
super(message, throwable);
public UtilException(final String message, final Throwable cause) {
super(message, cause);
}
public UtilException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, throwable, enableSuppression, writableStackTrace);
public UtilException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public UtilException(final Throwable throwable, final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
public UtilException(final Throwable cause, final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params), cause);
}
}

View File

@ -19,6 +19,11 @@ public class AppendableWriter extends Writer implements Appendable {
private final boolean flushable;
private boolean closed;
/**
* 构造
*
* @param appendable {@link Appendable}
*/
public AppendableWriter(final Appendable appendable) {
this.appendable = appendable;
this.flushable = appendable instanceof Flushable;

View File

@ -1,6 +1,7 @@
package cn.hutool.core.io;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import java.io.File;

View File

@ -23,7 +23,6 @@ import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
@ -38,6 +37,8 @@ import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Objects;
@ -525,15 +526,27 @@ public class IoUtil extends NioUtil {
}
/**
* 文件转为{@link FileInputStream}
* 文件转为{@link InputStream}
*
* @param file 文件
* @return {@link FileInputStream}
* @param file 文件非空
* @return {@link InputStream}
*/
public static FileInputStream toStream(final File file) {
public static InputStream toStream(final File file) {
Assert.notNull(file);
return toStream(file.toPath());
}
/**
* 文件转为{@link InputStream}
*
* @param path {@link Path}非空
* @return {@link InputStream}
*/
public static InputStream toStream(final Path path) {
Assert.notNull(path);
try {
return new FileInputStream(file);
} catch (final FileNotFoundException e) {
return Files.newInputStream(path);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
@ -869,6 +882,19 @@ public class IoUtil extends NioUtil {
}
}
/**
* 关闭<br>
* 关闭失败不会抛出异常
*
* @param closeable 被关闭的对象
* @throws IOException IO异常
*/
public static void nullSafeClose(final Closeable closeable) throws IOException {
if (null != closeable) {
closeable.close();
}
}
/**
* 尝试关闭指定对象<br>
* 判断对象如果实现了{@link AutoCloseable}则调用之

View File

@ -6,9 +6,9 @@ import cn.hutool.core.io.stream.EmptyOutputStream;
import cn.hutool.core.lang.Assert;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
import java.util.zip.Checksum;
@ -57,8 +57,8 @@ public class ChecksumUtil {
throw new IllegalArgumentException("Checksums can't be computed on directories");
}
try {
return checksum(new FileInputStream(file), checksum);
} catch (final FileNotFoundException e) {
return checksum(Files.newInputStream(file.toPath()), checksum);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}

View File

@ -1,313 +0,0 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.copier.SrcToDestCopier;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.text.StrUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
/**
* 文件拷贝器<br>
* 支持以下几种情况
* <pre>
* 1文件复制到文件
* 2文件复制到目录
* 3目录复制到目录
* 4目录下的文件和目录复制到另一个目录
* </pre>
*
* @author Looly
* @since 3.0.9
*/
public class FileCopier extends SrcToDestCopier<File, FileCopier> {
private static final long serialVersionUID = 1L;
/**
* 是否覆盖目标文件
*/
private boolean isOverride;
/**
* 是否拷贝所有属性
*/
private boolean isCopyAttributes;
/**
* 当拷贝来源是目录时是否只拷贝目录下的内容
*/
private boolean isCopyContentIfDir;
/**
* 当拷贝来源是目录时是否只拷贝文件而忽略子目录
*/
private boolean isOnlyCopyFile;
//-------------------------------------------------------------------------------------------------------- static method start
/**
* 新建一个文件复制器
*
* @param srcPath 源文件路径相对ClassPath路径或绝对路径
* @param destPath 目标文件路径相对ClassPath路径或绝对路径
* @return this
*/
public static FileCopier of(final String srcPath, final String destPath) {
return new FileCopier(FileUtil.file(srcPath), FileUtil.file(destPath));
}
/**
* 新建一个文件复制器
*
* @param src 源文件
* @param dest 目标文件
* @return this
*/
public static FileCopier of(final File src, final File dest) {
return new FileCopier(src, dest);
}
//-------------------------------------------------------------------------------------------------------- static method end
//-------------------------------------------------------------------------------------------------------- Constructor start
/**
* 构造
*
* @param src 源文件
* @param dest 目标文件
*/
public FileCopier(final File src, final File dest) {
this.src = src;
this.dest = dest;
}
//-------------------------------------------------------------------------------------------------------- Constructor end
//-------------------------------------------------------------------------------------------------------- Getters and Setters start
/**
* 是否覆盖目标文件
*
* @return 是否覆盖目标文件
*/
public boolean isOverride() {
return isOverride;
}
/**
* 设置是否覆盖目标文件
*
* @param isOverride 是否覆盖目标文件
* @return this
*/
public FileCopier setOverride(final boolean isOverride) {
this.isOverride = isOverride;
return this;
}
/**
* 是否拷贝所有属性
*
* @return 是否拷贝所有属性
*/
public boolean isCopyAttributes() {
return isCopyAttributes;
}
/**
* 设置是否拷贝所有属性
*
* @param isCopyAttributes 是否拷贝所有属性
* @return this
*/
public FileCopier setCopyAttributes(final boolean isCopyAttributes) {
this.isCopyAttributes = isCopyAttributes;
return this;
}
/**
* 当拷贝来源是目录时是否只拷贝目录下的内容
*
* @return 当拷贝来源是目录时是否只拷贝目录下的内容
*/
public boolean isCopyContentIfDir() {
return isCopyContentIfDir;
}
/**
* 当拷贝来源是目录时是否只拷贝目录下的内容
*
* @param isCopyContentIfDir 是否只拷贝目录下的内容
* @return this
*/
public FileCopier setCopyContentIfDir(final boolean isCopyContentIfDir) {
this.isCopyContentIfDir = isCopyContentIfDir;
return this;
}
/**
* 当拷贝来源是目录时是否只拷贝文件而忽略子目录
*
* @return 当拷贝来源是目录时是否只拷贝文件而忽略子目录
* @since 4.1.5
*/
public boolean isOnlyCopyFile() {
return isOnlyCopyFile;
}
/**
* 设置当拷贝来源是目录时是否只拷贝文件而忽略子目录
*
* @param isOnlyCopyFile 当拷贝来源是目录时是否只拷贝文件而忽略子目录
* @return this
* @since 4.1.5
*/
public FileCopier setOnlyCopyFile(final boolean isOnlyCopyFile) {
this.isOnlyCopyFile = isOnlyCopyFile;
return this;
}
//-------------------------------------------------------------------------------------------------------- Getters and Setters end
/**
* 执行拷贝<br>
* 拷贝规则为
* <pre>
* 1源为文件目标为已存在目录则拷贝到目录下文件名不变
* 2源为文件目标为不存在路径则目标以文件对待自动创建父级目录比如/dest/aaa如果aaa不存在则aaa被当作文件名
* 3源为文件目标是一个已存在的文件则当{@link #setOverride(boolean)}设为true时会被覆盖默认不覆盖
* 4源为目录目标为已存在目录{@link #setCopyContentIfDir(boolean)}为true时只拷贝目录中的内容到目标目录中否则整个源目录连同其目录拷贝到目标目录中
* 5源为目录目标为不存在路径则自动创建目标为新目录然后按照规则4复制
* 6源为目录目标为文件抛出IO异常
* 7源路径和目标路径相同时抛出IO异常
* </pre>
*
* @return 拷贝后目标的文件或目录
* @throws IORuntimeException IO异常
*/
@Override
public File copy() throws IORuntimeException {
final File src = this.src;
File dest = this.dest;
// check
Assert.notNull(src, "Source File is null !");
if (false == src.exists()) {
throw new IORuntimeException("File not exist: " + src);
}
Assert.notNull(dest, "Destination File or directiory is null !");
if (FileUtil.equals(src, dest)) {
throw new IORuntimeException("Files '{}' and '{}' are equal", src, dest);
}
if (src.isDirectory()) {// 复制目录
if (dest.exists() && false == dest.isDirectory()) {
//源为目录目标为文件抛出IO异常
throw new IORuntimeException("Src is a directory but dest is a file!");
}
if (FileUtil.isSub(src, dest)) {
throw new IORuntimeException("Dest is a sub directory of src !");
}
final File subTarget = isCopyContentIfDir ? dest : FileUtil.mkdir(FileUtil.file(dest, src.getName()));
internalCopyDirContent(src, subTarget);
} else {// 复制文件
dest = internalCopyFile(src, dest);
}
return dest;
}
//----------------------------------------------------------------------------------------- Private method start
/**
* 拷贝目录内容只用于内部不做任何安全检查<br>
* 拷贝内容的意思为源目录下的所有文件和目录拷贝到另一个目录下而不拷贝源目录本身
*
* @param src 源目录
* @param dest 目标目录
* @throws IORuntimeException IO异常
*/
private void internalCopyDirContent(final File src, final File dest) throws IORuntimeException {
if (null != copyPredicate && false == copyPredicate.test(src)) {
//被过滤的目录跳过
return;
}
if (false == dest.exists()) {
//目标为不存在路径创建为目录
//noinspection ResultOfMethodCallIgnored
dest.mkdirs();
} else if (false == dest.isDirectory()) {
throw new IORuntimeException(StrUtil.format("Src [{}] is a directory but dest [{}] is a file!", src.getPath(), dest.getPath()));
}
final String[] files = src.list();
if (ArrayUtil.isNotEmpty(files)) {
File srcFile;
File destFile;
for (final String file : files) {
srcFile = new File(src, file);
destFile = this.isOnlyCopyFile ? dest : new File(dest, file);
// 递归复制
if (srcFile.isDirectory()) {
internalCopyDirContent(srcFile, destFile);
} else {
internalCopyFile(srcFile, destFile);
}
}
}
}
/**
* 拷贝文件只用于内部不做任何安全检查<br>
* 情况如下
* <pre>
* 1如果目标是一个不存在的路径则目标以文件对待自动创建父级目录比如/dest/aaa如果aaa不存在则aaa被当作文件名
* 2如果目标是一个已存在的目录则文件拷贝到此目录下文件名与原文件名一致
* </pre>
*
* @param src 源文件必须为文件
* @param dest 目标文件如果非覆盖模式必须为目录
* @return 目标文件
* @throws IORuntimeException IO异常
*/
private File internalCopyFile(final File src, File dest) throws IORuntimeException {
if (null != copyPredicate && false == copyPredicate.test(src)) {
//被过滤的文件跳过
return dest;
}
// 如果已经存在目标文件切为不覆盖模式跳过之
if (dest.exists()) {
if (dest.isDirectory()) {
//目标为目录目录下创建同名文件
dest = new File(dest, src.getName());
}
if (dest.exists() && false == isOverride) {
//非覆盖模式跳过
return dest;
}
} else {
//路径不存在则创建父目录
FileUtil.mkParentDirs(dest);
}
final ArrayList<CopyOption> optionList = new ArrayList<>(2);
if (isOverride) {
optionList.add(StandardCopyOption.REPLACE_EXISTING);
}
if (isCopyAttributes) {
optionList.add(StandardCopyOption.COPY_ATTRIBUTES);
}
try {
Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[0]));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return dest;
}
//----------------------------------------------------------------------------------------- Private method end
}

View File

@ -1,9 +1,10 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.func.SerConsumer;
import cn.hutool.core.lang.func.SerFunction;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
@ -14,6 +15,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -207,14 +209,20 @@ public class FileReader extends FileWrapper {
* @return 从文件中read出的数据
* @throws IORuntimeException IO异常
*/
public <T> T read(final ReaderHandler<T> readerHandler) throws IORuntimeException {
public <T> T read(final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
BufferedReader reader = null;
T result;
try {
reader = FileUtil.getReader(this.file, charset);
result = readerHandler.handle(reader);
} catch (final IOException e) {
throw new IORuntimeException(e);
result = readerHandler.applying(reader);
} catch (final Exception e) {
if(e instanceof IOException){
throw new IORuntimeException(e);
} else if(e instanceof RuntimeException){
throw (RuntimeException)e;
} else{
throw new UtilException(e);
}
} finally {
IoUtil.close(reader);
}
@ -239,7 +247,7 @@ public class FileReader extends FileWrapper {
*/
public BufferedInputStream getInputStream() throws IORuntimeException {
try {
return new BufferedInputStream(new FileInputStream(this.file));
return new BufferedInputStream(Files.newInputStream(this.file.toPath()));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
@ -277,19 +285,6 @@ public class FileReader extends FileWrapper {
}
}
// -------------------------------------------------------------------------- Interface start
/**
* Reader处理接口
*
* @author Luxiaolei
*
* @param <T> Reader处理返回结果类型
*/
public interface ReaderHandler<T> {
T handle(BufferedReader reader) throws IOException;
}
// -------------------------------------------------------------------------- Interface end
/**
* 检查文件
*

View File

@ -1,10 +1,11 @@
package cn.hutool.core.io;
package cn.hutool.core.io.file;
import cn.hutool.core.codec.HexUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.StrUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@ -32,7 +33,7 @@ public class FileTypeUtil {
* @param extName 文件扩展名
* @return 之前已经存在的文件扩展名
*/
public static String putFileType(String fileStreamHexHead, String extName) {
public static String putFileType(final String fileStreamHexHead, final String extName) {
return FILE_TYPE_MAP.put(fileStreamHexHead, extName);
}
@ -42,7 +43,7 @@ public class FileTypeUtil {
* @param fileStreamHexHead 文件流头部Hex信息
* @return 移除的文件扩展名
*/
public static String removeFileType(String fileStreamHexHead) {
public static String removeFileType(final String fileStreamHexHead) {
return FILE_TYPE_MAP.remove(fileStreamHexHead);
}
@ -52,13 +53,13 @@ public class FileTypeUtil {
* @param fileStreamHexHead 文件流头部16进制字符串
* @return 文件类型未找到为{@code null}
*/
public static String getType(String fileStreamHexHead) {
for (Entry<String, String> fileTypeEntry : FILE_TYPE_MAP.entrySet()) {
public static String getType(final String fileStreamHexHead) {
for (final Entry<String, String> fileTypeEntry : FILE_TYPE_MAP.entrySet()) {
if (StrUtil.startWithIgnoreCase(fileStreamHexHead, fileTypeEntry.getKey())) {
return fileTypeEntry.getValue();
}
}
byte[] bytes = (HexUtil.decodeHex(fileStreamHexHead));
final byte[] bytes = (HexUtil.decodeHex(fileStreamHexHead));
return FileMagicNumber.getMagicNumber(bytes).getExtension();
}
@ -68,8 +69,9 @@ public class FileTypeUtil {
* @param in 文件流
* @param fileHeadSize 自定义读取文件头部的大小
* @return 文件类型未找到为{@code null}
* @throws IORuntimeException IO异常
*/
public static String getType(InputStream in,int fileHeadSize) throws IORuntimeException {
public static String getType(final InputStream in, final int fileHeadSize) throws IORuntimeException {
return getType((IoUtil.readHex(in, fileHeadSize,false)));
}
@ -82,7 +84,7 @@ public class FileTypeUtil {
* @return 类型文件的扩展名未找到为{@code null}
* @throws IORuntimeException 读取流引起的异常
*/
public static String getType(InputStream in,boolean isExact) throws IORuntimeException {
public static String getType(final InputStream in, final boolean isExact) throws IORuntimeException {
return isExact
?getType(readHex8192Upper(in))
:getType(readHex64Upper(in));
@ -96,7 +98,7 @@ public class FileTypeUtil {
* @return 类型文件的扩展名未找到为{@code null}
* @throws IORuntimeException 读取流引起的异常
*/
public static String getType(InputStream in) throws IORuntimeException {
public static String getType(final InputStream in) throws IORuntimeException {
return getType(in,false);
}
@ -116,7 +118,7 @@ public class FileTypeUtil {
* @return 类型文件的扩展名未找到为{@code null}
* @throws IORuntimeException 读取流引起的异常
*/
public static String getType(InputStream in, String filename) throws IORuntimeException {
public static String getType(final InputStream in, final String filename) throws IORuntimeException {
return getType(in,filename,false);
}
@ -136,14 +138,14 @@ public class FileTypeUtil {
* @return 类型文件的扩展名未找到为{@code null}
* @throws IORuntimeException 读取流引起的异常
*/
public static String getType(InputStream in, String filename,boolean isExact) throws IORuntimeException {
public static String getType(final InputStream in, final String filename, final boolean isExact) throws IORuntimeException {
String typeName = getType(in,isExact);
if (null == typeName) {
// 未成功识别类型扩展名辅助识别
typeName = FileUtil.extName(filename);
typeName = FileNameUtil.extName(filename);
} else if ("zip".equals(typeName)) {
// zip可能为docxxlsxpptxjarwarofd等格式扩展名辅助判断
final String extName = FileUtil.extName(filename);
final String extName = FileNameUtil.extName(filename);
if ("docx".equalsIgnoreCase(extName)) {
typeName = "docx";
} else if ("xlsx".equalsIgnoreCase(extName)) {
@ -161,7 +163,7 @@ public class FileTypeUtil {
}
} else if ("jar".equals(typeName)) {
// wps编辑过的.xlsx文件与.jar的开头相同,通过扩展名判断
final String extName = FileUtil.extName(filename);
final String extName = FileNameUtil.extName(filename);
if ("xlsx".equalsIgnoreCase(extName)) {
typeName = "xlsx";
} else if ("docx".equalsIgnoreCase(extName)) {
@ -193,8 +195,8 @@ public class FileTypeUtil {
* @return 类型文件的扩展名未找到为{@code null}
* @throws IORuntimeException 读取文件引起的异常
*/
public static String getType(File file,boolean isExact) throws IORuntimeException {
FileInputStream in = null;
public static String getType(final File file, final boolean isExact) throws IORuntimeException {
InputStream in = null;
try {
in = IoUtil.toStream(file);
return getType(in, file.getName(),isExact);
@ -216,7 +218,7 @@ public class FileTypeUtil {
* @return 类型文件的扩展名未找到为{@code null}
* @throws IORuntimeException 读取文件引起的异常
*/
public static String getType(File file) throws IORuntimeException {
public static String getType(final File file) throws IORuntimeException {
return getType(file,false);
}
@ -228,7 +230,7 @@ public class FileTypeUtil {
* @return 类型
* @throws IORuntimeException 读取文件引起的异常
*/
public static String getTypeByPath(String path,boolean isExact) throws IORuntimeException {
public static String getTypeByPath(final String path, final boolean isExact) throws IORuntimeException {
return getType(FileUtil.file(path),isExact);
}
@ -239,7 +241,7 @@ public class FileTypeUtil {
* @return 类型
* @throws IORuntimeException 读取文件引起的异常
*/
public static String getTypeByPath(String path) throws IORuntimeException {
public static String getTypeByPath(final String path) throws IORuntimeException {
return getTypeByPath(path,false);
}
@ -252,7 +254,6 @@ public class FileTypeUtil {
*/
private static String readHex8192Upper(final InputStream in) throws IORuntimeException {
try {
final int i = in.available();
return IoUtil.readHex(in, Math.min(8192, in.available()), false);
} catch (final IOException e) {
throw new RuntimeException(e);
@ -266,7 +267,7 @@ public class FileTypeUtil {
* @return 16进制字符串
* @throws IORuntimeException IO异常
*/
private static String readHex64Upper(InputStream in) throws IORuntimeException {
private static String readHex64Upper(final InputStream in) throws IORuntimeException {
return IoUtil.readHex(in, 64, false);
}
}

View File

@ -1,62 +1,29 @@
package cn.hutool.core.io;
package cn.hutool.core.io.file;
import cn.hutool.core.compress.ZipUtil;
import cn.hutool.core.io.file.FileCopier;
import cn.hutool.core.io.file.FileMode;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileReader.ReaderHandler;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.io.file.LineSeparator;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.io.file.Tailer;
import cn.hutool.core.io.BomReader;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.stream.BOMInputStream;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.SerConsumer;
import cn.hutool.core.lang.func.SerFunction;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.reflect.ClassUtil;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.SystemUtil;
import cn.hutool.core.util.*;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
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;
import java.io.Reader;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
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;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.nio.file.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
@ -693,11 +660,10 @@ public class FileUtil extends PathUtil {
* 某个文件删除失败会终止删除操作
*
* @param fullFileOrDirPath 文件或者目录的路径
* @return 成功与否
* @throws IORuntimeException IO异常
*/
public static boolean del(final String fullFileOrDirPath) throws IORuntimeException {
return del(file(fullFileOrDirPath));
public static void del(final String fullFileOrDirPath) throws IORuntimeException {
del(file(fullFileOrDirPath));
}
/**
@ -705,42 +671,13 @@ public class FileUtil extends PathUtil {
* 注意删除文件夹时不会判断文件夹是否为空如果不空则递归删除子文件或文件夹<br>
* 某个文件删除失败会终止删除操作
*
* <p>
* 从5.7.6开始删除文件使用{@link Files#delete(Path)}代替 {@link File#delete()}<br>
* 因为前者遇到文件被占用等原因时抛出异常而非返回false异常会指明具体的失败原因
* </p>
*
* @param file 文件对象
* @return 成功与否
* @throws IORuntimeException IO异常
* @see Files#delete(Path)
*/
public static boolean del(final File file) throws IORuntimeException {
if (file == null || false == file.exists()) {
// 如果文件不存在或已被删除此处返回true表示删除成功
return true;
}
if (file.isDirectory()) {
// 清空目录下所有文件和目录
final boolean isOk = clean(file);
if (false == isOk) {
return false;
}
}
// 删除文件或清空后的目录
final Path path = file.toPath();
try {
delFile(path);
} catch (final DirectoryNotEmptyException e) {
// 遍历清空目录没有成功此时补充删除一次可能存在部分软链
del(path);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return true;
public static void del(final File file) throws IORuntimeException {
Assert.notNull(file, "File must be not null!");
PathUtil.del(file.toPath());
}
/**
@ -749,12 +686,10 @@ public class FileUtil extends PathUtil {
* 某个文件删除失败会终止删除操作
*
* @param dirPath 文件夹路径
* @return 成功与否
* @throws IORuntimeException IO异常
* @since 4.0.8
*/
public static boolean clean(final String dirPath) throws IORuntimeException {
return clean(file(dirPath));
public static void clean(final String dirPath) throws IORuntimeException {
clean(file(dirPath));
}
/**
@ -763,52 +698,11 @@ public class FileUtil extends PathUtil {
* 某个文件删除失败会终止删除操作
*
* @param directory 文件夹
* @return 成功与否
* @throws IORuntimeException IO异常
* @since 3.0.6
*/
public static boolean clean(final File directory) throws IORuntimeException {
if (directory == null || directory.exists() == false || false == directory.isDirectory()) {
return true;
}
final File[] files = directory.listFiles();
if (null != files) {
for (final File childFile : files) {
if (false == del(childFile)) {
// 删除一个出错则本次删除任务失败
return false;
}
}
}
return true;
}
/**
* 清理空文件夹<br>
* 此方法用于递归删除空的文件夹不删除文件<br>
* 如果传入的文件夹本身就是空的删除这个文件夹
*
* @param directory 文件夹
* @return 成功与否
* @throws IORuntimeException IO异常
* @since 4.5.5
*/
public static boolean cleanEmpty(final File directory) throws IORuntimeException {
if (directory == null || false == directory.exists() || false == directory.isDirectory()) {
return true;
}
final File[] files = directory.listFiles();
if (ArrayUtil.isEmpty(files)) {
// 空文件夹则删除之
return directory.delete();
}
for (final File childFile : files) {
cleanEmpty(childFile);
}
return true;
public static void clean(final File directory) throws IORuntimeException {
Assert.notNull(directory, "File must be not null!");
PathUtil.clean(directory.toPath());
}
/**
@ -986,43 +880,6 @@ public class FileUtil extends PathUtil {
}
}
/**
* 通过JDK7+ Files#copy(Path, Path, CopyOption...) 方法拷贝文件
*
* @param src 源文件路径
* @param dest 目标文件或目录路径如果为目录使用与源文件相同的文件名
* @param options {@link StandardCopyOption}
* @return File
* @throws IORuntimeException IO异常
*/
public static File copyFile(final String src, final String dest, final StandardCopyOption... options) throws IORuntimeException {
Assert.notBlank(src, "Source File path is blank !");
Assert.notBlank(dest, "Destination File path is blank !");
return copyFile(Paths.get(src), Paths.get(dest), options).toFile();
}
/**
* 通过JDK7+ Files#copy(Path, Path, CopyOption...) 方法拷贝文件
*
* @param src 源文件
* @param dest 目标文件或目录如果为目录使用与源文件相同的文件名
* @param options {@link StandardCopyOption}
* @return 目标文件
* @throws IORuntimeException IO异常
*/
public static File copyFile(final File src, final File dest, final StandardCopyOption... options) throws IORuntimeException {
// check
Assert.notNull(src, "Source File is null !");
if (false == src.exists()) {
throw new IORuntimeException("File not exist: " + src);
}
Assert.notNull(dest, "Destination File or directiory is null !");
if (equals(src, dest)) {
throw new IORuntimeException("Files '{}' and '{}' are equal", src, dest);
}
return copyFile(src.toPath(), dest.toPath(), options).toFile();
}
/**
* 复制文件或目录<br>
* 如果目标文件为目录则将源文件以相同文件名拷贝到目标目录
@ -1048,13 +905,19 @@ public class FileUtil extends PathUtil {
* </pre>
*
* @param src 源文件
* @param dest 目标文件或目录目标不存在会自动创建目录文件都创建
* @param target 目标文件或目录目标不存在会自动创建目录文件都创建
* @param isOverride 是否覆盖目标文件
* @return 目标目录或文件
* @throws IORuntimeException IO异常
*/
public static File copy(final File src, final File dest, final boolean isOverride) throws IORuntimeException {
return FileCopier.of(src, dest).setOverride(isOverride).copy();
public static File copy(final File src, final File target, final boolean isOverride) throws IORuntimeException {
Assert.notNull(src, "Src file must be not null!");
Assert.notNull(target, "target file must be not null!");
return PathUtil.copy(
src.toPath(),
target.toPath(),
isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{})
.toFile();
}
/**
@ -1068,65 +931,43 @@ public class FileUtil extends PathUtil {
* </pre>
*
* @param src 源文件
* @param dest 目标文件或目录目标不存在会自动创建目录文件都创建
* @param target 目标文件或目录目标不存在会自动创建目录文件都创建
* @param isOverride 是否覆盖目标文件
* @return 目标目录或文件
* @throws IORuntimeException IO异常
*/
public static File copyContent(final File src, final File dest, final boolean isOverride) throws IORuntimeException {
return FileCopier.of(src, dest).setCopyContentIfDir(true).setOverride(isOverride).copy();
public static File copyContent(final File src, final File target, final boolean isOverride) throws IORuntimeException {
Assert.notNull(src, "Src file must be not null!");
Assert.notNull(target, "target file must be not null!");
return PathUtil.copyContent(
src.toPath(),
target.toPath(),
isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{})
.toFile();
}
/**
* 复制文件或目录<br>
* 情况如下
* 移动文件或目录到目标中例如
* <ul>
* <li>如果src为文件target为目录则移动到目标目录下存在同名文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为不存在的路径则重命名源文件到目标指定的文件如moveContent("/a/b", "/c/d"), d不存在则b变成d</li>
* <li>如果src为目录target为文件抛出{@link IllegalArgumentException}</li>
* <li>如果src为目录target为目录则将源目录及其内容移动到目标路径目录中如move("/a/b", "/c/d")结果为"/c/d/b"</li>
* <li>如果src为目录target为不存在的路径则创建目标路径为目录将源目录及其内容移动到目标路径目录中如move("/a/b", "/c/d")结果为"/c/d/b"</li>
* </ul>
*
* <pre>
* 1src和dest都为目录则将src下所有文件包括子目录拷贝到dest下
* 2src和dest都为文件直接复制名字为dest
* 3src为文件dest为目录将src拷贝到dest目录下
* </pre>
*
* @param src 源文件
* @param dest 目标文件或目录目标不存在会自动创建目录文件都创建
* @param src 源文件或目录路径
* @param target 目标路径如果为目录则移动到此目录下
* @param isOverride 是否覆盖目标文件
* @return 目标目录或文件
* @throws IORuntimeException IO异常
* @since 4.1.5
*/
public static File copyFilesFromDir(final File src, final File dest, final boolean isOverride) throws IORuntimeException {
return FileCopier.of(src, dest).setCopyContentIfDir(true).setOnlyCopyFile(true).setOverride(isOverride).copy();
}
/**
* 移动文件或者目录
*
* @param src 源文件或者目录
* @param target 目标文件或者目录
* @param isOverride 是否覆盖目标只有目标为文件才覆盖
* @return 目标文件或目录
* @throws IORuntimeException IO异常
* @see PathUtil#move(Path, Path, boolean)
*/
public static void move(final File src, final File target, final boolean isOverride) throws IORuntimeException {
public static File move(final File src, final File target, final boolean isOverride) throws IORuntimeException {
Assert.notNull(src, "Src file must be not null!");
Assert.notNull(target, "target file must be not null!");
move(src.toPath(), target.toPath(), isOverride);
}
/**
* 移动文件或者目录
*
* @param src 源文件或者目录
* @param target 目标文件或者目录
* @param isOverride 是否覆盖目标只有目标为文件才覆盖
* @throws IORuntimeException IO异常
* @see PathUtil#moveContent(Path, Path, boolean)
* @since 5.7.9
*/
public static void moveContent(final File src, final File target, final boolean isOverride) throws IORuntimeException {
Assert.notNull(src, "Src file must be not null!");
Assert.notNull(target, "target file must be not null!");
moveContent(src.toPath(), target.toPath(), isOverride);
return move(src.toPath(), target.toPath(), isOverride).toFile();
}
/**
@ -1172,7 +1013,7 @@ public class FileUtil extends PathUtil {
*/
public static File rename(final File file, String newName, final boolean isRetainExt, final boolean isOverride) {
if (isRetainExt) {
final String extName = FileUtil.extName(file);
final String extName = FileNameUtil.extName(file);
if (StrUtil.isNotBlank(extName)) {
newName = newName.concat(".").concat(extName);
}
@ -1604,129 +1445,6 @@ public class FileUtil extends PathUtil {
return filePath;
}
// -------------------------------------------------------------------------------------------- name start
/**
* 返回文件名
*
* @param file 文件
* @return 文件名
* @see FileNameUtil#getName(File)
* @since 4.1.13
*/
public static String getName(final File file) {
return FileNameUtil.getName(file);
}
/**
* 返回文件名<br>
* <pre>
* "d:/test/aaa" 返回 "aaa"
* "/test/aaa.jpg" 返回 "aaa.jpg"
* </pre>
*
* @param filePath 文件
* @return 文件名
* @see FileNameUtil#getName(String)
* @since 4.1.13
*/
public static String getName(final String filePath) {
return FileNameUtil.getName(filePath);
}
/**
* 获取文件后缀名扩展名不带.
*
* @param file 文件
* @return 扩展名
* @see FileNameUtil#getSuffix(File)
* @since 5.3.8
*/
public static String getSuffix(final File file) {
return FileNameUtil.getSuffix(file);
}
/**
* 获得文件后缀名扩展名不带.
*
* @param fileName 文件名
* @return 扩展名
* @see FileNameUtil#getSuffix(String)
* @since 5.3.8
*/
public static String getSuffix(final String fileName) {
return FileNameUtil.getSuffix(fileName);
}
/**
* 返回主文件名
*
* @param file 文件
* @return 主文件名
* @see FileNameUtil#getPrefix(File)
* @since 5.3.8
*/
public static String getPrefix(final File file) {
return FileNameUtil.getPrefix(file);
}
/**
* 返回主文件名
*
* @param fileName 完整文件名
* @return 主文件名
* @see FileNameUtil#getPrefix(String)
* @since 5.3.8
*/
public static String getPrefix(final String fileName) {
return FileNameUtil.getPrefix(fileName);
}
/**
* 返回主文件名
*
* @param file 文件
* @return 主文件名
* @see FileNameUtil#mainName(File)
*/
public static String mainName(final File file) {
return FileNameUtil.mainName(file);
}
/**
* 返回主文件名
*
* @param fileName 完整文件名
* @return 主文件名
* @see FileNameUtil#mainName(String)
*/
public static String mainName(final String fileName) {
return FileNameUtil.mainName(fileName);
}
/**
* 获取文件扩展名后缀名扩展名不带.
*
* @param file 文件
* @return 扩展名
* @see FileNameUtil#extName(File)
*/
public static String extName(final File file) {
return FileNameUtil.extName(file);
}
/**
* 获得文件的扩展名后缀名扩展名不带.
*
* @param fileName 文件名
* @return 扩展名
* @see FileNameUtil#extName(String)
*/
public static String extName(final String fileName) {
return FileNameUtil.extName(fileName);
}
// -------------------------------------------------------------------------------------------- name end
/**
* 判断文件路径是否有指定后缀忽略大小写<br>
* 常用语判断扩展名
@ -1765,6 +1483,7 @@ public class FileUtil extends PathUtil {
* @param file 文件
* @return 输入流
* @throws IORuntimeException 文件未找到
* @see IoUtil#toStream(File)
*/
public static BufferedInputStream getInputStream(final File file) throws IORuntimeException {
return IoUtil.toBuffered(IoUtil.toStream(file));
@ -1790,8 +1509,7 @@ public class FileUtil extends PathUtil {
*/
public static BOMInputStream getBOMInputStream(final File file) throws IORuntimeException {
try {
//noinspection IOStreamConstructor
return new BOMInputStream(new FileInputStream(file));
return new BOMInputStream(Files.newInputStream(file.toPath()));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
@ -2258,10 +1976,9 @@ public class FileUtil extends PathUtil {
* @param path 文件的绝对路径
* @return 从文件中load出的数据
* @throws IORuntimeException IO异常
* @since 3.1.1
*/
public static <T> T loadUtf8(final String path, final ReaderHandler<T> readerHandler) throws IORuntimeException {
return load(path, CharsetUtil.UTF_8, readerHandler);
public static <T> T readUtf8(final String path, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
return read(path, CharsetUtil.UTF_8, readerHandler);
}
/**
@ -2275,23 +1992,8 @@ public class FileUtil extends PathUtil {
* @throws IORuntimeException IO异常
* @since 3.1.1
*/
public static <T> T load(final String path, final String charset, final ReaderHandler<T> readerHandler) throws IORuntimeException {
return FileReader.of(file(path), CharsetUtil.charset(charset)).read(readerHandler);
}
/**
* 按照给定的readerHandler读取文件中的数据
*
* @param <T> 集合类型
* @param readerHandler Reader处理类
* @param path 文件的绝对路径
* @param charset 字符集
* @return 从文件中load出的数据
* @throws IORuntimeException IO异常
* @since 3.1.1
*/
public static <T> T load(final String path, final Charset charset, final ReaderHandler<T> readerHandler) throws IORuntimeException {
return FileReader.of(file(path), charset).read(readerHandler);
public static <T> T read(final String path, final Charset charset, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
return read(file(path), charset, readerHandler);
}
/**
@ -2304,8 +2006,8 @@ public class FileUtil extends PathUtil {
* @throws IORuntimeException IO异常
* @since 3.1.1
*/
public static <T> T loadUtf8(final File file, final ReaderHandler<T> readerHandler) throws IORuntimeException {
return load(file, CharsetUtil.UTF_8, readerHandler);
public static <T> T readUtf8(final File file, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
return read(file, CharsetUtil.UTF_8, readerHandler);
}
/**
@ -2319,7 +2021,7 @@ public class FileUtil extends PathUtil {
* @throws IORuntimeException IO异常
* @since 3.1.1
*/
public static <T> T load(final File file, final Charset charset, final ReaderHandler<T> readerHandler) throws IORuntimeException {
public static <T> T read(final File file, final Charset charset, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
return FileReader.of(file, charset).read(readerHandler);
}
@ -2335,8 +2037,7 @@ public class FileUtil extends PathUtil {
public static BufferedOutputStream getOutputStream(final File file) throws IORuntimeException {
final OutputStream out;
try {
//noinspection IOStreamConstructor
out = new FileOutputStream(touch(file));
out = Files.newOutputStream(touch(file).toPath());
} catch (final IOException e) {
throw new IORuntimeException(e);
}
@ -3005,30 +2706,6 @@ public class FileUtil extends PathUtil {
return FileWriter.of(file, charset).writeLines(lines, lineSeparator, false);
}
/**
* 清除文件名中的在Windows下不支持的非法字符包括 \ / : * ? " &lt; &gt; |
*
* @param fileName 文件名必须不包括路径否则路径符将被替换
* @return 清理后的文件名
* @see FileNameUtil#cleanInvalid(String)
* @since 3.3.1
*/
public static String cleanInvalid(final String fileName) {
return FileNameUtil.cleanInvalid(fileName);
}
/**
* 文件名中是否包含在Windows下不支持的非法字符包括 \ / : * ? " &lt; &gt; |
*
* @param fileName 文件名必须不包括路径否则路径符将被替换
* @return 是否包含非法字符
* @see FileNameUtil#containsInvalid(String)
* @since 3.3.1
*/
public static boolean containsInvalid(final String fileName) {
return FileNameUtil.containsInvalid(fileName);
}
/**
* 获取Web项目下的web root路径<br>
* 原理是首先获取ClassPath路径由于在web项目中ClassPath位于 WEB-INF/classes/故向上获取两级目录即可
@ -3051,7 +2728,7 @@ public class FileUtil extends PathUtil {
* getParent(file("d:/aaa/bbb/cc/ddd")) - "d:/aaa/bbb/cc"
* </pre>
*
* @param file 目录或文件
* @param file 目录或文件
* @return 路径File如果不存在返回null
* @since 6.0.0
*/

View File

@ -1,6 +1,5 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.func.Wrapper;
import cn.hutool.core.util.CharsetUtil;

View File

@ -1,11 +1,10 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharsetUtil;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
@ -13,9 +12,11 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Map;
import java.util.Map.Entry;
@ -336,9 +337,9 @@ public class FileWriter extends FileWrapper {
* @since 5.5.2
*/
public File writeFromStream(final InputStream in, final boolean isCloseIn) throws IORuntimeException {
FileOutputStream out = null;
OutputStream out = null;
try {
out = new FileOutputStream(FileUtil.touch(file));
out = Files.newOutputStream(FileUtil.touch(file).toPath());
IoUtil.copy(in, out);
} catch (final IOException e) {
throw new IORuntimeException(e);
@ -359,7 +360,7 @@ public class FileWriter extends FileWrapper {
*/
public BufferedOutputStream getOutputStream() throws IORuntimeException {
try {
return new BufferedOutputStream(new FileOutputStream(FileUtil.touch(file)));
return new BufferedOutputStream(Files.newOutputStream(FileUtil.touch(file).toPath()));
} catch (final IOException e) {
throw new IORuntimeException(e);
}

View File

@ -1,6 +1,5 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.watch.SimpleWatcher;
import cn.hutool.core.lang.func.SerConsumer;

View File

@ -0,0 +1,164 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.visitor.CopyVisitor;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.copier.SrcToDestCopier;
import cn.hutool.core.util.ObjUtil;
import java.io.IOException;
import java.nio.file.*;
/**
* 文件复制封装
*
* @author looly
* @since 6.0.0
*/
public class PathCopier extends SrcToDestCopier<Path, PathCopier> {
private static final long serialVersionUID = 1L;
/**
* 创建文件或目录拷贝器
*
* @param src 源文件或目录
* @param target 目标文件或目录
* @param isOverride 是否覆盖目标文件
* @return {@code PathCopier}
*/
public static PathCopier of(final Path src, final Path target, final boolean isOverride) {
return of(src, target, isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
}
/**
* 创建文件或目录拷贝器
*
* @param src 源文件或目录
* @param target 目标文件或目录
* @param options 拷贝参数
* @return {@code PathCopier}
*/
public static PathCopier of(final Path src, final Path target, final CopyOption[] options) {
return new PathCopier(src, target, options);
}
private final CopyOption[] options;
/**
* 构造
*
* @param src 源文件或目录不能为{@code null}且必须存在
* @param target 目标文件或目录
* @param options 移动参数
*/
public PathCopier(final Path src, final Path target, final CopyOption[] options) {
Assert.notNull(target, "Src path must be not null !");
if (false == PathUtil.exists(src, false)) {
throw new IllegalArgumentException("Src path is not exist!");
}
this.src = src;
this.target = Assert.notNull(target, "Target path must be not null !");
this.options = ObjUtil.defaultIfNull(options, new CopyOption[]{});
}
/**
* 复制src到target中
* <ul>
* <li>src路径和target路径相同时不执行操作</li>
* <li>src为文件target为已存在目录则拷贝到目录下文件名不变</li>
* <li>src为文件target为不存在路径则目标以文件对待自动创建父级目录相当于拷贝后重命名比如/dest/aaa如果aaa不存在则aaa被当作文件名</li>
* <li>src为文件target是一个已存在的文件则当{@link CopyOption}设为覆盖时会被覆盖默认不覆盖抛出{@link FileAlreadyExistsException}</li>
* <li>src为目录target为已存在目录整个src目录连同其目录拷贝到目标目录中</li>
* <li>src为目录target为不存在路径则自动创建目标为新目录并只拷贝src内容到目标目录中相当于重命名目录</li>
* <li>src为目录target为文件抛出{@link IllegalArgumentException}</li>
* </ul>
*
* @return 目标Path
* @throws IORuntimeException IO异常
*/
@Override
public Path copy() throws IORuntimeException {
if (PathUtil.isDirectory(src)) {
if (PathUtil.exists(target, false)) {
if (PathUtil.isDirectory(target)) {
return _copyContent(src, target.resolve(src.getFileName()), options);
} else {
// src目录target文件无法拷贝
throw new IllegalArgumentException("Can not copy directory to a file!");
}
} else {
// 目标不存在按照重命名对待
return _copyContent(src, target, options);
}
}
return copyFile(src, target, options);
}
/**
* 复制src的内容到target中
* <ul>
* <li>src路径和target路径相同时不执行操作</li>
* <li>src为文件target为已存在目录则拷贝到目录下文件名不变</li>
* <li>src为文件target为不存在路径则目标以文件对待自动创建父级目录相当于拷贝后重命名比如/dest/aaa如果aaa不存在则aaa被当作文件名</li>
* <li>src为文件target是一个已存在的文件则当{@link CopyOption}设为覆盖时会被覆盖默认不覆盖抛出{@link FileAlreadyExistsException}</li>
* <li>src为目录target为已存在目录整个src目录下的内容拷贝到目标目录中</li>
* <li>src为目录target为不存在路径则自动创建目标为新目录整个src目录下的内容拷贝到目标目录中相当于重命名目录</li>
* <li>src为目录target为文件抛出IO异常</li>
* </ul>
*
* @return 目标Path
* @throws IORuntimeException IO异常
*/
public Path copyContent() throws IORuntimeException {
if (PathUtil.isDirectory(src, false)) {
return _copyContent(src, target, options);
}
return copyFile(src, target, options);
}
/**
* 拷贝目录下的所有文件或目录到目标目录中此方法不支持文件对文件的拷贝
* <ul>
* <li>源文件为目录目标也为目录或不存在则拷贝目录下所有文件和目录到目标目录下</li>
* <li>源文件为文件目标为目录或不存在则拷贝文件到目标目录下</li>
* </ul>
*
* @param src 源文件路径如果为目录只在目标中创建新目录
* @param target 目标目录如果为目录使用与源文件相同的文件名
* @param options {@link StandardCopyOption}
* @return Path
* @throws IORuntimeException IO异常
*/
private static Path _copyContent(final Path src, final Path target, final CopyOption... options) throws IORuntimeException {
try {
Files.walkFileTree(src, new CopyVisitor(src, target, options));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return target;
}
/**
* 通过JDK7+ {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件<br>
* 此方法不支持递归拷贝目录如果src传入是目录只会在目标目录中创建空目录
*
* @param src 源文件路径如果为目录只在目标中创建新目录
* @param target 目标文件或目录如果为目录使用与源文件相同的文件名
* @param options {@link StandardCopyOption}
* @return Path
* @throws IORuntimeException IO异常
*/
private static Path copyFile(final Path src, final Path target, final CopyOption... options) throws IORuntimeException {
Assert.notNull(src, "Source File is null !");
Assert.notNull(target, "Destination File or directory is null !");
final Path targetPath = PathUtil.isDirectory(target) ? target.resolve(src.getFileName()) : target;
// 创建级联父目录
PathUtil.mkParentDirs(targetPath);
try {
return Files.copy(src, targetPath, options);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
}

View File

@ -0,0 +1,104 @@
package cn.hutool.core.io.file;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.visitor.DelVisitor;
import cn.hutool.core.lang.Assert;
import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Stream;
/**
* 文件删除封装
*
* @author looly
* @since 6.0.0
*/
public class PathDeleter {
/**
* 创建文件或目录移动器
*
* @param src 源文件或目录
* @return {@code PathMover}
*/
public static PathDeleter of(final Path src) {
return new PathDeleter(src);
}
private final Path path;
/**
* 构造
*
* @param path 文件或目录不能为{@code null}且必须存在
*/
public PathDeleter(final Path path) {
this.path = Assert.notNull(path, "Path must be not null !");
}
/**
* 删除文件或者文件夹不追踪软链<br>
* 注意删除文件夹时不会判断文件夹是否为空如果不空则递归删除子文件或文件夹<br>
* 某个文件删除失败会终止删除操作
*
* @throws IORuntimeException IO异常
*/
public void del() throws IORuntimeException {
final Path path = this.path;
if (Files.notExists(path)) {
return;
}
if (PathUtil.isDirectory(path)) {
_del(path);
} else {
delFile(path);
}
}
/**
* 清空目录
*/
public void clean() {
try (final Stream<Path> list = Files.list(this.path)){
list.forEach(PathUtil::del);
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* 删除目录
*
* @param path 目录路径
*/
private static void _del(final Path path) {
try {
Files.walkFileTree(path, DelVisitor.INSTANCE);
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* 删除文件或空目录不追踪软链
*
* @param path 文件对象
* @throws IORuntimeException IO异常
* @since 5.7.7
*/
private static void delFile(final Path path) throws IORuntimeException {
try {
Files.delete(path);
} catch (final IOException e) {
if (e instanceof AccessDeniedException) {
// 可能遇到只读文件无法删除.使用 file 方法删除
if (path.toFile().delete()) {
return;
}
}
throw new IORuntimeException(e);
}
}
}

Some files were not shown because too many files have changed in this diff Show More