从边文本文件构建图时遇Java NoSuchElementException运行时错误
问题分析与修复:Java图构建中的
NoSuchElementException 错误根源定位
先看报错指向的代码行(Graph.java:64):
adjLists[v] = new Vertex(fileScanner.next(), null);
这个异常的核心原因是你的Scanner已经被读空了:
- 你第一次用
while (fileScanner.hasNext())循环收集唯一节点时,已经把整个文件的内容读完,Scanner的指针移到了文件末尾。 - 紧接着你直接用同一个Scanner去读取顶点和边,此时
fileScanner.next()找不到任何可读取的内容,就抛出了NoSuchElementException。
另外你的顶点读取逻辑也有问题:第二次读取时,文件里存的是成对的边节点,不是单独的顶点列表,直接调用fileScanner.next()完全不符合文件内容结构。
两种可行修复方案
方案1:重新打开文件读取内容
收集完唯一节点后,关闭当前Scanner,重新创建一个新的Scanner从头读取文件,这样就能正常处理边的内容了:
public Graph(String file) throws FileNotFoundException { // 第一步:收集所有唯一节点 Scanner fileScanner = new Scanner(new File(file)); fileScanner.useDelimiter("[^A-Za-z0-9]"); ArrayList<String> uniqueWords = new ArrayList<>(); while (fileScanner.hasNext()) { String nextWord = fileScanner.next(); if (!uniqueWords.contains(nextWord)) { uniqueWords.add(nextWord); } } fileScanner.close(); // 关闭已读空的Scanner adjLists = new Vertex[uniqueWords.size()]; // 用收集到的唯一节点初始化顶点数组 for (int v = 0; v < adjLists.length; v++) { adjLists[v] = new Vertex(uniqueWords.get(v), null); } // 第二步:重新打开文件读取边 fileScanner = new Scanner(new File(file)); fileScanner.useDelimiter("[^A-Za-z0-9]"); // 读取成对的边节点 while (fileScanner.hasNext()) { if (!fileScanner.hasNext()) break; // 防止文件末尾出现单个无效节点 String name1 = fileScanner.next(); if (!fileScanner.hasNext()) break; String name2 = fileScanner.next(); int v1 = indexForName(name1); int v2 = indexForName(name2); // 双向添加邻接关系 adjLists[v1].adjList = new Neighbor(v2, adjLists[v1].adjList); adjLists[v2].adjList = new Neighbor(v1, adjLists[v2].adjList); } fileScanner.close(); }
方案2:一次性读取所有内容到内存
把文件所有内容先存入List,这样可以多次遍历,不需要重复打开文件,效率更高:
public Graph(String file) throws FileNotFoundException { Scanner fileScanner = new Scanner(new File(file)); fileScanner.useDelimiter("[^A-Za-z0-9]"); List<String> allWords = new ArrayList<>(); Set<String> uniqueSet = new HashSet<>(); // 用HashSet提升去重效率 // 读取所有单词,同时收集唯一节点 while (fileScanner.hasNext()) { String word = fileScanner.next(); allWords.add(word); uniqueSet.add(word); } fileScanner.close(); ArrayList<String> uniqueWords = new ArrayList<>(uniqueSet); adjLists = new Vertex[uniqueWords.size()]; // 初始化顶点,同时构建名称到索引的映射(优化后续查询) Map<String, Integer> nameToIndex = new HashMap<>(); for (int v = 0; v < adjLists.length; v++) { String name = uniqueWords.get(v); adjLists[v] = new Vertex(name, null); nameToIndex.put(name, v); } // 遍历allWords处理边(每次取两个单词为一组) for (int i = 0; i < allWords.size(); i += 2) { if (i + 1 >= allWords.size()) break; // 避免越界 String name1 = allWords.get(i); String name2 = allWords.get(i + 1); int v1 = nameToIndex.get(name1); int v2 = nameToIndex.get(name2); adjLists[v1].adjList = new Neighbor(v2, adjLists[v1].adjList); adjLists[v2].adjList = new Neighbor(v1, adjLists[v2].adjList); } } // 优化后的indexForName方法 int indexForName(String name) { // 这里可以直接用上面的nameToIndex映射,或者保持原逻辑,建议替换成map查询 return nameToIndex.getOrDefault(name, -1); }
额外优化提示
- 用
HashSet替代ArrayList做去重,把去重的时间复杂度从O(n)降到O(1)。 - 提前构建
nameToIndex的HashMap,查询节点索引的效率也会从O(n)优化到O(1),适合节点数量较多的场景。
内容的提问来源于stack exchange,提问作者Teju_M
相关产品推荐
相关产品推荐

