1. 概述
在这个教程中,我们将学习如何自动在给定字符数后包裹句子。因此,我们的程序将返回一个带有换行的新字符串。
2. 通用算法
让我们考虑以下句子:Baeldung 是一个提供深入教程和文章的流行网站,涵盖各种编程和软件开发主题,主要关注 Java 和相关技术。
我们希望每 n 个字符插入一行,其中 n 表示字符数。让我们看看如何实现这一点:
String wrapStringCharacterWise(String input, int n) {
StringBuilder stringBuilder = new StringBuilder(input);
int index = 0;
while(stringBuilder.length() > index + n) {
index = stringBuilder.lastIndexOf(" ", index + n);
stringBuilder.replace(index, index + 1, "\n");
index++;
}
return stringBuilder.toString();
}
以 n=20 为例,理解我们的示例代码:
- 我们首先找到距离 20 个字符处的最近的空格:在这个例子中,单词 a 和 popular 之间。
- 然后我们用换行符替换这个空格。
- 接着,我们从下一个单词的开头开始,例如我们的例子中是 popular。
当剩余的句子少于 20 个字符时,我们就停止算法。我们自然地通过一个 for 循环 实现此算法。此外,为了方便,我们内部使用了 StringBuilder,并将输入参数化:
我们可以编写一个 单元测试 来验证我们的方法对示例的预期结果:
@Test
void givenStringWithMoreThanNCharacters_whenWrapStringCharacterWise_thenCorrectlyWrapped() {
String input = "Baeldung is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
assertEquals("Baeldung is a\npopular website that\nprovides in-depth\ntutorials and\narticles on various\nprogramming and\nsoftware development\ntopics, primarily\nfocused on Java and\nrelated\ntechnologies.", wrapper.wrapStringCharacterWise(input, 20));
}
3. 边界情况
目前,我们编写了一个非常简单的代码。在实际应用中,可能需要处理一些边界情况。在这篇文章中,我们将探讨其中两个。
3.1. 单词长度超过字符限制
首先,如果一个单词太大,无法包裹怎么办?为了简单起见,**在这种情况下,我们可以抛出一个 IllegalArgumentException**。在循环的每次迭代中,我们需要检查给定长度前确实存在空格:
String wrapStringCharacterWise(String input, int n) {
StringBuilder stringBuilder = new StringBuilder(input);
int index = 0;
while(stringBuilder.length() > index + n) {
index = stringBuilder.lastIndexOf(" ", index + n);
if (index == -1) {
throw new IllegalArgumentException("impossible to slice " + stringBuilder.substring(0, n));
}
stringBuilder.replace(index, index + 1, "\n");
index++;
}
return stringBuilder.toString();
}
同样,我们可以编写一个简单的 JUnit 测试来验证:
@Test
void givenStringWithATooLongWord_whenWrapStringCharacterWise_thenThrows() {
String input = "The word straightforward has more than 10 characters";
assertThrows(IllegalArgumentException.class, () -> wrapper.wrapStringCharacterWise(input, 10));
}
3.2. 原始输入已有换行符
另一个边缘情况是,输入字符串中已经包含换行符。目前,如果我们向句子中的 Baeldung 后添加一个换行符,它会被同样包裹。但听起来更直观的是在现有换行符之后开始包裹。
为此,我们在算法的每次迭代中都会搜索最后一个换行符;如果存在,我们将移动光标并跳过包裹部分:
String wrapStringCharacterWise(String input, int n) {
StringBuilder stringBuilder = new StringBuilder(input);
int index = 0;
while(stringBuilder.length() > index + n) {
int lastLineReturn = stringBuilder.lastIndexOf("\n", index + n);
if (lastLineReturn > index) {
index = lastLineReturn;
} else {
index = stringBuilder.lastIndexOf(" ", index + n);
if (index == -1) {
throw new IllegalArgumentException("impossible to slice " + stringBuilder.substring(0, n));
}
stringBuilder.replace(index, index + 1, "\n");
index++;
}
}
return stringBuilder.toString();
}
再次,我们可以用我们的示例测试我们的代码:
@Test
void givenStringWithLineReturns_whenWrapStringCharacterWise_thenWrappedAccordingly() {
String input = "Baeldung\nis a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
assertEquals("Baeldung\nis a popular\nwebsite that\nprovides in-depth\ntutorials and\narticles on various\nprogramming and\nsoftware development\ntopics, primarily\nfocused on Java and\nrelated\ntechnologies.", wrapper.wrapStringCharacterWise(input, 20));
}
4. Apache WordUtils 的 wrap()
方法
我们可以使用 Apache WordUtils 的 wrap()
方法来实现所需的行为。 首先,让我们添加最新的 Apache*commons-text* 依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
与我们的代码相比,wrap()
方法的主要区别在于它默认使用平台无关的System 的换行符:
@Test
void givenStringWithMoreThanNCharacters_whenWrap_thenCorrectlyWrapped() {
String input = "Baeldung is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
assertEquals("Baeldung is a" + System.lineSeparator() + "popular website that" + System.lineSeparator() + "provides in-depth" + System.lineSeparator() + "tutorials and" + System.lineSeparator() + "articles on various" + System.lineSeparator() + "programming and" + System.lineSeparator() + "software development" + System.lineSeparator() + "topics, primarily" + System.lineSeparator() + "focused on Java and" + System.lineSeparator() + "related" + System.lineSeparator() + "technologies.", WordUtils.wrap(input, 20));
}
默认情况下,wrap()
方法接受长单词但不进行包裹:
@Test
void givenStringWithATooLongWord_whenWrap_thenLongWordIsNotWrapped() {
String input = "The word straightforward has more than 10 characters";
assertEquals("The word" + System.lineSeparator() + "straightforward" + System.lineSeparator() + "has more" + System.lineSeparator() + "than 10" + System.lineSeparator() + "characters", WordUtils.wrap(input, 10));
}
最后但并非最不重要的一点,这个库忽略了我们的另一个边缘情况:
@Test
void givenStringWithLineReturns_whenWrap_thenWrappedLikeThereWasNone() {
String input = "Baeldung" + System.lineSeparator() + "is a popular website that provides in-depth tutorials and articles on various programming and software development topics, primarily focused on Java and related technologies.";
assertEquals("Baeldung" + System.lineSeparator() + "is a" + System.lineSeparator() + "popular website that" + System.lineSeparator() + "provides in-depth" + System.lineSeparator() + "tutorials and" + System.lineSeparator() + "articles on various" + System.lineSeparator() + "programming and" + System.lineSeparator() + "software development" + System.lineSeparator() + "topics, primarily" + System.lineSeparator() + "focused on Java and" + System.lineSeparator() + "related" + System.lineSeparator() + "technologies.", WordUtils.wrap(input, 20));
}
总之,我们可以查看方法的重载签名:
static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn)
我们注意到额外的参数:
- newLineStr:用于插入新行的不同字符。
- wrapLongWords:一个布尔值,决定是否包裹长单词。
- wrapOn:可以使用任何正则表达式代替空格。
5. 总结
在这篇文章中,我们看到了在给定字符数后包裹字符串的算法。我们实现了它,并添加了对几个边缘情况的支持。
最后,我们意识到 Apache WordUtils 的 wrap()
方法非常灵活,大多数情况下应该足够使用。然而,如果我们不能使用外部依赖或需要特定行为,我们可以使用自己的实现。
一如既往,代码可以在 GitHub 上找到。