diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java index 3c8779b23..e8b929e34 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java @@ -12,6 +12,8 @@ package org.dromara.hutool.core.math; +import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.text.CharUtil; import org.dromara.hutool.core.text.StrUtil; @@ -123,6 +125,73 @@ public class NumberParser { } } + /** + * 转换char数组为一个int值,此方法拷贝自{@link Integer#parseInt(String, int)}
+ * 拷贝的原因是直接转换char[]避免创建String对象造成的多余拷贝
+ * 此方法自动跳过首尾空白符 + * + * @param chars char数组 + * @param radix 进制数 + * @return int值 + * @see Integer#parseInt(String, int) + */ + public int parseInt(final char[] chars, final int radix) { + if (ArrayUtil.isEmpty(chars)) { + throw new IllegalArgumentException("Empty chars!"); + } + + int result = 0; + boolean negative = false; + int i = 0; + int limit = -Integer.MAX_VALUE; + int digit; + + // 跳过空白符 + while (CharUtil.isBlankChar(chars[i])) { + i++; + } + + final char firstChar = chars[i]; + if (firstChar < '0') { // Possible leading "+" or "-" + if (firstChar == '-') { + negative = true; + limit = Integer.MIN_VALUE; + } else if (firstChar != '+') { + throw new NumberFormatException("Invalid first char: " + firstChar); + } + + if (chars.length == 1) { + // Cannot have lone "+" or "-" + throw new NumberFormatException("Invalid chars has lone: " + firstChar); + } + i++; + } + + final int multmin = limit / radix; + while (i < chars.length) { + // 跳过空白符 + if (CharUtil.isBlankChar(chars[i])) { + i++; + continue; + } + + // Accumulating negatively avoids surprises near MAX_VALUE + digit = Character.digit(chars[i++], radix); + if (digit < 0) { + throw new NumberFormatException(StrUtil.format("Invalid chars: {} at {}", chars, i - 1)); + } + if (result < multmin) { + throw new NumberFormatException(StrUtil.format("Invalid chars: {}", new Object[]{chars})); + } + result *= radix; + if (result < limit + digit) { + throw new NumberFormatException(StrUtil.format("Invalid chars: {}", new Object[]{chars})); + } + result -= digit; + } + return negative ? result : -result; + } + /** * 解析转换数字字符串为long型数字,规则如下: * @@ -293,7 +362,7 @@ public class NumberParser { if (null == locale) { locale = Locale.getDefault(Locale.Category.FORMAT); } - if(StrUtil.startWith(numberStr, CharUtil.PLUS)){ + if (StrUtil.startWith(numberStr, CharUtil.PLUS)) { // issue#I79VS7 numberStr = StrUtil.subSuf(numberStr, 1); } 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 d3cc57c00..1ee01e01b 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 @@ -1230,6 +1230,22 @@ public class NumberUtil extends NumberValidator { return NumberParser.INSTANCE.parseInt(numberStr); } + /** + * 转换char数组为一个int值,此方法拷贝自{@link Integer#parseInt(String, int)}
+ * 拷贝的原因是直接转换char[]避免创建String对象造成的多余拷贝。
+ * 此方法自动跳过首尾空白符 + * + * @param chars char数组 + * @param radix 进制数 + * @return int值 + * @throws NumberFormatException 数字格式异常 + * @see Integer#parseInt(String, int) + * @since 6.0.0 + */ + public static int parseInt(final char[] chars, final int radix) throws NumberFormatException { + return NumberParser.INSTANCE.parseInt(chars, radix); + } + /** * 解析转换数字字符串为 {@link java.lang.Long } 规则如下: * diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberParserTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberParserTest.java index d8b611011..4486f89e5 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberParserTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberParserTest.java @@ -15,10 +15,51 @@ package org.dromara.hutool.core.math; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class NumberParserTest { @Test void parseLongTest() { final long value = NumberParser.INSTANCE.parseLong("0.a"); Assertions.assertEquals(0L, value); } + + @Test + void testParseIntWithValidInputs() { + // Test with various valid inputs + assertEquals(123, NumberParser.INSTANCE.parseInt("123".toCharArray(), 10)); + assertEquals(-123, NumberParser.INSTANCE.parseInt("-123".toCharArray(), 10)); + assertEquals(123, NumberParser.INSTANCE.parseInt("+123".toCharArray(), 10)); + assertEquals(0, NumberParser.INSTANCE.parseInt("0".toCharArray(), 10)); + assertEquals(255, NumberParser.INSTANCE.parseInt("ff".toCharArray(), 16)); + assertEquals(-255, NumberParser.INSTANCE.parseInt("-ff".toCharArray(), 16)); + } + + @Test + void testParseIntWithInvalidInputs() { + // Test with various invalid inputs + assertThrows(IllegalArgumentException.class, () -> NumberParser.INSTANCE.parseInt("".toCharArray(), 10)); + assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("1234".toCharArray(), 2)); + assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("abc".toCharArray(), 10)); + assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("--123".toCharArray(), 10)); + assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("++123".toCharArray(), 10)); + assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("123".toCharArray(), 1)); + } + + @Test + void testParseIntWithLeadingAndTrailingWhitespace() { + // Test with leading and trailing whitespace + assertEquals(42, NumberParser.INSTANCE.parseInt(" 42 ".toCharArray(), 10)); + } + + @Test + void testParseIntWithMaxAndMinInt() { + // Test with values at the edge of int range + final char[] maxIntStr = Integer.toString(Integer.MAX_VALUE).toCharArray(); + final char[] minIntStr = Integer.toString(Integer.MIN_VALUE).toCharArray(); + + assertEquals(Integer.MAX_VALUE, NumberParser.INSTANCE.parseInt(maxIntStr, 10)); + assertEquals(Integer.MIN_VALUE, NumberParser.INSTANCE.parseInt(minIntStr, 10)); + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberUtilTest.java index 7c30a8b8d..62afa6b09 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/math/NumberUtilTest.java @@ -26,6 +26,7 @@ import java.text.NumberFormat; import java.text.ParseException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * {@link NumberUtil} 单元测试类 @@ -320,7 +321,7 @@ public class NumberUtilTest { @Test public void decimalFormatNaNTest() { - Assertions.assertThrows(IllegalArgumentException.class, ()->{ + assertThrows(IllegalArgumentException.class, ()->{ final Double a = 0D; final Double b = 0D; @@ -331,7 +332,7 @@ public class NumberUtilTest { @Test public void decimalFormatNaNTest2() { - Assertions.assertThrows(IllegalArgumentException.class, ()->{ + assertThrows(IllegalArgumentException.class, ()->{ final Double a = 0D; final Double b = 0D; @@ -383,7 +384,7 @@ public class NumberUtilTest { @Test void emptyToBigDecimalTest(){ - Assertions.assertThrows(IllegalArgumentException.class,()->{ + assertThrows(IllegalArgumentException.class,()->{ NumberUtil.toBigDecimal(""); }); } @@ -445,7 +446,7 @@ public class NumberUtilTest { @Test public void parseIntTest3() { - Assertions.assertThrows(NumberFormatException.class, ()->{ + assertThrows(NumberFormatException.class, ()->{ final int v1 = NumberUtil.parseInt("d"); assertEquals(0, v1); }); @@ -453,7 +454,7 @@ public class NumberUtilTest { @Test public void parseIntTest4() { - Assertions.assertThrows(NumberFormatException.class, ()->{ + assertThrows(NumberFormatException.class, ()->{ // issue#I5M55F // 科学计数法忽略支持,科学计数法一般用于表示非常小和非常大的数字,这类数字转换为int后精度丢失,没有意义。 final String numberStr = "429900013E20220812163344551"; @@ -692,7 +693,7 @@ public class NumberUtilTest { @Test public void rangeMinTest() { - Assertions.assertThrows(NegativeArraySizeException.class, ()->{ + assertThrows(NegativeArraySizeException.class, ()->{ NumberUtil.range(0, Integer.MIN_VALUE); }); } @@ -757,7 +758,7 @@ public class NumberUtilTest { @EnabledForJreRange(max = JRE.JAVA_8) void numberFormatTest() { // JDK8下,NaN解析报错,JDK9+中返回0 - Assertions.assertThrows(ParseException.class, ()->{ + assertThrows(ParseException.class, ()->{ NumberFormat.getInstance().parse("NaN"); }); } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/JSONTokener.java b/hutool-json/src/main/java/org/dromara/hutool/json/JSONTokener.java index bceb0b955..a2692366c 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/JSONTokener.java @@ -15,6 +15,7 @@ package org.dromara.hutool.json; import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.ReaderWrapper; import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.math.NumberUtil; import java.io.IOException; import java.io.InputStream; @@ -171,27 +172,21 @@ public class JSONTokener extends ReaderWrapper { } /** - * Get the last character read from the input or '\0' if nothing has been read yet. + * 获取上一个读取的字符,如果没有读取过则返回'\0' * - * @return the last character read from the input. + * @return 上一个读取的字符 */ protected char getPrevious() { return this.previous; } /** - * 读取下一个字符,并比对是否和指定字符匹配 - * - * @param c 被匹配的字符 - * @return The character 匹配到的字符 - * @throws JSONException 如果不匹配抛出此异常 + * 获取16进制unicode转义符对应的字符值,如: + *
{@code '4f60' -> '你'}
+ * @return 字符 */ - public char next(final char c) throws JSONException { - final char n = this.next(); - if (n != c) { - throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); - } - return n; + public char nextUnicode(){ + return (char) NumberUtil.parseInt(next(4), 16); } /** @@ -201,12 +196,9 @@ public class JSONTokener extends ReaderWrapper { * @return 获得的n个字符组成的字符串 * @throws JSONException 如果源中余下的字符数不足以提供所需的字符数,抛出此异常 */ - public String next(final int n) throws JSONException { - if (n == 0) { - return ""; - } - + public char[] next(final int n) throws JSONException { final char[] chars = new char[n]; + int pos = 0; while (pos < n) { chars[pos] = this.next(); @@ -215,7 +207,7 @@ public class JSONTokener extends ReaderWrapper { } pos += 1; } - return new String(chars); + return chars; } /** @@ -276,7 +268,7 @@ public class JSONTokener extends ReaderWrapper { sb.append('\r'); break; case 'u':// Unicode符 - sb.append((char) Integer.parseInt(this.next(4), 16)); + sb.append(nextUnicode()); break; case '"': case '\'':