Scanner读取完文件后重新从头读取内容的最优实现方案
嗨,我来帮你解决这个Scanner重读文件的问题~
首先得明白为啥你现在的代码里edgesSet啥都读不到:Scanner是基于流工作的,一旦你用hasNextLine()读到文件末尾,流的“指针”就停在那儿了,没法自动回到开头。那咱们有几种靠谱的方案来解决这个问题,我给你挨个分析:
方案1:重新创建Scanner(简单直接,大多数场景首选)
你提到的“每次新建Scanner”其实完全没问题,真的不用觉得这是“笨办法”!Scanner本身的创建开销非常小,尤其是针对小文件来说,几乎可以忽略不计。而且这种方式代码最直观,后续维护起来也省心。
你只需要调整main方法,每次处理完一个函数就关闭当前Scanner,再新建一个就行——用try-with-resources语法还能自动帮你关闭资源,避免内存泄漏:
public class Test{ public static void main(String[] args) { File fileName = new File("test.txt"); try { // 第一次读取顶点,用完自动关闭Scanner try (Scanner scanner = new Scanner(fileName)) { verticesSet(scanner); } // 第二次读取边,新建一个Scanner从头读 try (Scanner scanner = new Scanner(fileName)) { edgesSet(scanner); } } catch (FileNotFoundException exception) { System.err.println("file not found: " + exception.getMessage()); } } // 下面的verticesSet和edgesSet函数保持不变 public static void verticesSet(Scanner scanner) { Set<Integer> verticesSet = new HashSet<>(); while (scanner.hasNextLine()) { verticesSet.add(scanner.nextInt()); } System.out.println(verticesSet); } public static void edgesSet(Scanner scanner) { List<String> edges = new ArrayList<>(); while (scanner.hasNextLine()) { edges.add(scanner.nextLine()); } for (int i = 0; i < edges.size(); i++) { System.out.println("index" + i + " is" + edges.get(i)); } } }
就算你有10个函数要处理这个文件,每次新建Scanner也不会有性能问题,反而代码逻辑清晰,不容易出错。
方案2:先把文件内容读到内存(适合多次重复处理的场景)
如果你的文件不大,而且需要反复读取内容,那可以先把整个文件的内容一次性读到内存里(比如一个List<String>),之后每次处理都直接用内存里的数据,不用反复打开文件,能减少IO开销。
比如这样修改:
public class Test{ public static void main(String[] args) { File fileName = new File("test.txt"); try { // 一次性把所有行读到内存里 List<String> allFileLines = Files.readAllLines(fileName.toPath()); // 处理顶点:把内存里的行拼成字符串,创建Scanner try (Scanner scanner = new Scanner(String.join(System.lineSeparator(), allFileLines))) { verticesSet(scanner); } // 处理边:直接用内存里的列表,甚至可以不用Scanner edgesSet(allFileLines); } catch (IOException exception) { System.err.println("file error: " + exception.getMessage()); } } public static void verticesSet(Scanner scanner) { Set<Integer> verticesSet = new HashSet<>(); while (scanner.hasNextLine()) { verticesSet.add(scanner.nextInt()); } System.out.println(verticesSet); } // 修改edgesSet直接接收List<String>,更高效 public static void edgesSet(List<String> edges) { for (int i = 0; i < edges.size(); i++) { System.out.println("index" + i + " is" + edges.get(i)); } } }
这种方式的好处是只打开一次文件,后续操作都是在内存里完成,速度更快;缺点是如果文件特别大(比如几百MB以上),会占用较多内存,这时候就不适合了。
方案3:用流的mark/reset(不推荐,太麻烦)
还有一种技术上可行但不太实用的方法:用BufferedInputStream包装文件流,通过mark()标记文件开头,reset()回到开头。但这种方式需要处理流的缓冲区大小,而且Scanner本身没有重置方法,需要重新创建Scanner绑定到重置后的流上,代码会复杂很多,除非你有特殊需求,否则不建议用。
总结一下
- 如果只是少数几次读取文件,**方案1(重新创建Scanner)**是最优选择,简单又可靠;
- 如果需要多次重复处理文件内容,且文件不大,**方案2(预读到内存)**更高效;
- 方案3尽量不用,太折腾,性价比低。
备注:内容来源于stack exchange,提问作者user23819755

