在 substring / indexOf 期间接收 StringIndexOutOfBoundsException

Receiving StringIndexOutOfBoundsException during substring / indexOf

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Untitled {

    public static void main(String[] args) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get("YUGECORPUS.txt")));
        content = content.replace("\n", " ").replace("\r", " ");  
        String search = "George Bush is";
        System.out.print(content.substring(content.indexOf(search), content.substring(content.indexOf(search)).indexOf(".")));
    }

}

我在编译代码时收到的错误如下:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: -3073945 at java.lang.String.substring(String.java:1967) at Untitled.main(main.java:14).

如何修复此错误,为什么会出现此错误?上面代码段中以下代码的用途:

content.substring(content.indexOf(search), content.substring(content.indexOf(search)).indexOf("."))

打印字符串content从第一次出现的search开始到第一次出现句点.的文本。

您尝试查找 . 的索引时出错:

content.substring(content.indexOf(search)).indexOf(".")

将为您提供子字符串内的索引,而不是 content 内的索引。要修复,您应该将 search 的起始索引添加到它。

例如,如果 content 是:123George Bush is45. 那么我们将有:

content.indexOf(search) -> 3
content.substring(content.indexOf(search)) -> "George Bush is45."

因此:

content.substring(content.indexOf(search)).indexOf(".") -> 16

这是不正确的,正确的索引是 16 + 3 = 19:

content.substring(3, 16) -> "George Bush i"  // wrong
content.substring(3, 19) -> "George Bush is45"  // correct

此外,如果 content 不包含您的 search 字符串和连续的 . 您的代码也可能无法正常工作并产生异常。

为了使代码更加防错,您可以添加检查 content 是否包含您期望的内容,就像这样(注意 endIndex += startIndex 处的修复):

int startIndex = content.indexOf(search);
if(startIndex > -1) {
    int endIndex = content.substring(startIndex).indexOf(".");
    if(endIndex > -1) {
        endIndex += startIndex;
        String foundString = content.substring(startIndex, endIndex);
        System.out.print(foundString);
    }
}

明确地进行检查而不是将所有内容都放在一行中也会使代码更容易调试和发现错误。

编辑: 正如@Andreas 所指出的,说明

int endIndex = content.substring(startIndex).indexOf(".");
endIndex += startIndex;

可以简化为

int endIndex = content.indexOf('.', startIndex);

这是更新后的代码:

int startIndex = content.indexOf(search);
if(startIndex > -1) {
    int endIndex = content.indexOf('.', startIndex);
    if(endIndex > -1) {
        String foundString = content.substring(startIndex, endIndex);
        System.out.print(foundString);
    }
}

它获取您的子字符串并找到它,然后再次读取文件并找到 第一个 句点。所以,当

这个。某物。乔治·布什 (George Bush) 等等等等。

它得到的第一个参数比第二个参数大,因为它在 "This" 之后找到第一个句点。

如果您想继续这样做,您必须在找到 "George bush is" 后截断字符串以将其放在字符串的开头。

最小、完整且可验证的示例

为了帮助我们帮助您,您应该提供 MCVE。这很容易通过替换方法中的第一行来完成,例如

String content = "In a galaxy far, far away, George Bush is happy. That is good.";

那样的话,我们实际上可以重现您的问题。

问题

所以,完成后,让我们拆分您的代码,看看出了什么问题:

String content = "In a galaxy far, far away, George Bush is happy. That is good.";
content = content.replace("\n", " ").replace("\r", " ");  
String search = "George Bush is";

int searchIdx = content.indexOf(search);
String substring = content.substring(searchIdx);
int periodIdx = substring.indexOf(".");
System.out.println("searchIdx = " + searchIdx);
System.out.println("substring = " + substring);
System.out.println("periodIdx = " + periodIdx);
System.out.print("content.substring(" + searchIdx + ", " + periodIdx + ") = ");
System.out.flush();
System.out.println(content.substring(searchIdx, periodIdx));

输出

searchIdx = 27
substring = George Bush is happy. That is good.
periodIdx = 20
content.substring(27, 20) = Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: -7
    at java.lang.String.substring(String.java:1967)
    at Test.main(Test.java:18)

这里可以看到问题是periodIdx为20,即小于searchIdx的值27,导致substring(27, 20)失败

这是因为 periodIdxsubstring 的索引,而不是 content.

的索引

解决方案 1(不理想)

解决这个问题的一种方法是简单地将 searchIdx 添加到 periodIdx,例如

int periodIdx = substring.indexOf(".") + searchIdx;

输出

searchIdx = 27
substring = George Bush is happy. That is good.
periodIdx = 47
content.substring(27, 47) = George Bush is happy

方案二(不理想)

另一种解决方法是将 substring 变量改为子字符串,因为这是索引的用途:

int periodIdx = substring.indexOf(".");
System.out.print("substring.substring(0, " + periodIdx + ") = ");
System.out.println(substring.substring(0, periodIdx));

输出

substring.substring(0, 20) = George Bush is happy

方案三(理想)

前面的两种解决方案都会让您得到想要的结果。然而,它们不是 理想的 解决方案,因为 content.substring(searchIdx) 在创建子字符串时需要 copy

更好的解决方案是在第一次查找返回的点开始执行第二次索引查找:

int searchIdx = content.indexOf(search);
int periodIdx = content.indexOf('.', searchIdx);
System.out.print("content.substring(" + searchIdx + ", " + periodIdx + ") = ");
System.out.println(content.substring(searchIdx, periodIdx));

输出

content.substring(27, 47) = George Bush is happy

另请注意,indexOf() 的搜索值已从 "." 更改为 '.',因为搜索单个字符比搜索字符串更快,甚至是单字符字符串。

这是更好的代码。

结论

您的 main() 方法应该是:

public static void main(String[] args) throws IOException {
    String content = new String(Files.readAllBytes(Paths.get("YUGECORPUS.txt")));
    content = content.replace("\n", " ").replace("\r", " ");  
    String search = "George Bush is";
    int searchIdx = content.indexOf(search);
    System.out.print(content.substring(searchIdx, content.indexOf('.', searchIdx)));
}

请注意,searchIdx 是单独完成的,因此只需执行一次,这与您的代码不同,后者必须搜索超过 超过 300 万(!) 个字符两次.

这也使代码更具可读性。