This commit is contained in:
Looly 2023-03-25 14:04:23 +08:00
parent bece00d176
commit 9f9efdcef4
9 changed files with 334 additions and 133 deletions

View File

@ -43,6 +43,7 @@ public class NumberUtil {
*/ */
private static final int DEFAULT_DIV_SCALE = 10; private static final int DEFAULT_DIV_SCALE = 10;
// region ----- add
/** /**
* 提供精确的加法运算<br> * 提供精确的加法运算<br>
* 如果传入多个值为null或者空则返回0 * 如果传入多个值为null或者空则返回0
@ -90,7 +91,9 @@ public class NumberUtil {
} }
return result; return result;
} }
// endregion
// region ----- sub
/** /**
* 提供精确的减法运算<br> * 提供精确的减法运算<br>
* 如果传入多个值为null或者空则返回0 * 如果传入多个值为null或者空则返回0
@ -138,7 +141,9 @@ public class NumberUtil {
} }
return result; return result;
} }
// endregion
// region ----- mul
/** /**
* 提供精确的乘法运算<br> * 提供精确的乘法运算<br>
* 如果传入多个值为null或者空则返回0 * 如果传入多个值为null或者空则返回0
@ -181,7 +186,9 @@ public class NumberUtil {
return result; return result;
} }
// endregion
// region ----- div
/** /**
* 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入 * 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入
* *
@ -278,8 +285,9 @@ public class NumberUtil {
public static int ceilDiv(final int v1, final int v2) { public static int ceilDiv(final int v1, final int v2) {
return (int) Math.ceil((double) v1 / v2); return (int) Math.ceil((double) v1 / v2);
} }
// endregion
// ------------------------------------------------------------------------------------------- round // region ----- round
/** /**
* 保留固定位数小数<br> * 保留固定位数小数<br>
@ -500,8 +508,9 @@ public class NumberUtil {
public static BigDecimal roundDown(final BigDecimal value, final int scale) { public static BigDecimal roundDown(final BigDecimal value, final int scale) {
return round(value, scale, RoundingMode.DOWN); return round(value, scale, RoundingMode.DOWN);
} }
// endregion
// ------------------------------------------------------------------------------------------- decimalFormat // region ----- decimalFormat
/** /**
* 格式化double<br> * 格式化double<br>
@ -627,8 +636,9 @@ public class NumberUtil {
format.setMaximumFractionDigits(scale); format.setMaximumFractionDigits(scale);
return format.format(number); return format.format(number);
} }
// endregion
// ------------------------------------------------------------------------------------------- isXXX // region ----- isXXX
/** /**
* 是否为数字支持包括 * 是否为数字支持包括
@ -853,8 +863,9 @@ public class NumberUtil {
} }
return true; return true;
} }
// endregion
// ------------------------------------------------------------------------------------------- range // region ----- range
/** /**
* 生成一个从0开始的数字列表<br> * 生成一个从0开始的数字列表<br>
@ -947,6 +958,7 @@ public class NumberUtil {
} }
return values; return values;
} }
// endregion
// ------------------------------------------------------------------------------------------- others // ------------------------------------------------------------------------------------------- others
@ -986,6 +998,7 @@ public class NumberUtil {
return Long.parseLong(binaryStr, 2); return Long.parseLong(binaryStr, 2);
} }
// region ----- equals
/** /**
* 比较大小值相等 返回true<br> * 比较大小值相等 返回true<br>
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br> * 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br>
@ -1054,7 +1067,9 @@ public class NumberUtil {
public static boolean equals(final char c1, final char c2, final boolean ignoreCase) { public static boolean equals(final char c1, final char c2, final boolean ignoreCase) {
return CharUtil.equals(c1, c2, ignoreCase); return CharUtil.equals(c1, c2, ignoreCase);
} }
// endregion
// region ----- toStr
/** /**
* 数字转字符串<br> * 数字转字符串<br>
* 调用{@link Number#toString()}并去除尾小数点儿后多余的0 * 调用{@link Number#toString()}并去除尾小数点儿后多余的0
@ -1139,6 +1154,7 @@ public class NumberUtil {
} }
return bigDecimal.toPlainString(); return bigDecimal.toPlainString();
} }
// endregion
/** /**
* 数字转{@link BigDecimal}<br> * 数字转{@link BigDecimal}<br>
@ -1399,6 +1415,7 @@ public class NumberUtil {
return (n > 0) && ((n & (n - 1)) == 0); return (n > 0) && ((n & (n - 1)) == 0);
} }
// region ----- parse
/** /**
* 解析转换数字字符串为 {@link java.lang.Integer } 规则如下 * 解析转换数字字符串为 {@link java.lang.Integer } 规则如下
* *
@ -1680,6 +1697,7 @@ public class NumberUtil {
throw nfe; throw nfe;
} }
} }
// endregion
/** /**
* 检查是否为有效的数字<br> * 检查是否为有效的数字<br>

View File

@ -11,6 +11,9 @@ import cn.hutool.core.text.finder.CharFinder;
import cn.hutool.core.text.finder.CharMatcherFinder; import cn.hutool.core.text.finder.CharMatcherFinder;
import cn.hutool.core.text.finder.Finder; import cn.hutool.core.text.finder.Finder;
import cn.hutool.core.text.finder.StrFinder; 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.text.split.SplitUtil;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
@ -3263,7 +3266,8 @@ public class CharSequenceUtil extends StrChecker {
} }
/** /**
* 替换字符串中的指定字符串 * 替换字符串中的指定字符串<br>
* 如果指定字符串出现多次则全部替换
* *
* @param str 字符串 * @param str 字符串
* @param fromIndex 开始位置包括 * @param fromIndex 开始位置包括
@ -3273,85 +3277,27 @@ public class CharSequenceUtil extends StrChecker {
* @return 替换后的字符串 * @return 替换后的字符串
* @since 4.0.3 * @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)) { if (isEmpty(str) || isEmpty(searchStr)) {
return str(str); return str(str);
} }
if (null == replacement) { return new SearchReplacer(fromIndex, searchStr, replacement, ignoreCase).apply(str);
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();
} }
/** /**
* 替换指定字符串的指定区间内字符为固定字符<br> * 替换指定字符串的指定区间内字符为固定字符替换后字符串长度不变<br>
* 如替换的区间长度为10则替换后的字符重复10次<br>
* 此方法使用{@link String#codePoints()}完成拆分替换 * 此方法使用{@link String#codePoints()}完成拆分替换
* *
* @param str 字符串 * @param str 字符串
* @param startInclude 开始位置包含 * @param beginInclude 开始位置包含
* @param endExclude 结束位置不包含 * @param endExclude 结束位置不包含
* @param replacedChar 被替换的字符 * @param replacedChar 被替换的字符
* @return 替换后的字符串 * @return 替换后的字符串
* @since 3.2.1 * @since 3.2.1
*/ */
public static String replace(final CharSequence str, final int startInclude, int endExclude, final char replacedChar) { public static String replace(final CharSequence str, final int beginInclude, final int endExclude, final char replacedChar) {
if (isEmpty(str)) { return new RangeReplacerByChar(beginInclude, endExclude, replacedChar).apply(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();
} }
/** /**
@ -3359,40 +3305,14 @@ public class CharSequenceUtil extends StrChecker {
* 此方法使用{@link String#codePoints()}完成拆分替换 * 此方法使用{@link String#codePoints()}完成拆分替换
* *
* @param str 字符串 * @param str 字符串
* @param startInclude 开始位置包含 * @param beginInclude 开始位置包含
* @param endExclude 结束位置不包含 * @param endExclude 结束位置不包含
* @param replacedStr 被替换的字符串 * @param replacedStr 被替换的字符串
* @return 替换后的字符串 * @return 替换后的字符串
* @since 3.2.1 * @since 3.2.1
*/ */
public static String replace(final CharSequence str, final int startInclude, int endExclude, final CharSequence replacedStr) { public static String replace(final CharSequence str, final int beginInclude, final int endExclude, final CharSequence replacedStr) {
if (isEmpty(str)) { return new RangeReplacerByStr(beginInclude, endExclude, replacedStr).apply(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();
} }
/** /**

View File

@ -0,0 +1,73 @@
package cn.hutool.core.text.replacer;
import cn.hutool.core.text.StrUtil;
/**
* 区间字符串替换指定区间将区间中的所有字符去除替换为指定的字符字符重复次数为区间长度即替换后字符串长度不变<br>
* 此方法使用{@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;
}
}

View File

@ -0,0 +1,74 @@
package cn.hutool.core.text.replacer;
import cn.hutool.core.text.StrUtil;
/**
* 区间字符串替换指定区间将区间中的所有字符去除替换为指定的字符串字符串只重复一次<br>
* 此方法使用{@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;
}
}

View File

@ -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;
/**
* 查找替换器<br>
* 查找给定的字符串并全部替换为新的字符串其它字符不变
*
* @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;
}
}

View File

@ -23,17 +23,22 @@ public abstract class StrReplacer implements UnaryOperator<CharSequence>, Serial
*/ */
protected abstract int replace(CharSequence str, int pos, StringBuilder out); protected abstract int replace(CharSequence str, int pos, StringBuilder out);
/**
* 执行替换按照{@link #replace(CharSequence, int, StringBuilder)}逻辑替换对应部分其它部分保持原样
* @param str 被处理的字符串
* @return 替换后的字符串
*/
@Override @Override
public CharSequence apply(final CharSequence t) { public CharSequence apply(final CharSequence str) {
final int len = t.length(); final int len = str.length();
final StringBuilder builder = new StringBuilder(len); final StringBuilder builder = new StringBuilder(len);
int pos = 0;//当前位置 int pos = 0;//当前位置
int consumed;//处理过的字符数 int consumed;//处理过的字符数
while (pos < len) { while (pos < len) {
consumed = replace(t, pos, builder); consumed = replace(str, pos, builder);
if (0 == consumed) { if (0 == consumed) {
//0表示未处理或替换任何字符原样输出本字符并从下一个字符继续 //0表示未处理或替换任何字符原样输出本字符并从下一个字符继续
builder.append(t.charAt(pos)); builder.append(str.charAt(pos));
pos++; pos++;
} }
pos += consumed; pos += consumed;

View File

@ -221,7 +221,7 @@ public class StrUtilTest {
string = StrUtil.replace("aabbccdd", 2, 12, '*'); string = StrUtil.replace("aabbccdd", 2, 12, '*');
Assert.assertEquals("aa******", string); 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); Assert.assertEquals("\uD83D\uDE00a****ccdd", emoji);
} }
@ -231,12 +231,6 @@ public class StrUtilTest {
Assert.assertEquals("133", result); Assert.assertEquals("133", result);
} }
@Test
public void replaceTest3() {
final String result = StrUtil.replace(",abcdef,", ",", "|");
Assert.assertEquals("|abcdef|", result);
}
@Test @Test
public void replaceTest4() { public void replaceTest4() {
final String a = "1039"; final String a = "1039";
@ -642,7 +636,7 @@ public class StrUtilTest {
final String result = StrUtil.replace(replace, 5, 12, "***"); final String result = StrUtil.replace(replace, 5, 12, "***");
Assert.assertEquals("SSM15***01BeryAllen", result); 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); Assert.assertEquals("\uD83D\uDE00a***ccdd", emoji);
} }

View File

@ -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);
}
}

View File

@ -134,28 +134,6 @@ public class ReUtilTest {
Assert.assertThrows(IllegalArgumentException.class, () -> ReUtil.replaceAll(content, pattern, str)); 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 @Test
public void escapeTest() { public void escapeTest() {
//转义给定字符串为正则相关的特殊符号转义 //转义给定字符串为正则相关的特殊符号转义