从管道分隔的文本文件读取时出现 NoSuchElementException

问题描述

从“|”读取时我得到一个 NoSuchElementException (管道)分隔的文本文件

我认为这是导致错误的部分:

public void readFromFile(String file)
{
    operas.clear(); //clear the ArrayList
    try
    {
        //Read while there is data
        Scanner input = new Scanner(new File(file)); //alternative to FileReader & BufferedReader
        String line = "";
        Opera music = null;
        StringTokenizer token = null;
        while(input.hasNextLine())
        {
            line = input.nextLine();
            music = new Opera(); // create an opera
            token = new StringTokenizer(line,"|");
            while (token.hasMoreElements())
            {
                music.setName(token.nextToken());
                music.setComposer(token.nextToken());
                music.setYear(Integer.parseInt(token.nextToken()));
                music.setCity(token.nextToken());
                music.setSynopsis(token.nextToken());
                music.setLink((token.nextToken()));
            }
        }
    ...
}

错误信息:

Error message

解决方法

您正在 while 循环中检查是否有更多元素。 如果是这样,则保证您至少有 1 个元素。 但是,您正在尝试在此循环中读取 6 个元素...

因此,对于没有 6 个标记的行,您会收到此错误。

您应该检查您是否有足够的令牌来进行 6 次“nextToken”调用。

我建议使用不同的网络功能。有没有考虑过字符串拆分?

类似于:

    ...
    line = input.nextLine();
    String[] tokens = line.split("\\|");
    // Check that "tokens" is an array with 6 tokens as you expect...
    ...
,

除了 StringTokenizer 的问题可以通过在调用 token.hasNextToken() 中的每个 setter 之前额外检查 Opera 来解决,代码中还有一些其他问题需要重构:

  1. 使用 try-with-resources 确保 Scanner 实例自动关闭
  2. 修复局部变量的声明和使用
  3. 使用 String::split 方法代替 StringTokenizer
  4. 实现一个辅助方法以从 Opera 数组构建 String 的实例
  5. 在成功处理文件后使用读取的 operas 填充 Opera - 在当前实现中,现有列表会立即清除并随后填充 em>,即在 I/O 异常的情况下,现有数据被销毁。

所有提到的问题都在以下示例中得到解决:

public void readFromFile(String file) {
    
    try (Scanner input = new Scanner(new File(file))) { // auto-close Scanner when done
        List<Opera> readOperas = new ArrayList<>();
        while(input.hasNextLine()) {
            String line = input.nextLine();
            
            String[] row = line.split("\\|");
            
            Opera music = buildOpera(row); // create an opera
            
            readOperas.add(music);
        }
        operas.clear();
        operas.addAll(readOperas);
    } catch (IOException ioex) {
        // log the exception
    }
}
static Opera buildOpera(String ... row) {
    Opera opera = new Opera();

    if (row.length > 0) { opera.setName(row[0]); }
    if (row.length > 1) { opera.setComposer(row[1]); }
    if (row.length > 2) { opera.setYear(Integer.parseInt(row[2])); }
    if (row.length > 3) { opera.setCity(row[3]); }
    if (row.length > 4) { opera.setSynopsis(row[4]); }
    if (row.length > 5) { opera.setLink(row[5]); }

    return opera;
}

将 Java Stream API 与 java.nio 类一起使用将导致以下实现(重用方法 buildOpera):

public void readFromFile(String file) {
    try {
        List<Opera> readOperas = Files.lines(Path.of(inputFileName)) // get stream of lines from the input file
             .map(s -> s.split("\\|"))  // split a line into columns,get Stream<String[]>
             .filter(row -> row.length == 6) // make sure a row contains full data
             .map(MyClass::buildOpera)
             .collect(Collectors.toList());
        operas.clear();
        operas.addAll(readOperas);
    } catch (IOException ex) {
        // log IO exception
    }
}