【问题标题】:Splitting a string up via markers?通过标记拆分字符串?
【发布时间】:2025-12-19 07:35:11
【问题描述】:

我有一个 txt 文件,它是一本书的摘录。我已通过以下方法将文件转换为字符串

    File book = new File("WarAndPeace.txt");
    chapters = new ArrayList<String>();


    FileReader fileReader;
    fileReader = new FileReader(book);


    BufferedReader bufferedReader = new BufferedReader(fileReader);
    StringBuffer stringBuffer = new StringBuffer();
    String nextLine;
    while ((nextLine = bufferedReader.readLine()) != null) {
        stringBuffer.append(nextLine);
        stringBuffer.append("\n");
    }
    fileReader.close();

    myBook = stringBuffer.toString();

在文本文件中,每一章都包含在一个卷中。它用“CHAPTER”表示,然后是罗马数字。比如第五章以“CHAPTER V”开头

我需要将字符串分解为多个字符串并将它们全部添加到 ArrayList 中,这样我就可以编写诸如“nextChapter()”“previousChapter”“getChapter(int volumeNumber,int chapterNumber”)之类的函数,它将返回适用的字符串。例如我在想这个;其中的章节是一个 ArrayList

public String nextChapter(){
currentChapter++;
return chapters(currentChapter);
}

如何将书分成章节和卷(一卷包含多个章节)

我是否需要使用与 ArrayList 不同的数据结构,如果需要,使用什么以及如何使用?我听说 HashMap 可以使用键(也许 String 类型包含两个键,章节和卷?)如果是这样,我该怎么做?

谢谢

【问题讨论】:

  • 是否也可以更改从文件中读取图书的算法?
  • 如何表示一个卷?
  • myBook.split("Chapter [IVX]+\\s+"),也许?
  • 扩展@Teepeemm 也许ArrayList&lt;String&gt; chapters = new ArrayList&lt;String&gt;(Arrays.asList(myBook.split("Chapter [IVX]+\\s+")));

标签: java arrays string arraylist


【解决方案1】:

关于文件解析: String.split() 是一个非常有用的工具。它支持regular expressions,这意味着您可以为它提供类似"CHAPTER [IVXLCDM]+" 的内容,它将匹配任何出现的“CHAPTER”后跟罗马数字。但是,请记住,正则表达式区分大小写。此外,使用这种方法不会尊重章节的实际数量;无论哪一章先出现,都将首先放入结果数组中。这可能不是问题,因为大多数书籍都按顺序包含章节。

如果您使用上面的示例匹配,您可能必须从章节文本的开头和/或结尾去除换行符。 String.trim() 会为你做这件事。

关于数据结构:在这种情况下,面向对象编程可以让你受益匪浅。与其使用ArrayList&lt;String&gt; 来保存书的章节,不如为书的不同部分创建类。例如,Book 类的实例可能有一个 Volume 实例数组,每个实例都有一个 Chapter 实例数组。 Chapter 可能包含一个带有章节内容的 String,并且可能包含另一个 String 作为标题。

现在看起来可能需要做更多的工作,但从长远来看会有所回报。类为用户提供了一致的接口,还允许您作为程序员保护您的数据。通过定义Volume getVolume(int)Chapter getChapter(int) 之类的方法,您可以为用户提供一种更简洁、更有意义的数据交互方式。相比之下,调用List 的方法在含义上可能更加模糊。

【讨论】:

    【解决方案2】:

    您尝试的并不难,但并不像简单的按关键字拆分那么简单。在真正的书籍文本中,您可以轻松地在文本中找到“章节”一词。因此,如果您按“章节”的出现进行拆分,您最终会得到虚假的划分和错误构建的数据结构。

    因此,您必须小心考虑文本“章节”(或“卷”)实际上是章节标题的标准。你说过一个:

    • 章节以单词“CHAPTER”加总和加一个罗马数字开头。

    我会添加下一个:

    • 此标题占据整行文本。

    还有一个类似的卷规则,带有单词“VOLUME”(如果没有指定其他条件)。

    因此,如果您已经在逐行读取文件,则最好在读取文件时执行文本分析,从而更好地区分行的开始和结束位置,并且还可以避免在字符串中存储大量数据(这通常会导致性能下降)。所以,数据结构应该是在分析文本的过程中建立起来的。

    数据结构应该是这样的:

    class Book
    {
        private List<Volume> volumes=...
        public void addVolume(Volume volume) {...}
        public Volume getVolume(int volume) {...}
        public Chapter getChapter(int volume, int chapter) {...}
    }
    
    class Volume
    {
        private List<Chapter> chapters=...
        public void addChapter(Chapter chapter) {...}
        public Chapter getChapter(int chapter) {...}
    }
    
    class Chapter
    {
        private StringBuilder text=...
        public void addText(String text) {...}
        public String getText() {...}
    }
    

    解析算法是这样的:

    Pattern chapterPattern=Pattern.compile("CHAPTER\s+[IVXLDC]+");
    Pattern volumePattern=Pattern.compile("VOLUME\s+[IVXLDC]+");
    Book book=new Book(...);
    Volume currentVolume=null;
    Chapter currentChapter=null;
    while ((nextLine = bufferedReader.readLine()) != null) {
        if (volumePattern.matcher(nextLine)).matches())
        {
            // It is a volume heading:
            currentVolume=new Volume(...);
            currentChapter=null;
            book.addVolume(currentVolume);
        }
        else if (chapterPattern.matcher(nextLine)).matches())
        {
            // It is a chapter heading:
            currentChapter=new Chapter(...);
            currentVolume.addChapter(currentChapter);
        }
        else
        {
            currentChapter.addText((nextLine).append("\n"));
        }
    }
    

    这种解析算法总是期望书的格式正确:它必须总是以卷标题开头。在卷标题之后,必须有一个章节标题。并且所有卷和章节必须按顺序出现(因此罗马数字可以忽略)。如果没有,就会出现一个丑陋的异常。因此,如果您想控制可能出现的错误格式,则必须考虑 currentVolume 或 currentChapter 中的 null 值。

    此外,没有任何关于空行的规定。在卷标题和章节标题之间有一个空行是否合法?如果是这样,您必须考虑到这一点。

    【讨论】: