diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14a41417d..cb6ac30ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@
* 【poi 】 优化ExcelReader,采用只读模式(pr#2204@Gitee)
* 【poi 】 优化ExcelBase,将alias放入
* 【poi 】 优化ExcelBase,将alias放入
+* 【core 】 改进StrUtil#startWith、endWith性能
### 🐞Bug修复
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee)
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 4a308c14d..fc990bc62 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
@@ -694,7 +694,12 @@ public class CharSequenceUtil {
/**
* 是否以指定字符串开头
- * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false
+ *
+ * CharSequenceUtil.startWith("123", "123", false, true); -- false
+ * CharSequenceUtil.startWith("ABCDEF", "abc", true, true); -- true
+ * CharSequenceUtil.startWith("abc", "abc", true, true); -- false
+ *
*
* @param str 被监测字符串
* @param prefix 开头字符串
@@ -711,12 +716,8 @@ public class CharSequenceUtil {
return null == str && null == prefix;
}
- boolean isStartWith;
- if (ignoreCase) {
- isStartWith = str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase());
- } else {
- isStartWith = str.toString().startsWith(prefix.toString());
- }
+ boolean isStartWith = str.toString()
+ .regionMatches(ignoreCase, 0, prefix.toString(), 0, prefix.length());
if (isStartWith) {
return (false == ignoreEquals) || (false == equals(str, prefix, ignoreCase));
@@ -799,21 +800,42 @@ public class CharSequenceUtil {
* 是否以指定字符串结尾
* 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false
*
- * @param str 被监测字符串
- * @param suffix 结尾字符串
- * @param isIgnoreCase 是否忽略大小写
+ * @param str 被监测字符串
+ * @param suffix 结尾字符串
+ * @param ignoreCase 是否忽略大小写
* @return 是否以指定字符串结尾
*/
- public static boolean endWith(CharSequence str, CharSequence suffix, boolean isIgnoreCase) {
+ public static boolean endWith(CharSequence str, CharSequence suffix, boolean ignoreCase) {
+ return endWith(str, suffix, ignoreCase, false);
+ }
+
+ /**
+ * 是否以指定字符串结尾
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false
+ *
+ * @param str 被监测字符串
+ * @param suffix 结尾字符串
+ * @param ignoreCase 是否忽略大小写
+ * @param ignoreEquals 是否忽略字符串相等的情况
+ * @return 是否以指定字符串结尾
+ * @since 5.8.0
+ */
+ public static boolean endWith(CharSequence str, CharSequence suffix, boolean ignoreCase, boolean ignoreEquals) {
if (null == str || null == suffix) {
+ if (ignoreEquals) {
+ return false;
+ }
return null == str && null == suffix;
}
- if (isIgnoreCase) {
- return str.toString().toLowerCase().endsWith(suffix.toString().toLowerCase());
- } else {
- return str.toString().endsWith(suffix.toString());
+ final int strOffset = str.length() - suffix.length();
+ boolean isEndWith = str.toString()
+ .regionMatches(ignoreCase, strOffset, suffix.toString(), 0, suffix.length());
+
+ if (isEndWith) {
+ return (false == ignoreEquals) || (false == equals(str, suffix, ignoreCase));
}
+ return false;
}
/**
@@ -1020,7 +1042,7 @@ public class CharSequenceUtil {
// 如果被监测字符串和
return null == testStr;
}
- return str.toString().toLowerCase().contains(testStr.toString().toLowerCase());
+ return indexOfIgnoreCase(str, testStr) > -1;
}
/**
@@ -1420,7 +1442,7 @@ public class CharSequenceUtil {
}
final String str2 = str.toString();
- if (str2.toLowerCase().startsWith(prefix.toString().toLowerCase())) {
+ if (startWithIgnoreCase(str, prefix)) {
return subSuf(str2, prefix.length());// 截取后半段
}
return str2;
@@ -1469,7 +1491,7 @@ public class CharSequenceUtil {
}
final String str2 = str.toString();
- if (str2.toLowerCase().endsWith(suffix.toString().toLowerCase())) {
+ if (endWithIgnoreCase(str, suffix)) {
return subPre(str2, str2.length() - suffix.length());
}
return str2;
@@ -2641,6 +2663,21 @@ public class CharSequenceUtil {
return str.length() > position && c == str.charAt(position);
}
+ /**
+ * 截取第一个字串的部分字符,与第二个字符串比较(长度一致),判断截取的子串是否相同
+ * 任意一个字符串为null返回false
+ *
+ * @param str1 第一个字符串
+ * @param start1 第一个字符串开始的位置
+ * @param str2 第二个字符串
+ * @param ignoreCase 是否忽略大小写
+ * @return 子串是否相同
+ * @since 3.2.1
+ */
+ public static boolean isSubEquals(CharSequence str1, int start1, CharSequence str2, boolean ignoreCase) {
+ return isSubEquals(str1, start1, str2, 0, str2.length(), ignoreCase);
+ }
+
/**
* 截取两个字符串的不同部分(长度一致),判断截取的子串是否相同
* 任意一个字符串为null返回false
@@ -3608,7 +3645,7 @@ public class CharSequenceUtil {
* @param str 字符串
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
- * @param replacedStr 被替换的字符串
+ * @param replacedStr 被替换的字符串
* @return 替换后的字符串
* @since 3.2.1
*/
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
index 07afd2b4a..60e8d8099 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
@@ -15,7 +15,7 @@ public class CharSequenceUtilTest {
}
@Test
- public void replaceTest2(){
+ public void replaceTest2() {
// https://gitee.com/dromara/hutool/issues/I4M16G
String replace = "#{A}";
String result = CharSequenceUtil.replace(replace, "#{AAAAAAA}", "1");
@@ -23,14 +23,14 @@ public class CharSequenceUtilTest {
}
@Test
- public void replaceByStrTest(){
+ public void replaceByStrTest() {
String replace = "SSM15930297701BeryAllen";
String result = CharSequenceUtil.replace(replace, 5, 12, "***");
Assert.assertEquals("SSM15***01BeryAllen", result);
}
@Test
- public void addPrefixIfNotTest(){
+ public void addPrefixIfNotTest() {
String str = "hutool";
String result = CharSequenceUtil.addPrefixIfNot(str, "hu");
Assert.assertEquals(str, result);
@@ -40,21 +40,21 @@ public class CharSequenceUtilTest {
}
@Test
- public void addSuffixIfNotTest(){
+ public void addSuffixIfNotTest() {
String str = "hutool";
String result = CharSequenceUtil.addSuffixIfNot(str, "tool");
Assert.assertEquals(str, result);
result = CharSequenceUtil.addSuffixIfNot(str, " is Good");
- Assert.assertEquals( str + " is Good", result);
+ Assert.assertEquals(str + " is Good", result);
// https://gitee.com/dromara/hutool/issues/I4NS0F
result = CharSequenceUtil.addSuffixIfNot("", "/");
- Assert.assertEquals( "/", result);
+ Assert.assertEquals("/", result);
}
@Test
- public void normalizeTest(){
+ public void normalizeTest() {
// https://blog.csdn.net/oscar999/article/details/105326270
String str1 = "\u00C1";
@@ -68,7 +68,7 @@ public class CharSequenceUtilTest {
}
@Test
- public void indexOfTest(){
+ public void indexOfTest() {
int index = CharSequenceUtil.indexOf("abc123", '1');
Assert.assertEquals(3, index);
index = CharSequenceUtil.indexOf("abc123", '3');
@@ -78,7 +78,7 @@ public class CharSequenceUtilTest {
}
@Test
- public void indexOfTest2(){
+ public void indexOfTest2() {
int index = CharSequenceUtil.indexOf("abc123", '1', 0, 3);
Assert.assertEquals(-1, index);
@@ -87,7 +87,7 @@ public class CharSequenceUtilTest {
}
@Test
- public void subPreGbkTest(){
+ public void subPreGbkTest() {
// https://gitee.com/dromara/hutool/issues/I4JO2E
String s = "华硕K42Intel酷睿i31代2G以下独立显卡不含机械硬盘固态硬盘120GB-192GB4GB-6GB";
@@ -99,9 +99,53 @@ public class CharSequenceUtilTest {
}
@Test
- public void startWithTest(){
+ public void startWithTest() {
// https://gitee.com/dromara/hutool/issues/I4MV7Q
Assert.assertFalse(CharSequenceUtil.startWith("123", "123", false, true));
Assert.assertFalse(CharSequenceUtil.startWith(null, null, false, true));
+ Assert.assertFalse(CharSequenceUtil.startWith("abc", "abc", true, true));
+
+ Assert.assertTrue(CharSequenceUtil.startWithIgnoreCase(null, null));
+ Assert.assertFalse(CharSequenceUtil.startWithIgnoreCase(null, "abc"));
+ Assert.assertFalse(CharSequenceUtil.startWithIgnoreCase("abcdef", null));
+ Assert.assertTrue(CharSequenceUtil.startWithIgnoreCase("abcdef", "abc"));
+ Assert.assertTrue(CharSequenceUtil.startWithIgnoreCase("ABCDEF", "abc"));
+ }
+
+ @Test
+ public void endWithTest() {
+ Assert.assertFalse(CharSequenceUtil.endWith("123", "123", false, true));
+ Assert.assertFalse(CharSequenceUtil.endWith(null, null, false, true));
+ Assert.assertFalse(CharSequenceUtil.endWith("abc", "abc", true, true));
+
+ Assert.assertTrue(CharSequenceUtil.endWithIgnoreCase(null, null));
+ Assert.assertFalse(CharSequenceUtil.endWithIgnoreCase(null, "abc"));
+ Assert.assertFalse(CharSequenceUtil.endWithIgnoreCase("abcdef", null));
+ Assert.assertTrue(CharSequenceUtil.endWithIgnoreCase("abcdef", "def"));
+ Assert.assertTrue(CharSequenceUtil.endWithIgnoreCase("ABCDEF", "def"));
+ }
+
+ @Test
+ public void removePrefixIgnoreCaseTest(){
+ Assert.assertEquals("de", CharSequenceUtil.removePrefixIgnoreCase("ABCde", "abc"));
+ Assert.assertEquals("de", CharSequenceUtil.removePrefixIgnoreCase("ABCde", "ABC"));
+ Assert.assertEquals("de", CharSequenceUtil.removePrefixIgnoreCase("ABCde", "Abc"));
+ Assert.assertEquals("ABCde", CharSequenceUtil.removePrefixIgnoreCase("ABCde", ""));
+ Assert.assertEquals("ABCde", CharSequenceUtil.removePrefixIgnoreCase("ABCde", null));
+ Assert.assertEquals("", CharSequenceUtil.removePrefixIgnoreCase("ABCde", "ABCde"));
+ Assert.assertEquals("ABCde", CharSequenceUtil.removePrefixIgnoreCase("ABCde", "ABCdef"));
+ Assert.assertNull(CharSequenceUtil.removePrefixIgnoreCase(null, "ABCdef"));
+ }
+
+ @Test
+ public void removeSuffixIgnoreCaseTest(){
+ Assert.assertEquals("AB", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "cde"));
+ Assert.assertEquals("AB", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "CDE"));
+ Assert.assertEquals("AB", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "Cde"));
+ Assert.assertEquals("ABCde", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", ""));
+ Assert.assertEquals("ABCde", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", null));
+ Assert.assertEquals("", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "ABCde"));
+ Assert.assertEquals("ABCde", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "ABCdef"));
+ Assert.assertNull(CharSequenceUtil.removeSuffixIgnoreCase(null, "ABCdef"));
}
}