diff --git a/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java index 57b66e767..d1884957b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java @@ -43,6 +43,7 @@ public class NumberUtil { */ private static final int DEFAULT_DIV_SCALE = 10; + // region ----- add /** * 提供精确的加法运算
* 如果传入多个值为null或者空,则返回0 @@ -90,7 +91,9 @@ public class NumberUtil { } return result; } + // endregion + // region ----- sub /** * 提供精确的减法运算
* 如果传入多个值为null或者空,则返回0 @@ -138,7 +141,9 @@ public class NumberUtil { } return result; } + // endregion + // region ----- mul /** * 提供精确的乘法运算
* 如果传入多个值为null或者空,则返回0 @@ -181,7 +186,9 @@ public class NumberUtil { return result; } + // endregion + // region ----- div /** * 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入 * @@ -278,8 +285,9 @@ public class NumberUtil { public static int ceilDiv(final int v1, final int v2) { return (int) Math.ceil((double) v1 / v2); } + // endregion - // ------------------------------------------------------------------------------------------- round + // region ----- round /** * 保留固定位数小数
@@ -500,8 +508,9 @@ public class NumberUtil { public static BigDecimal roundDown(final BigDecimal value, final int scale) { return round(value, scale, RoundingMode.DOWN); } + // endregion - // ------------------------------------------------------------------------------------------- decimalFormat + // region ----- decimalFormat /** * 格式化double
@@ -627,8 +636,9 @@ public class NumberUtil { format.setMaximumFractionDigits(scale); return format.format(number); } + // endregion - // ------------------------------------------------------------------------------------------- isXXX + // region ----- isXXX /** * 是否为数字,支持包括: @@ -853,8 +863,9 @@ public class NumberUtil { } return true; } + // endregion - // ------------------------------------------------------------------------------------------- range + // region ----- range /** * 生成一个从0开始的数字列表
@@ -947,6 +958,7 @@ public class NumberUtil { } return values; } + // endregion // ------------------------------------------------------------------------------------------- others @@ -986,6 +998,7 @@ public class NumberUtil { return Long.parseLong(binaryStr, 2); } + // region ----- equals /** * 比较大小,值相等 返回true
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等
@@ -1054,7 +1067,9 @@ public class NumberUtil { public static boolean equals(final char c1, final char c2, final boolean ignoreCase) { return CharUtil.equals(c1, c2, ignoreCase); } + // endregion + // region ----- toStr /** * 数字转字符串
* 调用{@link Number#toString()},并去除尾小数点儿后多余的0 @@ -1139,6 +1154,7 @@ public class NumberUtil { } return bigDecimal.toPlainString(); } + // endregion /** * 数字转{@link BigDecimal}
@@ -1399,6 +1415,7 @@ public class NumberUtil { return (n > 0) && ((n & (n - 1)) == 0); } + // region ----- parse /** * 解析转换数字字符串为 {@link java.lang.Integer } 规则如下: * @@ -1680,6 +1697,7 @@ public class NumberUtil { throw nfe; } } + // endregion /** * 检查是否为有效的数字
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index be4b7c13a..149d12b0f 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -11,6 +11,9 @@ import cn.hutool.core.text.finder.CharFinder; import cn.hutool.core.text.finder.CharMatcherFinder; import cn.hutool.core.text.finder.Finder; import cn.hutool.core.text.finder.StrFinder; +import cn.hutool.core.text.replacer.RangeReplacerByChar; +import cn.hutool.core.text.replacer.RangeReplacerByStr; +import cn.hutool.core.text.replacer.SearchReplacer; import cn.hutool.core.text.split.SplitUtil; import cn.hutool.core.util.*; @@ -3263,7 +3266,8 @@ public class CharSequenceUtil extends StrChecker { } /** - * 替换字符串中的指定字符串 + * 替换字符串中的指定字符串
+ * 如果指定字符串出现多次,则全部替换 * * @param str 字符串 * @param fromIndex 开始位置(包括) @@ -3273,85 +3277,27 @@ public class CharSequenceUtil extends StrChecker { * @return 替换后的字符串 * @since 4.0.3 */ - public static String replace(final CharSequence str, int fromIndex, final CharSequence searchStr, CharSequence replacement, final boolean ignoreCase) { + public static String replace(final CharSequence str, final int fromIndex, final CharSequence searchStr, final CharSequence replacement, final boolean ignoreCase) { if (isEmpty(str) || isEmpty(searchStr)) { return str(str); } - if (null == replacement) { - replacement = EMPTY; - } - - final int strLength = str.length(); - final int searchStrLength = searchStr.length(); - if (strLength < searchStrLength) { - // issue#I4M16G@Gitee - return str(str); - } - - if (fromIndex > strLength) { - return str(str); - } else if (fromIndex < 0) { - fromIndex = 0; - } - - final StringBuilder result = new StringBuilder(strLength - searchStrLength + replacement.length()); - if (0 != fromIndex) { - result.append(str.subSequence(0, fromIndex)); - } - - int preIndex = fromIndex; - int index; - while ((index = indexOf(str, searchStr, preIndex, ignoreCase)) > -1) { - result.append(str.subSequence(preIndex, index)); - result.append(replacement); - preIndex = index + searchStrLength; - } - - if (preIndex < strLength) { - // 结尾部分 - result.append(str.subSequence(preIndex, strLength)); - } - return result.toString(); + return new SearchReplacer(fromIndex, searchStr, replacement, ignoreCase).apply(str); } /** - * 替换指定字符串的指定区间内字符为固定字符
+ * 替换指定字符串的指定区间内字符为固定字符,替换后字符串长度不变
+ * 如替换的区间长度为10,则替换后的字符重复10次
* 此方法使用{@link String#codePoints()}完成拆分替换 * * @param str 字符串 - * @param startInclude 开始位置(包含) + * @param beginInclude 开始位置(包含) * @param endExclude 结束位置(不包含) * @param replacedChar 被替换的字符 * @return 替换后的字符串 * @since 3.2.1 */ - public static String replace(final CharSequence str, final int startInclude, int endExclude, final char replacedChar) { - if (isEmpty(str)) { - return str(str); - } - final String originalStr = str(str); - final int[] strCodePoints = originalStr.codePoints().toArray(); - final int strLength = strCodePoints.length; - if (startInclude > strLength) { - return originalStr; - } - if (endExclude > strLength) { - endExclude = strLength; - } - if (startInclude > endExclude) { - // 如果起始位置大于结束位置,不替换 - return originalStr; - } - - final StringBuilder stringBuilder = new StringBuilder(originalStr.length()); - for (int i = 0; i < strLength; i++) { - if (i >= startInclude && i < endExclude) { - stringBuilder.append(replacedChar); - } else { - stringBuilder.appendCodePoint(strCodePoints[i]); - } - } - return stringBuilder.toString(); + public static String replace(final CharSequence str, final int beginInclude, final int endExclude, final char replacedChar) { + return new RangeReplacerByChar(beginInclude, endExclude, replacedChar).apply(str); } /** @@ -3359,40 +3305,14 @@ public class CharSequenceUtil extends StrChecker { * 此方法使用{@link String#codePoints()}完成拆分替换 * * @param str 字符串 - * @param startInclude 开始位置(包含) + * @param beginInclude 开始位置(包含) * @param endExclude 结束位置(不包含) * @param replacedStr 被替换的字符串 * @return 替换后的字符串 * @since 3.2.1 */ - public static String replace(final CharSequence str, final int startInclude, int endExclude, final CharSequence replacedStr) { - if (isEmpty(str)) { - return str(str); - } - final String originalStr = str(str); - final int[] strCodePoints = originalStr.codePoints().toArray(); - final int strLength = strCodePoints.length; - if (startInclude > strLength) { - return originalStr; - } - if (endExclude > strLength) { - endExclude = strLength; - } - if (startInclude > endExclude) { - // 如果起始位置大于结束位置,不替换 - return originalStr; - } - - // 新字符串长度 <= 旧长度 - (被替换区间codePoints数量) + 替换字符串长度 - final StringBuilder stringBuilder = new StringBuilder(originalStr.length() - (endExclude - startInclude) + replacedStr.length()); - for (int i = 0; i < startInclude; i++) { - stringBuilder.appendCodePoint(strCodePoints[i]); - } - stringBuilder.append(replacedStr); - for (int i = endExclude; i < strLength; i++) { - stringBuilder.appendCodePoint(strCodePoints[i]); - } - return stringBuilder.toString(); + public static String replace(final CharSequence str, final int beginInclude, final int endExclude, final CharSequence replacedStr) { + return new RangeReplacerByStr(beginInclude, endExclude, replacedStr).apply(str); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/replacer/RangeReplacerByChar.java b/hutool-core/src/main/java/cn/hutool/core/text/replacer/RangeReplacerByChar.java new file mode 100644 index 000000000..6109c7019 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/replacer/RangeReplacerByChar.java @@ -0,0 +1,73 @@ +package cn.hutool.core.text.replacer; + +import cn.hutool.core.text.StrUtil; + +/** + * 区间字符串替换,指定区间,将区间中的所有字符去除,替换为指定的字符,字符重复次数为区间长度,即替换后字符串长度不变
+ * 此方法使用{@link String#codePoints()}完成拆分替换 + * + * @author Looly + */ +public class RangeReplacerByChar extends StrReplacer { + private static final long serialVersionUID = 1L; + + private final int beginInclude; + private final int endExclude; + private final char replacedChar; + + /** + * 构造 + * + * @param beginInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @param replacedChar 被替换的字符串 + */ + public RangeReplacerByChar(final int beginInclude, final int endExclude, final char replacedChar) { + this.beginInclude = beginInclude; + this.endExclude = endExclude; + this.replacedChar = replacedChar; + } + + @Override + public String apply(final CharSequence str) { + if (StrUtil.isEmpty(str)) { + return StrUtil.str(str); + } + + final String originalStr = StrUtil.str(str); + final int[] strCodePoints = originalStr.codePoints().toArray(); + final int strLength = strCodePoints.length; + + final int beginInclude = this.beginInclude; + if (beginInclude > strLength) { + return originalStr; + } + int endExclude = this.endExclude; + if (endExclude > strLength) { + endExclude = strLength; + } + if (beginInclude > endExclude) { + // 如果起始位置大于结束位置,不替换 + return originalStr; + } + + // 新字符串长度不变 + final StringBuilder stringBuilder = new StringBuilder(originalStr.length()); + for (int i = 0; i < strLength; i++) { + if (i >= beginInclude && i < endExclude) { + // 区间内的字符全部替换 + replace(originalStr, i, stringBuilder); + } else { + // 其它字符保留 + stringBuilder.appendCodePoint(strCodePoints[i]); + } + } + return stringBuilder.toString(); + } + + @Override + protected int replace(final CharSequence str, final int pos, final StringBuilder out) { + out.appendCodePoint(replacedChar); + return pos; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/replacer/RangeReplacerByStr.java b/hutool-core/src/main/java/cn/hutool/core/text/replacer/RangeReplacerByStr.java new file mode 100644 index 000000000..6f61c3f9f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/replacer/RangeReplacerByStr.java @@ -0,0 +1,74 @@ +package cn.hutool.core.text.replacer; + +import cn.hutool.core.text.StrUtil; + +/** + * 区间字符串替换,指定区间,将区间中的所有字符去除,替换为指定的字符串,字符串只重复一次
+ * 此方法使用{@link String#codePoints()}完成拆分替换 + * + * @author Looly + */ +public class RangeReplacerByStr extends StrReplacer { + private static final long serialVersionUID = 1L; + + private final int beginInclude; + private final int endExclude; + private final CharSequence replacedStr; + + /** + * 构造 + * + * @param beginInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @param replacedStr 被替换的字符串 + */ + public RangeReplacerByStr(final int beginInclude, final int endExclude, final CharSequence replacedStr) { + this.beginInclude = beginInclude; + this.endExclude = endExclude; + this.replacedStr = replacedStr; + } + + @Override + public String apply(final CharSequence str) { + if (StrUtil.isEmpty(str)) { + return StrUtil.str(str); + } + + final String originalStr = StrUtil.str(str); + final int[] strCodePoints = originalStr.codePoints().toArray(); + final int strLength = strCodePoints.length; + + final int beginInclude = this.beginInclude; + if (beginInclude > strLength) { + return originalStr; + } + int endExclude = this.endExclude; + if (endExclude > strLength) { + endExclude = strLength; + } + if (beginInclude > endExclude) { + // 如果起始位置大于结束位置,不替换 + return originalStr; + } + + // 新字符串长度 <= 旧长度 - (被替换区间codePoints数量) + 替换字符串长度 + final StringBuilder stringBuilder = new StringBuilder(originalStr.length() - (endExclude - beginInclude) + replacedStr.length()); + for (int i = 0; i < beginInclude; i++) { + stringBuilder.appendCodePoint(strCodePoints[i]); + } + replace(originalStr, beginInclude, stringBuilder); + for (int i = endExclude; i < strLength; i++) { + stringBuilder.appendCodePoint(strCodePoints[i]); + } + return stringBuilder.toString(); + } + + @Override + protected int replace(final CharSequence str, final int pos, final StringBuilder out) { + // 由于区间替换,因此区间已确定,直接替换即可 + out.append(this.replacedStr); + + // 无意义的返回 + return endExclude; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/replacer/SearchReplacer.java b/hutool-core/src/main/java/cn/hutool/core/text/replacer/SearchReplacer.java new file mode 100644 index 000000000..fba9d4252 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/replacer/SearchReplacer.java @@ -0,0 +1,96 @@ +package cn.hutool.core.text.replacer; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.StrUtil; +import cn.hutool.core.text.finder.Finder; + +/** + * 查找替换器
+ * 查找给定的字符串,并全部替换为新的字符串,其它字符不变 + * + * @author looly + * @since 6.0.0 + */ +public class SearchReplacer extends StrReplacer { + private static final long serialVersionUID = 1L; + + private static final int INDEX_NOT_FOUND = Finder.INDEX_NOT_FOUND; + + private final int fromIndex; + private final CharSequence searchStr; + private final int searchStrLength; + private final CharSequence replacement; + private final boolean ignoreCase; + + /** + * 构造 + * + * @param fromIndex 开始位置(包括) + * @param searchStr 被查找的字符串 + * @param replacement 被替换的字符串 + * @param ignoreCase 是否忽略大小写 + */ + public SearchReplacer(final int fromIndex, final CharSequence searchStr, final CharSequence replacement, final boolean ignoreCase) { + this.fromIndex = Math.max(fromIndex, 0); + this.searchStr = Assert.notEmpty(searchStr, "'searchStr' must be not empty!"); + this.searchStrLength = searchStr.length(); + this.replacement = StrUtil.emptyIfNull(replacement); + this.ignoreCase = ignoreCase; + } + + @Override + public String apply(final CharSequence str) { + if (StrUtil.isEmpty(str)) { + return StrUtil.str(str); + } + + final int strLength = str.length(); + if (strLength < this.searchStrLength) { + // issue#I4M16G@Gitee + return StrUtil.str(str); + } + + final int fromIndex = this.fromIndex; + if (fromIndex > strLength) { + // 越界截断 + return StrUtil.EMPTY; + } + + final StringBuilder result = new StringBuilder( + strLength - this.searchStrLength + this.replacement.length()); + if (0 != fromIndex) { + // 开始部分 + result.append(str.subSequence(0, fromIndex)); + } + + // 替换部分 + int pos = fromIndex; + int consumed;//处理过的字符数 + while ((consumed = replace(str, pos, result)) > 0) { + pos += consumed; + } + + if (pos < strLength) { + // 结尾部分 + result.append(str.subSequence(pos, strLength)); + } + return result.toString(); + } + + @Override + protected int replace(final CharSequence str, final int pos, final StringBuilder out) { + final int index = StrUtil.indexOf(str, this.searchStr, pos, this.ignoreCase); + if (index > INDEX_NOT_FOUND) { + // 无需替换的部分 + out.append(str.subSequence(pos, index)); + // 替换的部分 + out.append(replacement); + + //已经处理的长度 = 无需替换的长度(查找字符串位置 - 开始的位置) + 替换的长度 + return index - pos + searchStrLength; + } + + // 未找到 + return INDEX_NOT_FOUND; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/replacer/StrReplacer.java b/hutool-core/src/main/java/cn/hutool/core/text/replacer/StrReplacer.java index 55c3a89ce..7e044a094 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/replacer/StrReplacer.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/replacer/StrReplacer.java @@ -23,17 +23,22 @@ public abstract class StrReplacer implements UnaryOperator, Serial */ protected abstract int replace(CharSequence str, int pos, StringBuilder out); + /** + * 执行替换,按照{@link #replace(CharSequence, int, StringBuilder)}逻辑替换对应部分,其它部分保持原样 + * @param str 被处理的字符串 + * @return 替换后的字符串 + */ @Override - public CharSequence apply(final CharSequence t) { - final int len = t.length(); + public CharSequence apply(final CharSequence str) { + final int len = str.length(); final StringBuilder builder = new StringBuilder(len); int pos = 0;//当前位置 int consumed;//处理过的字符数 while (pos < len) { - consumed = replace(t, pos, builder); + consumed = replace(str, pos, builder); if (0 == consumed) { //0表示未处理或替换任何字符,原样输出本字符并从下一个字符继续 - builder.append(t.charAt(pos)); + builder.append(str.charAt(pos)); pos++; } pos += consumed; diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrUtilTest.java index 015a6395e..ccbc3fd4f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrUtilTest.java @@ -221,7 +221,7 @@ public class StrUtilTest { string = StrUtil.replace("aabbccdd", 2, 12, '*'); Assert.assertEquals("aa******", string); - String emoji = StrUtil.replace("\uD83D\uDE00aabb\uD83D\uDE00ccdd", 2, 6, '*'); + final String emoji = StrUtil.replace("\uD83D\uDE00aabb\uD83D\uDE00ccdd", 2, 6, '*'); Assert.assertEquals("\uD83D\uDE00a****ccdd", emoji); } @@ -231,12 +231,6 @@ public class StrUtilTest { Assert.assertEquals("133", result); } - @Test - public void replaceTest3() { - final String result = StrUtil.replace(",abcdef,", ",", "|"); - Assert.assertEquals("|abcdef|", result); - } - @Test public void replaceTest4() { final String a = "1039"; @@ -642,7 +636,7 @@ public class StrUtilTest { final String result = StrUtil.replace(replace, 5, 12, "***"); Assert.assertEquals("SSM15***01BeryAllen", result); - String emoji = StrUtil.replace("\uD83D\uDE00aabb\uD83D\uDE00ccdd", 2, 6, "***"); + final String emoji = StrUtil.replace("\uD83D\uDE00aabb\uD83D\uDE00ccdd", 2, 6, "***"); Assert.assertEquals("\uD83D\uDE00a***ccdd", emoji); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/replacer/SearchReplacerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/replacer/SearchReplacerTest.java new file mode 100644 index 000000000..04049106b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/replacer/SearchReplacerTest.java @@ -0,0 +1,43 @@ +package cn.hutool.core.text.replacer; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.text.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class SearchReplacerTest { + + @Test + public void replaceOnlyTest() { + final String result = CharSequenceUtil.replace(",", ",", "|"); + Assert.assertEquals("|", result); + } + + @Test + public void replaceTestAtBeginAndEnd() { + final String result = CharSequenceUtil.replace(",abcdef,", ",", "|"); + Assert.assertEquals("|abcdef|", result); + } + + @Test + public void replaceTest() { + final String str = "AAABBCCCBBDDDBB"; + String replace = StrUtil.replace(str, 0, "BB", "22", false); + Assert.assertEquals("AAA22CCC22DDD22", replace); + + replace = StrUtil.replace(str, 3, "BB", "22", false); + Assert.assertEquals("AAA22CCC22DDD22", replace); + + replace = StrUtil.replace(str, 4, "BB", "22", false); + Assert.assertEquals("AAABBCCC22DDD22", replace); + + replace = StrUtil.replace(str, 4, "bb", "22", true); + Assert.assertEquals("AAABBCCC22DDD22", replace); + + replace = StrUtil.replace(str, 4, "bb", "", true); + Assert.assertEquals("AAABBCCCDDD", replace); + + replace = StrUtil.replace(str, 4, "bb", null, true); + Assert.assertEquals("AAABBCCCDDD", replace); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java index 0a2e43a72..526a43cca 100755 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java @@ -134,28 +134,6 @@ public class ReUtilTest { Assert.assertThrows(IllegalArgumentException.class, () -> ReUtil.replaceAll(content, pattern, str)); } - @Test - public void replaceTest() { - final String str = "AAABBCCCBBDDDBB"; - String replace = StrUtil.replace(str, 0, "BB", "22", false); - Assert.assertEquals("AAA22CCC22DDD22", replace); - - replace = StrUtil.replace(str, 3, "BB", "22", false); - Assert.assertEquals("AAA22CCC22DDD22", replace); - - replace = StrUtil.replace(str, 4, "BB", "22", false); - Assert.assertEquals("AAABBCCC22DDD22", replace); - - replace = StrUtil.replace(str, 4, "bb", "22", true); - Assert.assertEquals("AAABBCCC22DDD22", replace); - - replace = StrUtil.replace(str, 4, "bb", "", true); - Assert.assertEquals("AAABBCCCDDD", replace); - - replace = StrUtil.replace(str, 4, "bb", null, true); - Assert.assertEquals("AAABBCCCDDD", replace); - } - @Test public void escapeTest() { //转义给定字符串,为正则相关的特殊符号转义