diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java index 032f1bebe..e0be31f94 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java @@ -17,6 +17,7 @@ import org.dromara.hutool.core.comparator.CompareUtil; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.CharUtil; +import org.dromara.hutool.core.util.ObjUtil; import java.math.BigDecimal; import java.math.BigInteger; @@ -1003,47 +1004,6 @@ public class NumberUtil { // region ----- equals - /** - * 比较大小,值相等 返回true
- * 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等
- * 此方法判断值相等时忽略精度的,即0.00 == 0 - * - * @param num1 数字1 - * @param num2 数字2 - * @return 是否相等 - * @since 5.4.2 - */ - public static boolean equals(final double num1, final double num2) { - return Double.doubleToLongBits(num1) == Double.doubleToLongBits(num2); - } - - /** - * 比较大小,值相等 返回true
- * 此方法通过调用{@link Float#floatToIntBits(float)}方法来判断是否相等
- * 此方法判断值相等时忽略精度的,即0.00 == 0 - * - * @param num1 数字1 - * @param num2 数字2 - * @return 是否相等 - * @since 5.4.5 - */ - public static boolean equals(final float num1, final float num2) { - return Float.floatToIntBits(num1) == Float.floatToIntBits(num2); - } - - /** - * 比较大小,值相等 返回true
- * 此方法修复传入long型数据由于没有本类型重载方法,导致数据精度丢失 - * - * @param num1 数字1 - * @param num2 数字2 - * @return 是否相等 - * @since 5.7.19 - */ - public static boolean equals(final long num1, final long num2) { - return num1 == num2; - } - /** * 比较数字值是否相等,相等返回{@code true}
* 需要注意的是{@link BigDecimal}需要特殊处理
@@ -1068,20 +1028,6 @@ public class NumberUtil { } return Objects.equals(number1, number2); } - - /** - * 比较两个字符是否相同 - * - * @param c1 字符1 - * @param c2 字符2 - * @param ignoreCase 是否忽略大小写 - * @return 是否相同 - * @see CharUtil#equals(char, char, boolean) - * @since 3.2.1 - */ - public static boolean equals(final char c1, final char c2, final boolean ignoreCase) { - return CharUtil.equals(c1, c2, ignoreCase); - } // endregion // region ----- toStr @@ -1289,7 +1235,7 @@ public class NumberUtil { * @since 3.0.9 */ public static BigDecimal null2Zero(final BigDecimal decimal) { - return decimal == null ? BigDecimal.ZERO : decimal; + return ObjUtil.defaultIfNull(decimal, BigDecimal.ZERO); } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/URLDecoder.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/URLDecoder.java index 591bf704e..e803d5e0a 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/URLDecoder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/URLDecoder.java @@ -12,12 +12,11 @@ package org.dromara.hutool.core.net.url; -import org.dromara.hutool.core.util.ByteUtil; +import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream; +import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.CharUtil; import org.dromara.hutool.core.util.CharsetUtil; -import org.dromara.hutool.core.text.StrUtil; -import java.io.ByteArrayOutputStream; import java.io.Serializable; import java.nio.charset.Charset; @@ -36,7 +35,7 @@ public class URLDecoder implements Serializable { private static final long serialVersionUID = 1L; private static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; - private static final byte ESCAPE_CHAR = '%'; + private static final byte ESCAPE_CHAR = CharUtil.PERCENT; /** * 解码,不对+解码 @@ -118,10 +117,41 @@ public class URLDecoder implements Serializable { * @return 解码后的字符串 */ public static String decode(final String str, final Charset charset, final boolean isPlusToSpace) { - if (null == charset) { - return str; + if (null == str) { + return null; } - return StrUtil.str(decode(ByteUtil.toBytes(str, charset), isPlusToSpace), charset); + final int length = str.length(); + if(0 == length){ + return StrUtil.EMPTY; + } + + final StringBuilder result = new StringBuilder(length / 3); + + int begin = 0; + char c; + for (int i = 0; i < length; i++) { + c = str.charAt(i); + if(ESCAPE_CHAR == c || CharUtil.isHexChar(c)){ + continue; + } + + // 遇到非需要处理的字符跳过 + // 处理之前的hex字符 + if(i > begin){ + result.append(decodeSub(str, begin, i, charset, isPlusToSpace)); + } + + // 非Hex字符,忽略本字符 + result.append(c); + begin = i + 1; + } + + // 处理剩余字符 + if(begin < length){ + result.append(decodeSub(str, begin, length, charset, isPlusToSpace)); + } + + return result.toString(); } /** @@ -152,11 +182,12 @@ public class URLDecoder implements Serializable { * @return 解码后的bytes * @since 5.6.3 */ + @SuppressWarnings("resource") public static byte[] decode(final byte[] bytes, final boolean isPlusToSpace) { if (bytes == null) { return null; } - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length * 3); + final FastByteArrayOutputStream buffer = new FastByteArrayOutputStream(bytes.length / 3); int b; for (int i = 0; i < bytes.length; i++) { b = bytes[i]; @@ -182,4 +213,22 @@ public class URLDecoder implements Serializable { } return buffer.toByteArray(); } + + /** + * 解码子串 + * + * @param str 字符串 + * @param begin 开始位置(包含) + * @param end 结束位置(不包含) + * @param charset 编码 + * @param isPlusToSpace 是否+转换为空格 + * @return 解码后的字符串 + */ + private static String decodeSub(final String str, final int begin, final int end, + final Charset charset, final boolean isPlusToSpace){ + return new String(decode( + // 截取需要decode的部分 + str.substring(begin, end).getBytes(CharsetUtil.ISO_8859_1), isPlusToSpace + ), charset); + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/net/URLEncoderTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/net/URLEncoderTest.java index acd2b8ac4..47165ad70 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/net/URLEncoderTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/net/URLEncoderTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; public class URLEncoderTest { @Test - public void encodeTest() { + void encodeTest() { final String body = "366466 - 副本.jpg"; final String encode = URLEncoder.encodeAll(body); Assertions.assertEquals("366466%20-%20%E5%89%AF%E6%9C%AC.jpg", encode); @@ -18,14 +18,14 @@ public class URLEncoderTest { } @Test - public void encodeQueryPlusTest() { + void encodeQueryPlusTest() { final String body = "+"; final String encode2 = URLEncoder.encodeQuery(body); Assertions.assertEquals("+", encode2); } @Test - public void encodeEmojiTest(){ + void encodeEmojiTest(){ final String emoji = "🐶😊😂🤣"; final String encode = URLEncoder.encodeAll(emoji); Assertions.assertEquals("%F0%9F%90%B6%F0%9F%98%8A%F0%9F%98%82%F0%9F%A4%A3", encode); diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlDecoderTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlDecoderTest.java index efd18e1fd..1dffef709 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlDecoderTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlDecoderTest.java @@ -1,13 +1,44 @@ package org.dromara.hutool.core.net; import org.dromara.hutool.core.net.url.URLDecoder; +import org.dromara.hutool.core.net.url.URLEncoder; import org.dromara.hutool.core.util.CharsetUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + public class UrlDecoderTest { + @Test - public void decodeForPathTest(){ + void decodeForPathTest(){ Assertions.assertEquals("+", URLDecoder.decodeForPath("+", CharsetUtil.UTF_8)); } + + @Test + void issue3063Test() throws UnsupportedEncodingException { + // https://github.com/dromara/hutool/issues/3063 + + final String s = "测试"; + final String expectedDecode = "%FE%FF%6D%4B%8B%D5"; + + final String s1 = URLEncoder.encodeAll(s, StandardCharsets.UTF_16); + Assertions.assertEquals(expectedDecode, s1); + final String s2 = java.net.URLEncoder.encode(s, "UTF-16"); + Assertions.assertEquals(expectedDecode, s2); + + final String decode = URLDecoder.decode(s1, StandardCharsets.UTF_16); + Assertions.assertEquals(s, decode); + + // 测试编码字符串和非编码字符串混合 + final String mixDecoded = expectedDecode + "你好"; + final String decode2 = URLDecoder.decode(mixDecoded, StandardCharsets.UTF_16); + Assertions.assertEquals("测试你好", decode2); + + Assertions.assertEquals( + java.net.URLDecoder.decode(mixDecoded, "UTF-16"), + URLDecoder.decode(mixDecoded, StandardCharsets.UTF_16) + ); + } }